oci

package module
v0.0.29 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: Apache-2.0 Imports: 34 Imported by: 1

README

klaus-oci

Shared Go types and ORAS client for Klaus OCI artifacts.

Klaus uses OCI artifacts with custom media types for distributing plugins, personalities, and toolchains. This module provides:

  • Domain types: Plugin, Personality, Toolchain with OCI config blob serialization
  • Describe operations: fetch metadata without downloading content layers
  • Typed pull/push API for personalities and plugins
  • List operations with version enumeration
  • Reference resolution (short names to fully-qualified OCI references)
  • Dependency resolution for personalities (toolchain + plugin references)
  • Helpers for reading artifact metadata from source directories
  • Digest-based caching to avoid redundant pulls
  • Secure tar.gz archive extraction with path traversal protection

Both klausctl and the klaus-operator import this module to ensure artifact format consistency.

Installation

go get github.com/giantswarm/klaus-oci

Usage

Listing available artifacts
import oci "github.com/giantswarm/klaus-oci"

client := oci.NewClient(oci.WithRegistryAuthEnv("KLAUSCTL_REGISTRY_AUTH"))

// List available personalities (returns []ListEntry)
personalities, err := client.ListPersonalities(ctx)
for _, p := range personalities {
    fmt.Printf("%s (%s)\n", p.Name, p.Version) // "sre (v1.0.0)"
}

// List plugins from a custom registry
plugins, err := client.ListPlugins(ctx, oci.WithRegistry("custom.io/team/klaus-plugins"))

// List toolchains
toolchains, err := client.ListToolchains(ctx)
Listing versions for a specific artifact
// List all semver tags for a plugin, sorted descending
versions, err := client.ListPluginVersions(ctx, "gs-base")
// versions: ["v1.2.0", "v1.1.0", "v1.0.0"]

// Also works for personalities and toolchains
versions, err = client.ListPersonalityVersions(ctx, "sre")
versions, err = client.ListToolchainVersions(ctx, "go")
Describing artifacts (metadata only, no download)
// Describe a plugin -- fetches config blob, no content layer download
desc, err := client.DescribePlugin(ctx, "gs-base")
fmt.Println(desc.Plugin.Name)        // "gs-base"
fmt.Println(desc.Plugin.Version)     // "v1.0.0" (from OCI tag)
fmt.Println(desc.Plugin.Description) // "A general purpose plugin..."
fmt.Println(desc.Plugin.Skills)      // ["fluxcd", "kubernetes", ...]
fmt.Println(desc.Plugin.Commands)    // ["hello", "init-circleci", ...]
fmt.Println(desc.Digest)             // "sha256:abc..."

// Describe a personality -- metadata + composition, no soul text
desc, err := client.DescribePersonality(ctx, "sre:v1.0.0")
fmt.Println(desc.Personality.Name)      // "sre"
fmt.Println(desc.Personality.Toolchain) // {Repository: "gsoci.../go", Tag: "latest"}
fmt.Println(desc.Personality.Plugins)   // [{Repository: "gsoci.../gs-base", Tag: "latest"}, ...]

// Describe a toolchain -- metadata from OCI manifest annotations
desc, err := client.DescribeToolchain(ctx, "go")
fmt.Println(desc.Toolchain.Name)        // "go"
fmt.Println(desc.Toolchain.Version)     // "v1.2.0" (from OCI tag)
fmt.Println(desc.Toolchain.Description) // "Go toolchain for Klaus"
Pulling artifacts
// Pull a personality -- downloads config blob + content layer, extracts files
pulled, err := client.PullPersonality(ctx, "sre:v1.0.0", cacheDir)
fmt.Println(pulled.Personality.Name)      // "sre"
fmt.Println(pulled.Personality.Toolchain) // toolchain reference
fmt.Println(pulled.Soul)                  // SOUL.md contents (only available after pull)
fmt.Println(pulled.Dir)                   // local extraction directory
fmt.Println(pulled.Cached)               // true if skipped (cache hit)

// Pull a plugin
pulled, err := client.PullPlugin(ctx, "gs-base:v1.0.0", destDir)
fmt.Println(pulled.Plugin.Name)    // "gs-base"
fmt.Println(pulled.Plugin.Version) // "v1.0.0"
fmt.Println(pulled.Dir)            // local extraction directory
Pushing artifacts
// Read plugin metadata from source directory
plugin, err := oci.ReadPluginFromDir("./my-plugin")
// plugin.Name, plugin.Description, etc. from .claude-plugin/plugin.json
// plugin.Skills, plugin.Commands, etc. discovered by scanning the directory

