gitprovision

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package gitprovision materialises a workload's source tree where the workload is about to run.

Why this is its own package

Two very different callers have to do exactly the same thing:

  • the remote agent (pkg/executor/agent), which clones onto an edge device before starting a harness there;
  • `cloop workspace provision` (cmd), which the Kubernetes driver runs as an init container before the harness container starts.

The failure this whole subsystem exists to remove is a run that starts cleanly, streams a plausible transcript, and operates on no code at all. Two implementations of "how cloop clones a repo into a sandbox" would be two chances to reintroduce it — and the second copy would drift silently, because the symptom of a drifted provisioner is a run that *looks* fine. So there is one engine, here, and both callers are thin.

The package deliberately depends on nothing but the standard library and pkg/executor. `cloop workspace provision` must not have to import the edge agent (with its transport, vault and session machinery) to clone a repository, and the edge agent must not grow a dependency on the CLI.

What is shared and what is not

executor.Workspace.GitPlan renders the command sequence and is pure: no I/O, no clock, no environment. That is what lets a test assert on the sequence without a git binary. This package is everything the plan cannot be — which directory is safe to write to, whether git exists, how big the disk is — and it is shared because those answers are the same wherever the plan runs. The only thing a caller contributes is Request.Host: how the machine names itself in a diagnostic an operator will read.

The credential

It arrives out of band (the agent's start frame, the init container's environment), never in the Spec, and it leaves this package in exactly one place: the environment of the single git child process whose step is marked Authenticated. It is never written to disk, never placed in an argv (/proc publishes those to every process with the same uid), and never added to the harness's own environment — the harness is the untrusted party here, and a token it can read is a token it can exfiltrate. Everything this package emits or returns is passed through executor.RedactSecrets first, because git is perfectly willing to quote a header back in an error message.

Index

Constants

View Source
const (
	CommitIdentityName  = "cloop"
	CommitIdentityEmail = "cloop@localhost"
)

CommitIdentity is who commits made in a provisioned feature tree are attributed to when the harness sets no identity of its own. The write-back commit uses the same one (pkg/executor/gitwriteback): a commit attributed to whichever account a sandbox happens to run as describes the machine, not the change.

View Source
const MaxWalkEntries = 500_000

MaxWalkEntries bounds the size check's directory walk.

The walk runs over a tree a remote repository just chose the contents of, so it is attacker-influenced input on a machine the operator may never touch. A repository with ten million tiny files would exceed no byte limit while costing minutes of stat() calls. Half a million entries is far more than any real source tree and small enough to walk in well under a second.

Variables

This section is empty.

Functions

func BoundChild added in v0.0.2

func BoundChild(cmd *exec.Cmd)

BoundChild makes a git child process cancellable and its Wait bounded.

Neither property is free, and the absence of the first one is a hang rather than a slow path. `git fetch` and `git push` do not speak the wire protocol themselves — they exec a transport helper (git-remote-https), which inherits the captured stdout and stderr pipes. exec.CommandContext's default cancellation kills only the process it started, so cancelling a fetch kills git and leaves the helper holding the write end of a pipe that CombinedOutput is still reading. Wait then blocks until the helper exits on its own, which for a remote that has stopped answering means until the TCP stack gives up: the caller's context deadline expires and Provision does not return. That is the failure the context was threaded through this package to prevent, and it is invisible in any test whose remote answers.

So the child gets its own process group and cancellation signals the group, which reaches the helper too; WaitDelay is the backstop for a pipe-holder that somehow survives the group kill. This mirrors pkg/provider/claudecode, pkg/plugin and pkg/hooks, which reached the same conclusion for the same reason — and it is exported so the write-back engine (pkg/executor/gitwriteback) gets it from here rather than growing a fourth copy, exactly as it does with TransportEnv.

The Cmd must have been created by exec.CommandContext: os/exec refuses to Start a command that has a Cancel and no context. That refusal is not theoretical — it is how this function's first version was caught, by a context-less `git config` read that stopped running and reported its silence as "this checkout has no origin remote".

