Skip to content

Commit

Permalink
gitresolver: Pass ConfigSource from ResolutionRequest.Status to *Run.…
Browse files Browse the repository at this point in the history
…Status.

Related to tektoncd#5522

Change 1:
- Before: `ResolutionRequest.Status.ConfigSource` field was empty for the gitresolver.

- Now:
  - `ResolutionRequest.Status.ConfigSource.URI` is set to be the user-provided URI
for Anonymous Cloning, and is set to be the Repo's clone url for SCM API cloning.
  - `ResolutionRequest.Status.ConfigSource.Digest` is filled with one entry: the key is
"sha1", and the value is the actual commit sha at the time of the gitresolver resolving
the remote resource.
  - `ResolutionRequest.Status.ConfigSource.EntryPoint` is set to be the path of
the task/pipeline yaml file.

Change 2:
- Before: The `ConfigSource` information in `ResolutionRequest` was not passed to
pipeline/task reconciler.
- Now: The `ConfigSource` information is not passed to pipeline/task reconciler
and finally stored in pipelinerun/taskrun status.Provenance.ConfigSource field.

Motivation: See tektoncd#5522 for details
The tl;dr is that Tekton Chains needs to capture the source of the config
file (pipeline.yaml or task.yaml) in the SLSA provenance's
`predicate.invocation.configSource` field. Therefore, we need to pass
the ConfigSource information from resolvers and to make it available in
TaskRun/PipelineRun status.

Signed-off-by: Chuang Wang <chuangw@google.com>
  • Loading branch information
chuangw6 committed Oct 4, 2022
1 parent 4c25251 commit c1f5133
Show file tree
Hide file tree
Showing 23 changed files with 496 additions and 261 deletions.
41 changes: 41 additions & 0 deletions docs/git-resolver.md
Expand Up @@ -146,6 +146,47 @@ spec:
* BitBucket Server
* BitBucket Cloud

## `ResolutionRequest` Status
Since users can specify branch name or tag name for the param `revision `, the actual commit sha needs to be captured that was being used at the moment of resolving the resource. `ResolutionRequest.Status.Source` field captures this along with the `url` and `path` information.

Example:
- Pipeline Resolution
```yaml
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: git-demo
spec:
pipelineRef:
resolver: git
params:
- name: url
value: https://github.com/<username>/<reponame>.git
- name: revision
value: main
- name: pathInRepo
value: pipeline.yaml
```

- `ResolutionRequest`
```yaml
apiVersion: resolution.tekton.dev/v1alpha1
kind: ResolutionRequest
metadata:
...
spec:
params:
pathInRepo: pipeline.yaml
revision: main
url: https://github.com/<username>/<reponame>.git
status:
source:
uri: https://github.com/<username>/<reponame>.git
digest:
sha1: <The latest commit sha on main at the moment of resolving>
entrypoint: pipeline.yaml
data: a2luZDogUGxxxx...
```
---

Except as otherwise noted, the content of this page is licensed under the
Expand Down
74 changes: 25 additions & 49 deletions pkg/reconciler/pipelinerun/pipelinerun.go
Expand Up @@ -64,6 +64,7 @@ import (
"k8s.io/utils/clock"
"knative.dev/pkg/apis"
"knative.dev/pkg/controller"
"knative.dev/pkg/kmap"
"knative.dev/pkg/kmeta"
"knative.dev/pkg/logging"
pkgreconciler "knative.dev/pkg/reconciler"
Expand Down Expand Up @@ -364,7 +365,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
return nil
}

pipelineMeta, pipelineSpec, err := rprp.GetPipelineData(ctx, pr, getPipelineFunc)
resolvedObjectMeta, pipelineSpec, err := rprp.GetPipelineData(ctx, pr, getPipelineFunc)
switch {
case errors.Is(err, remote.ErrorRequestInProgress):
message := fmt.Sprintf("PipelineRun %s/%s awaiting remote resource", pr.Namespace, pr.Name)
Expand All @@ -378,7 +379,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
return controller.NewPermanentError(err)
default:
// Store the fetched PipelineSpec on the PipelineRun for auditing
if err := storePipelineSpecAndMergeMeta(pr, pipelineSpec, pipelineMeta); err != nil {
if err := storePipelineSpecAndMergeMeta(pr, pipelineSpec, resolvedObjectMeta); err != nil {
logger.Errorf("Failed to store PipelineSpec on PipelineRun.Status for pipelinerun %s: %v", pr.Name, err)
}
}
Expand Down Expand Up @@ -412,15 +413,15 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonFailedValidation,
"Pipeline %s/%s can't be Run; it has an invalid spec: %s",
pipelineMeta.Namespace, pipelineMeta.Name, err)
resolvedObjectMeta.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}

if err := resources.ValidateResourceBindings(pipelineSpec, pr); err != nil {
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonInvalidBindings,
"PipelineRun %s/%s doesn't bind Pipeline %s/%s's PipelineResources correctly: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
pr.Namespace, pr.Name, pr.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}
providedResources, err := resources.GetResourcesFromBindings(pr, c.resourceLister.PipelineResources(pr.Namespace).Get)
Expand All @@ -437,7 +438,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonCouldntGetResource,
"PipelineRun %s/%s can't be Run; it tries to bind Resources that don't exist: %s",
pipelineMeta.Namespace, pr.Name, err)
resolvedObjectMeta.Namespace, pr.Name, err)
return controller.NewPermanentError(err)
}
// Ensure that the PipelineRun provides all the parameters required by the Pipeline
Expand All @@ -455,7 +456,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonParameterTypeMismatch,
"PipelineRun %s/%s parameters have mismatching types with Pipeline %s/%s's parameters: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
pr.Namespace, pr.Name, pr.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}

Expand All @@ -464,7 +465,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonObjectParameterMissKeys,
"PipelineRun %s/%s parameters is missing object keys required by Pipeline %s/%s's parameters: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
pr.Namespace, pr.Name, pr.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}

Expand All @@ -473,15 +474,15 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
// This Run has failed, so we need to mark it as failed and stop reconciling it
pr.Status.MarkFailed(ReasonObjectParameterMissKeys,
"PipelineRun %s/%s parameters is missing object keys required by Pipeline %s/%s's parameters: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
pr.Namespace, pr.Name, pr.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}

// Ensure that the workspaces expected by the Pipeline are provided by the PipelineRun.
if err := resources.ValidateWorkspaceBindings(pipelineSpec, pr); err != nil {
pr.Status.MarkFailed(ReasonInvalidWorkspaceBinding,
"PipelineRun %s/%s doesn't bind Pipeline %s/%s's Workspaces correctly: %s",
pr.Namespace, pr.Name, pr.Namespace, pipelineMeta.Name, err)
pr.Namespace, pr.Name, pr.Namespace, resolvedObjectMeta.Name, err)
return controller.NewPermanentError(err)
}

Expand All @@ -495,7 +496,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get

// Apply parameter substitution from the PipelineRun
pipelineSpec = resources.ApplyParameters(ctx, pipelineSpec, pr)
pipelineSpec = resources.ApplyContexts(ctx, pipelineSpec, pipelineMeta.Name, pr)
pipelineSpec = resources.ApplyContexts(ctx, pipelineSpec, resolvedObjectMeta.Name, pr)
pipelineSpec = resources.ApplyWorkspaces(ctx, pipelineSpec, pr)
// Update pipelinespec of pipelinerun's status field
pr.Status.PipelineSpec = pipelineSpec
Expand All @@ -508,7 +509,7 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get
if len(pipelineSpec.Finally) > 0 {
tasks = append(tasks, pipelineSpec.Finally...)
}
pipelineRunState, err := c.resolvePipelineState(ctx, tasks, pipelineMeta, pr, providedResources)
pipelineRunState, err := c.resolvePipelineState(ctx, tasks, resolvedObjectMeta.ObjectMeta, pr, providedResources)
switch {
case errors.Is(err, remote.ErrorRequestInProgress):
message := fmt.Sprintf("PipelineRun %s/%s awaiting remote resource", pr.Namespace, pr.Name)
Expand Down Expand Up @@ -1245,55 +1246,30 @@ func (c *Reconciler) updateLabelsAndAnnotations(ctx context.Context, pr *v1beta1
// to deal with Patch (setting resourceVersion, and optimistic concurrency checks).
newPr = newPr.DeepCopy()
// Properly merge labels and annotations, as the labels *might* have changed during the reconciliation
newPr.Labels = merge(newPr.Labels, pr.Labels)
newPr.Annotations = merge(newPr.Annotations, pr.Annotations)
newPr.Labels = kmap.Union(newPr.Labels, pr.Labels)
newPr.Annotations = kmap.Union(newPr.Annotations, pr.Annotations)
return c.PipelineClientSet.TektonV1beta1().PipelineRuns(pr.Namespace).Update(ctx, newPr, metav1.UpdateOptions{})
}
return newPr, nil
}

func merge(new, old map[string]string) map[string]string {
if new == nil {
new = map[string]string{}
}
if old == nil {
return new
}
for k, v := range old {
new[k] = v
}
return new
}

func storePipelineSpecAndMergeMeta(pr *v1beta1.PipelineRun, ps *v1beta1.PipelineSpec, meta *metav1.ObjectMeta) error {
func storePipelineSpecAndMergeMeta(pr *v1beta1.PipelineRun, ps *v1beta1.PipelineSpec, resolvedObjectMeta *tresources.ResolvedObjectMeta) error {
// Only store the PipelineSpec once, if it has never been set before.
if pr.Status.PipelineSpec == nil {
pr.Status.PipelineSpec = ps

// Propagate labels from Pipeline to PipelineRun.
if pr.ObjectMeta.Labels == nil {
pr.ObjectMeta.Labels = make(map[string]string, len(meta.Labels)+1)
if resolvedObjectMeta == nil {
return nil
}
for key, value := range meta.Labels {
// Do not override duplicates between PipelineRun and Pipeline
// PipelineRun labels take precedences over Pipeline
if _, ok := pr.ObjectMeta.Labels[key]; !ok {
pr.ObjectMeta.Labels[key] = value
}
}
pr.ObjectMeta.Labels[pipeline.PipelineLabelKey] = meta.Name
// TODO (@chuangw6): Set configsource
// test ...
// https://github.com/tektoncd/pipeline/pull/5580
// pr.Status.Provenance.ConfigSource = resolvedObjectMeta.ConfigSource

// Propagate labels from Pipeline to PipelineRun.
pr.ObjectMeta.Labels = kmap.Union(resolvedObjectMeta.Labels, pr.ObjectMeta.Labels)
pr.ObjectMeta.Labels[pipeline.PipelineLabelKey] = resolvedObjectMeta.Name
// Propagate annotations from Pipeline to PipelineRun.
if pr.ObjectMeta.Annotations == nil {
pr.ObjectMeta.Annotations = make(map[string]string, len(meta.Annotations))
}
for key, value := range meta.Annotations {
// Do not override duplicates between PipelineRun and Pipeline
// PipelineRun labels take precedences over Pipeline
if _, ok := pr.ObjectMeta.Annotations[key]; !ok {
pr.ObjectMeta.Annotations[key] = value
}
}
pr.ObjectMeta.Annotations = kmap.Union(resolvedObjectMeta.Annotations, pr.ObjectMeta.Annotations)

}
return nil
Expand Down
19 changes: 16 additions & 3 deletions pkg/reconciler/pipelinerun/pipelinerun_test.go
Expand Up @@ -36,9 +36,11 @@ import (
"github.com/tektoncd/pipeline/pkg/apis/pipeline/pod"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
resolutionv1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resolution/v1alpha1"
resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
"github.com/tektoncd/pipeline/pkg/reconciler/events/cloudevent"
"github.com/tektoncd/pipeline/pkg/reconciler/pipelinerun/resources"
tresources "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources"
ttesting "github.com/tektoncd/pipeline/pkg/reconciler/testing"
"github.com/tektoncd/pipeline/pkg/reconciler/volumeclaim"
resolutioncommon "github.com/tektoncd/pipeline/pkg/resolution/common"
Expand Down Expand Up @@ -5557,15 +5559,24 @@ metadata:
want.ObjectMeta.Labels["tekton.dev/pipeline"] = pr.ObjectMeta.Name

// The first time we set it, it should get copied.
if err := storePipelineSpecAndMergeMeta(pr, &ps, &pr.ObjectMeta); err != nil {
if err := storePipelineSpecAndMergeMeta(pr, &ps, &tresources.ResolvedObjectMeta{
ObjectMeta: &pr.ObjectMeta,
ConfigSource: &resolutionv1alpha1.ConfigSource{
URI: "abc.com",
Digest: map[string]string{
"sha1": "a123",
},
EntryPoint: "foo/bar",
},
}); err != nil {
t.Errorf("storePipelineSpec() error = %v", err)
}
if d := cmp.Diff(pr, want); d != "" {
t.Fatalf(diff.PrintWantGot(d))
}

// The next time, it should not get overwritten
if err := storePipelineSpecAndMergeMeta(pr, &ps1, &metav1.ObjectMeta{}); err != nil {
if err := storePipelineSpecAndMergeMeta(pr, &ps1, &tresources.ResolvedObjectMeta{}); err != nil {
t.Errorf("storePipelineSpec() error = %v", err)
}
if d := cmp.Diff(pr, want); d != "" {
Expand All @@ -5585,7 +5596,9 @@ func Test_storePipelineSpec_metadata(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "foo", Labels: pipelinerunlabels, Annotations: pipelinerunannotations},
}
meta := metav1.ObjectMeta{Name: "bar", Labels: pipelinelabels, Annotations: pipelineannotations}
if err := storePipelineSpecAndMergeMeta(pr, &v1beta1.PipelineSpec{}, &meta); err != nil {
if err := storePipelineSpecAndMergeMeta(pr, &v1beta1.PipelineSpec{}, &tresources.ResolvedObjectMeta{
ObjectMeta: &meta,
}); err != nil {
t.Errorf("storePipelineSpecAndMergeMeta error = %v", err)
}
if d := cmp.Diff(pr.ObjectMeta.Labels, wantedlabels); d != "" {
Expand Down
23 changes: 16 additions & 7 deletions pkg/reconciler/pipelinerun/pipelinespec/pipelinespec.go
Expand Up @@ -23,34 +23,39 @@ import (

"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"github.com/tektoncd/pipeline/pkg/apis/resolution/v1alpha1"
tresources "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// GetPipeline is a function used to retrieve Pipelines.
type GetPipeline func(context.Context, string) (v1beta1.PipelineObject, error)
type GetPipeline func(context.Context, string) (v1beta1.PipelineObject, *v1alpha1.ConfigSource, error)

// GetPipelineData will retrieve the Pipeline metadata and Spec associated with the
// provided PipelineRun. This can come from a reference Pipeline or from the PipelineRun's
// metadata and embedded PipelineSpec.
func GetPipelineData(ctx context.Context, pipelineRun *v1beta1.PipelineRun, getPipeline GetPipeline) (*metav1.ObjectMeta, *v1beta1.PipelineSpec, error) {
func GetPipelineData(ctx context.Context, pipelineRun *v1beta1.PipelineRun, getPipeline GetPipeline) (*tresources.ResolvedObjectMeta, *v1beta1.PipelineSpec, error) {
pipelineMeta := metav1.ObjectMeta{}
var configSource *v1alpha1.ConfigSource
pipelineSpec := v1beta1.PipelineSpec{}

cfg := config.FromContextOrDefaults(ctx)
switch {
case pipelineRun.Spec.PipelineRef != nil && pipelineRun.Spec.PipelineRef.Name != "":
// Get related pipeline for pipelinerun
t, err := getPipeline(ctx, pipelineRun.Spec.PipelineRef.Name)
pipeline, source, err := getPipeline(ctx, pipelineRun.Spec.PipelineRef.Name)
if err != nil {
return nil, nil, fmt.Errorf("error when listing pipelines for pipelineRun %s: %w", pipelineRun.Name, err)
}
pipelineMeta = t.PipelineMetadata()
pipelineSpec = t.PipelineSpec()
pipelineMeta = pipeline.PipelineMetadata()
pipelineSpec = pipeline.PipelineSpec()
pipelineSpec.SetDefaults(ctx)
configSource = source
case pipelineRun.Spec.PipelineSpec != nil:
pipelineMeta = pipelineRun.ObjectMeta
pipelineSpec = *pipelineRun.Spec.PipelineSpec
case cfg.FeatureFlags.EnableAPIFields == config.AlphaAPIFields && pipelineRun.Spec.PipelineRef != nil && pipelineRun.Spec.PipelineRef.Resolver != "":
pipeline, err := getPipeline(ctx, "")
pipeline, source, err := getPipeline(ctx, "")
switch {
case err != nil:
return nil, nil, err
Expand All @@ -60,8 +65,12 @@ func GetPipelineData(ctx context.Context, pipelineRun *v1beta1.PipelineRun, getP
pipelineMeta = pipeline.PipelineMetadata()
pipelineSpec = pipeline.PipelineSpec()
}
configSource = source
default:
return nil, nil, fmt.Errorf("pipelineRun %s not providing PipelineRef or PipelineSpec", pipelineRun.Name)
}
return &pipelineMeta, &pipelineSpec, nil
return &tresources.ResolvedObjectMeta{
ObjectMeta: &pipelineMeta,
ConfigSource: configSource,
}, &pipelineSpec, nil
}

0 comments on commit c1f5133

Please sign in to comment.