From d990d18af11d229660397db5d7bf47bc6c6b4a61 Mon Sep 17 00:00:00 2001 From: Lucas Mundim Date: Thu, 4 Jun 2026 23:43:47 -0300 Subject: [PATCH 1/2] feat(deploy): unify image + chart publishing under Docker Hub OCI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the GAR-coupled `deployments.yml` workflow with a single `docker-publish.yml` modeled on the plainsight-deployment-agent workflow but extended to cover this repo's two-image build (controller + claimer) and to publish the Helm chart as an OCI artifact alongside the images. Changes: - `.github/workflows/deployments.yml` removed; `.github/workflows/ docker-publish.yml` added. The new workflow: - Builds both images (controller, claimer) across amd64 + arm64, pushes-by-digest to `plainsightai/openfilter-pipelines-controller` and `plainsightai/openfilter-pipelines-claimer`. PR triggers build-but-no-push so reviewers get the same compile signal as main without leaking PR digests. - Merges per-arch digests into a multi-arch manifest tagged with the commit SHA, the semver tag (on tag pushes), and `latest` (on tag pushes only — `main` never bumps `latest`, matching the deployment-agent contract). - On `refs/tags/v*.*.*`, packages the Helm chart under `deployment/openfilter-pipelines-controller` and pushes it to `oci://registry-1.docker.io/plainsightai/...` so consumers can `helm install` from Docker Hub OCI without a chart repo. The chart `--version` and `--app-version` both track the git tag (`vX.Y.Z` → `X.Y.Z`), keeping image and chart releases in lock-step. - Preserves the existing deploy + deploy-lab jobs on main pushes (still call the reusable `PlainsightAI/gh-actions-public` workflows; same `service-name` and `manifests-path` contract). - `Makefile`: default `IMG` / `CLAIMER_IMG` flipped from a GAR-prefixed pattern (gated on an env var) to plain `plainsightai/openfilter-pipelines-controller` / `plainsightai/openfilter-pipelines-claimer`. Local builds without any env override now match what CI publishes, so a `make docker-build` produces the same image name a release would. - `deployment/openfilter-pipelines-controller/overrides/{dev,lab, staging,production}.yaml`: `image.repository` and `claimerImage.repository` switched from `us-west1-docker.pkg.dev/plainsightai-prod/oci/...` to `plainsightai/...`. Tag pins (with the `$imagepolicy` annotation) are unchanged — the image-policy automation only rewrites the `tag` field, not the repository. - `deployment/openfilter-pipelines-controller/README.md`: documents the new `helm install ... oci://registry-1.docker.io/plainsightai/ openfilter-pipelines-controller` flow and points readers at the two Docker Hub repos for the container images. The local-chart install instructions are kept as the fallback. Validation: - `helm dependency update` + `helm lint` clean (only the pre-existing icon-recommended INFO). - `helm template` against `overrides/development.yaml` renders `image: plainsightai/openfilter-pipelines-controller:` as expected; no remaining GAR strings in the rendered manifests. Coordination note: there are two of @stwilt's PRs in flight (openfilter-pipelines-controller#62 — DT-164 deployments workflow refactor — and gitops#130 — DT-166 ArgoCD Image Updater annotations). This PR intentionally does NOT touch the gitops side; once both PRs settle we'll align on whether the deploy/deploy-lab jobs here stay or migrate to the new flow. --- .github/workflows/deployments.yml | 136 ----------- .github/workflows/docker-publish.yml | 213 ++++++++++++++++++ Makefile | 17 +- .../openfilter-pipelines-controller/README.md | 22 +- .../overrides/development.yaml | 4 +- .../overrides/lab.yaml | 4 +- .../overrides/production.yaml | 4 +- .../overrides/staging.yaml | 4 +- 8 files changed, 249 insertions(+), 155 deletions(-) delete mode 100644 .github/workflows/deployments.yml create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/deployments.yml b/.github/workflows/deployments.yml deleted file mode 100644 index c06a3f4..0000000 --- a/.github/workflows/deployments.yml +++ /dev/null @@ -1,136 +0,0 @@ -name: Deployments -permissions: - contents: write - id-token: write - -on: - workflow_dispatch: - push: - branches: - - main - paths-ignore: - - 'deployment/**' - -env: - IMAGE_REGISTRY: us-west1-docker.pkg.dev/plainsightai-prod/oci - -jobs: - build: - runs-on: ${{ matrix.platform.runner }} - strategy: - fail-fast: false - matrix: - image: - - name: openfilter-pipelines-controller - context: . - dockerfile: Dockerfile - - name: openfilter-pipelines-claimer - context: . - dockerfile: cmd/claimer/Dockerfile - platform: - - arch: amd64 - runner: blacksmith-2vcpu-ubuntu-2404 - - arch: arm64 - runner: blacksmith-2vcpu-ubuntu-2204-arm - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Authenticate to Google Cloud - uses: PlainsightAI/gh-actions-public/google-cloud-auth@main - with: - environment: production - - - name: Configure Docker for GAR - shell: bash - run: gcloud auth configure-docker us-west1-docker.pkg.dev --quiet - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Build and push by digest - id: build - uses: docker/build-push-action@v6 - with: - context: ${{ matrix.image.context }} - file: ${{ matrix.image.dockerfile }} - platforms: linux/${{ matrix.platform.arch }} - cache-from: type=gha,scope=${{ matrix.image.name }}-${{ matrix.platform.arch }} - cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}-${{ matrix.platform.arch }} - outputs: type=image,name=${{ env.IMAGE_REGISTRY }}/${{ matrix.image.name }},push-by-digest=true,name-canonical=true,push=true - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ matrix.image.name }}-${{ matrix.platform.arch }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - needs: build - runs-on: blacksmith-2vcpu-ubuntu-2404 - strategy: - fail-fast: false - matrix: - image: - - openfilter-pipelines-controller - - openfilter-pipelines-claimer - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests - pattern: digests-${{ matrix.image }}-* - merge-multiple: true - - - name: Authenticate to Google Cloud - uses: PlainsightAI/gh-actions-public/google-cloud-auth@main - with: - environment: production - - - name: Configure Docker for GAR - shell: bash - run: gcloud auth configure-docker us-west1-docker.pkg.dev --quiet - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Create multi-arch manifest - working-directory: /tmp/digests - run: | - docker buildx imagetools create \ - --tag "${IMAGE_REGISTRY}/${{ matrix.image }}:${{ github.sha }}" \ - $(printf "${IMAGE_REGISTRY}/${{ matrix.image }}@sha256:%s " *) - - - name: Inspect manifest - run: | - docker buildx imagetools inspect \ - "${IMAGE_REGISTRY}/${{ matrix.image }}:${{ github.sha }}" - - deploy: - needs: merge - uses: PlainsightAI/gh-actions-public/.github/workflows/deploy-service.yaml@main - with: - service-name: openfilter-pipelines-controller - manifests-path: deployment - secrets: - GH_BOT_USER_PAT: ${{ secrets.GH_BOT_USER_PAT }} - - deploy-lab: - needs: deploy - runs-on: ubuntu-latest - steps: - - name: Deploy to Lab - uses: PlainsightAI/gh-actions-public/deploy-env@main - with: - environment: lab - service-name: openfilter-pipelines-controller - manifests-path: deployment - gh-bot-user-pat: ${{ secrets.GH_BOT_USER_PAT }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..60b3772 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,213 @@ +name: Build and Push Docker Image + +on: + workflow_dispatch: + push: + branches: [main] + tags: ["v*.*.*"] + paths-ignore: + - "deployment/**" + pull_request: + branches: [main] + paths-ignore: + - "deployment/**" + +permissions: + contents: write + id-token: write + +env: + DOCKERHUB_USERNAME: plainsightai + +jobs: + build-and-push: + strategy: + fail-fast: false + matrix: + image: + - { name: openfilter-pipelines-controller, dockerfile: Dockerfile, context: . } + - { name: openfilter-pipelines-claimer, dockerfile: cmd/claimer/Dockerfile, context: . } + arch: + - { name: amd64, runner: blacksmith-2vcpu-ubuntu-2404 } + - { name: arm64, runner: blacksmith-2vcpu-ubuntu-2204-arm } + runs-on: ${{ matrix.arch.runner }} + permissions: + contents: read + steps: + - name: Validate secrets configuration + if: github.event_name != 'pull_request' + run: | + if [ -z "${{ secrets.DOCKERHUB_ACCESS_TOKEN }}" ]; then + echo "ERROR: DOCKERHUB_ACCESS_TOKEN secret not configured" + echo "Add secret via: Settings > Secrets and variables > Actions" + exit 1 + fi + + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Log in to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + - name: Extract image labels + id: meta + uses: docker/metadata-action@v6 + with: + images: plainsightai/${{ matrix.image.name }} + + - name: Build and push image by digest + id: build + uses: docker/build-push-action@v7 + with: + context: ${{ matrix.image.context }} + file: ${{ matrix.image.dockerfile }} + platforms: linux/${{ matrix.arch.name }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=plainsightai/${{ matrix.image.name }},push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} + cache-from: type=gha,scope=${{ matrix.image.name }}-${{ matrix.arch.name }} + cache-to: type=gha,mode=max,scope=${{ matrix.image.name }}-${{ matrix.arch.name }} + + - name: Export digest + if: github.event_name != 'pull_request' + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.image.name }}-${{ matrix.arch.name }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + needs: build-and-push + if: github.event_name != 'pull_request' + runs-on: blacksmith-2vcpu-ubuntu-2404 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + image: + - openfilter-pipelines-controller + - openfilter-pipelines-claimer + steps: + - name: Validate secrets configuration + run: | + if [ -z "${{ secrets.DOCKERHUB_ACCESS_TOKEN }}" ]; then + echo "ERROR: DOCKERHUB_ACCESS_TOKEN secret not configured" + echo "Add secret via: Settings > Secrets and variables > Actions" + exit 1 + fi + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-${{ matrix.image }}-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Log in to DockerHub + uses: docker/login-action@v4 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} + + - name: Extract metadata (tags) + id: meta + uses: docker/metadata-action@v6 + with: + images: plainsightai/${{ matrix.image }} + tags: | + type=ref,event=pr + type=sha,format=long,prefix= + type=semver,pattern={{version}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + + - name: Create multi-arch manifest + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'plainsightai/${{ matrix.image }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect plainsightai/${{ matrix.image }}:${{ steps.meta.outputs.version }} + + publish-chart: + needs: merge + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Extract version + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT + + - name: Set up Helm + uses: azure/setup-helm@v5 + + - name: Add chart dependency repos + run: | + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo update + + - name: Update Helm dependencies + run: | + cd deployment/openfilter-pipelines-controller + helm dependency update + + - name: Log in to DockerHub (Helm OCI) + run: echo "${{ secrets.DOCKERHUB_ACCESS_TOKEN }}" | helm registry login registry-1.docker.io -u ${{ env.DOCKERHUB_USERNAME }} --password-stdin + + - name: Package Helm chart + run: | + helm package deployment/openfilter-pipelines-controller \ + --version ${{ steps.version.outputs.version }} \ + --app-version ${{ steps.version.outputs.version }} + + - name: Push Helm chart to DockerHub OCI + run: | + helm push openfilter-pipelines-controller-${{ steps.version.outputs.version }}.tgz \ + oci://registry-1.docker.io/plainsightai + + deploy: + needs: merge + if: github.ref == 'refs/heads/main' + uses: PlainsightAI/gh-actions-public/.github/workflows/deploy-service.yaml@main + with: + service-name: openfilter-pipelines-controller + manifests-path: deployment + secrets: inherit + + deploy-lab: + needs: deploy + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - name: Deploy to Lab + uses: PlainsightAI/gh-actions-public/deploy-env@main + with: + environment: lab + service-name: openfilter-pipelines-controller + manifests-path: deployment + gh-bot-user-pat: ${{ secrets.GH_BOT_USER_PAT }} diff --git a/Makefile b/Makefile index 9fb8814..ca738ad 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ -# Image URL to use all building/pushing image targets. -# IMAGE_REGISTRY is empty by default so `make docker-build` / `make publish-image` -# produce plain, locally-taggable images for OSS contributors. CI (the -# publish-docker-image reusable action) sets IMAGE_REGISTRY — or IMG/CLAIMER_IMG -# directly — via env to push to the Plainsight registry. -IMAGE_REGISTRY ?= +# Image URLs to use for all building/pushing image targets. +# Defaults pin to Docker Hub under the plainsightai org — that is where both +# images are published from the docker-publish.yml workflow and where +# image-policy-applier looks up tags for the env overrides. Override IMG / +# CLAIMER_IMG directly if you need to push elsewhere. +IMAGE = plainsightai/openfilter-pipelines-controller +CLAIMER_IMAGE = plainsightai/openfilter-pipelines-claimer VERSION ?= $(shell git rev-parse --short HEAD) -IMG ?= $(if $(IMAGE_REGISTRY),$(IMAGE_REGISTRY)/)openfilter-pipelines-controller:$(VERSION) -CLAIMER_IMG ?= $(if $(IMAGE_REGISTRY),$(IMAGE_REGISTRY)/)openfilter-pipelines-claimer:$(VERSION) +IMG ?= $(IMAGE):$(VERSION) +CLAIMER_IMG ?= $(CLAIMER_IMAGE):$(VERSION) # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) diff --git a/deployment/openfilter-pipelines-controller/README.md b/deployment/openfilter-pipelines-controller/README.md index 834bd94..d6b5686 100644 --- a/deployment/openfilter-pipelines-controller/README.md +++ b/deployment/openfilter-pipelines-controller/README.md @@ -9,10 +9,26 @@ A Kubernetes operator for managing Pipeline custom resources in the `filter.plai ## Installation -The chart is not published to a Helm repository. Install it from this -repository's local copy under `deployment/openfilter-pipelines-controller`. +### Install from Docker Hub (recommended) -### Install from local chart +The chart is published as an OCI artifact to Docker Hub. Helm 3.8+ supports OCI +directly — no `helm repo add` is needed: + +```bash +helm install openfilter-pipelines-controller \ + oci://registry-1.docker.io/plainsightai/openfilter-pipelines-controller \ + --version 0.5.1 +``` + +Container images: +[`plainsightai/openfilter-pipelines-controller`](https://hub.docker.com/r/plainsightai/openfilter-pipelines-controller) +and +[`plainsightai/openfilter-pipelines-claimer`](https://hub.docker.com/r/plainsightai/openfilter-pipelines-claimer). + +### Install from a local chart copy + +You can also install directly from this repository's local copy under +`deployment/openfilter-pipelines-controller`. ```bash # Install with default values diff --git a/deployment/openfilter-pipelines-controller/overrides/development.yaml b/deployment/openfilter-pipelines-controller/overrides/development.yaml index c8e50ac..33e2d12 100644 --- a/deployment/openfilter-pipelines-controller/overrides/development.yaml +++ b/deployment/openfilter-pipelines-controller/overrides/development.yaml @@ -1,8 +1,8 @@ image: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-controller + repository: plainsightai/openfilter-pipelines-controller tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "development:openfilter-pipelines-controller:tag"} claimerImage: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-claimer + repository: plainsightai/openfilter-pipelines-claimer tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "development:openfilter-pipelines-controller:tag"} telemetry: exporterType: otlp diff --git a/deployment/openfilter-pipelines-controller/overrides/lab.yaml b/deployment/openfilter-pipelines-controller/overrides/lab.yaml index 5d317cd..3640d17 100644 --- a/deployment/openfilter-pipelines-controller/overrides/lab.yaml +++ b/deployment/openfilter-pipelines-controller/overrides/lab.yaml @@ -1,8 +1,8 @@ image: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-controller + repository: plainsightai/openfilter-pipelines-controller tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "lab:openfilter-pipelines-controller:tag"} claimerImage: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-claimer + repository: plainsightai/openfilter-pipelines-claimer tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "lab:openfilter-pipelines-controller:tag"} telemetry: # Lab has no monitoring overlay (no controllers/monitoring/overrides/lab.yaml), diff --git a/deployment/openfilter-pipelines-controller/overrides/production.yaml b/deployment/openfilter-pipelines-controller/overrides/production.yaml index 00a328c..8355e11 100644 --- a/deployment/openfilter-pipelines-controller/overrides/production.yaml +++ b/deployment/openfilter-pipelines-controller/overrides/production.yaml @@ -1,8 +1,8 @@ image: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-controller + repository: plainsightai/openfilter-pipelines-controller tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "production:openfilter-pipelines-controller:tag"} claimerImage: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-claimer + repository: plainsightai/openfilter-pipelines-claimer tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "production:openfilter-pipelines-controller:tag"} resources: requests: diff --git a/deployment/openfilter-pipelines-controller/overrides/staging.yaml b/deployment/openfilter-pipelines-controller/overrides/staging.yaml index 2041a54..04145b8 100644 --- a/deployment/openfilter-pipelines-controller/overrides/staging.yaml +++ b/deployment/openfilter-pipelines-controller/overrides/staging.yaml @@ -1,8 +1,8 @@ image: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-controller + repository: plainsightai/openfilter-pipelines-controller tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "staging:openfilter-pipelines-controller:tag"} claimerImage: - repository: us-west1-docker.pkg.dev/plainsightai-prod/oci/openfilter-pipelines-claimer + repository: plainsightai/openfilter-pipelines-claimer tag: "62146d3f171083e027f2638fee27db767d50b4ff" # {"$imagepolicy": "staging:openfilter-pipelines-controller:tag"} telemetry: exporterType: otlp From 435e01ee31fbfa26d2d8c9ee7a6141c71d6ed47e Mon Sep 17 00:00:00 2001 From: Lucas Mundim Date: Fri, 5 Jun 2026 18:50:57 -0300 Subject: [PATCH 2/2] chore: address review nits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy-lab gains an explicit `permissions: contents: read` block so it stops inheriting the workflow-level `contents: write, id-token: write` it never needs. Matches the other jobs that scope down. - README chart-install snippet swaps the hard-coded `--version 0.5.1` for a `` placeholder plus a note that chart tags match the git release tags (`vX.Y.Z` → `X.Y.Z`), so the docs do not silently rot to a stale pin on the next release. --- .github/workflows/docker-publish.yml | 2 ++ deployment/openfilter-pipelines-controller/README.md | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 60b3772..2f5f640 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -203,6 +203,8 @@ jobs: needs: deploy if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Deploy to Lab uses: PlainsightAI/gh-actions-public/deploy-env@main diff --git a/deployment/openfilter-pipelines-controller/README.md b/deployment/openfilter-pipelines-controller/README.md index d6b5686..75e6dbe 100644 --- a/deployment/openfilter-pipelines-controller/README.md +++ b/deployment/openfilter-pipelines-controller/README.md @@ -17,9 +17,12 @@ directly — no `helm repo add` is needed: ```bash helm install openfilter-pipelines-controller \ oci://registry-1.docker.io/plainsightai/openfilter-pipelines-controller \ - --version 0.5.1 + --version ``` +Replace `` with the desired release (e.g. `0.5.2`); the published +tags match the git tags on this repository (`vX.Y.Z` → chart `X.Y.Z`). + Container images: [`plainsightai/openfilter-pipelines-controller`](https://hub.docker.com/r/plainsightai/openfilter-pipelines-controller) and