Skip to content

Latest commit

 

History

History
738 lines (573 loc) · 31.3 KB

File metadata and controls

738 lines (573 loc) · 31.3 KB

Configuration Model

This guide explains the real configuration objects that drive gitops-reverser after the install steps in the root README.

The short version:

  • GitProvider defines where and how to push
  • GitTarget defines which branch and repository path to write into
  • WatchRule defines which namespaced resources should produce Git writes
  • ClusterWatchRule does the same for cluster-scoped or cross-namespace watching
  • CommitRequest optionally asks the operator to close the current commit window now

The chart's optional quickstart values are just a convenience layer that creates starter instances of those same resources.

For a first trial, use the root README quick start. It runs committer-only: Git writes work without kube-apiserver audit delivery, and every commit uses the configured committer identity. Add audit attribution later only when you need named Kubernetes users or service accounts in Git history.

How the objects fit together

Config basics diagram showing the relationship between GitProvider, GitTarget, and WatchRule

The usual flow is:

  1. Create a GitProvider for repository access and commit behavior.
  2. Create a GitTarget that points at that provider plus a branch and repository path.
  3. Create one or more WatchRule or ClusterWatchRule objects that point at that target.
  4. Create a CommitRequest only when you want to flush an open window before the normal timer.

That means one repository connection can back multiple targets, and one target can be fed by multiple watch rules.

GitProvider

GitProvider defines the Git remote, credentials, allowed branches, push strategy, and commit behavior.

The important fields are:

  • spec.url: repository URL
  • spec.secretRef.name: Secret with Git credentials such as SSH or HTTPS auth
  • spec.knownHostsRef: optional ConfigMap/Secret with SSH known_hosts shared across providers
  • spec.allowedBranches: branches this provider is allowed to write
  • spec.push.commitWindow: rolling silence window that coalesces events into one commit per author
  • spec.commit: committer identity, commit templates, and signing

Example:

apiVersion: configbutler.ai/v1alpha3
kind: GitProvider
metadata:
  name: example-provider
  namespace: default
spec:
  url: git@github.com:example-org/example-repo.git
  secretRef:
    name: git-creds
  allowedBranches:
    - main

GitProvider.spec.secretRef — the credentials Secret

The referenced Secret holds the Git credentials. The examples use the Kubernetes-native keys, which match the built-in Secret types and the tooling around them (kubectl create secret generic --type=…, Sealed Secrets, External Secrets, SOPS):

Auth Keys
SSH ssh-privatekey (+ optional ssh-password passphrase, known_hosts)
HTTP basic username + password
HTTP bearer token bearerToken (GitHub fine-grained PAT, GitLab access token; no username)

Reusing a Flux or Argo CD credentials Secret

The credential reader's design is inspired by both Flux and Argo CD: it accepts their Secret key names alongside the native ones, so you can reuse a Git credentials Secret you already have instead of re-authoring it. The keys read for each auth method:

Credential Native key (recommended) Flux key (also read) Argo CD key (also read)
SSH private key ssh-privatekey identity sshPrivateKey
SSH key passphrase ssh-password password (when an SSH key is present) (unsupported by Argo)
SSH host keys known_hosts known_hosts external ConfigMap → supply via spec.knownHostsRef
HTTP basic auth username + password username + password username + password
HTTP bearer token bearerToken bearerToken bearerToken

Auth precedence is SSH key → HTTP basic → bearer token. Client certificates (mTLS), custom CA certificates, and GitHub App credentials are not supported.

A reused Secret needs write access. Flux and Argo CD only clone, so their Git credentials are often read-only (a read-only deploy key, a read-scoped token). GitOps Reverser pushes commits, so a reused Secret's key or token must have write access on the repository — otherwise the commits will fail to push.

SSH host keys are resolved in priority order: the credentials Secret's own known_hosts, then spec.knownHostsRef (a namespace-local ConfigMap or Secret keyed known_hosts, or ssh_known_hosts for data copied out of Argo's argocd-ssh-known-hosts-cm), then an install-level default known-hosts ConfigMap in the controller's namespace (--default-known-hosts-configmap). If none yields a valid host key, SSH fails closed. Host-key rotation is an admin-owned declarative update; verify fingerprints out of band. The controller flag --insecure-allow-missing-known-hosts relaxes this for throwaway/dev clusters only — it permits SSH when no source provided any known_hosts; a known_hosts that is present but unparseable is always a hard error.

GitProvider.spec.push

