snapshotter

package
v0.21.1 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Overview

Package snapshotter captures comprehensive system configuration snapshots.

Overview

The snapshotter package orchestrates parallel collection of system measurements from multiple sources (Kubernetes, GPU, OS, SystemD) and produces structured snapshots that can be serialized for analysis, auditing, or recommendation generation.

Core Types

NodeSnapshotter: collects from the current node (or, when AgentConfig is set, deploys a Kubernetes Job to capture from a remote GPU node).

type NodeSnapshotter struct {
    Version     string                // Snapshotter version
    Factory     collector.Factory     // Collector factory (optional)
    Serializer  serializer.Serializer // Output serializer (optional)
    AgentConfig *AgentConfig          // Optional remote agent deployment
    RequireGPU  bool                  // Fail snapshot if no GPU detected
}

The exported entry point is the Measure method:

func (n *NodeSnapshotter) Measure(ctx context.Context) error

Job-mode collection

Deploying the agent as a Kubernetes Job is split into two exported steps so callers that need the captured bytes and callers that need them written out share one implementation:

func DeployAndCollect(ctx context.Context, config *AgentConfig) (*Snapshot, []byte, error)
func DeliverSnapshot(ctx context.Context, data []byte, dest SnapshotDelivery) error

NodeSnapshotter.Measure composes them when AgentConfig is set. Most callers should instead go through the aicr.Client facade's CollectSnapshot, which wraps DeployAndCollect (`aicr snapshot` and `aicr validate` both do); reach for NodeSnapshotter directly only for LOCAL collection, which deploys no Job and needs a collector.Factory and serializer.Serializer.

Deliver the RAW bytes DeployAndCollect returns, not a re-serialization of the parsed Snapshot: a newer agent image can emit fields the local Snapshot type does not model, and a typed round trip drops them silently.

The agent stages YAML regardless of what the caller asked for, so SnapshotDelivery.Format is where a JSON or table rendering is applied. Its zero value, and FormatYAML, deliver those bytes unchanged to a file or stdout; a cm:// destination re-serializes the document to derive its data key and labels, preserving unmodeled fields but not the exact bytes.

Snapshot: Captured configuration data

type Snapshot struct {
    Header                            // API version, kind, metadata
    Measurements []*measurement.Measurement // Collected data
}

Usage

Basic snapshot with defaults (stdout YAML):

snapshotter := &snapshotter.NodeSnapshotter{
    Version: "v1.0.0",
}

ctx := context.Background()
if err := snapshotter.Measure(ctx); err != nil {
    log.Fatalf("snapshot failed: %v", err)
}

Custom collector factory:

factory := collector.NewDefaultFactory(
    collector.WithSystemDServices([]string{"containerd.service"}),
)

snapshotter := &snapshotter.NodeSnapshotter{
    Version: "v1.0.0",
    Factory: factory,
}

if err := snapshotter.Measure(context.Background()); err != nil {
    log.Fatal(err)
}

Custom output serializer:

serializer, err := serializer.NewFileSerializer("snapshot.json")
if err != nil {
    log.Fatal(err)
}
defer serializer.Close()

snapshotter := &snapshotter.NodeSnapshotter{
    Version:    "v1.0.0",
    Serializer: serializer,
}

if err := snapshotter.Measure(context.Background()); err != nil {
    log.Fatal(err)
}

With timeout:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

snapshotter := &snapshotter.NodeSnapshotter{Version: "v1.0.0"}
if err := snapshotter.Measure(ctx); err != nil {
    log.Fatal(err)
}

Snapshot Structure

Snapshots contain a header and measurements:

apiVersion: aicr.run/v1alpha2
kind: Snapshot
metadata:
  version: v1.0.0
  source: node-1
  timestamp: 2025-01-15T10:30:00Z
measurements:
  - type: K8s
    subtypes:
      - subtype: server
        data:
          version: 1.33.5
          platform: linux/amd64
      - subtype: node
        data:
          provider: eks
          kernel-version: 6.8.0
      - subtype: image
        data:
          kube-apiserver: v1.33.5
      - subtype: policy
        data:
          driver.version: 570.86.16
      - subtype: helm
        data:
          gpu-operator.chart: gpu-operator
          gpu-operator.version: 25.3.0
      - subtype: argocd
        data:
          gpu-operator.source.chart: gpu-operator
          gpu-operator.syncStatus: Synced
  - type: GPU
    subtypes:
      - subtype: device
        data:
          driver: 570.158.01
          model: H100