// Push plugin -- version is conveyed via the OCI tag, not stored in config blob
result, err := client.PushPlugin(ctx, "./my-plugin",
    "gsoci.azurecr.io/giantswarm/klaus-plugins/my-plugin:v1.0.0", *plugin)
fmt.Println(result.Digest) // "sha256:abc..."

// Read personality metadata from source directory
personality, err := oci.ReadPersonalityFromDir("./my-personality")
// personality.Name, personality.Toolchain, personality.Plugins from personality.yaml

// Push personality -- version via OCI tag, SOUL.md included automatically
result, err := client.PushPersonality(ctx, "./my-personality",
    "gsoci.azurecr.io/giantswarm/klaus-personalities/my-personality:v1.0.0", *personality)
Resolving references
// Resolve short names to fully-qualified references with latest semver tag
ref, err := client.ResolvePersonalityRef(ctx, "sre")       // -> "gsoci.../sre:v1.0.0"
ref, err = client.ResolvePluginRef(ctx, "gs-base")          // -> "gsoci.../gs-base:v1.2.0"
ref, err = client.ResolveToolchainRef(ctx, "go")            // -> "gsoci.../go:v1.1.0"

// Refs with explicit tags are returned as-is (unless "latest")
ref, err = client.ResolvePluginRef(ctx, "gs-base:v0.5.0")   // -> "gsoci.../gs-base:v0.5.0"
Resolving personality dependencies
// Describe a personality, then resolve its toolchain and plugin references
desc, err := client.DescribePersonality(ctx, "sre:v1.0.0")

deps, err := client.ResolvePersonalityDeps(ctx, desc.Personality)
fmt.Println(deps.Toolchain.Name)       // "go"
fmt.Println(deps.Toolchain.Version)    // "v1.2.0"
for _, p := range deps.Plugins {
    fmt.Printf("  plugin: %s (%s)\n", p.Name, p.Version)
}
for _, w := range deps.Warnings {
    fmt.Println("  warning:", w) // e.g. "plugin gs-sre: not found in registry"
}
Registry response cache

Network roundtrips dominate the latency of Describe*, Resolve*Ref, and List*Versions. Enable the on-disk cache to make repeated invocations fast:

client := oci.NewClient(
    oci.WithCache("/var/cache/klaus/oci"),
    oci.WithCacheTTL(30*time.Second, 24*time.Hour),
    oci.WithCacheMaxSize(2*1024*1024*1024),
    oci.WithBackgroundRefresh(true),
)
defer client.CloseCache()

The cache is stale-while-revalidate with digest-based invalidation:

  • Fresh TTL (default 30s): entries are returned without contacting the registry.
  • Stale TTL (default 24h for tags/refs, 7 days for the catalog): entries past the fresh window are returned immediately and a background refresh is issued. HEAD probes on refs use Docker-Content-Digest; tag and catalog lists use conditional GETs (If-None-Match / ETag).
  • Past stale TTL: the next call refetches synchronously.
  • HEAD/digest mismatch: when a HEAD returns a new digest, the ref entry is rewritten so subsequent reads see the updated manifest.

Concurrent misses for the same key coalesce via singleflight, so a burst of parallel Resolve calls produces a single registry request.

On-disk layout
<root>/
  blobs/                 # oras-go content store (content-addressed)
    sha256/aa/bb.../...
  refs/<sha256-of-key>.json    # tag -> digest index (per full ref)
  tags/<sha256-of-key>.json    # tag list per repository
  catalog/<sha256-of-key>.json # catalog per registry base

JSON indexes are written via temp-file + rename in the same directory, so concurrent writers never observe partial files. The content store is an LRU (by mtime) bounded by WithCacheMaxSize; a non-positive size disables eviction.

If caching is not configured (no WithCache), the client behaves exactly as before. This cache is orthogonal to the per-destination .oci-cache.json written by PullPersonality / PullPlugin, which remains the authority for "is this artifact already extracted at this path".

Artifact Types

Klaus has three artifact types with different OCI representations:

Artifact OCI Format Metadata Source Config Media Type Content Media Type
Plugin Custom OCI artifact Config blob (JSON) application/vnd.giantswarm.klaus-plugin.config.v1+json application/vnd.giantswarm.klaus-plugin.content.v1.tar+gzip
Personality Custom OCI artifact Config blob (JSON) application/vnd.giantswarm.klaus-personality.config.v1+json application/vnd.giantswarm.klaus-personality.content.v1.tar+gzip
Toolchain Standard Docker image Manifest annotations (standard Docker config) (standard Docker layers)
Version handling

The version is never stored in the OCI config blob. For all three artifact types, the version is conveyed exclusively via the OCI tag. The Version field on domain types (Plugin, Personality, Toolchain) is populated from the resolved OCI tag during describe/pull operations.

License

Apache 2.0 - see LICENSE.

