diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2c74e9f..c9c5315 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -20,8 +20,15 @@ on: type: boolean jobs: - build: + tags: runs-on: ubuntu-24.04 + outputs: + # version is the immutable release/build tag; the controller image is always pinned to it. + version: ${{ steps.tags.outputs.version }} + # extra-tags are additional tags (e.g. "latest") that point at the same content as version. + # Both image jobs build from version + extra-tags, so the operator image and the manifests + # artifact stay in lockstep. + extra-tags: ${{ steps.tags.outputs.extra-tags }} steps: - name: Checkout uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 @@ -29,6 +36,41 @@ jobs: # NOTE: We fetch depth so that we can put the right `GIT` reference fetch-depth: 0 ref: ${{ github.ref }} + - name: Compute tags + id: tags + # If `is-development` then suffix the version with `-dev` + # If `is-latest` then also add a `latest` tag + # If not `is-stable` then suffix `-dev` to `latest` + run: | + version="$(git describe --tags --always --dirty --match='v[0-9]*')${{ inputs.is-development && '-dev' || '' }}" + extra="" + if [ "${{ inputs.is-latest }}" = "true" ]; then + extra="latest${{ ! inputs.is-stable && '-dev' || '' }}" + fi + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "extra-tags=${extra}" >> "$GITHUB_OUTPUT" + + operator-image: + needs: tags + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + - name: Compute image refs + id: refs + env: + VERSION: ${{ needs.tags.outputs.version }} + EXTRA_TAGS: ${{ needs.tags.outputs.extra-tags }} + run: | + image="ghcr.io/${{ github.repository }}" + refs="${image}:${VERSION}" + for tag in $EXTRA_TAGS; do + refs="${refs},${image}:${tag}" + done + echo "refs=${refs}" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Login to the registry @@ -37,26 +79,46 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Compute tags - id: tags - # If `is-development` then suffix tag with `-dev` - # If `is-latest` then add `latest` tag - # If not `is-stable` then suffix `-dev` to `latest - run: | - version="$(git describe --tags --always --dirty --match='v[0-9]*')${{ inputs.is-development && '-dev' || '' }}" - tags="ghcr.io/${{ github.repository }}:${version}" - if [ "${{ inputs.is-latest }}" = "true" ]; then - tags="$tags,ghcr.io/${{ github.repository }}:latest${{ ! inputs.is-stable && '-dev' || '' }}" - fi - echo "tags=$tags" >> $GITHUB_OUTPUT - echo "version=$version" >> $GITHUB_OUTPUT - name: Build image uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . build-args: - VERSION=${{ steps.tags.outputs.version }} + VERSION=${{ needs.tags.outputs.version }} push: true - tags: ${{ steps.tags.outputs.tags }} + tags: ${{ steps.refs.outputs.refs }} cache-from: type=gha,scope=crl-operator cache-to: type=gha,mode=max,scope=crl-operator + + manifests-image: + needs: tags + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.ref }} + - name: Set up Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + - name: Login to the registry + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + # Publishes the config/ tree as an OCI artifact (ghcr.io/-manifests), pinned to the + # versioned operator image. Extra tags (e.g. latest) point at the same artifact, so they keep + # pinning that version rather than a floating image tag. flux reads the credentials written + # by docker/login-action. + - name: Push manifests OCI artifact + env: + VERSION: ${{ needs.tags.outputs.version }} + EXTRA_TAGS: ${{ needs.tags.outputs.extra-tags }} + run: | + make push-manifests \ + IMG=ghcr.io/${{ github.repository }}:${VERSION} \ + MANIFESTS_IMG=ghcr.io/${{ github.repository }}-manifests \ + VERSION="${VERSION}" \ + MANIFESTS_EXTRA_TAGS="${EXTRA_TAGS}" diff --git a/Makefile b/Makefile index ad8d764..a6a254c 100644 --- a/Makefile +++ b/Makefile @@ -50,8 +50,10 @@ endif # This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. # renovate: datasource=github-releases depName=operator-framework/operator-sdk OPERATOR_SDK_VERSION ?= v1.41.1 +# REGISTRY is the registry and namespace for the operator's published images and artifacts. +REGISTRY ?= ghcr.io/scality # Image URL to use all building/pushing image targets -IMG ?= controller:latest +IMG ?= $(REGISTRY)/crl-operator:$(VERSION) # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -377,3 +379,47 @@ catalog-build: opm ## Build a catalog image. .PHONY: catalog-push catalog-push: ## Push a catalog image. $(MAKE) docker-push IMG=$(CATALOG_IMG) + +##@ Flux OCI manifests +# Everything below publishes the operator's config/ tree as an OCI artifact that Flux can +# consume directly (OCIRepository + Kustomization). It is intentionally kept separate from the +# operator-sdk / kubebuilder scaffolding above. + +# MANIFESTS_IMG is the OCI artifact repository (no tag) for the config/ bundle. +MANIFESTS_IMG ?= $(REGISTRY)/crl-operator-manifests +# MANIFESTS_EXTRA_TAGS are extra tags to also point at the published artifact (e.g. "latest"), +# space-separated. They reference the SAME artifact, so - like VERSION - they pin the controller +# image at IMG (a specific version), never a floating tag: a `latest` manifests bundle must keep +# pinning the controller version it was built for, otherwise it would break on the next release. +MANIFESTS_EXTRA_TAGS ?= + +FLUX ?= $(LOCALBIN)/flux +# renovate: datasource=github-releases depName=fluxcd/flux2 +FLUX_VERSION ?= v2.8.8 + +.PHONY: flux +flux: ## Download flux CLI locally if necessary. +ifeq (,$(wildcard $(FLUX))) +ifeq (,$(shell which flux 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(FLUX)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSL https://github.com/fluxcd/flux2/releases/download/$(FLUX_VERSION)/flux_$(patsubst v%,%,$(FLUX_VERSION))_$${OS}_$${ARCH}.tar.gz | tar -xzf - -C $(LOCALBIN) flux ;\ + chmod +x $(FLUX) ;\ + } +else +FLUX = $(shell which flux) +endif +endif + +.PHONY: push-manifests +push-manifests: kustomize flux ## Publish config/ as an OCI artifact for Flux, pinned to IMG (a specific version). + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(FLUX) push artifact oci://$(MANIFESTS_IMG):$(VERSION) \ + --path=./config \ + --source="$(shell git config --get remote.origin.url)" \ + --revision="$(VERSION)@sha1:$(shell git rev-parse HEAD)" + for tag in $(MANIFESTS_EXTRA_TAGS); do \ + $(FLUX) tag artifact oci://$(MANIFESTS_IMG):$(VERSION) --tag $$tag ; \ + done diff --git a/README.md b/README.md index d891754..50df624 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,45 @@ make uninstall make undeploy ``` +### OCI manifests artifact + +Each build also publishes the `config/` tree as an OCI artifact +(`ghcr.io/scality/crl-operator-manifests:`), pinned to the matching +controller image, so Flux can deploy crl-operator straight from it: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: crl-operator + namespace: flux-system +spec: + interval: 1h + url: oci://ghcr.io/scality/crl-operator-manifests + ref: + tag: latest +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: crl-operator + namespace: flux-system +spec: + interval: 1h + sourceRef: + kind: OCIRepository + name: crl-operator + path: ./default + prune: true + wait: true +``` + +To publish the artifact manually (e.g. from a branch build): + +```sh +make push-manifests VERSION= +``` + ## Contributing See [contributing](CONTRIBUTING.md) for details.