Parallel Collection

NodeSnapshotter runs all collectors concurrently using errgroup:

  1. Metadata collection (node name, version)
  2. Kubernetes resources (cluster config, policies)
  3. SystemD services (containerd, kubelet)
  4. OS configuration (grub, sysctl, modules)
  5. GPU hardware (driver, model, settings)

Individual collector failures are logged and skipped — the snapshot contains all measurements that could be successfully collected. The overall Measure call only returns an error for setup, context, or serialization failures (and for missing GPU when RequireGPU is set).

Node Name Detection

Node name is determined with fallback priority:

  1. NODE_NAME environment variable
  2. KUBERNETES_NODE_NAME environment variable
  3. HOSTNAME environment variable

This ensures correct node identification in various deployment scenarios.

Error Handling

Measure() returns an error when:

  • Context is canceled or times out
  • Serialization fails
  • RequireGPU is set and no GPU was detected

Individual collector errors do not fail the snapshot; they are logged and the affected measurement is omitted, so partial snapshots are the expected outcome on heterogeneous hosts.

Observability

The snapshotter exports Prometheus metrics:

  • snapshot_collection_duration_seconds: Total time to collect snapshot
  • snapshot_collector_duration_seconds{collector}: Per-collector timing

Structured logs are emitted for:

  • Snapshot start
  • Collector progress
  • Errors and failures

Resource Requirements

Collectors may require:

  • Kubernetes API access (in-cluster config or kubeconfig)
  • NVIDIA GPU and nvidia-smi binary
  • systemd and systemctl binary
  • Read access to /proc, /sys, /etc

Failures due to missing resources are reported as errors.

Integration

The snapshotter is invoked by:

  • pkg/cli - snapshot command
  • Kubernetes Job - aicr-agent deployment

It depends on:

  • pkg/collector - Data collection implementations
  • pkg/serializer - Output formatting
  • pkg/measurement - Data structures

Snapshots are consumed by:

  • pkg/recipe - Recipe generation from snapshots
  • External analysis tools
  • Auditing and compliance systems

Index

Constants

View Source
const FullAPIVersion = header.StableGroupVersion

FullAPIVersion is the complete API version string stamped into snapshot headers. Snapshot is on the ADR-022 stable artifact track, so this aliases header.StableGroupVersion; the track's target is header.GroupVersionV1.

Variables

This section is empty.

Functions

func DefaultTolerations

func DefaultTolerations() []corev1.Toleration

DefaultTolerations returns tolerations that accept all taints. This allows the agent Job to be scheduled on any node regardless of taints.

func DeliverSnapshot added in v0.19.0

func DeliverSnapshot(ctx context.Context, data []byte, dest SnapshotDelivery) error

DeliverSnapshot writes captured snapshot bytes to the user's destination: a Go template render when TemplatePath is set, otherwise stdout (Output empty, "-", or the stdout URI), a ConfigMap (cm://namespace/name), or a file.

data must be the RAW bytes from DeployAndCollect, not a re-serialization of the parsed Snapshot — see DeployAndCollect for why. Stdout and file destinations copy those bytes when Format is YAML (or unset). Three modes necessarily parse the document instead: a template, which exposes Snapshot fields to the template, a non-YAML Format, and any cm:// destination.

ConfigMap destinations

A cm:// Output is WRITTEN here, not assumed. When the snapshot came from DeployAndCollect with the same URI as AgentConfig.Output the agent Job already staged those bytes, so this apply is redundant but idempotent — and it is what makes the function total: a caller that collected to the default internal ConfigMap and then delivers to cm://ns/name gets the artifact it asked for instead of a silent no-op. Failures surface; a destination the caller named is not something to log and skip past.