spec.push.commitWindow controls how arriving events are grouped into commits. The timer resets on every event; when it has been silent for the configured duration, the buffered events for a given (author, gitTarget) are written as one commit. The default is 5s. Setting 0s opts into per-event commits in the steady-state.

spec:
  push:
    commitWindow: "5s"

A burst (e.g. kubectl apply -k, helm upgrade, an ArgoCD sync wave) becomes one commit per author with a summary subject; isolated edits still produce one commit each.

GitProvider.spec.commit

spec.commit configures how gitops-reverser writes commits:

  • committer: the operator identity written as the Git committer
  • message: the subject format for per-event and batch commits
  • signing: the SSH signing key configuration

If spec.commit is omitted, gitops-reverser uses its built-in defaults.

Author vs committer

These are different on purpose:

  • Author: who made the cluster change
  • Committer: who wrote the Git commit object

For mirrored-resource commits, the author comes from the configured committer identity unless attribution.enabled=true and a matching kube-apiserver audit event names the Kubernetes user or service account. Snapshot/reconcile commits are operator-authored.

That distinction is useful in practice:

  • git log --author=alice answers "what did Alice change?"
  • git log --committer="GitOps Reverser" answers "what did the operator write?"

When signing is enabled, Git hosting platforms usually verify the committer identity, not the Kubernetes author.

Committer identity

Use spec.commit.committer to control the bot identity written as the Git committer:

spec:
  commit:
    committer:
      name: GitOps Reverser
      email: 12345678+gitops-reverser-bot@users.noreply.github.com

Defaults:

  • name: GitOps Reverser
  • email: noreply@configbutler.ai

If signing is enabled, spec.commit.committer.email should be an email that the Git hosting platform recognizes for the account that owns the signing key.

Commit message templates

There are three templates, one per commit shape:

  • spec.commit.message.eventTemplate: per-event commits (only used when commitWindow is 0s).
  • spec.commit.message.groupTemplate: grouped commits produced by the commit window (the common case).
  • spec.commit.message.reconcileTemplate: reconcile commits (the mark-and-sweep reconcile path; one commit per synced type).
spec:
  commit:
    message:
      eventTemplate: "[{{.Operation}}] {{.APIVersion}}/{{.Resource}}/{{.Name}}"
      groupTemplate: "{{.Author}} on {{.GitTarget}}: {{.Count}} resource(s)"
      reconcileTemplate: "reconciled {{.Count}} {{.Resource}}"

eventTemplate can use:

  • Operation
  • Group
  • Version
  • Resource
  • Namespace
  • Name
  • APIVersion
  • Username
  • GitTarget

groupTemplate can use:

  • Author
  • GitTarget
  • Count
  • Operations (map of CREATE/UPDATE/DELETE counts)
  • Resources (slice of {Group, Version, Resource, Namespace, Name})

reconcileTemplate can use:

  • Count
  • GitTarget
  • Group
  • Version
  • Resource
  • APIVersion
  • Revision

Group/Version/Resource/APIVersion name the synced type for a per-type reconcile and Revision is the cluster resourceVersion the reconcile was pinned to. The default, reconciled {{.Count}} {{if .Resource}}{{.Resource}}{{else}}resources{{end}}{{if .Revision}} (last resourceVersion: {{.Revision}}){{end}}, renders e.g. reconciled 6 secrets (last resourceVersion: 1331). The type and revision fields are empty for a whole-target reconcile or a pure sweep, so guard a template that references them (the default uses {{if .Resource}} / {{if .Revision}}) to avoid an identity-less subject.

Examples:

spec:
  commit:
    message:
      eventTemplate: "chore: [{{.Operation}}] {{.APIVersion}}/{{.Resource}}/{{.Name}}"
spec:
  commit:
    message:
      eventTemplate: "[{{.Operation}}] {{.Resource}}/{{.Name}} ({{.Username}})"
spec:
  commit:
    message:
      reconcileTemplate: "reconciled {{.Count}} {{.Resource}}@{{.Revision}}"

Commit signing

GitOps Reverser signs commits from spec.commit.signing.

The signing Secret uses these data keys:

  • signing.key: PEM-encoded SSH private key
  • passphrase: optional passphrase for encrypted private keys
  • signing.pub: optional convenience copy of the public key

The operator publishes the effective public key in .status.signingPublicKey.

Let the operator generate the signing key:

apiVersion: configbutler.ai/v1alpha3
kind: GitProvider
metadata:
  name: example-provider
  namespace: default
