Skip to content

cfgate.iocfgate v0.2.0-alpha.11 · Release documentation

Contributing to cfgate

Terminal window
git clone https://github.com/cfgate/cfgate.git
cd cfgate
mise install
mise tasks
mise run codegen
mise run build
mise run lint
TaskAliasDescription
codegengenGenerate DeepCopy and CRD manifests
buildbBuild manager binary with version info
lintnoneRun golangci-lint
lint:fixfixRun golangci-lint with auto-fix
formatfmtFormat Go, shell, and workflow YAML; vet Go code
format:checknoneCheck formatting without rewriting files
manifestsdistGenerate release manifests to dist/
testtRun unit tests
test:covernoneRun unit tests with coverage report
e2enoneRun local E2E tests against live Cloudflare API
e2e:filterfe2eRun E2E tests with a Ginkgo --focus filter
e2e:cleanupcleanPreview aged orphaned E2E resources; apply requires explicit opt-ins
test:offlinenoneTest cleanup effects, bounded fuzzing, and build/release helpers without live services
coveragecovRun local unit, E2E, merged coverage, and assurance scoring
coverage:mergenoneMerge unit and E2E coverage into out/coverage/merged.coverprofile
coverage:reportnoneWrite out/coverage/merged-summary.txt with totals and file deltas
coverage:scorenoneWrite out/reports/assurance-score.json dual-ledger report
benchnoneRun benchmark suite with allocation stats
profile:benchnoneCapture CPU and heap profiles for a benchmark package
profile:viewnoneOpen the pprof web UI for a captured profile
profile:exportnoneExport text and proto views for a captured profile
smokenoneRun a fast local smoke suite
cluster:createnoneCreate dedicated cfgate dev cluster
cluster:deletenoneDelete cfgate dev cluster
cluster:statusnoneCheck cfgate dev cluster status
local:installnoneInstall Gateway API and cfgate CRDs
local:deploynoneDeploy controller to current cluster (kustomize)
local:undeploynoneRemove controller from current cluster
local:uninstallnoneUninstall CRDs from current cluster
runnoneRun controller locally (outside cluster)
docker:builddbBuild Docker image
docker:pushdpPush Docker image to registry
docker:buildxnoneBuild multi-arch image (amd64 + arm64)

cfgate uses sops with age encryption for local development secrets. E2E and cleanup tasks load secrets.enc.yaml through their task-specific environment; ordinary unit tests do not load Cloudflare credentials.

  1. Generate an age keypair:
Terminal window
age-keygen -o ~/.config/sops/age/keys.txt

The output includes your public key (starts with age1...). Save it for the next step.

  1. Configure sops to use your key by editing .sops.yaml in the repo root:
creation_rules:
- age: age1your-public-key-here
  1. Create and encrypt secrets.enc.yaml:
Terminal window
cat > secrets.enc.yaml <<'EOF'
CLOUDFLARE_API_TOKEN: your-api-token
CLOUDFLARE_ACCOUNT_ID: your-account-id
CLOUDFLARE_ZONE_NAME: your-zone.com
EOF
sops -e -i secrets.enc.yaml
KeyPurpose
CLOUDFLARE_API_TOKENCloudflare API token
CLOUDFLARE_ACCOUNT_IDCloudflare account ID
KeyPurpose
CLOUDFLARE_ZONE_NAMEZone for DNS and Access E2E tests
CLOUDFLARE_IDP_IDIdentity Provider ID for IdP-dependent tests
CLOUDFLARE_TEST_EMAILEmail for email rule tests
CLOUDFLARE_TEST_GROUPGroup for GSuite group rule tests

Validate required variable presence without printing credential values:

Terminal window
mise run e2e:preflight

The preflight requires all six Cloudflare keys listed above for full release coverage. Presence alone does not verify API permissions or expiry.

Create a token at Cloudflare Dashboard > API Tokens with:

ScopePermissionRequired For
AccountCloudflare Tunnel: EditTunnel tests
AccountAccess: Apps and Policies: EditAccess tests
AccountAccess: Service Tokens: EditService token tests
ZoneDNS: EditDNS tests

Scope zone permissions to the zone matching CLOUDFLARE_ZONE_NAME.

See docs/TESTING.md for the full testing guide.