A ConfigMap is a structured resource, not a byte sink: the writer derives the snapshot.<ext> data key, the format and timestamp entries, and the resource labels from the parsed document. So this destination re-serializes even for YAML — deterministically, via serializer.MarshalYAMLDeterministic, and through a generic map so no unmodeled field is lost. Only the exact bytes are not preserved. A caller that needs byte-identical YAML should deliver to a file or stdout.

func ParseNodeSelectors

func ParseNodeSelectors(selectors []string) (map[string]string, error)

ParseNodeSelectors parses node selector strings in format "key=value".

func ParseResourceList added in v0.13.0

func ParseResourceList(spec string) (corev1.ResourceList, error)

ParseResourceList converts a comma-separated "name=quantity" list (e.g. "cpu=500m,memory=1Gi,ephemeral-storage=1Gi") into a corev1.ResourceList for use as a per-container request or limit override. An empty string returns a nil ResourceList so the caller can distinguish "no override supplied" (defaults apply) from "override supplied" (replace per-key); a sentinel error would force every call site to special-case the empty-flag path. Each quantity is parsed via resource.ParseQuantity, so the same suffixes accepted everywhere else in Kubernetes work here (m, Ki, Mi, Gi, Ti, ...).

func ParseTaint

func ParseTaint(taintStr string) (*corev1.Taint, error)

ParseTaint parses a single taint string in format "key=value:effect" or "key:effect". Returns a corev1.Taint struct.

func ParseTolerations

func ParseTolerations(tolerations []string) ([]corev1.Toleration, error)

ParseTolerations parses toleration strings in format "key=value:effect" or "key:effect". If no tolerations are provided, returns DefaultTolerations() which accepts all taints.

Types

type AgentConfig