Documentation

Overview

Package oci provides shared types and an ORAS client for Klaus OCI artifacts.

Klaus uses OCI artifacts with custom media types for distributing plugins and personalities. This package defines the media types, metadata structs, and a registry client that both klausctl and the klaus-operator can use.

Index

Constants

View Source
const (
	AnnotationName        = "io.giantswarm.klaus.name"
	AnnotationDescription = "io.giantswarm.klaus.description"
	AnnotationHomepage    = "io.giantswarm.klaus.homepage"
	AnnotationRepository  = "io.giantswarm.klaus.repository"
	AnnotationLicense     = "io.giantswarm.klaus.license"
	AnnotationKeywords    = "io.giantswarm.klaus.keywords"
	AnnotationAuthorName  = "io.giantswarm.klaus.author.name"
	AnnotationAuthorEmail = "io.giantswarm.klaus.author.email"
	AnnotationAuthorURL   = "io.giantswarm.klaus.author.url"
)

Klaus-specific OCI manifest annotation keys. All artifact types (plugins, personalities, toolchains) use these annotations to carry common metadata on the manifest.

View Source
const (
	// MediaTypePluginConfig is the OCI media type for the plugin config blob.
	MediaTypePluginConfig = "application/vnd.giantswarm.klaus-plugin.config.v1+json"

	// MediaTypePluginContent is the OCI media type for the plugin content layer.
	MediaTypePluginContent = "application/vnd.giantswarm.klaus-plugin.content.v1.tar+gzip"
)

Media types for Klaus plugin artifacts.

View Source
const (
	// MediaTypePersonalityConfig is the OCI media type for the personality config blob.
	MediaTypePersonalityConfig = "application/vnd.giantswarm.klaus-personality.config.v1+json"

	// MediaTypePersonalityContent is the OCI media type for the personality content layer.
	MediaTypePersonalityContent = "application/vnd.giantswarm.klaus-personality.content.v1.tar+gzip"
)

Media types for Klaus personality artifacts.

View Source
const (
	DefaultPluginRegistry      = "gsoci.azurecr.io/giantswarm/klaus-plugins"
	DefaultPersonalityRegistry = "gsoci.azurecr.io/giantswarm/klaus-personalities"
	DefaultToolchainRegistry   = "gsoci.azurecr.io/giantswarm/klaus-toolchains"
)

Default OCI registry base paths for each Klaus artifact type.

View Source
const (
	// DefaultCacheFreshTTL is the window within which a cache entry is
	// returned with zero network traffic.
	DefaultCacheFreshTTL = 30 * time.Second
	// DefaultCacheStaleTTL is the default outer bound for tag and reference
	// entries: beyond this age the entry is discarded and refetched
	// synchronously.
	DefaultCacheStaleTTL = 24 * time.Hour
	// DefaultCacheCatalogStaleTTL is the outer bound for catalog entries.
	// Registry catalogs change slowly so we keep them much longer.
	DefaultCacheCatalogStaleTTL = 7 * 24 * time.Hour
	// DefaultCacheMaxSize is the default LRU budget (bytes) for the content
	// store.
	DefaultCacheMaxSize int64 = 2 * 1024 * 1024 * 1024
)

Default TTLs and size limit for the on-disk cache. Exposed as constants so callers can reason about cache behavior.

Variables

This section is empty.

Functions

func IsCached

func IsCached(dir string, digest string) bool

IsCached returns true if the directory has a cache entry matching the given manifest digest.

func LatestSemverTag added in v0.0.5

func LatestSemverTag(tags []string) string

LatestSemverTag returns the highest semver tag from the given list. Tags that are not valid semver are silently ignored.

func RepositoryFromRef added in v0.0.5

func RepositoryFromRef(ref string) string

RepositoryFromRef extracts the repository part from an OCI reference, stripping the tag or digest suffix. Handles both repo:tag and repo@sha256:digest formats. Port-only colons (e.g. localhost:5000/repo) are preserved. References without a path component (e.g. "localhost:5000") are returned unchanged.

func ShortName

func ShortName(repository string) string

ShortName extracts the last segment of a repository path. For example, "gsoci.azurecr.io/giantswarm/klaus-plugins/gs-platform" returns "gs-platform".

func SplitNameTag added in v0.0.5

func SplitNameTag(ref string) (string, string)

SplitNameTag splits "name:tag" into name and tag. If no tag-position colon is present, tag is empty. Port-only colons (e.g. "localhost:5000/repo") are not treated as tag separators.

func SplitRegistryBase added in v0.0.3

func SplitRegistryBase(base string) (host, prefix string)