spec:
  url: git@github.com:example-org/example-repo.git
  allowedBranches:
    - main
  secretRef:
    name: git-creds
  commit:
    committer:
      name: GitOps Reverser
      email: 12345678+gitops-reverser-bot@users.noreply.github.com
    signing:
      secretRef:
        name: gitops-reverser-signing-key
      generateWhenMissing: true

Bring your own signing key:

ssh-keygen -t ed25519 -f /tmp/gitops-reverser-signing -N ""

kubectl create secret generic gitops-reverser-signing-key \
  -n default \
  --from-file=signing.key=/tmp/gitops-reverser-signing \
  --from-file=signing.pub=/tmp/gitops-reverser-signing.pub
spec:
  commit:
    committer:
      name: GitOps Reverser
      email: 12345678+gitops-reverser-bot@users.noreply.github.com
    signing:
      secretRef:
        name: gitops-reverser-signing-key

If you start from the Helm chart quickstart, edit the generated GitProvider directly when you want custom spec.commit behavior because the starter values do not currently expose those fields.

For the platform-facing behavior behind "valid signature" versus "verified badge", see commit-signing.md.

GitTarget

GitTarget decides where inside the repository resources are written.

The important fields are:

  • spec.providerRef: which GitProvider backs this target
  • spec.branch: which allowed branch to write to
  • spec.path: required relative path inside the repository; use . only when you deliberately want the repository root
  • spec.encryption: how Secret resources should be encrypted before commit
  • spec.placement: optional policy for where new resources are written (see Where new resources are written); omit it to follow the repository's existing layout

Example:

apiVersion: configbutler.ai/v1alpha3
kind: GitTarget
metadata:
  name: example-target
  namespace: default
spec:
  providerRef:
    name: example-provider
  branch: main
  path: live-cluster

spec.path is required so a target never writes to the repository root by accident. Use a path such as live-cluster for the first install. To deliberately target the repository root, set path: ".". Do not use a leading slash, and do not add a trailing slash.

The target path is authoritative for snapshot reconciliation. A root target can create, update, and delete managed manifest files at the repository root, so use . only for a repository layout that is dedicated to this target.

If you enable spec.encryption, that applies to Secret resource writes for this target. For SOPS and age details, see sops-age-guide.md.

spec.providerRef references a GitProvider in the same namespace as the GitTarget. Its group and kind default to configbutler.ai / GitProvider, so in practice you only set name.

The most useful status fields are:

  • Ready: true when the target is valid, the Git path is accepted, and watched streams are running.
  • Reconciling: true while initial replay, a recheck, or another coarse progress step is in flight.
  • Stalled: true when the target is blocked until a human fixes configuration, RBAC, or Git path content.
  • Validated and EncryptionConfigured: control-plane details.
  • StreamsRunning: true when the source watches are past initial replay and routing live events.
  • GitPathAccepted: true when the target Git path is safe to materialize.
  • status.streams: bounded counts for tracked, running, replaying, and blocked streams.

Use conditions for automation.

Where new resources are written (spec.placement)

Placement decides the file path for a resource that has no document in Git yet. Once a document exists, updates and deletes always edit it in place at its current location (found by manifest identity, not path), so changing placement never moves an existing file — it only affects resources created after the change.

How a path is chosen (the resolution ladder)

For each new resource the operator walks this order and stops at the first that produces a path:

  1. spec.placement.byType[<exact type>] — an explicit template for that resource's type, if you declared one.
  2. spec.placement.default — your explicit catch-all template, if you declared one.
  3. Sibling inference — follow the layout the repository already uses for resources like this one (described next).
  4. Built-in canonical path{namespace}/{group}/{resource}/{name}.yaml: namespace first, the group omitted for core resources, no version segment, _cluster/ in place of the namespace for cluster-scoped resources (an illegal namespace name, so it can never clash with a real one), and a .sops.yaml suffix for sensitive resources.

If you set no spec.placement, only steps 3 and 4 run — which is why pointing a target at an existing repository "just continues" that repo's conventions, and a brand-new empty repo gets the tidy canonical layout.

Following the existing layout (sibling inference)

This is the part that looks like magic but isn't: the operator never reverse-engineers a template. It reads the files already in the target and makes two observed decisions for the new resource — which directory (the nearest cohort of resources like it — same type, then same type in any namespace) and one-file-or-bundle (does that cohort keep one resource per file, or share a multi-document file?).

Worked example — a target at spec.path: clusters/prod already looks like:

