agentpush

package
v0.8.10 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package agentpush isolates the live-push flow that decrypts a user's bound secret snapshot with their DEK and delivers it to the running workspace pod's agentd via HTTP. It exists as a service (not a handler method) because multiple call sites need it:

  • SetBindings (handler) — user toggled a binding in the settings drawer.
  • ReloadSecrets (handler) — explicit POST /workspaces/:id/reload-secrets.
  • workspace.Service.GetWorkspaceStatus — auto-push on pod-identity transition (worklog 0589), the reason this package was extracted.

The service takes sessionID and matchedSigningKey from context (via the package-provided helpers) rather than as function args so callers that only have a context.Context (i.e., non-handler callers like the workspace status reader) can supply them the same way handlers do. Handlers that hold a *gin.Context should first build ctx = agentpush.WithAuth(ctx, sessionID, matchedSigningKey) before calling Push.

See worklog 0589 for the design rationale — this package is the concrete satisfier of the SecretPusher interface defined by consumers (workspace.Service and the handlers).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoPodIPResolver — the service was constructed without a resolver.
	// A wiring bug; the service can't deliver anything and should not have
	// been used. Return 503 at the HTTP boundary.
	ErrNoPodIPResolver = errors.New("agentpush: pod IP resolver not configured")
	// ErrNoRunningPod — the workspace exists but has no reachable pod
	// right now (Pending, Suspended, or recreating). Not a hard failure
	// for the push flow: user-initiated callers surface 409, the
	// pod-recreation auto-push logs at info and increments the "no_pod"
	// metric outcome (this is transient, expected during pod boot races).
	ErrNoRunningPod = errors.New("agentpush: workspace has no running pod")
)

Sentinels — callers switch on these to map to HTTP status codes.

Functions

func AuthFromContext

func AuthFromContext(ctx context.Context) (string, []byte)

AuthFromContext extracts the sessionID + matchedSigningKey previously attached via WithAuth. Returns zero values when unset (Push then relies on the injector's skip-with-audit degradation for user-DEK entries).

func WithAuth

func WithAuth(ctx context.Context, sessionID string, matchedSigningKey []byte) context.Context

WithAuth returns ctx with sessionID and matchedSigningKey attached so Push can decrypt the caller's DEK-bound secrets. Handlers that hold a *gin.Context should extract these via extractAuth + extractMatchedSigningKey and call WithAuth before invoking a service method that will Push.

Types

type ModelCache

type ModelCache interface {
	Evict(workspaceID string)
}

ModelCache is invalidated after a successful push so ListModels reflects the fresh provider set. Optional; nil skips the eviction.

type Option

type Option func(*Service)

Option configures a Service.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient overrides the default 5s-timeout HTTP client (tests).

func WithLogger

func WithLogger(l pkginterfaces.LoggerInterface) Option

WithLogger installs the logger used for non-fatal warnings.

func WithMetricsHook

func WithMetricsHook(hook func(outcome string)) Option

WithMetricsHook installs an outcome-recording callback used by the pod-recreation auto-push path. Outcomes: "success", "inject_failed", "reload_failed", "no_pod". Optional; nil is silently skipped.

func WithModelCache

func WithModelCache(c ModelCache) Option

WithModelCache installs the cache to evict after a successful push.

func WithPodIPResolver

func WithPodIPResolver(r PodIPResolver) Option

WithPodIPResolver installs the pod-IP lookup.

type PodIPResolver

type PodIPResolver interface {
	GetWorkspacePodIP(ctx context.Context, userID, workspaceID string) (string, error)
}

PodIPResolver looks up the running pod IP for a workspace. Returns an empty string (and nil error, or a wrapped one) when no pod is running.

type Result

type Result struct {
	Reloaded  int  `json:"reloaded"`
	Restarted bool `json:"restarted"`
}

Result summarizes what agentd did with the pushed payload.

type SecretInjector

type SecretInjector interface {
	InjectSecrets(ctx context.Context, userID, sessionID string, matchedSigningKey []byte, workspaceID string) ([]byte, error)
}

SecretInjector is the minimum surface Push needs to build the payload: decrypt all bound secrets for the workspace with the user's DEK, degrading to skip-with-audit for user-DEK entries when the DEK is unavailable. Satisfied by *secrets.SecretService.InjectSecrets.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the concrete SecretPusher.

func New

func New(injector SecretInjector, opts ...Option) *Service

New builds a Service. Only injector is required; podResolver may be nil during early wiring in which case Push returns ErrNoPodIPResolver. modelCache and logger are optional.

func (*Service) Push

func (s *Service) Push(ctx context.Context, userID, workspaceID string) (Result, error)

Push runs InjectSecrets to build the encrypted payload with the user's DEK (from ctx auth) and posts it to the workspace pod's agentd. Callers MUST set sessionID and matchedSigningKey on ctx via WithAuth before calling; otherwise the injector degrades to skip-with-audit for user-DEK entries (phase-1 outcome).

Empty payloads ('[]') are still sent — agentd uses them to CLEAR its in-memory secret materialisations. Without this, an unbind would leave the live pod with stale plaintext until restart.

Jump to

Keyboard shortcuts

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