Terminal window
mise run test # unit tests
mise run test:offline # offline cleanup, fuzz, and build/release checks
mise run test:cover # unit tests with coverage
mise run coverage # local unit + E2E + merged coverage + assurance score
mise run coverage:merge # merge unit and E2E profiles
mise run coverage:report # write merged coverage summary
mise run coverage:score # write dual-ledger assurance score
mise run cluster:create # repo-local helper for a dedicated kind cluster
mise run e2e # local E2E against live Cloudflare API
mise run e2e:cleanup # preview orphaned E2E resources
mise run bench # benchmark suite
mise run smoke # fast local sanity check

Normal GitHub Actions CI runs lint, unit tests, build validation, and unit coverage. It does not provision a cluster or run Cloudflare-backed E2E.

Changes under test/ trigger CI, but the live E2E suite is still separate. Manual CI dispatch runs the full lint, unit test, build, and coverage jobs on the selected ref. Pushes to dev retain the reduced lint and coverage checks.

A manual GitHub Actions workflow named Remote Release E2E exists for release-grade remote E2E timing and Codecov upload without publishing release artifacts.

For local coverage work, treat out/coverage/merged.coverprofile as the canonical 100% ledger and out/reports/assurance-score.json as the canonical 200% dual-ledger artifact. In the assurance artifact, possible is the full rubric ceiling and automated_possible is the portion the current script can verify. Today that means behavioral automation tops out at 70/100, not 100/100.

mise run smoke builds bin/manager, requires ./bin/manager --help to exit successfully, then runs the fast package test set. cmd/cleanup is not part of the smoke or release CLI contract in this pass.

Two local bootstrap paths are first-class:

Terminal window
# Path A: broader local stack bootstrap
cd ~/production/abaddon
mise run 000-colima
mise run 001-kind
# Path B: repo-local convenience helper
cd ~/production/cfgate/cfgate
mise run cluster:create

mise run e2e creates a disposable kind cluster by default. To reuse an explicitly selected disposable cluster, set E2E_USE_EXISTING_CLUSTER=true CLUSTER_NAME=<name>. The task checks that its API server is reachable. Default cleanup is scoped to the current run; mise run e2e:cleanup previews aged orphan candidates. See the testing guide before enabling orphan deletion.

Local binary and Docker tasks source hack/build-metadata.sh for the same tag, short commit, UTC build date and optional VERSION_SUFFIX. An exact tag uses its version without the leading v; other commits use <tag>-dev+<commit>, or 0.0.0-dev+<commit> when no tag exists. Release workflows retain their separately validated tag and full source SHA. Local metadata does not certify a clean tree or authorize publication.

Keep reconciliation dependencies explicit. Access publication passes one accessSyncSession through route collection and protection verification while holding the acquired locks. Its observations and read caches belong to that attempt; do not retain them across reconciliations or hide them in context values. An absent session permits ordinary public routes but cannot authorize an Access-required route. Keep shared route acceptance helpers aligned with both status and emitted-configuration tests before adding another resolution layer.

  1. Create a feature branch from main
  2. Make changes
  3. Regenerate CRDs if types changed: mise run codegen
  4. Lint: mise run lint
  5. Build: mise run build
  6. Test: mise run test
  7. Run local E2E when your change touches reconciler or Cloudflare behavior: mise run e2e
  8. Submit PR against main

When modifying files in api/v1alpha1/, regenerate and reinstall:

Terminal window
mise run codegen
mise run local:install
Terminal window
mise run run

The controller runs outside the cluster but connects via kubeconfig.