clusters/prod/
  all.yaml                       # 9 ConfigMaps in one multi-document file (a "bundle")
  team-a/secrets/db.sops.yaml    # one Secret, encrypted, one file per Secret
  • A new ConfigMap cache arrives: its type-cohort (ConfigMaps) lives entirely in the all.yaml bundle → the new document is appended to all.yaml. No new file, no canonical tree is created.
  • A new Secret api-token arrives: it is sensitive, so plaintext siblings are ignored; the only encrypted cohort is team-a/secrets/ (one-per-file) → a new encrypted file team-a/secrets/api-token.sops.yaml.
  • A new ConfigMap in a brand-new namespace billing: the ConfigMap cohort is still the all.yaml bundle, which is namespace-agnostic, so it is appended to all.yaml too — the new namespace needs no new segment.

The boundaries that keep it predictable:

  • A sensitive resource never infers from (or is appended into) a plaintext file; it only follows encrypted siblings, otherwise it uses the secure canonical path.
  • When a type genuinely lives in two layouts at once, the tie-break is deterministic (the cohort with the most members wins, then the lexically smallest path) — never a coin-flip.
  • Inference can only continue a layout that already exists. It cannot invent a greenfield one — "I want all ConfigMaps bundled even though none exist yet" is a job for byType below.

The full ladder, tie-break rules, and edge cases are in design/manifest/version2/gittarget-new-file-placement-rules.md; the vision behind it is design/manifest/file-agnostic-placement.md.

Declaring a layout (byType / default)

