Skip to content
Merged
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
175 changes: 175 additions & 0 deletions .github/workflows/conformance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
name: Conformance

on:
schedule:
- cron: '0 4 * * *' # 04:00 UTC nightly
push:
tags:
- 'v*'
pull_request:
paths:
- 'test/conformance/**'
- 'internal/**'
- 'api/**'
- '.github/workflows/conformance.yaml'
workflow_dispatch:

jobs:
negative:
name: Negative (schema and CEL deny paths)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: helm/kind-action@v1
with:
cluster_name: paperclip-conformance
- name: Install CRDs
run: make install
- name: Run negative conformance
env:
KUBECONFIG: /home/runner/.kube/config
run: make conformance-negative

# Advisory on PRs until the kind conformance harness is verified stable for
# paperclip's managed-DB workload; flip continue-on-error off once green.
# See https://github.com/paperclipinc/paperclip-operator/issues (conformance
# harness hardening follow-up).
# These jobs build/load the operator image, deploy the controller-manager, and
# run operator-dependent conformance categories on kind. The Negative job
# above is API-server-only and stays required/blocking.
idempotency:
name: Idempotency
runs-on: ubuntu-latest
timeout-minutes: 45
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: helm/kind-action@v1
with:
cluster_name: paperclip-conformance
- name: Build and load operator image
run: |
make docker-build IMG=paperclip-operator:dev
kind load docker-image paperclip-operator:dev --name paperclip-conformance
- name: Deploy operator
run: make deploy IMG=paperclip-operator:dev
- name: Wait for operator to be Available
# make deploy only applies manifests and returns immediately; the
# controller-manager Pod still has to pull its image, start, and win
# leader election before it reconciles anything. Block here until the
# Deployment is Available so the conformance suite never races the
# operator. Mirrors openclaw-operator's `helm install --wait`.
run: |
kubectl rollout status \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=10m
kubectl wait --for=condition=Available \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=5m
- name: Run idempotency conformance
env:
KUBECONFIG: /home/runner/.kube/config
run: make conformance-idempotency

gitops-coexistence:
name: GitOps coexistence
runs-on: ubuntu-latest
timeout-minutes: 45
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: helm/kind-action@v1
with:
cluster_name: paperclip-conformance
- name: Build and load operator image
run: |
make docker-build IMG=paperclip-operator:dev
kind load docker-image paperclip-operator:dev --name paperclip-conformance
- name: Deploy operator
run: make deploy IMG=paperclip-operator:dev
- name: Wait for operator to be Available
run: |
kubectl rollout status \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=10m
kubectl wait --for=condition=Available \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=5m
- name: Run gitops coexistence conformance
env:
KUBECONFIG: /home/runner/.kube/config
run: make conformance-gitops

failure-modes:
name: Failure modes
runs-on: ubuntu-latest
timeout-minutes: 45
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: helm/kind-action@v1
with:
cluster_name: paperclip-conformance
- name: Build and load operator image
run: |
make docker-build IMG=paperclip-operator:dev
kind load docker-image paperclip-operator:dev --name paperclip-conformance
- name: Deploy operator
run: make deploy IMG=paperclip-operator:dev
- name: Wait for operator to be Available
run: |
kubectl rollout status \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=10m
kubectl wait --for=condition=Available \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=5m
- name: Run failure-injection conformance
env:
KUBECONFIG: /home/runner/.kube/config
run: make conformance-failure

upgrade:
name: Upgrade path
runs-on: ubuntu-latest
timeout-minutes: 60
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: helm/kind-action@v1
with:
cluster_name: paperclip-conformance
- name: Build and load operator image
run: |
make docker-build IMG=paperclip-operator:dev
kind load docker-image paperclip-operator:dev --name paperclip-conformance
- name: Deploy operator
run: make deploy IMG=paperclip-operator:dev
- name: Wait for operator to be Available
run: |
kubectl rollout status \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=10m
kubectl wait --for=condition=Available \
deploy -l control-plane=controller-manager \
-n paperclip-operator-system --timeout=5m
- name: Run upgrade-path conformance
env:
KUBECONFIG: /home/runner/.kube/config
run: make conformance-upgrade
50 changes: 50 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Docs Build

on:
push:
branches: [main]
paths:
- 'docs/**'
- 'docs-site/**'
- 'api/v1alpha1/**'
- '.github/workflows/docs.yaml'
pull_request:
paths:
- 'docs/**'
- 'docs-site/**'
- 'api/v1alpha1/**'
- '.github/workflows/docs.yaml'
workflow_dispatch:

permissions:
contents: read

jobs:
build:
name: Build docs site (strict)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true

- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: docs-site/requirements.txt

- name: Regenerate API reference and verify it is committed
run: |
make api-docs
if ! git diff --quiet -- docs/api-reference.md; then
echo "::error::docs/api-reference.md is out of date. Run 'make api-docs' and commit the result."
git diff -- docs/api-reference.md
exit 1
fi

- name: Build docs site (strict)
run: make docs-build
44 changes: 44 additions & 0 deletions .github/workflows/verify-signing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Verify Signing (drift detection)

on:
schedule:
# Mondays at 13:00 UTC. Catches infra-broken signatures before users hit them.
- cron: '0 13 * * 1'
workflow_dispatch:

jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v6

- name: Install Cosign
uses: sigstore/cosign-installer@v3

- name: Install gh
run: |
type -p gh >/dev/null 2>&1 || (sudo apt-get update && sudo apt-get install -y gh)

