Documentation
¶
Index ¶
- Constants
- Variables
- func AddSSHKey(ctx context.Context, client *circleci.Client, ...) (*circleci.AddSSHKeyResponse, error)
- func AllProjectRoots() ([]string, error)
- func BundleSync(ctx context.Context, client *circleci.Client, ...) error
- func ClearActive(ctx context.Context) error
- func ClearActiveByOrg(orgID string) (int, error)
- func ClearActiveFrom(ctx context.Context, dir string) error
- func ClearActiveSnapshot(ctx context.Context) error
- func ClearSnapshotFrom(ctx context.Context, dir string) error
- func Create(ctx context.Context, client *circleci.Client, orgID, name, image string) (*circleci.Sidecar, error)
- func CurrentBranch(root string) string
- func DefaultKeyPath() (string, error)
- func Exec(ctx context.Context, client *circleci.Client, sidecarID, command string, ...) (*circleci.ExecResponse, error)
- func GenerateKeyPair(path string) error
- func InteractiveShell(ctx context.Context, session *Session, envVars map[string]string) (err error)
- func LoadEnvFileAt(path string) (_ map[string]string, err error)
- func MergeEnv(layers ...map[string]string) map[string]string
- func ParseEnvFile(r io.Reader) (map[string]string, error)
- func ParseEnvPairs(pairs []string) (map[string]string, error)
- func PruneID(ctx context.Context, client *circleci.Client, sidecarID string, ...) error
- func ResolveWorkspace(ctx context.Context, cliWorkdir, repo string) (string, error)
- func SSH(ctx context.Context, client *circleci.Client, ...) error
- func SaveActive(ctx context.Context, a ActiveSidecar) error
- func SaveActiveSnapshot(ctx context.Context, a ActiveSnapshot) error
- func SaveActiveTo(ctx context.Context, dir string, a ActiveSidecar) error
- func SaveSnapshotTo(ctx context.Context, dir string, a ActiveSnapshot) error
- func ShellEscape(arg string) string
- func ShellJoin(args []string) string
- func StateDir() (string, error)
- func StateFileName(sessionID, branch string) string
- func Sync(ctx context.Context, client *circleci.Client, ...) error
- func SyncEphemeral(ctx context.Context, client *circleci.Client, ...) error
- type ActiveSidecar
- type ActiveSnapshot
- type ExecResult
- type KeyNotFoundError
- type NoOriginRemoteError
- type PublicKeyNotFoundError
- type ReapResult
- type RemoteBaseError
- type Session
- type SnapshotCriteria
- type SnapshotMatch
Constants ¶
const StaleAfter = 5 * 24 * time.Hour
StaleAfter is how long a sidecar's state file may go untouched before the sidecar is treated as abandoned and deleted.
Every save rewrites the state file, so its mtime is the only record of when the sidecar was last used, and a cold file means nobody has used it since. Five days keeps a sidecar alive across a long weekend while still reclaiming spend on the ones whose session was closed and forgotten.
Variables ¶
var ( // ErrMutuallyExclusiveKeys indicates both a key string and key file were provided. ErrMutuallyExclusiveKeys = errors.New("public key and public key file are mutually exclusive") // ErrPublicKeyRequired indicates neither a key string nor key file was provided. ErrPublicKeyRequired = errors.New("public key is required") // ErrPrivateKeyProvided indicates a private key was given where a public key was expected. ErrPrivateKeyProvided = errors.New("provided key is a private key") // ErrAuthSockNotSet indicates the SSH agent socket is not configured. ErrAuthSockNotSet = errors.New("ssh auth socket not set") )
Functions ¶
func AllProjectRoots ¶ added in v0.7.138
AllProjectRoots returns the roots of all projects that have ever saved a sidecar state, by reading the breadcrumb files written by SaveActiveTo.
func BundleSync ¶ added in v0.7.122
func BundleSync(ctx context.Context, client *circleci.Client, sidecarID, identityFile, authSock, workdir, cwd string, status iostream.StatusFunc) error
BundleSync synchronises local commits and working-tree changes to a sidecar using git bundle, without requiring the branch to be pushed to GitHub.
On first sync (no LastSyncedRef) a full bundle of HEAD is sent. On subsequent syncs only commits since the last synced ref are bundled (incremental). In both cases any uncommitted working-tree changes are applied on top as a patch.
When neither the HEAD commit nor the working-tree patch has changed since the previous sync, all remote operations are skipped entirely. When only the working-tree changed (no new commits), a reverse+apply delta is used instead of a full reset+clean+apply, leaving committed files untouched.
func ClearActive ¶
ClearActive removes the active sidecar state file.
func ClearActiveByOrg ¶ added in v0.7.160
ClearActiveByOrg removes all sidecar state files across every known project whose OrgID matches orgID. It is called after a bulk prune so that the TUI does not show deleted sidecars on the next watch tick. Returns the number of files removed. Per-project and per-file failures do not stop the sweep; they are accumulated into the returned error so the caller can warn about a prune that only partially cleared state.
func ClearActiveFrom ¶ added in v0.7.38
ClearActiveFrom removes the active sidecar state file in dir.
func ClearActiveSnapshot ¶ added in v0.7.35
ClearActiveSnapshot removes the active snapshot state file.
func ClearSnapshotFrom ¶ added in v0.7.38
ClearSnapshotFrom removes the active snapshot state file in dir.
func CurrentBranch ¶ added in v0.7.60
CurrentBranch returns the current git branch for the repo rooted at root. Returns "" on any error (no git, detached HEAD, etc.).
func DefaultKeyPath ¶
DefaultKeyPath returns the default SSH private key path used by chunk.
func GenerateKeyPair ¶
GenerateKeyPair generates an ed25519 keypair and writes the private key to path and the public key to path+".pub". The .ssh directory is created if it does not exist.
func InteractiveShell ¶
InteractiveShell opens an interactive shell session to the sidecar with PTY. It intentionally uses os.Stdin/os.Stdout/os.Stderr directly rather than iostream.Streams: term.MakeRaw and term.GetSize require a real *os.File fd, and PTY I/O must be wired to the process's actual terminal.
func LoadEnvFileAt ¶
LoadEnvFileAt reads the env file at path. Returns nil, nil if the file does not exist. Returns an error for permission or parse failures.
func ParseEnvFile ¶
ParseEnvFile reads dotenv-format KEY=VALUE lines from r. Supports blank lines, # comments, optional "export " prefix, and optional single/double quoting of values. No variable interpolation.
func ParseEnvPairs ¶
ParseEnvPairs parses a slice of KEY=VALUE strings and returns a map. Returns an error if any entry does not contain "=".
func PruneID ¶ added in v0.7.144
func PruneID(ctx context.Context, client *circleci.Client, sidecarID string, deleteRemote bool) error
PruneID removes every state file for the current project that names sidecarID, deleting the sidecar through the API first when it still exists.
Removing one file is not enough. Adoption re-keys an ID under the adopting session and branch, and older chunk versions copied rather than moved the file, so the same ID can be recorded two or three times over; dropping a single file leaves a duplicate behind that resurrects the dead ID on the next run.
deleteRemote separates the two ways a sidecar stops being usable. Already deleted (404) leaves nothing to delete. Out of date (410) means it is still running, still costing money, and can never be used again, so it is deleted before its state is dropped.
func ResolveWorkspace ¶ added in v0.7.51
ResolveWorkspace determines the workspace path. Priority: 1. CLI --workdir flag 2. sidecar.json workspace 3. default <sidecarHome>/<repo>. Returns an error if no repo-specific path can be determined (repo empty and no saved workspace), because the bare home dir is not safe to pass to rm -rf.
func SSH ¶
func SSH(ctx context.Context, client *circleci.Client, sidecarID, identityFile, authSock string, args []string, envVars map[string]string, streams iostream.Streams, stdin io.Reader) error
SSH opens a session and either runs a command or starts an interactive shell. stdin is forwarded to the remote command when non-nil; callers should pass os.Stdin when the process stdin is a pipe, nil otherwise.
func SaveActive ¶
func SaveActive(ctx context.Context, a ActiveSidecar) error
SaveActive writes the active sidecar to XDG_DATA_HOME for the current project.
func SaveActiveSnapshot ¶ added in v0.7.35
func SaveActiveSnapshot(ctx context.Context, a ActiveSnapshot) error
SaveActiveSnapshot writes the active snapshot to XDG_DATA_HOME for the current project.
func SaveActiveTo ¶ added in v0.7.38
func SaveActiveTo(ctx context.Context, dir string, a ActiveSidecar) error
SaveActiveTo writes the active sidecar to dir.
func SaveSnapshotTo ¶ added in v0.7.38
func SaveSnapshotTo(ctx context.Context, dir string, a ActiveSnapshot) error
SaveSnapshotTo writes the active snapshot to dir.
func ShellEscape ¶
ShellEscape escapes a string for safe use in a POSIX shell single-quoted context.
func StateDir ¶ added in v0.7.38
StateDir returns the XDG_DATA_HOME directory for the current project. Callers performing multiple sidecar or snapshot operations can resolve once and pass the result to the dir-accepting variants (LoadActiveFrom, SaveActiveTo, ClearActiveFrom, LoadSnapshotFrom, SaveSnapshotTo, ClearSnapshotFrom) to avoid repeated filesystem walks.
func StateFileName ¶ added in v0.7.60
StateFileName returns the sidecar state file name for the given session ID and git branch. Exposed so tests can construct expected paths.
func Sync ¶
func Sync(ctx context.Context, client *circleci.Client, sidecarID, identityFile, authSock, workdir string, status iostream.StatusFunc) error
Sync synchronises local changes to a sidecar over SSH. It ensures the workspace base exists, clones the repo into workdir if absent, then resets to the remote base and applies a patch of local changes. workdir overrides the destination path; defaults to /home/user/<repo>.
func SyncEphemeral ¶ added in v0.7.147
func SyncEphemeral(ctx context.Context, client *circleci.Client, sidecarID, identityFile, authSock, workdir string, status iostream.StatusFunc) error
SyncEphemeral synchronises like Sync but neither reads nor writes the active sidecar file. Callers that drive several sidecars concurrently — mutation variants, one sidecar per variant — would otherwise race on that shared file and leave it naming whichever worker happened to finish last, silently repointing the user's own session at a sidecar that is about to be deleted. workdir is required for the same reason: there is no shared state to fall back on, so each caller must name its own destination.
Types ¶
type ActiveSidecar ¶
type ActiveSidecar struct {
SidecarID string `json:"sidecar_id"`
Name string `json:"name,omitempty"`
// OrgID records which org the sidecar belongs to, so Reap can tell a sidecar
// that has been deleted from one that simply lives in an org it is not
// listing. Empty on state written before this field existed.
OrgID string `json:"org_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
Workspace string `json:"workspace,omitempty"`
LastSyncedRef string `json:"last_synced_ref,omitempty"`
// LastSyncedPatchHash is a SHA-256 hex digest of the working-tree patch
// last applied to the sidecar.
LastSyncedPatchHash string `json:"last_synced_patch_hash,omitempty"`
}
ActiveSidecar holds the currently active sidecar for a project.
func LoadActive ¶
func LoadActive(ctx context.Context) (*ActiveSidecar, error)
LoadActive reads the active sidecar for the current project from XDG_DATA_HOME. Returns nil if not found.
func LoadActiveFrom ¶ added in v0.7.38
func LoadActiveFrom(ctx context.Context, dir string) (*ActiveSidecar, error)
LoadActiveFrom reads the active sidecar from dir.
type ActiveSnapshot ¶ added in v0.7.35
ActiveSnapshot holds the most recently created snapshot for a project.
func LoadActiveSnapshot ¶ added in v0.7.35
func LoadActiveSnapshot(ctx context.Context) (*ActiveSnapshot, error)
LoadActiveSnapshot reads the active snapshot for the current project from XDG_DATA_HOME. Returns nil if not found.
func LoadSnapshotFrom ¶ added in v0.7.38
func LoadSnapshotFrom(ctx context.Context, dir string) (*ActiveSnapshot, error)
LoadSnapshotFrom reads the active snapshot from dir.
type ExecResult ¶
ExecResult holds the output of a command executed over SSH.
type KeyNotFoundError ¶
type KeyNotFoundError struct {
Path string
}
KeyNotFoundError indicates the SSH private key file does not exist.
func (*KeyNotFoundError) Error ¶
func (e *KeyNotFoundError) Error() string
type NoOriginRemoteError ¶ added in v0.7.67
type NoOriginRemoteError struct {
Err error
}
NoOriginRemoteError indicates git remote "origin" is not configured.
func (*NoOriginRemoteError) Error ¶ added in v0.7.67
func (e *NoOriginRemoteError) Error() string
func (*NoOriginRemoteError) Unwrap ¶ added in v0.7.67
func (e *NoOriginRemoteError) Unwrap() error
type PublicKeyNotFoundError ¶
PublicKeyNotFoundError indicates the SSH public key file does not exist.
func (*PublicKeyNotFoundError) Error ¶
func (e *PublicKeyNotFoundError) Error() string
type ReapResult ¶ added in v0.7.144
type ReapResult struct {
// Deleted holds sidecars that were still running but abandoned, and so were
// deleted through the API.
Deleted []string
// Vanished holds sidecars already absent server-side. Only their local state
// files were removed; there was nothing left to delete.
Vanished []string
// Failed holds sidecars the API refused to delete. Their state is still
// dropped, so the leak is reported rather than silent.
Failed []string
}
ReapResult records what Reap did, so the caller can report it.
func Reap ¶ added in v0.7.144
Reap deletes abandoned sidecars for the current project and removes their local state files.
State accumulates one file per session and branch, and nothing else removes them. Forgotten sidecars keep costing money, and a state file naming a sidecar that no longer exists causes every command against it to fail with a bare 404.
Three cases, deliberately treated differently:
- Absent from the org's sidecar list: the state is worthless, so the file goes. This includes the current session's own file, because there is no live sidecar left to protect and leaving it is exactly what causes the resurrection.
- Present but untouched for longer than StaleAfter: deleted through the API, then the file goes. The current session's file is spared, since deleting the sidecar this run is about to use only forces an immediate recreate.
- Present and recently used: left alone. A concurrent session on another branch legitimately owns its own sidecar, and its warm mtime says so.
Reap fails open. If the sidecar list cannot be fetched, nothing is deleted and nothing is pruned: an empty or partial list is not proof of absence, and destroying a live sidecar is far worse than leaving a stale file behind.
func (ReapResult) Empty ¶ added in v0.7.144
func (r ReapResult) Empty() bool
Empty reports whether the reap changed nothing.
func (ReapResult) Summary ¶ added in v0.7.144
func (r ReapResult) Summary() string
Summary describes the result in one line, or "" when nothing changed.
type RemoteBaseError ¶
type RemoteBaseError struct {
Err error
}
RemoteBaseError indicates the remote merge base could not be resolved.
func (*RemoteBaseError) Error ¶
func (e *RemoteBaseError) Error() string
func (*RemoteBaseError) Unwrap ¶
func (e *RemoteBaseError) Unwrap() error
type Session ¶
type Session struct {
URL string // WebSocket tunnel URL (ws:// or wss://)
IdentityFile string // path to SSH private key (empty when using agent)
KnownHosts string // path to known_hosts file
UseAgent bool // true when authenticating via ssh-agent
AuthSock string // SSH_AUTH_SOCK path (only used when UseAgent is true)
}
Session holds the info needed to SSH into a sidecar. It is a plain value type with no open connections or resources to close. Each call to ExecOverSSH opens and closes its own SSH connection.
func OpenSession ¶
func OpenSession(ctx context.Context, client *circleci.Client, sidecarID, identityFile, authSock string) (*Session, error)
OpenSession registers an SSH key with the sidecar and returns session info. authSock is the SSH_AUTH_SOCK path; when non-empty and no identityFile is given, the agent is tried first.
type SnapshotCriteria ¶ added in v0.7.158
type SnapshotCriteria struct {
// Repo is the repository name, e.g. "chunk-cli".
Repo string
// Stack is the detected tech stack as envbuilder names it, e.g. "go" or
// "typescript". Callers pass "" when detection failed.
Stack string
}
SnapshotCriteria describes the repository a sidecar is being created for. Both fields are optional: an empty Repo or Stack simply contributes no score.
type SnapshotMatch ¶ added in v0.7.158
type SnapshotMatch struct {
Snapshot circleci.Snapshot
// Reason is a short human-readable justification, e.g. "matches repo
// chunk-cli". Callers surface it so the choice is never silent.
Reason string
}
SnapshotMatch is a selected snapshot together with why it was selected.
func ResolveSnapshot ¶ added in v0.7.158
func ResolveSnapshot(ctx context.Context, client *circleci.Client, orgID string, c SnapshotCriteria) (SnapshotMatch, bool, error)
ResolveSnapshot lists the org's snapshots and selects the one that best fits criteria. It reports false when the org has no suitable snapshot.
func SelectSnapshot ¶ added in v0.7.158
func SelectSnapshot(snapshots []circleci.Snapshot, c SnapshotCriteria) (SnapshotMatch, bool)
SelectSnapshot picks the snapshot that best fits criteria, reporting false when none of them relate to the repository at all. Returning false rather than an arbitrary snapshot is deliberate: booting the wrong prepared environment is more confusing than booting the plain default image, because the failures it produces look like the repo's own.