SplitRegistryBase splits a registry base path into the registry host and the repository name prefix (with trailing slash). For example, "gsoci.azurecr.io/giantswarm/klaus-plugins" returns ("gsoci.azurecr.io", "giantswarm/klaus-plugins/"). If the base contains no slash, the prefix is empty (matches all repositories).

func ToolchainRegistryRef added in v0.0.5

func ToolchainRegistryRef(name string) string

ToolchainRegistryRef returns the full registry reference for a toolchain image name. Toolchains use the pattern gsoci.azurecr.io/giantswarm/klaus-toolchains/<name>. If the name already starts with the toolchain registry base, it is returned as-is.

func TruncateDigest

func TruncateDigest(d string) string

TruncateDigest shortens a digest string for human-readable display. For example, "sha256:abc123def456..." becomes "sha256:abc123def456".

func WriteCacheEntry

func WriteCacheEntry(dir string, entry CacheEntry) error

WriteCacheEntry writes cache metadata to a directory. The PulledAt timestamp is always set to the current time.

Types

type ArtifactInfo added in v0.0.2

type ArtifactInfo struct {
	Ref    string // Fully-qualified OCI reference (includes tag)
	Tag    string // Resolved OCI tag (e.g. "v1.0.0") -- source of truth for Version
	Digest string // Manifest digest
}

ArtifactInfo holds OCI-level metadata returned by all operations that contact the registry (describe, pull).

type Author added in v0.0.8

type Author struct {
	Name  string `json:"name,omitempty" yaml:"name,omitempty"`
	Email string `json:"email,omitempty" yaml:"email,omitempty"`
	URL   string `json:"url,omitempty" yaml:"url,omitempty"`
}

Author represents the author of an artifact.

type CacheEntry

type CacheEntry struct {
	// Digest is the OCI manifest digest.
	Digest string `json:"digest"`
	// Ref is the original OCI reference that was pulled.
	Ref string `json:"ref"`
	// PulledAt is when the artifact was last pulled.
	PulledAt time.Time `json:"pulledAt"`
	// ConfigJSON is the raw OCI config blob, persisted so that metadata
	// remains available on cache hits without re-fetching.
	ConfigJSON json.RawMessage `json:"configJSON,omitempty"`
	// Annotations are the OCI manifest annotations, persisted so that
	// common metadata is available on cache hits.
	Annotations map[string]string `json:"annotations,omitempty"`
}

CacheEntry holds metadata about a cached artifact.

func ReadCacheEntry

func ReadCacheEntry(dir string) (*CacheEntry, error)

ReadCacheEntry reads the cache metadata from a directory.

type CacheStore added in v0.0.17

type CacheStore interface {
	// ResolveTag returns the manifest digest for a tag reference of the form
	// "host/repo:tag". Entries are revalidated with HEAD probes against the
	// registry.
	ResolveTag(ctx context.Context, ref string) (string, error)

	// ResolveManifest returns the full manifest descriptor (digest, size,
	// media type) for a tag reference. Callers that plan to fetch the
	// manifest body should use this method rather than ResolveTag so the
	// returned descriptor carries enough information for downstream
	// verification.
	ResolveManifest(ctx context.Context, ref string) (ocispec.Descriptor, error)

	// Tags returns all tags for a repository of the form "host/repo". Tag
	// lists are revalidated with conditional GETs (If-None-Match).
	Tags(ctx context.Context, repo string) ([]string, error)

	// Repositories returns all repositories under a registry base of the
	// form "host/prefix". Empty prefixes enumerate the whole registry.
	Repositories(ctx context.Context, base string) ([]string, error)

	// Fetch returns a reader for the content of desc. The content is served
	// from the local content store when present; otherwise it is fetched
	// from the registry and inserted into the store before returning.
	Fetch(ctx context.Context, repo string, desc ocispec.Descriptor) (io.ReadCloser, error)

	// Close releases resources. It waits for any in-flight background
	// revalidations to complete.
	Close() error
}

CacheStore provides cached lookups against an OCI registry.

When a CacheStore is attached to a Client via WithCache, every read path (tag resolve, tag list, repository catalog, manifest/blob fetch) consults the store first and falls back to the network on miss. The store is a pure cache: it never serves stale data that contradicts the registry, only data that is either fresh or known-good-pending-revalidation.

Implementations must be safe for concurrent use, and must be safe to use across concurrent processes sharing the same backing directory.

type Client

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

Client is an ORAS-based client for interacting with OCI registries that host Klaus artifacts (plugins and personalities).

func NewClient

func NewClient(opts ...ClientOption) *Client

NewClient creates a new OCI client for Klaus artifacts.

func (*Client) CloseCache added in v0.0.17

func (c *Client) CloseCache() error

CloseCache releases any cache resources held by the client. It is safe to call even when no cache was configured.