func Collapse

func Collapse(s string) string

Collapse folds command output onto one line for an error message, keeping the tail — git puts the actual reason last.

func EnforceSizeLimit

func EnforceSizeLimit(dir string, w executor.Workspace) error

EnforceSizeLimit refuses a tree that exceeds the workload's disk budget.

The limit comes from the project's .cloop/sandbox.yaml resources.disk, and on a machine with no runtime quota it has to be enforced rather than advertised: the disk being filled belongs to whoever owns the machine, not to the operator who wrote the spec, and a limit that only the least-trusted party can enforce is not a limit. A container driver gets this from the runtime; here there is nothing between the fetch and the filesystem except this check.

func FilterDrivers added in v0.0.5

func FilterDrivers(ctx context.Context, dir string, extraEnv []string, extraConfig ...[2]string) ([]string, error)

FilterDrivers lists the filter driver names the configuration of the repository at dir defines, so each can be neutralised with executor.HardenedGitConfig. Reading configuration runs nothing. extraEnv is appended to the closed base environment — GIT_DIR, for a caller pinning it — and extraConfig joins its configuration block (safe.directory, for a caller reading a repository its account does not own).

func GitCAConfig added in v0.0.5

func GitCAConfig(ca *executor.GitCA) ([][2]string, func(), error)

GitCAConfig writes ca's bundle to a file in a fresh 0700 directory and returns the http.<url>.sslCAInfo pairs naming it, and the cleanup that removes it (Task 20409). A nil ca returns nothing and a no-op cleanup.

For cloop's own git children — the provisioning fetch, the write-back push — whose environment is closed: the same per-URL form TransportConfig imports, so the hub's CA is trusted for the proxy's URL and every other host keeps the machine's store. A directory of the call's own rather than a shared path, because whoever can choose this file's contents chooses which certificate the fetch accepts for the proxy.

func HideControlDir added in v0.0.5

func HideControlDir(ctx context.Context, dir string) error

HideControlDir keeps the project's control directory out of the tree's commits: .cloop/ is excluded for untracked files, and files the repository does track under it are marked skip-worktree, so the seed written over them and the state database written beside them never show up as changes.

It is the same treatment the hub gives a feature's own worktree (pkg/featureops), applied to the copy a sandbox works in.

func HostLabel

func HostLabel(noun string) string

HostLabel renders how the machine provisioning runs on refers to itself.

The hostname is the identifier an operator can act on — an edge device's name in the fleet, a Pod's name in the namespace. When it is unavailable the phrasing still has to read as a sentence.

func Provision

func Provision(ctx context.Context, r Request) error

Provision clones r.Workspace into r.Dir so the harness starts against real code.

Every error it returns wraps executor.ErrWorkspaceUnavailable and has already been through executor.RedactSecrets, so a caller may log it, ship it across the remote boundary, or print it to a terminal without further filtering.

func RootOwnedTrust added in v0.0.5

func RootOwnedTrust(dir string) [][2]string

RootOwnedTrust returns the configuration that lets git work in dir when dir is owned by root and this process is not — the shape of a volume a platform created for a workload. A Pod's emptyDir is owned by root whatever runAsUser says; fsGroup changes its group, never its owner. Nil for any other dir.

git refuses, since 2.35.2, to run in a work tree another user owns, because that user's .git/config could run code as you — "detected dubious ownership". It is a defence against a *peer*. Root is not one: it can already run anything as anyone, so trusting a directory root owns grants nothing root could not take. The first live Kubernetes run found every workspace fetch refused there, because the provisioner runs as the harness's uid in a root-owned /workspace.

Trust goes to dir alone, by its exact path, and only while root owns it. A directory another non-root user owns stays refused.

func SameRemote

func SameRemote(a, b string) bool

SameRemote reports whether two clone URLs name the same repository.

The comparison is deliberately narrow — a trailing slash and a trailing .git are noise that GitHub and every other forge treat as equivalent — and deliberately stops there. Anything cleverer (case folding a path, resolving a redirect) would risk calling two different repositories the same one, and the cost of a false match is a fetch into somebody else's working tree.