Set spec.placement when you want to prescribe a layout rather than follow the repo (for example a greenfield repo, or a convention inference can't reach):

spec:
  placement:
    byType:
      v1/configmaps: "{namespace}/configmaps.yaml"     # bundle every ConfigMap of a namespace into one file
      v1/secrets: "{namespace}/secrets/{name}.yaml"    # one file per Secret
    default: "{namespaceOrCluster}/{group}/{resource}/{name}.yaml"
  • byType maps an exact [group/]version/resource key (core resources omit the group, e.g. v1/configmaps; grouped resources include it, e.g. apps/v1/deployments) to a path template.
  • default is the template for any type with no byType entry. Omit it to fall through to sibling inference and then the built-in path.
  • Templates are small brace-variable path templates (see the table below), validated statically as part of the Validated gate — an unknown variable, a path that escapes spec.path (a leading / or ..), or a non-.yaml/.yml suffix fails the target before any write.

Template variables

Every value is sanitized for use as a single path segment. An empty segment (an omitted variable, e.g. {group} for a core resource) is dropped from the final path, so {group}/{resource}/{name}.yaml renders configmaps/app.yaml, not /configmaps/app.yaml. Example values are for an apps/v1 Deployment named api in namespace team-a:

Variable Renders Example
{name} resource name api
{namespace} the resource's namespace; empty for a cluster-scoped resource team-a
{namespaceOrCluster} the namespace, or the literal _cluster for a cluster-scoped resource team-a (a Node → _cluster)
{resource} plural resource name deployments
{group} API group; empty for core resources apps (a ConfigMap → empty)
{groupPath} the API group as a path segment; equivalent to {group} today (the empty core-group segment is dropped either way) apps
{version} API version v1
{apiVersion} manifest apiVersiongroup/version, or just version for core apps/v1 (a ConfigMap → v1)
{kind} manifest kind Deployment
{scope} namespaced or cluster (a readable label — not a namespace-position value) namespaced
{sensitiveSuffix} .sops.yaml for a sensitive resource, .yaml otherwise .yaml (a Secret → .sops.yaml)

{namespace} vs {namespaceOrCluster} — the one to get right. For a cluster-scoped resource {namespace} is empty, so its whole path segment vanishes: a template {namespace}/{resource}/{name}.yaml renders clusterroles/admin.yaml for a ClusterRole (no scope folder at all). Use {namespaceOrCluster} when a single template must also place cluster-scoped resources — it keeps a stable _cluster/ segment (_cluster/clusterroles/admin.yaml) so namespaced and cluster-scoped resources stay cleanly separated. {scope} is a descriptor (cluster/namespaced), not a substitute — don't use it as the folder for cluster resources.

Sensitivity is a write-safety rule, not a placement setting

Sensitivity is enforced by the operator whatever path is chosen. A Secret (and any operator-configured sensitive type) is always written encrypted, is never appended to an existing file, and is never co-mingled with a plaintext document. Two consequences for your templates:

  • A byType route for a sensitive type must be identity-complete — it must contain {name} and a scope such as {namespace} — so two of them can never collide onto one file.
  • A bundling default that is not identity-complete (e.g. "all.yaml") is rejected unless every sensitive type has its own identity-complete byType entry, so a Secret can never fall through into a shared file. If an operator-configured sensitive type still reaches such a path at write time, that resource is skipped fail-safe — logged and counted in the resync summary (placementSkipped) — rather than written unsafely. It is not surfaced as a dedicated status condition today.

Additional sensitive resources

Core Kubernetes Secret resources always use the encrypted Git write path. For a Secret-shaped custom resource such as CozyStack tenantsecrets, add the resource type to the controller startup values:

controllerManager:
  additionalSensitiveResources:
    - core.cozystack.io/tenantsecrets

Entries are resource for the core API group or group/resource for grouped APIs. The match ignores API version, so a served CRD version change does not change the sensitive classification. The custom resource still needs a GitTarget with spec.encryption configured before Git writes can succeed.

Kustomize support in the target path

A target path may contain kustomization.yaml files. The operator retains them as build directives (it never sweeps them) and understands a deliberately small, round-trippable subset:

  • namespace: + resources:/bases (local files and directory bases): a namespace-less resource file inherits its namespace from the kustomization that references it, and metadata.namespace is kept out of the file on write.
  • images: and replicas: overrides: a live change produced by an override entry — an image tag, name, or digest pinned by images:, or a replica count pinned by replicas: (including kubectl scale) — is written back to that entry, preserving comments, and the source manifest keeps its bytes. Only fields the entry already declares are updated; the operator never adds or removes entries. Note that one entry is a shared knob, exactly as in kustomize itself: updating it affects every resource in the build whose image matches.

Anything outside that subset refuses the whole target path before anything is written: patches/patchesStrategicMerge/patchesJson6902, generators, components, Helm fields, replacements, transformers, namePrefix/nameSuffix, remote bases, and images:/replicas: values that do not parse (those would fail kustomize build too). A refusal is loud: the target reports GitPathAccepted=False, Stalled=True, and Ready=False with reason UnsupportedContent until the path is cleaned up.

Two situations fall back to plain in-place editing of the source manifest instead of refusing: a resource file reachable from more than one render root with differing override chains (ambiguous — the operator will not guess which chain governs), and a live change an entry cannot express (for example a removed digest, or two containers demanding different values from one entry). These fallbacks are recorded as store diagnostics — visible in the analyzer CLI and, for the running operator, in the logs at debug verbosity (manifest store diagnostic).

For design details and the exact boundary, see design/gitops-api/f1-images-replicas-edit-through.md.

WatchRule

WatchRule is the normal namespaced watcher. It only watches resources in its own namespace and writes them to the referenced GitTarget.

Status uses ResourcesResolved for selector resolution, StreamsRunning for source-watch readiness, and GitTargetReady for the referenced target's write readiness. A rule can have StreamsRunning=True and still remain Ready=False when its GitTarget reports GitPathAccepted=False.

The important fields are:

  • spec.targetRef.name: target to write to
  • spec.rules: one or more resource-match rules

Each entry in spec.rules is a logical OR. A resource matching any rule is watched. The rule fields are:

  • operations: CREATE, UPDATE, DELETE, or *; omitted means all operations.
  • apiGroups: "" for the core group, * for all groups, or omitted to resolve the named resource across the served API surface.
  • apiVersions: a served version such as v1; omitted means the preferred served version.
  • resources: plural resource names such as configmaps, secrets, or *.

Subresources such as deployments/scale are not valid rule resources. GitOps Reverser mirrors top-level resources; selected subresource effects are handled separately by the controller.

Example:

apiVersion: configbutler.ai/v1alpha3
kind: WatchRule
metadata:
  name: example-watchrule
  namespace: default
spec:
  targetRef:
    name: example-target
  rules:
    - operations: [CREATE, UPDATE, DELETE]
      apiGroups: [""]
      apiVersions: ["v1"]
      resources: ["configmaps", "secrets"]

Use WatchRule when the watched resources and the GitTarget live in the same namespace.

ClusterWatchRule

ClusterWatchRule is the cluster-scoped variant. Use it when you need to watch:

  • cluster-scoped resources such as nodes, clusterroles, or CRDs
  • namespaced resources across multiple namespaces

Because it is cluster-scoped, its targetRef must include the namespace of the referenced GitTarget.

Example:

apiVersion: configbutler.ai/v1alpha3
kind: ClusterWatchRule
metadata:
  name: cluster-rbac
spec:
  targetRef:
    name: example-target
    namespace: default
  rules:
    - operations: [CREATE, UPDATE, DELETE]
      apiGroups: ["rbac.authorization.k8s.io"]
      apiVersions: ["v1"]
      resources: ["clusterroles", "clusterrolebindings"]
      scope: Cluster

Use this sparingly. It is the more powerful option and usually belongs to cluster-admin-managed setups.

CommitRequest

CommitRequest is a one-shot "save now" signal for a same-namespace GitTarget. It does not create or change watch rules. Instead, it asks the branch worker to finalize a matching open commit window for the request's author instead of waiting for GitProvider.spec.push.commitWindow.

The important fields are:

  • spec.targetRef.name: target whose open window should be finalized
  • spec.message: optional verbatim commit message
  • spec.closeDelaySeconds: optional 0-300 second delay before the open window is closed, after the request author is known — an extra collect window

Example:

apiVersion: configbutler.ai/v1alpha3
kind: CommitRequest
metadata:
  name: save-now
  namespace: default
spec:
  targetRef:
    name: example-target
  message: "save default/example-target"
  closeDelaySeconds: 2

The entire spec is immutable. Create a new CommitRequest for each save attempt.

Progress and outcome are reported through kstatus-compatible conditions (no phase string). kubectl get commitrequest surfaces Ready, AuthorAttributed, and Pushed; kubectl wait --for=condition=Ready blocks until the request settles:

  • Ready (summary): True once the request reached a non-error terminal outcome. The Ready condition's reason says which: Committed (a commit was pushed; status.branch/status.sha set), or a benign no-commit — NoWindowInGrace, WindowMismatch, or AlreadyPresent. A failed finalize is Ready=False with reason FinalizeFailed.
  • Reconciling / Stalled: the kstatus progress/blocked pair. Reconciling=True while the request is finalizing or waiting through closeDelaySeconds; Stalled=True when the finalize failed and needs attention (kstatus reports the object Failed).
  • AuthorAttributed: True with reason AttributedFromAdmission when the internal commands admission webhook captured the request submitter. False with reason CommitterFallback when no admission record exists; that is not a failure, and the command still commits as the configured committer.
  • Pushed: True once the commit is in the remote repository.

Audit ingestion settings

Object state comes from Kubernetes watch, not from audit. Audit is an optional attribution lookup: kube-apiserver posts audit events to a single HTTP path, /audit-webhook, and the operator extracts a minimal attribution fact from each (auditID, user, verb, resourceVersion, GVR, namespace, name, UID, status, timestamps) into a Redis attribution index keyed for the join. A resolver attaches the commit author to each watch event by matching a fact (by resourceVersion/UID) within a bounded grace window. The same Redis connection also stores per-watch resume cursors, so short reconnects can resume a normal watch from the last processed resourceVersion when the apiserver can still serve that history.

Redis/Valkey is required: it stores each GitTarget's watch resume cursors (state continuity, so a restart or reconnect resumes where it left off), and when attribution is enabled it also stores the audit facts. The Helm chart defaults to committer-only (attribution.enabled: false): the audit webhook is unused and every mirrored-resource commit is authored by the configured committer. Redis stays required either way.

queue:
  redis:
    addr: "valkey:6379"
    auth:
      existingSecret: "valkey-auth"
      existingSecretKey: "password"

When attribution is enabled, these flags tune the join:

  • --author-attribution-ttl (default 10m): how long an attribution fact is retained waiting for the matching watch event to join it.
  • --author-attribution-grace (default 3s): bounded per-event wait for a matching audit fact before a watch event ships authored by the committer.

A matched actor is always named by its own username — humans and service accounts alike (e.g. system:serviceaccount:flux-system:kustomize-controller); there is no option to collapse service accounts to the committer.

attribution:
  ttl: "10m"
  grace: "3s"

Quickstart vs hand-managed resources

Keep using the root README quickstart when you want the fastest first commit. The chart's quickstart values create a starter GitProvider, GitTarget, and WatchRule for you.

The starter GitTarget writes under live-cluster by default. Override quickstart.gitTarget.path=. only when you want the starter target to own the repository root.

Move to hand-managed resources when you want:

  • more than one GitTarget
  • more than one watch rule
  • cluster-scoped watching with ClusterWatchRule
  • ad hoc save requests with CommitRequest
  • direct control over GitProvider.spec.commit
  • direct control over encryption settings

The chart value reference for the starter quickstart block lives in charts/gitops-reverser/README.md.

What to read next