type AgentConfig struct {
	// Kubeconfig path (optional override)
	Kubeconfig string

	// Namespace for agent deployment
	Namespace string

	// Image for agent container
	Image string

	// ImagePullSecrets for pulling the agent image from private registries
	ImagePullSecrets []string

	// JobName for the agent Job
	JobName string

	// ServiceAccountName selects the ServiceAccount the agent pod runs
	// as. It is EXACT-IF-EXISTS, so it carries two meanings resolved once
	// per deployment:
	//
	//   - A ServiceAccount of exactly this name already exists in
	//     Namespace: it is used verbatim, and the run creates NO
	//     ServiceAccount, Role, RoleBinding, ClusterRole or
	//     ClusterRoleBinding — and deletes none at cleanup. aicr adds and
	//     removes no permissions on an identity it did not create. This
	//     is how a ServiceAccount carrying IRSA
	//     (eks.amazonaws.com/role-arn) or GKE Workload Identity
	//     (iam.gke.io/gcp-service-account) annotations stays usable: both
	//     providers pin trust to the ServiceAccount NAME, which a
	//     run-scoped name can never satisfy. Generate its RBAC manifests
	//     with WriteAgentRoleManifests, then apply them out of band.
	//   - Otherwise: a name prefix. The run creates "<prefix>-<RunID>"
	//     and the full run-scoped RBAC set, and deletes them at cleanup.
	//
	// Empty falls back to NameBase and is never probed for existence, so
	// a stray ServiceAccount sitting at the default base cannot silently
	// capture the run.
	//
	// Using an existing ServiceAccount waives per-run permission
	// isolation: concurrent runs sharing it share its grants, and grants
	// provisioned for DiscoverNetwork persist beyond any one run.
	ServiceAccountName string

	// NodeSelector for targeting specific nodes
	NodeSelector map[string]string

	// Tolerations for scheduling on tainted nodes. Nil uses
	// DefaultTolerations; a non-nil empty slice explicitly disables that default.
	Tolerations []corev1.Toleration

	// Timeout for waiting for Job completion
	Timeout time.Duration

	// Cleanup determines whether to remove Job and RBAC on completion
	Cleanup bool

	// Output destination for snapshot
	Output string

	// Debug enables debug logging
	Debug bool

	// Privileged enables privileged mode (hostPID, hostNetwork, privileged container).
	// Required for GPU and SystemD collectors. When false, only K8s and OS collectors work.
	Privileged bool

	// RequireGPU requests nvidia.com/gpu resource for the agent pod.
	// Required in CDI environments (e.g., kind with nvkind) where GPU devices
	// are only injected when explicitly requested.
	RequireGPU bool

	// RuntimeClassName sets runtimeClassName on the agent pod and injects
	// NVIDIA_VISIBLE_DEVICES=all. Use instead of RequireGPU when all GPUs
	// are allocated — gives the agent nvidia-smi access without consuming
	// a GPU from the Device Plugin.
	RuntimeClassName string

	// TemplatePath is the path to a Go template file for custom output formatting.
	// When set, the snapshot output will be processed through this template.
	TemplatePath string

	// MaxNodesPerEntry limits node names per topology entry (0 = unlimited).
	MaxNodesPerEntry int

	// OS is the recipe OS criteria value (e.g., "ubuntu", "talos"). Drives
	// per-OS pod construction and in-pod collector backend selection. When
	// empty, defaults preserve the systemd-based behavior.
	OS string

	// ClusterConfigPath, when set, asks the in-pod network collector to
	// ingest a pre-existing l8k cluster-config.yaml at this path. In
	// Job-mode the path must resolve inside the agent pod (ConfigMap
	// mount, etc.) — this iteration plumbs the field through but does
	// not yet auto-mount the file; the typical use today is local mode
	// (AICR_AGENT_MODE=true) where the file lives on the caller's host.
	ClusterConfigPath string

	// AKSGPUPoolsPath, when set, points at an operator-supplied
	// `az aks nodepool list -o json` dump on the CALLER's filesystem.
	// The projection is pure file processing, so unlike ClusterConfigPath
	// it never enters the pod: the controller-side CLI projects it before
	// deploying (fail-loud on a bad file, before any cluster work) and
	// merges the aks-gpu-pools subtype into the snapshot the Job returns.
	AKSGPUPoolsPath string

	// OKEAddonsPath, when set, points at an operator-supplied
	// `oci ce cluster list-addons --cluster-id <cluster-ocid> --all --output json` dump on the
	// CALLER's filesystem. Same contract as AKSGPUPoolsPath: projected
	// controller-side before deploying, merged into the returned
	// snapshot as the oke-addons subtype.
	OKEAddonsPath string

	// DiscoverNetwork enables the in-pod network collector's live l8k
	// discovery path. Discovery is NOT read-only — it writes node labels
	// (nvidia.kubernetes-launch-kit.*) and patches NicClusterPolicy via
	// server-side-apply. RBAC must allow those writes.
	DiscoverNetwork bool

	// Requests overrides the agent container's per-resource requests.
	// When nil, the privileged/restricted defaults baked into
	// pkg/k8s/agent are used. Useful for right-sizing the agent on
	// resource-constrained dev clusters (e.g. talosctl Docker
	// provisioner workers).
	Requests corev1.ResourceList

	// Limits overrides the agent container's per-resource limits. When
	// nil, the privileged/restricted defaults are used. RequireGPU
	// defaults nvidia.com/gpu=1 only when the caller has not supplied
	// that key in Limits — e.g. --require-gpu --limits nvidia.com/gpu=4
	// keeps 4, not 1.
	Limits corev1.ResourceList

	// RunID scopes every resource this deployment creates (Job, RBAC, and
	// the internal staging ConfigMap when Output does not name one) to a
	// single run, so concurrent snapshot-agent runs never collide on a
	// shared resource name. DeployAndCollect generates one with
	// runid.Generate() when this is empty — callers normally leave it
	// unset; setting it explicitly is for correlating this run with an
	// external identifier (e.g. sharing one ID with a downstream
	// validator run).
	//
	// DeployAndCollect never writes the generated value back here: the
	// AgentConfig belongs to the caller, and a caller reusing one config
	// pointer across two runs would otherwise silently become a caller
	// pinning a duplicate RunID — the one state ADR-020 declares
	// unsupported, which fails the second run with ErrCodeInternal on the
	// first still-existing run-scoped object.
	RunID string

	// NameBase prefixes generated resource names (Job, ServiceAccount,
	// Role/RoleBinding). It applies per name: JobName falls back to it
	// when JobName is empty, and ServiceAccountName (which also names the
	// Role and RoleBinding) falls back to it when ServiceAccountName is
	// empty — so setting only one of the two leaves NameBase governing the
	// other. Forwarded verbatim to pkg/k8s/agent.Config.NameBase, which
	// defaults to "aicr" when also empty.
	NameBase string
}