- name: Verify latest release
id: verify
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set +e
make verify-signing
rc=$?
echo "rc=$rc" >> "$GITHUB_OUTPUT"
exit 0

- name: Open issue on failure
if: steps.verify.outputs.rc != '0'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh issue create \
--title "[drift] Cosign verify failed for latest release" \
--label infra-broken \
--body "The weekly verify-signing workflow failed. The latest release's image is no longer cosign-verifiable. Investigate: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ go.work
*.swp
*.swo
*~

# docs site build artifacts
docs-site/.venv/
docs-site/site/
docs-site/__pycache__/
73 changes: 73 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,50 @@ cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
scorecard: operator-sdk ## Run operator-sdk scorecard tests.
$(OPERATOR_SDK) scorecard bundle --wait-time 120s

.PHONY: verify-signing
verify-signing: ## Verify the latest published release is Cosign-signed and SBOM-attested.
@VERSION=$$(gh release view --repo paperclipinc/paperclip-operator --json tagName --jq .tagName); \
IMAGE="ghcr.io/paperclipinc/paperclip-operator:$${VERSION}"; \
echo "Verifying $${IMAGE}..."; \
cosign verify "$${IMAGE}" \
--certificate-identity-regexp 'https://github.com/paperclipinc/paperclip-operator/.github/workflows/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com >/dev/null || { echo "::error::signature verification failed for $${IMAGE}"; exit 1; }; \
echo "Verifying SBOM attestation..."; \
cosign verify-attestation "$${IMAGE}" --type spdxjson \
--certificate-identity-regexp 'https://github.com/paperclipinc/paperclip-operator/.github/workflows/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com >/dev/null || { echo "::error::SBOM attestation verification failed for $${IMAGE}"; exit 1; }; \
echo "OK: $${IMAGE} is signed and SBOM-attested."

.PHONY: bench
bench: ## Run benchmarks for resource builders.
go test ./internal/resources/ -bench=. -benchmem -run=^$$ -count=1

##@ Conformance

.PHONY: conformance
conformance: ## Run the full conformance suite. Requires KUBECONFIG to a cluster with the operator installed.
cd test/conformance && go test -v -timeout 60m -ginkgo.v ./...

.PHONY: conformance-negative
conformance-negative: ## Run the negative (schema/CEL deny) conformance category.
cd test/conformance && go test -v -timeout 10m -ginkgo.v -ginkgo.focus="negative" ./...

.PHONY: conformance-idempotency
conformance-idempotency: ## Run the idempotency conformance category.
cd test/conformance && go test -v -timeout 30m -ginkgo.v -ginkgo.focus="idempotency" ./...

.PHONY: conformance-upgrade
conformance-upgrade: ## Run the upgrade-path conformance category.
cd test/conformance && go test -v -timeout 60m -ginkgo.v -ginkgo.focus="upgrade-path matrix" ./...

.PHONY: conformance-gitops
conformance-gitops: ## Run the GitOps coexistence conformance category.
cd test/conformance && go test -v -timeout 20m -ginkgo.v -ginkgo.focus="GitOps coexistence" ./...

.PHONY: conformance-failure
conformance-failure: ## Run the failure-injection conformance category.
cd test/conformance && go test -v -timeout 20m -ginkgo.v -ginkgo.focus="failure injection" ./...

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT) run
Expand All @@ -165,6 +205,32 @@ lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
lint-config: golangci-lint ## Verify golangci-lint linter configuration
$(GOLANGCI_LINT) config verify

##@ Docs

.PHONY: api-docs
api-docs: manifests crd-ref-docs ## Regenerate docs/api-reference.md from CRD types.
$(CRD_REF_DOCS) \
--config docs-site/crd-ref-docs.yaml \
--source-path api/v1alpha1 \
--output-path docs/api-reference.md \
--renderer markdown

.PHONY: docs-venv
docs-venv: docs-site/.venv/bin/activate ## Create the docs-site Python virtualenv.
docs-site/.venv/bin/activate: docs-site/requirements.txt
python3 -m venv docs-site/.venv
docs-site/.venv/bin/pip install --upgrade pip
docs-site/.venv/bin/pip install -r docs-site/requirements.txt
touch docs-site/.venv/bin/activate

.PHONY: docs-serve
docs-serve: docs-venv ## Run the docs site locally (http://127.0.0.1:8000).
docs-site/.venv/bin/mkdocs serve -f docs-site/mkdocs.yml

.PHONY: docs-build
docs-build: docs-venv ## Build the docs site (strict mode -- fails on broken links / warnings).
docs-site/.venv/bin/mkdocs build --strict -f docs-site/mkdocs.yml

##@ Build

.PHONY: build
Expand Down Expand Up @@ -246,10 +312,12 @@ KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
CRD_REF_DOCS ?= $(LOCALBIN)/crd-ref-docs

## Tool Versions
KUSTOMIZE_VERSION ?= v5.6.0
CONTROLLER_TOOLS_VERSION ?= v0.18.0
CRD_REF_DOCS_VERSION ?= v0.3.0
#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
Expand Down Expand Up @@ -284,6 +352,11 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))

.PHONY: crd-ref-docs
crd-ref-docs: $(CRD_REF_DOCS) ## Download crd-ref-docs locally if necessary.
$(CRD_REF_DOCS): $(LOCALBIN)
$(call go-install-tool,$(CRD_REF_DOCS),github.com/elastic/crd-ref-docs,$(CRD_REF_DOCS_VERSION))

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
Expand Down
Loading
Loading