func (*Client) DescribePersonality added in v0.0.8

func (c *Client) DescribePersonality(ctx context.Context, ref string) (*DescribedPersonality, error)

DescribePersonality fetches the config blob for a personality artifact and returns metadata without downloading the content layer. The soul text is NOT available via describe -- use PullPersonality to get it.

func (*Client) DescribePlugin added in v0.0.8

func (c *Client) DescribePlugin(ctx context.Context, ref string) (*DescribedPlugin, error)

DescribePlugin fetches the config blob for a plugin artifact and returns metadata without downloading the content layer. The ref parameter supports short names (e.g. "gs-base"), name:tag, or full OCI references.

func (*Client) DescribeToolchain added in v0.0.8

func (c *Client) DescribeToolchain(ctx context.Context, ref string) (*DescribedToolchain, error)

DescribeToolchain fetches the manifest for a toolchain image and returns metadata derived from OCI manifest annotations. No config blob or layers are downloaded.

func (*Client) List

func (c *Client) List(ctx context.Context, repository string) ([]string, error)

List returns all tags in the given repository.

func (*Client) ListPersonalities added in v0.0.8

func (c *Client) ListPersonalities(ctx context.Context, opts ...ListOption) ([]ListEntry, error)

ListPersonalities discovers all personality artifacts under the default personality registry (or a custom one via WithRegistry) and returns ListEntry results with name and version extracted from the repository path and tag.

func (*Client) ListPersonalityVersions added in v0.0.8

func (c *Client) ListPersonalityVersions(ctx context.Context, nameOrRef string) ([]string, error)

ListPersonalityVersions returns all semver tags for a personality, sorted descending. nameOrRef can be a short name (e.g. "sre") or a full OCI repository path.

func (*Client) ListPluginVersions added in v0.0.8

func (c *Client) ListPluginVersions(ctx context.Context, nameOrRef string) ([]string, error)

ListPluginVersions returns all semver tags for a plugin, sorted descending. nameOrRef can be a short name (e.g. "gs-base") or a full OCI repository path.

func (*Client) ListPlugins added in v0.0.8

func (c *Client) ListPlugins(ctx context.Context, opts ...ListOption) ([]ListEntry, error)

ListPlugins discovers all plugin artifacts under the default plugin registry (or a custom one via WithRegistry) and returns ListEntry results.

func (*Client) ListToolchainVersions added in v0.0.8

func (c *Client) ListToolchainVersions(ctx context.Context, nameOrRef string) ([]string, error)

ListToolchainVersions returns all semver tags for a toolchain, sorted descending. nameOrRef can be a short name (e.g. "go") or a full OCI repository path.

func (*Client) ListToolchains added in v0.0.8

func (c *Client) ListToolchains(ctx context.Context, opts ...ListOption) ([]ListEntry, error)

ListToolchains discovers all toolchain images under the default toolchain registry (or a custom one via WithRegistry) and returns ListEntry results.

func (*Client) PullPersonality added in v0.0.8

func (c *Client) PullPersonality(ctx context.Context, ref string, cacheDir string) (*PulledPersonality, error)

PullPersonality downloads a personality artifact from an OCI registry and returns a PulledPersonality with metadata, composition, and soul content. Both annotations (common metadata) and the config blob (composition data) are persisted in the cache entry so that metadata is always populated, even on cache hits.

func (*Client) PullPlugin added in v0.0.8

func (c *Client) PullPlugin(ctx context.Context, ref string, destDir string) (*PulledPlugin, error)

PullPlugin downloads a plugin artifact from an OCI registry and returns a PulledPlugin with metadata and the extraction directory. Common metadata is populated from manifest annotations; type-specific fields come from the config blob.

func (*Client) PushPersonality added in v0.0.8

func (c *Client) PushPersonality(ctx context.Context, sourceDir, ref string, p Personality) (*PushResult, error)

PushPersonality pushes a personality artifact to an OCI registry. Common metadata (name, description, author, etc.) is stored as Klaus annotations on the manifest. The config blob contains only composition data (toolchain + plugins). Version is conveyed through the OCI tag.

func (*Client) PushPlugin added in v0.0.8

func (c *Client) PushPlugin(ctx context.Context, sourceDir, ref string, p Plugin) (*PushResult, error)

PushPlugin pushes a plugin artifact to an OCI registry. Common metadata (name, description, author, etc.) is stored as Klaus annotations on the manifest. The config blob contains only discovered components (skills, commands, etc.). Version is conveyed through the OCI tag.

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, ref string) (string, error)

Resolve resolves a reference (tag or digest) to its manifest digest.

func (*Client) ResolveLatestVersion added in v0.0.5

func (c *Client) ResolveLatestVersion(ctx context.Context, repository string) (string, error)