AgentConfig contains configuration for Kubernetes agent deployment.

type AgentRoleObject added in v0.21.0

type AgentRoleObject struct {
	// Kind is the Kubernetes kind ("Role", "RoleBinding", "ClusterRole",
	// "ClusterRoleBinding").
	Kind string

	// Name is the object's metadata.name.
	Name string

	// Path is the manifest's path, including the output directory.
	Path string
}

AgentRoleObject identifies one written manifest so a caller can report what landed where without re-deriving either the name or the file.

type AgentRolesConfig added in v0.21.0

type AgentRolesConfig struct {
	// Namespace is the namespace of the ServiceAccount, and the namespace
	// the rendered Role and RoleBinding declare. Required.
	Namespace string

	// ServiceAccountName is the name of the ServiceAccount the rendered
	// bindings name as their subject. Required, and not verified to
	// exist — see WriteAgentRoleManifests.
	ServiceAccountName string

	// DiscoverNetwork also renders the cluster-scoped MUTATING rules that
	// `aicr snapshot --discover-network` needs, with a header enumerating
	// each one and the discovery step it exists for.
	DiscoverNetwork bool

	// RunID names the output directory (`snapshot-rbac-<RunID>`). Empty
	// generates one, which is the normal path; it is injectable so tests
	// and automation can pin a directory name.
	RunID string
}

AgentRolesConfig selects the ServiceAccount that WriteAgentRoleManifests renders the snapshot agent's RBAC for.

There is deliberately no Kubeconfig field: writing the manifests contacts no cluster, so there is no connection to configure.

type AgentRolesResult added in v0.21.0

type AgentRolesResult struct {
	// Dir is the output directory, relative to the working directory the
	// call was made from.
	Dir string

	// RunID is the run ID the directory name was built from.
	RunID string

	Namespace          string
	ServiceAccountName string

	// Objects lists what was written, in the order the files apply.
	Objects []AgentRoleObject

	// DiscoverNetwork echoes AgentRolesConfig.DiscoverNetwork: it is the
	// difference between a read-only grant and one carrying cluster-scoped
	// mutating rules, so anything reporting this result can say which was
	// written.
	DiscoverNetwork bool
}

AgentRolesResult names the directory WriteAgentRoleManifests wrote and what it put there.

It is snapshotter-owned rather than pkg/k8s/agent's own Manifest type so callers presenting the outcome — the CLI among them — need no dependency on the Kubernetes-facing package.

func WriteAgentRoleManifests added in v0.21.0

func WriteAgentRoleManifests(config *AgentRolesConfig) (*AgentRolesResult, error)

WriteAgentRoleManifests writes the RBAC manifests that grant the snapshot agent's permissions to an operator-supplied ServiceAccount into a new `snapshot-rbac-<runID>` directory in the current working directory.

It APPLIES NOTHING and contacts no cluster. No clientset is built, the ServiceAccount is never looked up, and no permission pre-flight runs — so the call succeeds with no kubeconfig and no cluster privileges at all. The operator reviews the files and then applies them:

kubectl apply -f snapshot-rbac-<runID>/

and removes the grant with the matching delete:

kubectl delete -f snapshot-rbac-<runID>/

The ServiceAccount named in ServiceAccountName is NOT verified to exist. That is a deliberate simplification of the earlier behavior, which failed with ErrCodeNotFound against the cluster: a mistyped name now yields manifests the operator inspects before applying, and the rendered RoleBinding tells them how to check.

The directory must not already exist. Colliding with one returns ErrCodeConflict rather than overwriting, because the manifests an operator is midway through reviewing are exactly what must not change under them.

The objects are outside every run's lifecycle: no run-ID label, never in a run's created-set, never deleted by run cleanup. Teardown is the operator's `kubectl delete`.

type NodeSnapshotter

