Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ jobs:
- name: Unit tests
run: make unit

- name: Check release assets
run: make verify-release-manifest

- name: Vet
run: make vet

Expand Down
29 changes: 17 additions & 12 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,17 @@ jobs:
echo "version=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "tag=v${VERSION}" >> "${GITHUB_OUTPUT}"

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true # zizmor: ignore[cache-poisoning]

- name: Build and check release manifests
env:
TAG: ${{ steps.version.outputs.tag }}
run: make verify-release-manifest IMG="${IMAGE}:${TAG}"

- name: Create and push tag
env:
TAG: ${{ steps.version.outputs.tag }}
Expand All @@ -107,12 +118,6 @@ jobs:
-f "sha=${SHA}"
fi

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: true # zizmor: ignore[cache-poisoning]

- name: Build and push container image
env:
TAG: ${{ steps.version.outputs.tag }}
Expand All @@ -123,11 +128,6 @@ jobs:
podman login -u "${ACTOR}" -p "${GH_TOKEN}" ghcr.io
podman push "${IMAGE}:${TAG}"

- name: Build install manifest
env:
TAG: ${{ steps.version.outputs.tag }}
run: make release-manifest IMG="${IMAGE}:${TAG}"

- name: Package and push Helm chart
env:
VERSION: ${{ steps.version.outputs.version }}
Expand All @@ -154,6 +154,11 @@ jobs:
printf 'kubectl apply -f https://github.com/%s/releases/download/%s/install.yaml\n' \
"${GITHUB_REPOSITORY}" "${TAG}"
printf '```\n\n'
printf 'Optional configuration example: [operator-config.yaml](https://github.com/%s/releases/download/%s/operator-config.yaml). ' \
"${GITHUB_REPOSITORY}" "${TAG}"
printf 'Before applying it, check for an existing instance; see the [configuration guide](https://github.com/%s/blob/%s/docs/src/operations/configuration.md).\n' \
"${GITHUB_REPOSITORY}" "${TAG}"
printf '\n'
printf '### Helm\n\n'
printf '```bash\n'
printf 'helm install bootc-operator oci://ghcr.io/%s/charts/bootc-operator --version %s \\\n' \
Expand All @@ -166,4 +171,4 @@ jobs:
--notes-file notes.md \
--generate-notes \
--draft \
install.yaml
install.yaml operator-config.yaml
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ bin/
kubeconfig-*
docs/book/
user-docs
/install.yaml
/operator-config.yaml
chart/bootc-operator/crds/
chart/bootc-operator/templates/controller-clusterrole.yaml
chart/bootc-operator/templates/daemon-clusterrole.yaml
Expand Down
28 changes: 17 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,21 @@ buildimg: ## Build container image.
$(CONTAINER_TOOL) build -t $(IMG) .

.PHONY: release-manifest
release-manifest: kustomize yq ## Build install manifest (override IMG to set the image reference).
release-manifest: kustomize yq ## Build install manifest and optional configuration asset (override IMG).
"$(KUSTOMIZE)" build config/default | \
"$(YQ)" '(select(.kind == "Deployment") | .spec.template.spec.containers[] | select(.name == "manager")).image = "$(IMG)"' | \
"$(YQ)" '(select(.kind == "DaemonSet") | .spec.template.spec.containers[] | select(.name == "daemon")).image = "$(IMG)"' \
> install.yaml
cp config/samples/bootc_v1alpha1_bootcoperatorconfig.yaml operator-config.yaml

.PHONY: verify-release-manifest
verify-release-manifest: release-manifest ## Check the generated release assets.
@cmp -s config/samples/bootc_v1alpha1_bootcoperatorconfig.yaml operator-config.yaml || \
{ echo "operator-config.yaml differs from the validated example"; exit 1; }
@test "$$("$(YQ)" ea '[.] | map(select(.kind == "CustomResourceDefinition" and .metadata.name == "bootcoperatorconfigs.node.bootc.dev")) | length' install.yaml)" -eq 1 || \
{ echo "install.yaml must contain the BootcOperatorConfig CRD exactly once"; exit 1; }
@test "$$("$(YQ)" ea '[.] | map(select(.kind == "BootcOperatorConfig")) | length' install.yaml)" -eq 0 || \
{ echo "install.yaml must not create an administrator-owned BootcOperatorConfig"; exit 1; }

.PHONY: build-update-image
build-update-image: ## Build derived node images for update testing and push to bink registry.
Expand Down Expand Up @@ -245,17 +255,13 @@ start-bink: seed-node-image ## Start a bink cluster (idempotent).
kubectl --kubeconfig $(KUBECONFIG_BINK) wait --for=condition=Ready node/controller --timeout=5m