ResolveLatestVersion lists tags for a repository and returns the full reference with the highest semver tag (e.g. "repo:v1.2.3").

func (*Client) ResolvePersonalityDeps added in v0.0.8

func (c *Client) ResolvePersonalityDeps(ctx context.Context, p Personality) (*ResolvedDependencies, error)

ResolvePersonalityDeps resolves a personality's toolchain and plugin references by describing each dependency from the registry. The toolchain and all plugins are resolved concurrently, bounded by the client's concurrency limit.

Missing or unreachable artifacts produce warnings rather than hard failures, allowing callers to present partial results (e.g. "plugin gs-sre: not found in registry").

func (*Client) ResolvePersonalityRef added in v0.0.5

func (c *Client) ResolvePersonalityRef(ctx context.Context, ref string) (string, error)

ResolvePersonalityRef resolves a personality short name or OCI reference to a fully-qualified reference with its latest semver tag. Short names (e.g. "sre") are expanded using the default personality registry (e.g. "gsoci.azurecr.io/giantswarm/klaus-personalities/sre:v0.2.0").

func (*Client) ResolvePluginRef added in v0.0.5

func (c *Client) ResolvePluginRef(ctx context.Context, ref string) (string, error)

ResolvePluginRef resolves a plugin short name or OCI reference to a fully-qualified reference with its latest semver tag. Short names (e.g. "gs-ae") are expanded using the default plugin registry (e.g. "gsoci.azurecr.io/giantswarm/klaus-plugins/gs-ae:v0.0.3").

func (*Client) ResolveToolchainRef added in v0.0.5

func (c *Client) ResolveToolchainRef(ctx context.Context, ref string) (string, error)

ResolveToolchainRef resolves a toolchain short name or OCI reference to a fully-qualified reference with its latest semver tag. Short names (e.g. "go") are expanded using the default toolchain registry (e.g. "gsoci.azurecr.io/giantswarm/klaus-toolchains/go:v1.0.0").

type ClientOption

type ClientOption func(*Client)

ClientOption configures the OCI client.

func WithBackgroundRefresh added in v0.0.17

func WithBackgroundRefresh(enabled bool) ClientOption

WithBackgroundRefresh toggles async revalidation of stale-but-usable cache entries. Default on. When off, stale entries still return immediately but no background probe is issued; the entry is refetched synchronously only when it ages past the stale TTL.

func WithCache added in v0.0.17

func WithCache(dir string) ClientOption

WithCache enables the on-disk registry response cache rooted at dir. The cache accelerates tag resolves, tag lists, catalog lookups, and manifest/blob fetches. See CacheStore for invalidation details. An empty dir leaves the cache disabled.

func WithCacheMaxSize added in v0.0.17

func WithCacheMaxSize(bytes int64) ClientOption

WithCacheMaxSize overrides the default LRU budget (bytes) for the content store. A non-positive value disables eviction.

func WithCacheTTL added in v0.0.17

func WithCacheTTL(fresh, stale time.Duration) ClientOption

WithCacheTTL overrides the default fresh and stale TTLs for reference and tag-list entries. The catalog TTL is not affected. A non-positive value keeps the default for that field.

func WithConcurrency added in v0.0.5

func WithConcurrency(n int) ClientOption

WithConcurrency sets the maximum number of concurrent registry operations for batch listing methods. Defaults to 10.

func WithPlainHTTP

func WithPlainHTTP(plain bool) ClientOption

WithPlainHTTP disables TLS for registry communication. This is useful for local testing with insecure registries.

func WithRegistryAuthEnv

func WithRegistryAuthEnv(envName string) ClientOption

WithRegistryAuthEnv sets the environment variable name to check for base64-encoded Docker config JSON credentials. If empty (the default), no environment variable is checked and only Docker/Podman config files are used for credential resolution.

type DescribedPersonality added in v0.0.8

type DescribedPersonality struct {
	ArtifactInfo
	Personality
}

DescribedPersonality is a Personality with its OCI metadata.

type DescribedPlugin added in v0.0.8

type DescribedPlugin struct {
	ArtifactInfo
	Plugin
}

DescribedPlugin is a Plugin with its OCI metadata. Returned by DescribePlugin (config blob fetch only, no layer download).

type DescribedToolchain added in v0.0.8

type DescribedToolchain struct {
	ArtifactInfo
	Toolchain
}

DescribedToolchain is a Toolchain with its OCI metadata.

type ListEntry added in v0.0.8

type ListEntry struct {
	Name       string // Short name (e.g. "sre", "gs-base")
	Version    string // Latest semver tag (e.g. "v1.0.0")
	Repository string // Full OCI repository path
	Reference  string // Full OCI reference with tag
}

