gitops-commit
Go module that lands rendered files in GitOps repositories as the person: Flux provenance, SOPS encryption for the repository's recipients, branch, commit, pull request and merge.
Packages
provenance — where a target's files live. Flux.Resolve(namespace, name) follows a Flux Kustomization to its GitRepository and returns the GitHub repository, branch and directory (spec.path), and Flux.FindKustomization the Kustomization itself with its service account, interval, timeout, prune and keys; Explicit(repository, branch, directory) for a location the caller names. Input is data the caller has read (typed structs or decoded objects via Flux.Add); the package talks to no cluster.
sopsenc — encryption of a repository's secret files. Which files are secret files follows the repository's .sops.yaml: a file a creation rule's path_regex matches is SOPS-encrypted for that rule's age recipients whatever its name (secrets/api-key.yaml by its directory), public keys only; a file named *secret*/*credential* — the naming convention callers follow — that no rule covers is refused, never written in plaintext. Values declared as Generated are drawn from crypto/rand at commit time — once per name, so two files sharing a name carry the same value — and exist only in the encrypted output. A declaration names the encoding its placeholder receives: the value as it is, or its base64 where the consumer decodes the leaf (a Secret's data:, a chart that copies the value under data: unencoded), so one value lands raw in one file and encoded in another. Three kinds: base64 (random bytes), alphanumeric (random characters) and keypair-es256 (an ECDSA P-256 key pair whose halves are PEM — PKCS #8 private, SubjectPublicKeyInfo public — written as one-line YAML scalars; each declaration names the half its placeholder receives, the private half in secret files only, the public half in plain files too). A secret file that already exists in the repository is left untouched, never re-generated. The module holds no private key and has no decryption code path; a test fails the build if one appears.
layout — where a writer's files go and what one write changes there. A Directory is the writer's own directory (cluster-manager, model-manager) under the directory a Flux Kustomization builds from, one file per object (ObjectFile: a Secret in a secret file of its own) and its own kustomization.yaml listing them; the parent's kustomization.yaml, where there is one, lists the directory, so Flux builds it with or without one. Build decides a write against the base branch — every file added, updated, removed or unchanged, a new secret file encrypted for the repository's .sops.yaml recipients and one that exists never re-encrypted, both kustomization files edited on their YAML nodes with comments kept and removed with the last file; a secret file the repository cannot take encrypted is a SecretError, never plaintext — and the plan's Change is what commit.Open lands. A re-run whose files are on the base changes nothing. A new workload cluster (Cluster, from NewCluster over the Flux Kustomization that reconciles its Organization) goes where a gitops-template repository keeps every cluster: its objects in organizations/<org>/workload-clusters/<cluster>/ with their own kustomization.yaml, the per-cluster Flux Kustomization <installation>-clusters-<cluster> in <cluster>.yaml beside it (the Organization's source, service account, interval, timeout and keys, prune: true, no postBuild, so ${…} in a person's values stays as written), and its entry in workload-clusters/kustomization.yaml, created when absent. BuildCluster refuses a root without organizations/<org>/, naming the path; RemoveCluster removes the directory, the Kustomization file and the entry, and ClusterPlan.Prune says whether the merge alone removes the cluster — without it the caller's live step is deleting the per-cluster Kustomization, and Flux collects the cluster. A writer that adds to the cluster later (node pools) takes its own Directory under <cluster>/.
commit — landing the files as the person. Open takes a Remote built from the caller's GitHub token (NewGitHub) or from an HTTP client that already carries the caller's identity (NewGitHubWithClient: a GitHub App installation transport, a rotated token file), a Request (head branch, title, body) and one Change per location, and makes for every repository one branch, one commit with all of that repository's files and one pull request whose title and body are the caller's text — an open pull request of the same branch is reused, and a branch an earlier run left behind its base is brought up to date first (the base merged into it as the person, never a force push) or refused with ErrStaleBranch naming the branch and its pull request when the base does not merge in, so files composed from the base's current content never land on a head that lacks it; a path whose content is nil is removed in that commit. ReadFile (the Reader seam of GitHub and Fake) reads a file at a branch's head — the kustomization.yaml a change extends, the .sops.yaml it encrypts for — and answers ErrFileNotFound for a path the branch does not carry; ListFiles (the Lister seam) lists every file under a directory, what a removal of a whole directory takes away. Merge merges as the person and is refused until the caller says approved and every status and check run on the head is green, and when the head is no longer the commit the caller saw; a repository's own protection — the approving review that satisfies enforce_admins and rulesets — applies at the merge call, and no administrative path exists. The Remote also carries the pull-request seams a caller's own flow needs: FindPullRequest (the open pull request of a branch), OpenDraftPullRequest, EnableAutoMerge (arms GitHub's auto-merge with the repository's merge method — the caller's choice, never the module's default), Approve (an approving review as the person, pinned to the head the caller saw), Close (with or without deleting the head branch; a closed pull request is left alone) and Revert (a pull request undoing a merged one: its files inverted in one commit on the revert branch, created at the base's tip or brought up to date with it like any other branch, added files removed, changed files restored, named paths overridden; a second revert adds a commit to the open revert pull request; history is never rewritten). Fake is the in-process remote for tests, the module's and its callers', and implements every seam.
Who imports it
The platform manager, mctl, the cluster manager (with layout, like the model manager and the agent manager that follow its commit mode): one implementation of "render, encrypt, commit, open the pull request, merge on approval" for every GitOps repository the platform writes to.
Identity
The module holds no token, no App and no private key. Every call acts with the token the caller passes in — for the managers, the person's user token of the manager's GitHub App — so commits, pull requests and merges are the person's. A token GitHub refuses (401 or 403) surfaces as commit.ErrAuth with the status; the caller answers with its sign-in flow, and nothing is retried with another identity.