cfgate/
api/v1alpha1/ CRD type definitions
cmd/
manager/ Controller entrypoint
cleanup/ E2E resource cleanup utility
internal/
accesstags/ Shared Access owner tag helpers
controller/ Reconcilers (tunnel, dns, access, gateway, httproute)
controller/annotations/ Annotation parsing and validation
controller/context/ CRD-to-controller data wrappers
controller/features/ Runtime feature gate detection
controller/status/ Status condition composition
cloudflare/ Cloudflare API client abstraction
cloudflared/ cloudflared config and deployment builders
config/
crd/ Generated CRD manifests
default/ Kustomize overlay for deployment
manager/ Controller deployment resources
rbac/ RBAC resources
test/e2e/ E2E test suite
examples/ Applyable YAML examples
docs/ User-facing reference documentation
hack/ Build utilities
RepositoryDescription
cfgate/helm-chartHelm chart (OCI at oci://ghcr.io/cfgate/charts/cfgate)
cfgate/cfgate.ioProject website

Conventional-style prefixes: feat:, fix:, chore:, ci:, docs:, test:, refactor:, perf:, build:

Subject line in imperative mood, under 72 characters. Body explains why, not what. Bullet points for multi-line bodies.

Scopes are optional; use when the change targets a specific subsystem:

fix(controller): correct DNS record drift detection
test(e2e): add multi-zone ownership verification

For contributor PRs, the maintainer squash-merges with a clean conventional subject line. You do not need to rewrite your branch history.

Release notes are generated via git-cliff from commit history. Configuration is in cliff.toml. Do not edit CHANGELOG.md manually; regenerate it locally with git-cliff when you need to refresh the repo changelog.

Run mise run format and mise run lint before submitting. Tool versions are pinned in mise.toml; install them with mise install. CI and release quality checks run mise run format:check and mise run lint using the same repository configuration. Formatting failures report the command to apply fixes locally; CI does not commit changes to contributor branches.

.golangci.yml enables the standard golangci-lint rules, the existing ginkgolinter checks, and gofmt. Generated Go files are excluded from formatting; regenerate them with mise run codegen. .editorconfig defines editor whitespace settings and the two-space indentation used by shfmt for shell files in hack/ and .github/scripts/. .yamlfmt.yml limits YAML formatting to GitHub workflows and the lint/formatter configuration files. Generated manifests, encrypted secrets, and examples are outside that YAML formatting scope. Shell embedded in mise tasks or YAML blocks is not automatically reformatted by these commands.

The README’s golangci-lint badge links to the tool documentation. The existing CI status badge reports the automated checks; there is no external quality grade.

Follow existing patterns in the codebase. When in doubt, match the surrounding code.

Use structured logging via logr (controller-runtime convention). Log at appropriate levels:

  • log.Info() for reconciler phase transitions and significant state changes
  • log.V(1).Info() for per-resource operational detail
  • log.Error() for errors that will cause requeue or degraded status

Default to no comments. Code should be self-explanatory through naming and structure. Comment when:

  • The “why” is non-obvious (a workaround, an API quirk, a spec requirement)
  • The behavior has surprising side effects
  • A constant comes from an external specification
// RFC 1035 section 2.3.4: DNS labels must not exceed 63 octets.
const maxDNSLabelLength = 63

Do not comment what the code already says.

Every exported type, function, and method gets a doc comment. The first sentence is a summary used by godoc. Cover what the symbol does, input expectations, side effects, and error conditions.

Write doc comments with future doc-gen tooling in mind: structured, factual, no marketing language.

// TunnelService manages Cloudflare Tunnel lifecycle operations including
// creation, configuration updates, and deletion. It uses idempotent
// ensure semantics; calling Ensure on an existing tunnel updates its
// configuration rather than creating a duplicate.
type TunnelService struct { ... }
  • Match json tags in user-facing references (sessionDuration, not SessionDuration)
  • Use descriptive names; avoid single-letter variables outside loop indices
  • Constants use camelCase for package-level, SCREAMING_SNAKE is not idiomatic Go

README.md is the hub document: CRD tables, feature matrix, annotation summary, quickstart, and links to docs/. Keep it scannable.

docs/*.md files are deep reference, one file per topic. These are the source of truth for user-facing field documentation.

CONTRIBUTING.md covers development workflow, code style, and writing conventions. Not user-facing.

examples/ contains applyable YAML. Every example directory should work with kubectl apply -k examples/<name> against a cluster with cfgate installed.

CRD type changes (api/v1alpha1/*_types.go) and annotation changes (internal/controller/annotations/annotations.go) are the two sources of truth. When you change either, update the corresponding docs/ file in the same commit. Treat it like running mise run codegen; it is part of the change, not a follow-up.

Use complete sentences with natural compound structure. Technical reference tone; the voice of a well-written man page, not a keynote.

Prohibited in prose: em-dashes, en-dashes, double-hyphens. Use semicolons, commas, or colons instead. Double-hyphens in CLI flags (--leader-elect), YAML comments, and code are fine.

Avoid: superlatives (“powerful,” “elegant,” “seamless”), fragment-sentence drama (“One operator. Three concerns.”), and long-then-short restatement. Say it once, clearly, and move on.

When documenting a field, state what it does, valid values, and the default, in that order. Skip explanation of why unless the behavior is surprising.

YAML snippets in docs must parse cleanly. Introduce code blocks with one sentence explaining when or why to use them, then let the code speak.

By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.