type NodeSnapshotter struct {
	// Version is the snapshotter version.
	Version string

	// Factory is the collector factory to use. If nil, the default factory is used.
	Factory collector.Factory

	// Serializer is the serializer to use for output. If nil, a default stdout JSON serializer is used.
	Serializer serializer.Serializer

	// AgentConfig contains configuration for agent deployment mode. If nil or Enabled=false, runs locally.
	AgentConfig *AgentConfig

	// RequireGPU when true causes the snapshot to fail if no GPU is detected.
	RequireGPU bool

	// AKSGPUPoolsPath, when set, points at an operator-supplied
	// `az aks nodepool list -o json` dump. Local mode projects it into
	// the K8s measurement's aks-gpu-pools subtype up front (fail-loud —
	// explicit operator input never rides the collectSafe degrade-to-
	// warning policy). Agent Job mode carries the equivalent field on
	// AgentConfig and merges controller-side after retrieval.
	AKSGPUPoolsPath string

	// OKEAddonsPath, when set, points at an operator-supplied
	// `oci ce cluster list-addons --cluster-id <cluster-ocid> --all --output json` dump. Same
	// contract as AKSGPUPoolsPath: projected fail-loud into the K8s
	// measurement's oke-addons subtype before any collector runs.
	OKEAddonsPath string
}

NodeSnapshotter collects system configuration measurements from the current node. It coordinates multiple collectors in parallel to gather data about Kubernetes, GPU hardware, OS configuration, and systemd services, then serializes the results. If AgentConfig is provided with Enabled=true, it deploys a Kubernetes Job instead.

func (*NodeSnapshotter) Measure

func (n *NodeSnapshotter) Measure(ctx context.Context) error

Measure collects configuration measurements and serializes the snapshot. When AgentConfig is set, it deploys a Kubernetes Job to capture the snapshot on a GPU node. Otherwise, it runs collectors locally in parallel. Individual collector failures are logged and skipped — the snapshot contains all measurements that could be successfully collected.

type Snapshot

type Snapshot struct {
	header.Header `json:",inline" yaml:",inline"`

	// Fingerprint is a structured cluster identity derived from the
	// raw measurements: detected service, accelerator, OS,
	// Kubernetes server version, region, and node count. Populated
	// after all collectors finish so it reflects the final
	// measurement set.
	//
	// The embedded Fingerprint is advisory: it is a convenience for
	// humans reading the snapshot file, not an authoritative claim.
	// Consumers of the snapshot that bear trust — notably the
	// ADR-007 bundler when building the predicate body and the
	// evidence verifier when re-checking it — MUST recompute the
	// Fingerprint from Measurements via fingerprint.FromMeasurements
	// rather than read this field. The snapshot YAML is not signed
	// at this layer; an attacker controlling the file could swap
	// the embedded Fingerprint without touching the measurements
	// that back it.
	Fingerprint *fingerprint.Fingerprint `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"`

	// Measurements contains the collected measurements from various collectors.
	Measurements []*measurement.Measurement `json:"measurements" yaml:"measurements"`
}

Snapshot represents a collected configuration snapshot from a system node. It contains metadata and measurements from various collectors including Kubernetes, GPU, OS configuration, and systemd services.

func DeployAndCollect added in v0.19.0

func DeployAndCollect(ctx context.Context, config *AgentConfig) (*Snapshot, []byte, error)

DeployAndCollect deploys the agent Job, waits for it, and returns both the parsed Snapshot and the RAW bytes the agent emitted. It is the single deploy-and-retrieve implementation behind every caller — the aicr.Client facade's CollectSnapshot (and therefore `aicr snapshot` and `aicr validate`) and NodeSnapshotter.measureWithAgent.

Why both return values

The parsed *Snapshot is what consumers reason about; the raw bytes are what gets written out. They are NOT interchangeable: re-serializing the typed struct drops any field a newer agent image emitted that this binary's Snapshot type does not know about. Callers that persist the snapshot must deliver the raw bytes (see DeliverSnapshot) so `aicr snapshot` output stays byte-identical to what the agent produced.

Where the agent writes