.PHONY: deploy-bink
deploy-bink: start-bink build-update-image $(if $(RELEASED_OPERATOR_IMG),push-released-operator-image) kustomize ## Deploy to a bink cluster (requires: buildimg).
deploy-bink: start-bink kustomize yq ## Deploy to a bink cluster (requires: buildimg).
$(MAKE) build-update-image
$(if $(RELEASED_OPERATOR_IMG),$(MAKE) push-released-operator-image)
podman push --tls-verify=false $(IMG) localhost:5000/bootc-operator-e2e:latest
# On re-deploy, restart the rollout to force a re-pull of the :latest tag.
# On fresh deploy, skip the restart -- the pod is already pulling the correct image.
@existed=$$(kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator get deploy bootc-operator-controller-manager -o name 2>/dev/null || true) && \
$(MAKE) deploy KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) IMG=$(IMG_BINK) \
MANAGER_EXTRA_ARGS='"--allow-insecure-registry","--tag-resolution-interval=10s"' && \
if [ -n "$$existed" ]; then \
kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout restart deployment/bootc-operator-controller-manager; \
fi
kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout status deployment/bootc-operator-controller-manager --timeout=3m
$(MAKE) install KUBECONFIG="$(abspath $(KUBECONFIG_BINK))"
KUBECONFIG="$(abspath $(KUBECONFIG_BINK))" IMG="$(IMG_BINK)" \
KUBECTL="$(KUBECTL)" YQ="$(YQ)" MAKE="$(MAKE)" ./hack/deploy-bink.sh

.PHONY: gather-bink
gather-bink: ## Gather diagnostic logs from the bink cluster.
Expand Down
8 changes: 8 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ resources:
kind: BootcNode
path: github.com/jlebon/bootc-operator/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: false
domain: bootc.dev
group: node
kind: BootcOperatorConfig
path: github.com/bootc-dev/bootc-operator/api/v1alpha1
version: v1alpha1
version: "3"
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ kubectl apply -k https://github.com/bootc-dev/bootc-operator//config/default
This creates the `bootc-operator` namespace and deploys the controller and
daemon. The operator does nothing until you create a BootcNodePool.

To configure the controller and daemon with an optional
`BootcOperatorConfig` resource, see
[Configuring the operator](docs/src/operations/configuration.md). Releases
publish an editable `operator-config.yaml` example separately from
`install.yaml`.

> [!NOTE]
> The operator namespace requires a [Pod Security Admission] exemption for the
> `privileged` level. The daemon DaemonSet runs privileged to execute bootc
Expand Down
81 changes: 81 additions & 0 deletions api/v1alpha1/bootcoperatorconfig_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

const (
// DefaultTagResolutionPeriodSeconds is the default interval between resolving image tags.
DefaultTagResolutionPeriodSeconds int32 = 300

// DefaultStatusPollPeriodSeconds is the default interval between fallback bootc status polls.
DefaultStatusPollPeriodSeconds int32 = 300
)

// OperatorControllerConfig configures the operator controller at startup.
type OperatorControllerConfig struct {
// allowInsecureRegistry allows falling back to HTTP when accessing image
// registries. Enable this only for trusted registries that do not support TLS.
// +optional
// +kubebuilder:default=false
AllowInsecureRegistry *bool `json:"allowInsecureRegistry,omitempty"`

// tagResolutionPeriodSeconds is the interval in seconds between resolving
// image tags to digests. Defaults to 300 seconds.
// +optional
// +kubebuilder:default=300
// +kubebuilder:validation:Minimum=1
TagResolutionPeriodSeconds *int32 `json:"tagResolutionPeriodSeconds,omitempty"`
}

// OperatorDaemonConfig configures each node daemon at startup.
type OperatorDaemonConfig struct {
// statusPollPeriodSeconds is the interval in seconds between bootc status
// polls when filesystem notifications are unavailable. Defaults to 300 seconds.
// +optional
// +kubebuilder:default=300
// +kubebuilder:validation:Minimum=1
StatusPollPeriodSeconds *int32 `json:"statusPollPeriodSeconds,omitempty"`
}

// BootcOperatorConfigSpec defines operator settings consumed at process startup.
// Restart the affected controller or daemon pods after changing these settings.
type BootcOperatorConfigSpec struct {
// controller configures the controller. Omitted settings use their defaults.
// +optional
// +kubebuilder:default={}
Controller *OperatorControllerConfig `json:"controller,omitempty"`

// daemon configures all node daemons. Omitted settings use their defaults.
// +optional
// +kubebuilder:default={}
Daemon *OperatorDaemonConfig `json:"daemon,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster

// BootcOperatorConfig configures the controller and node daemons in a cluster.
// The optional resource is administrator-owned and may have any valid name.
// The operator rejects multiple configurations at startup. Changes take effect
// when the affected pods restart; explicit command-line flags take precedence.
type BootcOperatorConfig struct {
metav1.TypeMeta `json:",inline"`

// metadata is standard object metadata.
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// spec defines the configuration consumed when operator processes start.
// +required
Spec BootcOperatorConfigSpec `json:"spec"`
}

// +kubebuilder:object:root=true

// BootcOperatorConfigList contains a list of BootcOperatorConfig.
type BootcOperatorConfigList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []BootcOperatorConfig `json:"items"`
}
1 change: 1 addition & 0 deletions api/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&BootcNode{}, &BootcNodeList{},
&BootcNodePool{}, &BootcNodePoolList{},
&BootcOperatorConfig{}, &BootcOperatorConfigList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
Expand Down
128 changes: 128 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading