Documentation
¶
Overview ¶
Package refresolver fetches Tekton Tasks/Pipelines referenced via taskRef.resolver / pipelineRef.resolver. It is intentionally distinct from internal/resolver, which performs $(...) variable substitution. The two packages share no types and do not depend on each other; the naming separation exists because both senses of "resolver" appear in the Tekton spec.
Phase 1 (Track 1 #9) lands the type scaffolding and an "inline" stub resolver used by the test harness. Concrete resolvers (git, hub, http, bundles, cluster) and the remote ResolutionRequest driver land in subsequent phases. See docs/superpowers/plans/2026-05-04-resolvers.md.
Index ¶
- Constants
- Variables
- func CacheKey(resolverName string, params map[string]string) string
- type BundlesOptions
- type BundlesResolver
- type CacheEntry
- type ClusterResolver
- type ClusterResolverOptions
- type DiskCache
- func (c *DiskCache) Clear() (int, error)
- func (c *DiskCache) Get(req Request) (Resolved, bool, error)
- func (c *DiskCache) Has(req Request) bool
- func (c *DiskCache) List() ([]CacheEntry, error)
- func (c *DiskCache) PruneOlderThan(older time.Duration) (int, error)
- func (c *DiskCache) Put(req Request, resolved Resolved) error
- func (c *DiskCache) Root() string
- type GitResolver
- type HTTPOptions
- type HTTPResolver
- type HubOptions
- type HubResolver
- type InlineResolver
- type Kind
- type Options
- type Registry
- func (r *Registry) Cache() *DiskCache
- func (r *Registry) Inline() *InlineResolver
- func (r *Registry) Offline() bool
- func (r *Registry) Register(res Resolver)
- func (r *Registry) Remote() *RemoteResolver
- func (r *Registry) Resolve(ctx context.Context, req Request) (Resolved, error)
- func (r *Registry) SetAllow(names []string)
- func (r *Registry) SetCache(c *DiskCache)
- func (r *Registry) SetOffline(b bool)
- func (r *Registry) SetRemote(rr *RemoteResolver)
- type RemoteResolver
- type RemoteResolverOptions
- type Request
- type Resolved
- type Resolver
Constants ¶
const HTTPTokenEnv = "TKNACT_HTTP_RESOLVER_TOKEN"
HTTPTokenEnv is the environment variable name the http resolver consults for a bearer token. An explicit HTTPOptions.Token wins over the env var.
Variables ¶
var ( // ErrResolverNotRegistered fires when no Resolver is registered // for the requested name. In Phase 1, every name except "inline" // triggers this. ErrResolverNotRegistered = errors.New("refresolver: resolver not registered") // ErrResolverNotAllowed fires when the requested name is registered // but excluded by the active allow-list (--resolver-allow=...). ErrResolverNotAllowed = errors.New("refresolver: resolver not allowed") // ErrInlineNoData fires when the inline resolver is asked for a // name the test harness didn't preload. Distinct from // ErrResolverNotRegistered so tests can branch on harness misuse // vs Phase-1-not-implemented errors. ErrInlineNoData = errors.New("refresolver: inline resolver has no data for the requested key") )
Sentinel errors. Engine code uses errors.Is to branch on these.
var ErrClusterContextRequired = errors.New("refresolver: cluster resolver requires a kubeconfig context (set --cluster-resolver-context or add `cluster` to --resolver-allow with KUBECONFIG explicitly set)")
ErrClusterContextRequired fires when the cluster resolver is asked to resolve without a kubeconfig context — either explicitly named via --cluster-resolver-context or implied by KUBECONFIG plus a current-context. The resolver refuses by default to avoid silently reading from whatever KUBECONFIG happens to point at.
Functions ¶
func CacheKey ¶
CacheKey computes the per-(resolver, substituted-params) cache key per spec §3:
sha256(resolver-name + "\x00" + sortedKVs(SUBSTITUTED-params))
The hash is computed AFTER the engine has substituted each param's $(...) references against the dispatch-time resolver.Context, so two PipelineTasks resolving the same upstream Pipeline / Task via the same resolver but whose `resolver.params` substitute to different values yield different cache keys (and miss the per-run cache independently). This invariant is exercised by TestRunOneDoesNotCacheAcrossDifferentSubstitutedParams.
Types ¶
type BundlesOptions ¶
type BundlesOptions struct {
// AllowInsecureHTTP opts the bundles resolver into plain HTTP for
// non-loopback registries. Loopback registries (127.0.0.1, ::1,
// localhost) always permit plain HTTP so unit tests using
// httptest.NewServer + go-containerregistry's pkg/registry work
// out of the box.
//
// Threaded through from --resolver-allow-insecure-http on the run
// command (CI-only escape hatch). The default keychain via
// ~/.docker/config.json is honored regardless.
AllowInsecureHTTP bool
// Keychain overrides the authentication source. Production callers
// pass nil; the resolver then uses authn.DefaultKeychain which reads
// ~/.docker/config.json. Tests may inject a custom keychain (e.g.
// authn.NewMultiKeychain).
Keychain authn.Keychain
// RemoteOptions, when non-nil, replaces the default remote.Option
// list passed to go-containerregistry. Tests use this to inject
// registry transports without going through global state.
RemoteOptions []remote.Option
}
BundlesOptions configures a bundles resolver. All fields optional.
type BundlesResolver ¶
type BundlesResolver struct {
// contains filtered or unexported fields
}
BundlesResolver implements Resolver for taskRef.resolver: bundles.
Resolver params:
bundle required. OCI ref like gcr.io/foo/catalog:v1. name required. metadata.name of the resource to extract. kind optional, default "task". One of "task", "pipeline".
On a happy path the resolver pulls the named OCI image, walks each layer's annotations looking for the matching (kind, name) pair, and returns the YAML bytes embedded in that layer's tar entry.
func NewBundlesResolver ¶
func NewBundlesResolver(opts BundlesOptions) *BundlesResolver
NewBundlesResolver constructs a bundles resolver.
type CacheEntry ¶
type CacheEntry struct {
Resolver string `json:"resolver"`
Key string `json:"key"`
Path string `json:"path"`
Size int64 `json:"size"`
ModTime time.Time `json:"mod_time"`
}
CacheEntry describes one row in `tkn-act cache list`.
type ClusterResolver ¶
type ClusterResolver struct {
// contains filtered or unexported fields
}
ClusterResolver implements Resolver for taskRef.resolver: cluster.
Resolver params:
name required. metadata.name of the resource to read. kind optional, default "task". One of "task", "pipeline". namespace optional, default "default".
The resolver reads via a kube dynamic client built from --cluster-resolver-kubeconfig (or KUBECONFIG / ~/.kube/config) at the context named by --cluster-resolver-context. The result is serialized back to YAML so the engine can feed it through the standard loader path.
SECURITY: this resolver is OFF BY DEFAULT in NewDefaultRegistry. KUBECONFIG may point at a production cluster; we require the user to opt in explicitly via --resolver-allow=...,cluster or by setting --cluster-resolver-context.
func NewClusterResolver ¶
func NewClusterResolver(opts ClusterResolverOptions) (*ClusterResolver, error)
NewClusterResolver constructs a cluster resolver. Returns an error if neither opts.Dynamic nor a usable kubeconfig+context can be found — that's distinct from ErrClusterContextRequired (which fires at resolve-time when the registry decides whether to dispatch).
type ClusterResolverOptions ¶
type ClusterResolverOptions struct {
// Context names the kubeconfig context to read from. Empty means
// "use the kubeconfig's current-context."
Context string
// Kubeconfig overrides the kubeconfig file path. Empty falls back to
// $KUBECONFIG / ~/.kube/config per `clientcmd.NewDefaultClientConfigLoadingRules`.
Kubeconfig string
// Dynamic, when non-nil, replaces the dynamic client constructed
// from the kubeconfig. Tests use this to inject a fake client.
Dynamic dynamic.Interface
}
ClusterResolverOptions configures a cluster resolver. All fields are optional; the production CLI plumbs values from --cluster-resolver-context and --cluster-resolver-kubeconfig.
type DiskCache ¶
type DiskCache struct {
// contains filtered or unexported fields
}
DiskCache is the on-disk resolved-bytes cache. Entries are keyed by (resolver, sortedKVs(SUBSTITUTED-params)) per the spec §3 cache-key invariant. The on-disk layout is:
<root>/<resolver>/<key>.yaml // resolved bytes <root>/<resolver>/<key>.json // metadata (resolver, params, source, sha256, fetched-at)
Splitting by resolver lets `tkn-act cache prune` / `cache list` scope operations to a single resolver if desired, and matches the spec's documented layout.
The Cache is concurrency-safe across processes only at the OS-level — two tkn-act runs racing to populate the same key may both write; the last writer wins (and both bytes are content-equivalent if the upstream is deterministic, which it is for git/hub/http/bundles by their cache-key invariants).
func NewDiskCache ¶
NewDiskCache returns a DiskCache rooted at root. root may not exist yet — the first Put will MkdirAll.
func (*DiskCache) Clear ¶
Clear deletes every entry under the cache root. Returns the number of entries removed. A missing root is a no-op.
func (*DiskCache) Get ¶
Get returns the cached Resolved for req. ok=false on miss; non-nil err only on filesystem corruption (truncated meta JSON, etc.).
The returned Resolved has Cached=true so callers don't need to set it separately.
func (*DiskCache) Has ¶
Has returns true iff a cached entry exists for req. Errors reading the filesystem are conservative: the file is treated as missing.
func (*DiskCache) List ¶
func (c *DiskCache) List() ([]CacheEntry, error)
List walks the cache root and returns every cached blob. Errors reading individual sub-directories abort with the partial list.
func (*DiskCache) PruneOlderThan ¶
PruneOlderThan deletes every cache entry whose mod-time is older than cutoff (now - older). Returns the number of entries pruned. A missing root is a no-op.
type GitResolver ¶
type GitResolver struct {
// contains filtered or unexported fields
}
GitResolver implements Resolver for taskRef.resolver: git. It honors the standard Tekton resolver params:
url (required) — repo URL (file://, https://, ssh://, git@..) revision (default "main") — branch, tag, or full SHA pathInRepo (required) — relative path to the YAML inside the repo
The implementation uses go-git for cross-platform portability (no git CLI exec on the host required). Clones are shallow (Depth: 1) for the happy path and land in <cacheDir>/git/<sha256(url+revision)>/repo/. On a cache hit (the directory already exists), bytes are served from the cached working tree without any network IO.
HTTPS / file:// URLs are accepted by default; plain http:// is refused unless AllowInsecureHTTP is true. SSH URLs (ssh:// or git@host:path) are passed through to go-git, which uses ssh-agent transparently when present (no in-tkn-act key handling).
func NewGit ¶
func NewGit(cacheDir string) *GitResolver
NewGit returns a GitResolver that caches under cacheDir. cacheDir may be empty — in which case clones go to a tempdir and are cleaned up at the end of each Resolve call (no cache reuse). Production callers should pass a non-empty cacheDir (the CLI default is $XDG_CACHE_HOME/tkn-act/resolved).
func (*GitResolver) SetAllowInsecureHTTP ¶
func (g *GitResolver) SetAllowInsecureHTTP(b bool)
SetAllowInsecureHTTP toggles whether plain http:// URLs are accepted. The Registry constructor wires this from Options.AllowInsecureHTTP.
type HTTPOptions ¶
type HTTPOptions struct {
// AllowInsecureHTTP opts the resolver into plain http:// URLs against
// non-loopback hosts. Loopback URLs are always allowed (so unit tests
// using httptest.NewServer work). The CLI plumbs this from
// --resolver-allow-insecure-http (a CI-only escape hatch).
AllowInsecureHTTP bool
// Token, if non-empty, is sent as `Authorization: Bearer <token>`. If
// empty the resolver consults the TKNACT_HTTP_RESOLVER_TOKEN env var
// at Resolve time; a non-empty env value is then used.
Token string
// Client overrides http.Client. Tests may inject a httptest server's
// transport. The default has a 30-second timeout.
Client *http.Client
}
HTTPOptions configures an http resolver. All fields optional.
type HTTPResolver ¶
type HTTPResolver struct {
// contains filtered or unexported fields
}
HTTPResolver fetches Tasks/Pipelines from a plain HTTPS URL.
Resolver params:
url required. https:// by default; http:// only against loopback or
with --resolver-allow-insecure-http.
5xx responses retry once with a 250ms backoff. Bearer tokens come from HTTPOptions.Token (precedence) or the TKNACT_HTTP_RESOLVER_TOKEN env var.
func NewHTTPResolver ¶
func NewHTTPResolver(opts HTTPOptions) *HTTPResolver
NewHTTPResolver constructs an http resolver.
type HubOptions ¶
type HubOptions struct {
// BaseURL points at a Tekton Hub HTTP API. Default
// https://api.hub.tekton.dev. HTTPS is required (the spec is firm
// about hub being HTTPS-only).
BaseURL string
// Token, if non-empty, is sent as `Authorization: Bearer <token>`.
Token string
// Client overrides the http.Client. Tests inject httptest server
// transport when needed; the default http.DefaultClient with a
// 30-second timeout is used otherwise.
Client *http.Client
}
HubOptions configures a hub resolver. All fields optional.
type HubResolver ¶
type HubResolver struct {
// contains filtered or unexported fields
}
HubResolver fetches Tasks/Pipelines from a Tekton Hub HTTP API per https://github.com/tektoncd/hub. It honors the public API shape:
GET <baseURL>/v1/resource/<catalog>/<kind>/<name>/<version>/yaml
Resolver params:
name required version optional, default "latest" kind optional, default "task" catalog optional, default "tekton"
HTTPS is required regardless of the run-level --resolver-allow-insecure-http flag (that flag scopes to the http resolver only). 5xx responses are retried once with a 250ms backoff; 404s are surfaced with a "not found" diagnostic.
func NewHubResolver ¶
func NewHubResolver(opts HubOptions) *HubResolver
NewHubResolver constructs a hub resolver with the given options.
type InlineResolver ¶
type InlineResolver struct {
// contains filtered or unexported fields
}
InlineResolver is a magic resolver used by the test harness. Tests preload (key, bytes) pairs via Add; the engine's lazy-dispatch path invokes Resolve with the same key in Request.Params["name"]. Real resolvers (git, hub, ...) land in Phase 2-4.
func NewInlineResolver ¶
func NewInlineResolver() *InlineResolver
NewInlineResolver returns an empty InlineResolver. Use Add to load bytes the engine will look up by Request.Params["name"].
func (*InlineResolver) Add ¶
func (i *InlineResolver) Add(key string, bytes []byte)
Add registers bytes for the given key. The engine's resolver Request must carry Params["name"] equal to key for the inline lookup to fire.
type Options ¶
type Options struct {
// Allow restricts which resolver names will dispatch. Empty
// means "no allow-list applied" (every registered resolver
// dispatches). The CLI default in Phase 1 is
// {"git","hub","http","bundles"} — none of those are registered
// yet, but the validator's allow-list checks already use this
// list.
Allow []string
// CacheDir is the on-disk cache location. Phase 1 stores it but
// does not yet read or write it (Phase 6 wires the cache).
CacheDir string
// Offline rejects any cache miss with an error. Phase 1 stores it
// but does not yet enforce it (Phase 6 wires --offline).
Offline bool
// AllowInsecureHTTP opts the http resolver into plain http://. Phase
// 1 stores it but does nothing with it (Phase 3 wires it).
AllowInsecureHTTP bool
// AllowCluster opts the cluster resolver into the default registry.
// The cluster resolver reads from the user's KUBECONFIG and is
// disabled by default for safety (KUBECONFIG may point at
// production). Set true via --resolver-allow=...,cluster on the
// CLI; alternatively the engine flips this when --cluster-resolver-
// context=<ctx> is set explicitly.
AllowCluster bool
// ClusterResolverContext, when non-empty, names the kubeconfig
// context the cluster resolver reads from. Empty means "use the
// kubeconfig's current-context."
ClusterResolverContext string
// ClusterResolverKubeconfig overrides the path the cluster resolver
// loads its kubeconfig from. Empty falls back to the standard
// $KUBECONFIG / ~/.kube/config resolution chain.
ClusterResolverKubeconfig string
}
Options configures a Registry built via NewDefaultRegistry. Phase 1 only honors Allow + CacheDir + Offline; later phases plug concrete resolvers in here.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry routes Requests to one of its registered Resolvers, applies the allow-list, and layers a per-run + on-disk cache on top.
func NewDefaultRegistry ¶
NewDefaultRegistry returns a Registry pre-populated with whatever resolvers ship by default. Phase 1 shipped just the inline stub; Phase 2 adds the git resolver. Phase 3-4 add hub/http/bundles/cluster here.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty Registry. Tests that want a single stub resolver call this and then Register their stub.
func (*Registry) Inline ¶
func (r *Registry) Inline() *InlineResolver
Inline returns the inline resolver registered with this Registry, or nil if none was registered. Used by tests to feed bytes in.
func (*Registry) Register ¶
Register adds a Resolver to the registry, keyed by its Name(). A repeat call with the same Name() replaces the prior entry.
func (*Registry) Remote ¶
func (r *Registry) Remote() *RemoteResolver
Remote returns the currently-configured remote driver, or nil if Mode B is not active.
func (*Registry) Resolve ¶
Resolve dispatches the Request to the appropriate Resolver. The per-run cache short-circuits repeated identical requests within a single run. Phase 1 does not yet consult the on-disk cache (Phase 6).
Mode B (remote) routing: when SetRemote has installed a remote driver, EVERY Resolve goes through it regardless of the Request's Resolver name (custom names are the whole point of Mode B; the validator's RemoteResolverEnabled Option has already cleared arbitrary names). Direct registrations are bypassed in this mode.
func (*Registry) SetAllow ¶
SetAllow replaces the allow-list. Empty / nil means "no allow-list applied" (every registered resolver dispatches).
func (*Registry) SetCache ¶
SetCache installs the on-disk cache layer. Pass nil to disable. The cache is consulted between the per-run map and the registered Resolver: hits short-circuit the network call; misses fall through and Put the result on success.
func (*Registry) SetOffline ¶
SetOffline toggles the --offline gate. When true, every cache miss surfaces an error before dispatching to a Resolver.
func (*Registry) SetRemote ¶
func (r *Registry) SetRemote(rr *RemoteResolver)
SetRemote installs the Mode B remote-resolver driver. When non-nil, every dispatch goes through remote (the validator's RemoteResolverEnabled Option already short-circuits the allow-list for any resolver name in this mode). Setting nil restores direct dispatch.
type RemoteResolver ¶
type RemoteResolver struct {
// contains filtered or unexported fields
}
RemoteResolver is the Mode B implementation: it submits a ResolutionRequest to a remote Tekton cluster and waits for the controller to fill in status.data.
SECURITY: the remote resolver speaks to whichever cluster the supplied kubeconfig context points at, under that context's service-account RBAC. tkn-act never elevates privileges and never stores credentials; the user's kubectl identity is the only thing the remote cluster sees.
func NewRemoteResolver ¶
func NewRemoteResolver(opts RemoteResolverOptions) (*RemoteResolver, error)
NewRemoteResolver builds a RemoteResolver, loading the kube client from kubeconfig if opts.Dynamic is unset. Returns an error if the kubeconfig context can't be loaded.
func NewRemoteResolverFromOptions ¶
func NewRemoteResolverFromOptions(opts RemoteResolverOptions) *RemoteResolver
NewRemoteResolverFromOptions returns a RemoteResolver with the supplied options, no kubeconfig loading. Tests use this to bypass the on-disk kubeconfig loader; the CLI uses NewRemoteResolver.
func (*RemoteResolver) Name ¶
func (r *RemoteResolver) Name() string
Name implements Resolver. The literal name is `remote`, but Registry routes any unknown name through Remote when SetRemote() has been called (Mode B short-circuits the direct allow-list).
func (*RemoteResolver) Resolve ¶
Resolve implements Resolver. The lifecycle is:
- Build a ResolutionRequest with spec.params from req.Params.
- Create() it via the dynamic client; pick v1beta1 first, fall back to v1alpha1 on NoKindMatchError (older clusters).
- Poll status.conditions[Succeeded] until True / False / timeout.
- On True: base64-decode status.data and return as Resolved.Bytes.
- On False or any error: surface a typed error including reason+message.
- ALWAYS Delete() the ResolutionRequest on the way out — using context.Background() so SIGINT mid-resolution still triggers cleanup (Critical 5 invariant).
type RemoteResolverOptions ¶
type RemoteResolverOptions struct {
// Dynamic, when non-nil, replaces the dynamic client constructed
// from the kubeconfig. Tests use this to bypass kubeconfig loading.
Dynamic dynamic.Interface
// Kubeconfig overrides the kubeconfig file path. Empty falls back
// to KUBECONFIG / ~/.kube/config per
// clientcmd.NewDefaultClientConfigLoadingRules.
Kubeconfig string
// Context names the kubeconfig context to load. Empty means "use
// the kubeconfig's current-context."
Context string
// Namespace where ResolutionRequests are submitted. Default "default".
Namespace string
// Timeout is the per-request wall-clock budget. Default 60s.
Timeout time.Duration
// PollInterval gates how often status is polled when no Watch event
// arrives. Default 500ms.
PollInterval time.Duration
}
RemoteResolverOptions configures a RemoteResolver. The CLI plumbs these from --remote-resolver-context / --remote-resolver-namespace / --remote-resolver-timeout. Tests inject Dynamic directly.
type Request ¶
type Request struct {
Kind Kind
Resolver string
// Params hold resolver-specific keys. Always already substituted —
// resolver implementations must never see "$(tasks.X.results.Y)".
Params map[string]string
}
Request is the input shape resolvers consume. Params are already substituted (the engine ran the same $(...) substitution it runs on PipelineTask.Params before dispatching to the resolver), so resolver implementations see only literal values.
type Resolved ¶
type Resolved struct {
Bytes []byte
Source string
SHA256 string
// Cached is true if the bytes came from a cache layer rather than
// a fresh fetch. Surfaces in resolver-end events.
Cached bool
}
Resolved is the output shape. Bytes is the raw YAML the loader consumes via loader.LoadBytes; Source is a human-readable origin string for the resolver-end event; SHA256 is the hex digest of the returned Bytes (used for the on-disk cache invalidation diagnostic).