cfgate.iocfgate v0.2.0-alpha.11 · Release documentation
Contributing to cfgate
Prerequisites
Section titled “Prerequisites”- Go 1.27.1
- mise (task runner and tool manager)
- Docker (container builds and kind clusters)
- sops + age (secrets management)
- A Cloudflare account with API token (see API Token Permissions)
Getting Started
Section titled “Getting Started”git clone https://github.com/cfgate/cfgate.gitcd cfgatemise installmise tasksmise run codegenmise run buildmise run lintTask Reference
Section titled “Task Reference”| Task | Alias | Description |
|---|---|---|
codegen | gen | Generate DeepCopy and CRD manifests |
build | b | Build manager binary with version info |
lint | none | Run golangci-lint |
lint:fix | fix | Run golangci-lint with auto-fix |
format | fmt | Format Go, shell, and workflow YAML; vet Go code |
format:check | none | Check formatting without rewriting files |
manifests | dist | Generate release manifests to dist/ |
test | t | Run unit tests |
test:cover | none | Run unit tests with coverage report |
e2e | none | Run local E2E tests against live Cloudflare API |
e2e:filter | fe2e | Run E2E tests with a Ginkgo --focus filter |
e2e:cleanup | clean | Preview aged orphaned E2E resources; apply requires explicit opt-ins |
test:offline | none | Test cleanup effects, bounded fuzzing, and build/release helpers without live services |
coverage | cov | Run local unit, E2E, merged coverage, and assurance scoring |
coverage:merge | none | Merge unit and E2E coverage into out/coverage/merged.coverprofile |
coverage:report | none | Write out/coverage/merged-summary.txt with totals and file deltas |
coverage:score | none | Write out/reports/assurance-score.json dual-ledger report |
bench | none | Run benchmark suite with allocation stats |
profile:bench | none | Capture CPU and heap profiles for a benchmark package |
profile:view | none | Open the pprof web UI for a captured profile |
profile:export | none | Export text and proto views for a captured profile |
smoke | none | Run a fast local smoke suite |
cluster:create | none | Create dedicated cfgate dev cluster |
cluster:delete | none | Delete cfgate dev cluster |
cluster:status | none | Check cfgate dev cluster status |
local:install | none | Install Gateway API and cfgate CRDs |
local:deploy | none | Deploy controller to current cluster (kustomize) |
local:undeploy | none | Remove controller from current cluster |
local:uninstall | none | Uninstall CRDs from current cluster |
run | none | Run controller locally (outside cluster) |
docker:build | db | Build Docker image |
docker:push | dp | Push Docker image to registry |
docker:buildx | none | Build multi-arch image (amd64 + arm64) |
Secrets Configuration
Section titled “Secrets Configuration”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.
Setting Up Secrets
Section titled “Setting Up Secrets”- Generate an age keypair:
age-keygen -o ~/.config/sops/age/keys.txtThe output includes your public key (starts with age1...). Save it for the next step.
- Configure sops to use your key by editing
.sops.yamlin the repo root:
creation_rules: - age: age1your-public-key-here- Create and encrypt
secrets.enc.yaml:
cat > secrets.enc.yaml <<'EOF'CLOUDFLARE_API_TOKEN: your-api-tokenCLOUDFLARE_ACCOUNT_ID: your-account-idCLOUDFLARE_ZONE_NAME: your-zone.comEOF
sops -e -i secrets.enc.yamlRequired Keys
Section titled “Required Keys”| Key | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN | Cloudflare API token |
CLOUDFLARE_ACCOUNT_ID | Cloudflare account ID |
Additional Release E2E Keys
Section titled “Additional Release E2E Keys”| Key | Purpose |
|---|---|
CLOUDFLARE_ZONE_NAME | Zone for DNS and Access E2E tests |
CLOUDFLARE_IDP_ID | Identity Provider ID for IdP-dependent tests |
CLOUDFLARE_TEST_EMAIL | Email for email rule tests |
CLOUDFLARE_TEST_GROUP | Group for GSuite group rule tests |
Verifying Secrets
Section titled “Verifying Secrets”Validate required variable presence without printing credential values:
mise run e2e:preflightThe preflight requires all six Cloudflare keys listed above for full release coverage. Presence alone does not verify API permissions or expiry.
API Token Permissions
Section titled “API Token Permissions”Create a token at Cloudflare Dashboard > API Tokens with:
| Scope | Permission | Required For |
|---|---|---|
| Account | Cloudflare Tunnel: Edit | Tunnel tests |
| Account | Access: Apps and Policies: Edit | Access tests |
| Account | Access: Service Tokens: Edit | Service token tests |
| Zone | DNS: Edit | DNS tests |
Scope zone permissions to the zone matching CLOUDFLARE_ZONE_NAME.
Testing
Section titled “Testing”See docs/TESTING.md for the full testing guide.
mise run test # unit testsmise run test:offline # offline cleanup, fuzz, and build/release checksmise run test:cover # unit tests with coveragemise run coverage # local unit + E2E + merged coverage + assurance scoremise run coverage:merge # merge unit and E2E profilesmise run coverage:report # write merged coverage summarymise run coverage:score # write dual-ledger assurance scoremise run cluster:create # repo-local helper for a dedicated kind clustermise run e2e # local E2E against live Cloudflare APImise run e2e:cleanup # preview orphaned E2E resourcesmise run bench # benchmark suitemise run smoke # fast local sanity checkNormal 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:
# Path A: broader local stack bootstrapcd ~/production/abaddonmise run 000-colimamise run 001-kind
# Path B: repo-local convenience helpercd ~/production/cfgate/cfgatemise run cluster:createmise 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.
Development Workflow
Section titled “Development Workflow”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.
Making Changes
Section titled “Making Changes”- Create a feature branch from
main - Make changes
- Regenerate CRDs if types changed:
mise run codegen - Lint:
mise run lint - Build:
mise run build - Test:
mise run test - Run local E2E when your change touches reconciler or Cloudflare behavior:
mise run e2e - Submit PR against
main
CRD Changes
Section titled “CRD Changes”When modifying files in api/v1alpha1/, regenerate and reinstall:
mise run codegenmise run local:installRunning the Controller Locally
Section titled “Running the Controller Locally”mise run runThe controller runs outside the cluster but connects via kubeconfig.
Project Structure
Section titled “Project Structure”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 utilitiesRelated Repositories
Section titled “Related Repositories”| Repository | Description |
|---|---|
| cfgate/helm-chart | Helm chart (OCI at oci://ghcr.io/cfgate/charts/cfgate) |
| cfgate/cfgate.io | Project website |
Commits
Section titled “Commits”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 detectiontest(e2e): add multi-zone ownership verificationFor contributor PRs, the maintainer squash-merges with a clean conventional subject line. You do not need to rewrite your branch history.
Changelog
Section titled “Changelog”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.
Code Style
Section titled “Code Style”General
Section titled “General”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.
Logging
Section titled “Logging”Use structured logging via logr (controller-runtime convention). Log at appropriate levels:
log.Info()for reconciler phase transitions and significant state changeslog.V(1).Info()for per-resource operational detaillog.Error()for errors that will cause requeue or degraded status
Comments
Section titled “Comments”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 = 63Do not comment what the code already says.
Doc Comments
Section titled “Doc Comments”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 { ... }Naming
Section titled “Naming”- Match json tags in user-facing references (
sessionDuration, notSessionDuration) - Use descriptive names; avoid single-letter variables outside loop indices
- Constants use
camelCasefor package-level,SCREAMING_SNAKEis not idiomatic Go
Documentation
Section titled “Documentation”Where Things Live
Section titled “Where Things Live”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.
When to Update Docs
Section titled “When to Update Docs”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.
Writing Style
Section titled “Writing Style”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.
License
Section titled “License”By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.