The Job always stages its result in a ConfigMap. When config.Output is a cm:// URI that ConfigMap IS the user's destination, so the Job writes there directly; otherwise the Job writes to an internal ConfigMap in config.Namespace and the caller delivers the returned bytes.

Fail-before-mutate

Every input that can be rejected without contacting the cluster is checked up front — before the Kubernetes client is even built, so a rejection is never masked by a kubeconfig error and never leaves RBAC or a Job behind (with Cleanup false, the zero value, they would persist). That covers a malformed cm:// Output, an empty Namespace, and a Job-mode ClusterConfigPath.

The *Snapshot return has no consumer inside this package — measureWithAgent only delivers the bytes — but it is the value aicr.Client.CollectSnapshot hands to SDK callers and to `aicr validate`, hence the unparam exemption below. Keep the rationale in prose: gofmt moves //nolint (a directive-shaped comment) to the end of the doc block, so continuation lines written under it get hoisted above and orphaned.

func LoadFromFile added in v0.16.0

func LoadFromFile(ctx context.Context, path string) (*Snapshot, error)

LoadFromFile reads a snapshot from path (local file, HTTP(S) URL, or cm:// ConfigMap URI) and enforces apiVersion compatibility. It is equivalent to LoadFromFileWithKubeconfig with an empty kubeconfig.

func LoadFromFileWithKubeconfig added in v0.16.0

func LoadFromFileWithKubeconfig(ctx context.Context, path, kubeconfig string) (*Snapshot, error)

LoadFromFileWithKubeconfig reads a snapshot from path using kubeconfig for cm:// resolution, then rejects a document that is not a snapshot this build can consume.

Deserialization is non-strict, so any YAML mapping decodes into a Snapshot with zero-value fields. Without an identity gate a wrong file (typo'd path, an AICRConfig, arbitrary YAML) would decode into an empty Snapshot, derive criteria(any), and silently emit a fallback recipe with exit 0. We fail closed instead, in identity → version → content order:

  • A non-empty kind other than Snapshot (e.g. AICRConfig) is the wrong document type. An empty kind is tolerated because older snapshots predate the field.
  • A non-empty apiVersion this build does not understand means the snapshot came from an incompatible aicr version, so we fail closed rather than risk a schema mismatch during validation. An empty apiVersion is tolerated for the same backward-compatibility reason.
  • A document with no usable measurement — regardless of kind — cannot be distinguished from empty cluster state and would still derive criteria(any). A measurement is usable only if it is non-nil and carries a type; fingerprinting ignores nil and typeless entries alike. This gate backstops a correctly stamped but empty (kind: Snapshot, measurements: []) file as well as slices of only nil (- null) or typeless (- {}) entries.

func NewSnapshot

func NewSnapshot() *Snapshot

NewSnapshot creates a new Snapshot instance with an initialized Measurements slice.

type SnapshotDelivery added in v0.19.0

type SnapshotDelivery struct {
	// Output is the destination: empty, "-", or the stdout URI for stdout;
	// a cm://namespace/name URI for a ConfigMap; any other value is a file
	// path.
	Output string

	// TemplatePath, when set, renders the snapshot through a Go template
	// instead of copying bytes. Takes precedence over the Output scheme —
	// Output then names the rendered report's destination.
	TemplatePath string

	// Kubeconfig is the path used to reach the cluster for a cm:// Output.
	// Empty means in-cluster or the standard discovery chain. Ignored for
	// every other destination.
	Kubeconfig string

	// Format is the rendering the caller asked for. The zero value and
	// serializer.FormatYAML both deliver the agent's bytes verbatim to
	// stdout and file destinations; JSON and table re-render the document
	// (see renderSnapshotFormat). A cm:// destination always re-serializes,
	// whatever the format — see the ConfigMap section on DeliverSnapshot.
	// Ignored when TemplatePath is set, since a template supplies its own
	// rendering.
	Format serializer.Format
}

SnapshotDelivery describes where DeliverSnapshot writes captured bytes. Mirrors the delivery-relevant subset of AgentConfig so callers that already hold one can forward it, and callers that hold only bytes (an SDK consumer with a Snapshot.Raw) can construct one directly.

Jump to

Keyboard shortcuts

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