ListEntry holds metadata for an artifact discovered by list operations. Populated from the registry catalog + tag resolution (no config fetch).

type ListOption added in v0.0.6

type ListOption func(*listConfig)

ListOption configures the behaviour of listing methods.

func WithFilter added in v0.0.6

func WithFilter(fn func(repository string) bool) ListOption

WithFilter sets a predicate that is applied to each discovered repository before any network-intensive resolution. Only repositories for which fn returns true will be resolved.

func WithRegistry added in v0.0.8

func WithRegistry(base string) ListOption

WithRegistry overrides the default registry base path for a listing operation. This supports multi-source registry configurations where the base path comes from user configuration rather than the default constants.

type Personality added in v0.0.8

type Personality struct {
	Name        string   `yaml:"name" json:"name"`
	Description string   `yaml:"description,omitempty" json:"description,omitempty"`
	Author      *Author  `yaml:"author,omitempty" json:"author,omitempty"`
	Homepage    string   `yaml:"homepage,omitempty" json:"homepage,omitempty"`
	SourceRepo  string   `yaml:"repository,omitempty" json:"repository,omitempty"`
	License     string   `yaml:"license,omitempty" json:"license,omitempty"`
	Keywords    []string `yaml:"keywords,omitempty" json:"keywords,omitempty"`

	// Toolchain is the container image that provides the runtime environment.
	Toolchain ToolchainReference `yaml:"toolchain,omitempty" json:"toolchain,omitempty"`
	// Plugins lists the plugin artifacts that compose this personality's capabilities.
	Plugins []PluginReference `yaml:"plugins,omitempty" json:"plugins,omitempty"`

	// Version is NOT stored in the config blob or personality.yaml. It is
	// conveyed via the OCI tag when pushing, and populated from the resolved
	// OCI tag when fetching (describe/pull).
	Version string `yaml:"-" json:"-"`
}

Personality represents a Klaus personality. Common metadata (name, description, author, etc.) is stored as io.giantswarm.klaus.* manifest annotations in the OCI registry. Only composition fields (toolchain + plugins) are stored in the OCI config blob. JSON tags on metadata fields are retained for display serialization; OCI storage uses personalityConfigBlob.

Personalities are Giant Swarm's composition layer: they combine a toolchain (container image), a set of plugins, and a behavioral identity (soul) into a coherent agent configuration.

Unlike plugins (where the manifest format is defined by upstream Claude Code), the personality definition format is our own. The on-disk source is personality.yaml (YAML) + SOUL.md (Markdown).

Fields are grouped by origin:

  • Metadata: from personality.yaml (stored as OCI manifest annotations)
  • Composition: from personality.yaml (stored in OCI config blob)
  • Version: from OCI tags (not in personality.yaml, not in config blob)

func ReadPersonalityFromDir added in v0.0.8

func ReadPersonalityFromDir(dir string) (*Personality, error)

ReadPersonalityFromDir reads a personality's metadata from its source directory by parsing personality.yaml.

Version is NOT set -- it is conveyed via the OCI tag at push time. SOUL.md is NOT read -- it lives in the content layer and is included automatically when PushPersonality tar.gz's the source directory.

type Plugin added in v0.0.8

type Plugin struct {
	Name string `json:"name"`
	// Version is NOT stored in the config blob. It is conveyed via the
	// OCI tag when pushing, and populated from the resolved OCI tag when
	// fetching (describe/pull).
	Version     string   `json:"-"`
	Description string   `json:"description,omitempty"`
	Author      *Author  `json:"author,omitempty"`
	Homepage    string   `json:"homepage,omitempty"`
	SourceRepo  string   `json:"repository,omitempty"`
	License     string   `json:"license,omitempty"`
	Keywords    []string `json:"keywords,omitempty"`

	// Skills lists skill directory names found under skills/ (e.g. "kubernetes", "fluxcd").
	Skills []string `json:"skills,omitempty"`
	// Commands lists command file names found under commands/ (e.g. "hello", "init-kubernetes").
	Commands []string `json:"commands,omitempty"`
	// Agents lists agent file names found under agents/ (e.g. "code-reviewer", "security-reviewer").
	Agents []string `json:"agents,omitempty"`
	// HasHooks is true if the plugin contains hooks/ or a hooks configuration.
	HasHooks bool `json:"hasHooks,omitempty"`
	// MCPServers lists MCP server names (keys from .mcp.json).
	MCPServers []string `json:"mcpServers,omitempty"`
	// LSPServers lists LSP server names (keys from .lsp.json).
	LSPServers []string `json:"lspServers,omitempty"`
}