func SandboxedRepoEnv added in v0.0.5

func SandboxedRepoEnv(ctx context.Context, dir string, extra ...[2]string) ([]string, error)

SandboxedRepoEnv makes the repository at dir safe to run git in from outside the sandbox that wrote it, and returns the environment those git commands must run with: executor.GitEnv carrying executor.HardenedGitConfig for the repository's filter drivers plus extra, and GIT_DIR/GIT_WORK_TREE pinned.

extra joins the same configuration block — see executor.GitEnv for why it cannot be appended as a second one.

func TransportConfig added in v0.0.5

func TransportConfig() [][2]string

TransportConfig returns the URL-scoped certificate settings in the machine's own GIT_CONFIG_COUNT block: http.<https-url>.sslCAInfo and .sslCAPath, and nothing else (Task 20385).

They are the per-URL form of GIT_SSL_CAINFO and GIT_SSL_CAPATH, which transportVars already forwards, and they exist for the case those cannot serve: trusting one private CA for one host — the hub's git proxy, served under an in-cluster name — without replacing the trust store every other host is verified against. The Kubernetes executor sets them in a Pod's environment from executors.kubernetes.git_ca_bundle.

Only these two keys, and only scoped to an https URL. Everything else in the block — a credential helper, an insteadOf, an extraHeader — would let the machine's configuration redirect or authenticate a fetch the control plane asked for, which is what the closed environment exists to prevent. A certificate path can only make a connection to the host already named in the URL verify, or fail to.

Callers fold the pairs into their one configuration block (see executor.GitEnv) rather than appending them as a second.

func TransportEnv

func TransportEnv() []string

TransportEnv reads the machine's transport settings, skipping anything unset or empty (an empty proxy variable means something different from an absent one to libcurl).

It is exported because the write-back engine (pkg/executor/gitwriteback) has to reach the same remote through the same proxy a moment later. Two allowlists would drift, and the drift would show up as a clone that works and a push that hangs on a machine nobody can reproduce.

func TreeSize

func TreeSize(dir string) (size int64, entries int, err error)

TreeSize sums the regular files under dir, with a bounded walk.

Symlinks are counted as themselves and never followed — filepath.WalkDir does not descend into them — so a repository containing a link to / measures as one small entry rather than as the whole filesystem.

Types

type Request

type Request struct {
	// Dir is the absolute directory the tree is materialised into. It must
	// already exist, and the caller is responsible for having confined it —
	// only the caller knows what its own confinement boundary is (the agent's
	// root, the Pod's workspace volume).
	Dir string
	// Workspace describes what to fetch. Only Kind git is provisionable.
	Workspace executor.Workspace
	// Credential authenticates the single step that contacts the remote. The
	// zero value means an unauthenticated fetch, which only works for a public
	// repository.
	Credential executor.GitCredential
	// Emit receives the git output as it completes, already redacted, so
	// provisioning shows up in the run's live log rather than as a silent pause
	// before the harness starts. It may be nil.
	Emit func(string)
	// BranchBundleFile is where the bundle Workspace.Branch describes sits on
	// this machine. Required when the shipped branch carries bytes; see
	// branch.go.
	BranchBundleFile string
	// Host is how the machine names itself in a diagnostic: "this device
	// (edge-3)". Empty falls back to HostLabel("machine").
	//
	// It is a caller's choice rather than a lookup because the operator reading
	// the failure is looking at a fleet, and "this device" and "this workspace
	// container" send them to different places.
	Host string
	// GitCA is the hub's own CA, trusted for the URLs it names and nothing
	// else (Spec.GitCA, Task 20409): how a device's fetch through a git proxy
	// serving a certificate the hub issued verifies it. Written to a private
	// file for the length of the provisioning and named by
	// http.<url>.sslCAInfo, beside the machine's own URL-scoped settings.
	GitCA *executor.GitCA
}

Request is one provisioning.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL