Tekton Pipelines — Interfaces#

Interface catalog#

Resolver (remoteresolution framework)#

  • Package: github.com/tektoncd/pipeline/pkg/remoteresolution/resolver/framework
  • File: pkg/remoteresolution/resolver/framework/interface.go
  • Methods:
    Initialize(ctx context.Context) error
    GetName(ctx context.Context) string
    GetSelector(ctx context.Context) map[string]string
    Validate(ctx context.Context, req *v1beta1.ResolutionRequestSpec) error
    Resolve(ctx context.Context, req *v1beta1.ResolutionRequestSpec) (ResolvedResource, error)
  • Purpose: Defines the contract every remote resolver must fulfil. Each resolver (git, OCI bundle, Tekton Hub, cluster, HTTP) implements this to fetch Task/Pipeline YAML from its respective backend. The framework routes a ResolutionRequest CRD to the resolver whose GetSelector labels match.
  • Implementations: GitResolver, BundleResolver, HubResolver, ClusterResolver, HTTPResolver (all in pkg/remoteresolution/resolver/)
  • Design quality: Well-segregated. The interface has exactly as many methods as needed to drive the framework lifecycle (init, name, selector, validate, resolve). Optional behaviour (custom timeout, admin config) is split into separate optional interfaces (TimedResolution, ConfigWatcher), correctly applying ISP.
  • Note: A legacy copy exists at pkg/resolution/resolver/framework/interface.go with a // Deprecated comment; the new version uses ResolutionRequestSpec instead of []Param for the validate/resolve signatures.

Requester#

  • Package: github.com/tektoncd/pipeline/pkg/remoteresolution/resource
  • File: pkg/remoteresolution/resource/request.go
  • Methods:
    Submit(ctx context.Context, name ResolverName, req Request) (ResolvedResource, error)
  • Purpose: Abstracts how a reconciler submits a resource resolution request. The concrete implementation (CRDRequester) creates a ResolutionRequest CRD and polls for its completion; tests inject a fake. This single-method interface is the seam between reconcilers and the resolution subsystem.
  • Implementations: CRDRequester (production), inline fakes in reconciler tests.
  • Design quality: Minimal and focused — one method is all callers need. Follows the consumer-defined interface pattern: the reconciler packages own this interface definition, not the resolution package.

ResolvedResource#

  • Package: github.com/tektoncd/pipeline/pkg/resolution/resolver/framework
  • File: pkg/resolution/resolver/framework/interface.go
  • Methods:
    Data() []byte
    Annotations() map[string]string
    RefSource() *pipelinev1.RefSource
  • Purpose: The output of a successful resolution. Data() is the raw YAML bytes of the fetched resource; Annotations() carries resolver-specific metadata; RefSource() provides provenance for supply chain security (Sigstore verification).
  • Implementations: Concrete structs within each resolver implementation.
  • Design quality: Clean value-object interface. The RefSource() method was added specifically to support trusted resource verification without coupling the resolver framework to security logic.

RunObject#

  • Package: github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1
  • File: pkg/apis/pipeline/v1beta1/run_interface.go
  • Methods:
    // Embedded: runtime.Object (GetObjectKind, DeepCopyObject)
    // Embedded: metav1.ObjectMetaAccessor (GetObjectMeta)
    GetStatusCondition() apis.ConditionAccessor
    IsSuccessful() bool
    IsCancelled() bool
    HasStarted() bool
    IsDone() bool
  • Purpose: Unified status-query interface across all run types: TaskRun, PipelineRun, Run, and CustomRun. Reconcilers and helper functions that need to inspect execution state use RunObject so they work across all four types without type-switching.
  • Implementations: v1.TaskRun, v1.PipelineRun, v1alpha1.Run, v1beta1.CustomRun
  • Design quality: Good use of interface embedding — builds on runtime.Object and metav1.ObjectMetaAccessor from the Kubernetes API machinery instead of re-declaring those methods. The extension interface RunObjectWithRetries (adds GetRetryCount()) handles the case where only two types support retries.

TaskObject / PipelineObject#

  • Package: github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1
  • File: pkg/apis/pipeline/v1beta1/task_interface.go, pipeline_interface.go
  • Methods (TaskObject):
    apis.Defaultable  // SetDefaults(ctx)
    TaskMetadata() metav1.ObjectMeta
    TaskSpec() TaskSpec
    Copy() TaskObject
  • Methods (PipelineObject):
    apis.Defaultable
    PipelineMetadata() metav1.ObjectMeta
    PipelineSpec() PipelineSpec
    Copy() PipelineObject
  • Purpose: Abstract over CRD version variants (v1alpha1/v1beta1/v1) of Task and Pipeline. Code that retrieves and processes task/pipeline definitions uses these interfaces rather than concrete version-specific types, decoupling the reconciler from API version changes.
  • Implementations: v1.Task, v1beta1.Task, v1.Pipeline, v1beta1.Pipeline
  • Design quality: Follows the same pattern as RunObject. Embedding apis.Defaultable is a Knative convention that ensures webhooks can call SetDefaults uniformly. The Copy() method returns the interface type to support safe deep-copy within the reconcile loop.

dag.Task / dag.Tasks#

  • Package: github.com/tektoncd/pipeline/pkg/reconciler/pipeline/dag
  • File: pkg/reconciler/pipeline/dag/dag.go
  • Methods:
    // Task
    HashKey() string
    Deps() []string
    
    // Tasks
    Items() []Task
  • Purpose: The DAG package is intentionally decoupled from Kubernetes types. These interfaces let dag.Build work with any pipeline task representation. Callers implement Task on their PipelineTask wrapper types, and Tasks on their list wrappers.
  • Implementations: PipelineRunFacts (pipelinerun reconciler) adapts real PipelineTask objects to these interfaces.
  • Design quality: Excellent. The pure-logic DAG package has zero Kubernetes dependencies. The two-interface design (Task + Tasks) mirrors the standard collection pattern. Because this is a private package the interfaces primarily exist for clarity and testability rather than extensibility.

Waiter / Runner / PostWriter#

  • Package: github.com/tektoncd/pipeline/pkg/entrypoint
  • File: pkg/entrypoint/entrypointer.go
  • Methods:
    // Waiter
    Wait(ctx context.Context, file string, expectContent bool, breakpointOnFailure bool) error
    
    // Runner
    Run(ctx context.Context, args ...string) error
    
    // PostWriter
    Write(file, content string)
  • Purpose: These three interfaces decompose the Entrypointer’s responsibilities so each can be tested in isolation. Waiter blocks until a semaphore file appears (previous step done), Runner executes the actual user command, and PostWriter writes the completion file signalling the next step.
  • Implementations: Production implementations in the same package; fakes used in tests via table-driven test structs.
  • Design quality: Textbook application of the single-responsibility principle. Each interface is one method. By injecting these through the Entrypointer struct, the entire sequential-execution logic can be unit tested without spawning processes or touching the filesystem.

ControllerAPIClient / EntrypointerAPIClient (SPIRE)#

  • Package: github.com/tektoncd/pipeline/pkg/spire
  • File: pkg/spire/spire.go
  • Methods (ControllerAPIClient):
    AppendStatusInternalAnnotation(ctx context.Context, tr *v1beta1.TaskRun) error
    CheckSpireVerifiedFlag(tr *v1beta1.TaskRun) bool
    Close() error
    CreateEntries(ctx context.Context, tr *v1beta1.TaskRun, pod *corev1.Pod, ttl time.Duration) error
    DeleteEntry(ctx context.Context, tr *v1beta1.TaskRun, pod *corev1.Pod) error
    VerifyStatusInternalAnnotation(ctx context.Context, tr *v1beta1.TaskRun, logger *zap.SugaredLogger) error
    VerifyTaskRunResults(ctx context.Context, prs []result.RunResult, tr *v1beta1.TaskRun) error
    SetConfig(c spireconfig.SpireConfig)
  • Methods (EntrypointerAPIClient):
    Close() error
    Sign(ctx context.Context, results []result.RunResult) ([]result.RunResult, error)
  • Purpose: Decouple the reconciler and entrypoint from the concrete SPIFFE/SPIRE gRPC client. When SPIRE is disabled (build tag), a no-op implementation is substituted without changing any calling code. The controller client manages SPIRE workload entries for pods; the entrypointer client signs step results.
  • Implementations: Real gRPC client (SpireControllerAPIClient, SpireEntrypointerAPIClient), no-op stubs for disabled/test builds.
  • Design quality: The split into two interfaces (controller vs. entrypointer) respects the different trust boundaries and lifetimes. ControllerAPIClient is broader (8 methods) reflecting the controller’s security-management role, but all methods are coherently related to a single responsibility (SPIRE workload identity management).

remote.Resolver (legacy object fetcher)#

  • Package: github.com/tektoncd/pipeline/pkg/remote
  • File: pkg/remote/resolver.go
  • Methods:
    List(ctx context.Context) ([]ResolvedObject, error)
    Get(ctx context.Context, kind, name string) (runtime.Object, *v1.RefSource, error)
  • Purpose: The older, pre-CRD mechanism for fetching remote Tekton objects (e.g., from OCI registries). Still used by the bundle resolver’s internal implementation. Returns deserialized runtime.Object rather than raw bytes, unlike the newer ResolvedResource interface.
  • Implementations: OCIResolver in pkg/remote/oci/
  • Design quality: Simple two-method interface consistent with standard read-only repository patterns. Being superseded by the CRD-based resolution framework but retained for OCI bundle support.

Interface patterns#

  • Size distribution: Predominantly small — the majority are 1–4 methods. ControllerAPIClient at 8 methods is the outlier, but each method is cohesive. No god interfaces visible outside of generated clientset code.
  • Embedding: Moderate use of interface composition. RunObject embeds runtime.Object + metav1.ObjectMetaAccessor. RunObjectWithRetries embeds RunObject. TaskObject and PipelineObject embed apis.Defaultable (Knative). This builds on Kubernetes API machinery conventions rather than inventing new hierarchies.
  • Implicit satisfaction: Mixed. Core domain interfaces (Resolver, Requester, RunObject) are defined in the package that consumes them (or in a dedicated interface file), not in the implementing packages. This is idiomatic Go. Generated clientset interfaces are defined alongside their implementations.
  • Stdlib interfaces used: context.Context pervasive (not an interface Tekton defines but used as the primary cancellation/injection mechanism). The runtime.Object and metav1.ObjectMetaAccessor interfaces from Kubernetes API machinery function like stdlib here. No direct io.Reader/io.Writer usage at the core abstraction layer.

Key abstractions#

  1. Resolver (remoteresolution framework): The primary extension point of the entire system. Every remote source (git, OCI, HTTP, Tekton Hub) is an implementation. The separation of Validate and Resolve lets the framework reject bad requests early. The optional TimedResolution and ConfigWatcher sub-interfaces keep the core contract minimal while allowing optional capabilities.

  2. Requester: A single-method interface that is the boundary between reconcilers and the async CRD-based resolution protocol. Its minimalism makes it trivially fakeable, which enables thorough unit testing of both reconcilers.

  3. RunObject: The polymorphic handle on all run types. Code that reports on, monitors, or finalizes runs (metrics, events, cloud events) uses this interface to work uniformly across TaskRun, PipelineRun, and custom run types without version-specific type switches.

  4. dag.Task / dag.Tasks: A zero-dependency abstraction layer that keeps the DAG engine pure. The DAG package can be tested and reasoned about entirely independently of Kubernetes. This is the clearest example of thoughtful interface placement in the codebase.

  5. Waiter / Runner / PostWriter: Three single-method interfaces that together make the entrypoint’s sequential-execution logic fully unit-testable. Demonstrates the Go idiom of decomposing a struct’s collaborators into narrow interfaces that express exactly what the struct needs from each dependency.

Interface-driven extensibility#

Tekton’s main extensibility mechanism is the Resolver interface. Adding a new source type (e.g., a new VCS or artifact store) requires only implementing Resolver, registering it with a controller, and deploying it — no changes to the core reconcilers. The framework handles CRD routing, timeouts, and error reporting. Optional ConfigWatcher and TimedResolution sub-interfaces follow the “optional interface” pattern (runtime type assertion) to add capabilities without breaking existing implementations.

The RunObject + TaskObject + PipelineObject interfaces provide a softer form of extensibility: CustomRun (v1beta1) lets third-party controllers intercept pipeline task execution, implementing RunObject to integrate with the PipelineRun reconciler’s status tracking.

The SPIRE interfaces (ControllerAPIClient, EntrypointerAPIClient) enable the security subsystem to be compiled out via build tags (spire / no spire) without any conditional logic in calling code — a clean build-time extensibility seam.