Plugin represents a Klaus plugin. Common metadata (name, description, author, etc.) is stored as io.giantswarm.klaus.* manifest annotations in the OCI registry. Only type-specific fields are stored in the OCI config blob. JSON tags on metadata fields are retained for display serialization (CLI output, API responses); OCI storage uses pluginConfigBlob.

The first group of fields comes directly from .claude-plugin/plugin.json and aligns with the Claude Code plugin manifest schema: https://code.claude.com/docs/en/plugins-reference#plugin-manifest-schema

The second group (Skills, Commands, Agents, HasHooks, MCPServers, LSPServers) is computed at push time by scanning the plugin directory. These are NOT plugin.json fields -- in the upstream spec, "commands", "skills", "agents" etc. are path overrides (string|array) that tell Claude Code where to find components. Here we store the *discovered* component names so that Describe can report what the plugin provides without downloading the content layer.

func ReadPluginFromDir added in v0.0.8

func ReadPluginFromDir(dir string) (*Plugin, error)

ReadPluginFromDir reads a plugin's metadata from its source directory.

It reads .claude-plugin/plugin.json for manifest metadata (name, description, author, etc.) and then scans the directory tree to discover components:

  • skills/ subdirectories containing SKILL.md -> Skills
  • commands/*.md files -> Commands
  • agents/*.md files -> Agents
  • hooks/ directory or hooks config in plugin.json -> HasHooks
  • .mcp.json top-level keys -> MCPServers
  • .lsp.json top-level keys -> LSPServers

Version is NOT set -- it is conveyed via the OCI tag at push time.

type PluginReference

type PluginReference struct {
	Repository string `yaml:"repository" json:"repository"`
	Tag        string `yaml:"tag,omitempty" json:"tag,omitempty"`
	Digest     string `yaml:"digest,omitempty" json:"digest,omitempty"`
}

PluginReference points to a plugin OCI artifact.

func (PluginReference) Ref

func (p PluginReference) Ref() string

Ref returns the full OCI reference string for this plugin. If Digest is set, it is used (repo@digest). Otherwise Tag is used (repo:tag). If neither is set, the bare repository is returned.

type PulledPersonality added in v0.0.8

type PulledPersonality struct {
	ArtifactInfo
	Personality
	Soul   string // Behavioral identity text from SOUL.md (content layer only)
	Dir    string
	Cached bool
}

PulledPersonality is a Personality with OCI metadata, local file state, and the soul text (which is only available after pulling the content layer).

type PulledPlugin added in v0.0.8

type PulledPlugin struct {
	ArtifactInfo
	Plugin
	Dir    string // Local directory where files were extracted
	Cached bool   // True if pull was skipped (cache hit)
}

PulledPlugin is a Plugin with OCI metadata and local file state.

type PushResult

type PushResult struct {
	Digest string
}

PushResult holds the outcome of a push operation.

type ResolvedDependencies added in v0.0.8

type ResolvedDependencies struct {
	Toolchain *DescribedToolchain
	Plugins   []DescribedPlugin
	Warnings  []string // e.g. "plugin gs-sre: not found in registry"
}

ResolvedDependencies holds the result of resolving a personality's toolchain and plugin references.

type Toolchain added in v0.0.8

type Toolchain struct {
	Name string `json:"name"`
	// Version is populated from the OCI tag (not from annotations or
	// any config blob). Same convention as Plugin and Personality.
	Version     string   `json:"-"`
	Description string   `json:"description,omitempty"`
	Author      *Author  `json:"author,omitempty"`
	Homepage    string   `json:"homepage,omitempty"`
	SourceRepo  string   `json:"repository,omitempty"`
	License     string   `json:"license,omitempty"`
	Keywords    []string `json:"keywords,omitempty"`
}

Toolchain represents a Klaus toolchain (container image). Derived from OCI manifest annotations since toolchains are standard Docker images, not custom OCI artifacts.

Fields mirror the metadata fields of Plugin and Personality for consistency. Each field maps to a Klaus annotation (io.giantswarm.klaus.*), parsed via metadataFromAnnotations.

Version is populated from the OCI tag, same as Plugin and Personality.

type ToolchainReference added in v0.0.8

type ToolchainReference struct {
	Repository string `yaml:"repository" json:"repository"`
	Tag        string `yaml:"tag,omitempty" json:"tag,omitempty"`
	Digest     string `yaml:"digest,omitempty" json:"digest,omitempty"`
}

ToolchainReference points to a toolchain container image. Same shape as PluginReference but a distinct type for clarity.

func (ToolchainReference) Ref added in v0.0.8

func (t ToolchainReference) Ref() string

Ref returns the full OCI reference string for this toolchain. If Digest is set, it is used (repo@digest). Otherwise Tag is used (repo:tag). If neither is set, the bare repository is returned.

Jump to

Keyboard shortcuts

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