Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set ConfigSource in clusterresolver #5687

Merged
merged 1 commit into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/cluster-resolver.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,60 @@ spec:
value: namespace-containing-pipeline
```

## `ResolutionRequest` Status
`ResolutionRequest.Status.Source` field captures the source where the remote resource came from. It includes the 3 subfields: `url`, `digest` and `entrypoint`.
- `url`: url is the unique full identifier for the resource in the cluster. It is in the format of `<resource uri>@<uid>`. Resource URI part is the namespace-scoped uri i.e. `/apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME`. See [K8s Resource URIs](https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-uris) for more details.
- `digest`: hex-encoded sha256 checksum of the content in the in-cluster resource's spec field. The reason why it's the checksum of the spec content rather than the whole object is because the metadata of in-cluster resources might be modified i.e. annotations. Therefore, the checksum of the spec content should be sufficient for source verifiers to verify if things have been changed maliciously even though the metadata is modified with good intentions.
- `entrypoint`: ***empty*** because the path information is already available in the url field.

Example:
- TaskRun Resolution

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: cluster-demo
spec:
taskRef:
resolver: cluster
params:
- name: kind
value: task
- name: name
value: a-simple-task
- name: namespace
value: default
```


- `ResolutionRequest`
```yaml
apiVersion: resolution.tekton.dev/v1beta1
kind: ResolutionRequest
metadata:
labels:
resolution.tekton.dev/type: cluster
name: cluster-7a04be6baa3eeedd232542036b7f3b2d
namespace: default
ownerReferences: ...
spec:
params:
- name: kind
value: task
- name: name
value: a-simple-task
- name: namespace
value: default
status:
annotations: ...
conditions: ...
data: xxx
source:
digest:
sha256: 245b1aa918434cc8195b4d4d026f2e43df09199e2ed31d4dfd9c2cbea1c7ce54
uri: /apis/tekton.dev/v1beta1/namespaces/default/task/a-simple-task@3b82d8c4-f89e-47ea-a49d-3be0dca4c038
```
---

Except as otherwise noted, the content of this page is licensed under the
Expand Down
52 changes: 46 additions & 6 deletions pkg/resolution/resolver/cluster/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package cluster

import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"strings"

resolverconfig "github.com/tektoncd/pipeline/pkg/apis/config/resolver"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
pipelinev1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
clientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned"
pipelineclient "github.com/tektoncd/pipeline/pkg/client/injection/client"
Expand Down Expand Up @@ -101,6 +104,8 @@ func (r *Resolver) Resolve(ctx context.Context, origParams []pipelinev1beta1.Par
}

var data []byte
var spec []byte
var uid string
groupVersion := pipelinev1beta1.SchemeGroupVersion.String()

switch params[KindParam] {
Expand All @@ -110,35 +115,51 @@ func (r *Resolver) Resolve(ctx context.Context, origParams []pipelinev1beta1.Par
logger.Infof("failed to load task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
uid = string(task.UID)
task.Kind = "Task"
task.APIVersion = groupVersion
data, err = yaml.Marshal(task)
if err != nil {
logger.Infof("failed to marshal task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}

spec, err = yaml.Marshal(task.Spec)
if err != nil {
logger.Infof("failed to marshal the spec of the task %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
case "pipeline":
pipeline, err := r.pipelineClientSet.TektonV1beta1().Pipelines(params[NamespaceParam]).Get(ctx, params[NameParam], metav1.GetOptions{})
if err != nil {
logger.Infof("failed to load pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
uid = string(pipeline.UID)
pipeline.Kind = "Pipeline"
pipeline.APIVersion = groupVersion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, are we also going to support v1 in the remote resolution after V1 release?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we will. Basically, this should always be whatever version is used in the PipelineRun reconciler by default, but we should probably add a param for apiVersion to allow overriding that...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, are we also going to support v1 in the remote resolution after V1 release?

Yeah agreed. We will need to support v1 in remote resolution. Thanks for asking @XinruZhang .

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for a new param for apiVersion, it looks like a future refactor work XD

data, err = yaml.Marshal(pipeline)
if err != nil {
logger.Infof("failed to marshal pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}

spec, err = yaml.Marshal(pipeline.Spec)
if err != nil {
logger.Infof("failed to marshal the spec of the pipeline %s from namespace %s: %v", params[NameParam], params[NamespaceParam], err)
return nil, err
}
default:
logger.Infof("unknown or invalid resource kind %s", params[KindParam])
return nil, fmt.Errorf("unknown or invalid resource kind %s", params[KindParam])
}

return &ResolvedClusterResource{
Content: data,
Name: params[NameParam],
Namespace: params[NamespaceParam],
Content: data,
Spec: spec,
Name: params[NameParam],
Namespace: params[NamespaceParam],
Identifier: fmt.Sprintf("/apis/%s/namespaces/%s/%s/%s@%s", groupVersion, params[NamespaceParam], params[KindParam], params[NameParam], uid),
}, nil
}

Expand All @@ -161,9 +182,19 @@ func (r *Resolver) isDisabled(ctx context.Context) bool {
// ResolvedClusterResource implements framework.ResolvedResource and returns
// the resolved file []byte data and an annotation map for any metadata.
type ResolvedClusterResource struct {
Content []byte
Name string
// Content is the actual resolved resource data.
Content []byte
// Spec is the data in the resolved task/pipeline CRD spec.
Spec []byte
// Name is the resolved resource name in the cluster
Name string
// Namespace is the namespace in the cluster under which the resolved resource was created.
Namespace string
// Identifier is the unique identifier for the resource in the cluster.
// It is in the format of <resource uri>@<uid>.
// Resource URI is the namespace-scoped uri i.e. /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME.
// https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-uris
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This isn't quite a resource URI (it's <resource uri>@<uid>). We should update the comment and/or name to reflect this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Updated both comment and name, and also updated the clusterresolver doc. Thank you!

Identifier string
}

var _ framework.ResolvedResource = &ResolvedClusterResource{}
Expand All @@ -184,7 +215,16 @@ func (r *ResolvedClusterResource) Annotations() map[string]string {
// Source is the source reference of the remote data that records where the remote
// file came from including the url, digest and the entrypoint.
func (r ResolvedClusterResource) Source() *pipelinev1beta1.ConfigSource {
return nil
h := sha256.New()
h.Write(r.Spec)
sha256CheckSum := hex.EncodeToString(h.Sum(nil))
chuangw6 marked this conversation as resolved.
Show resolved Hide resolved

return &v1beta1.ConfigSource{
URI: r.Identifier,
Digest: map[string]string{
"sha256": sha256CheckSum,
},
}
}

func populateParamsWithDefaults(ctx context.Context, origParams []pipelinev1beta1.Param) (map[string]string, error) {
Expand Down
100 changes: 82 additions & 18 deletions pkg/resolution/resolver/cluster/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package cluster

import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"testing"
"time"
Expand All @@ -36,6 +39,7 @@ import (
"github.com/tektoncd/pipeline/test/diff"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
duckv1 "knative.dev/pkg/apis/duck/v1"
"knative.dev/pkg/system"
"sigs.k8s.io/yaml"

Expand Down Expand Up @@ -188,6 +192,7 @@ func TestResolve(t *testing.T) {
Name: "example-task",
Namespace: "task-ns",
ResourceVersion: "00002",
UID: "a123",
},
TypeMeta: metav1.TypeMeta{
Kind: string(pipelinev1beta1.NamespacedTaskKind),
Expand All @@ -205,12 +210,17 @@ func TestResolve(t *testing.T) {
if err != nil {
t.Fatalf("couldn't marshal task: %v", err)
}
taskSpec, err := yaml.Marshal(exampleTask.Spec)
if err != nil {
t.Fatalf("couldn't marshal task spec: %v", err)
}

examplePipeline := &pipelinev1beta1.Pipeline{
ObjectMeta: metav1.ObjectMeta{
Name: "example-pipeline",
Namespace: defaultNS,
ResourceVersion: "00001",
UID: "b123",
},
TypeMeta: metav1.TypeMeta{
Kind: "Pipeline",
Expand All @@ -230,6 +240,10 @@ func TestResolve(t *testing.T) {
if err != nil {
t.Fatalf("couldn't marshal pipeline: %v", err)
}
pipelineSpec, err := yaml.Marshal(examplePipeline.Spec)
if err != nil {
t.Fatalf("couldn't marshal pipeline spec: %v", err)
}

testCases := []struct {
name string
Expand All @@ -242,27 +256,71 @@ func TestResolve(t *testing.T) {
expectedErr error
}{
{
name: "successful task",
kind: "task",
resourceName: exampleTask.Name,
namespace: exampleTask.Namespace,
expectedStatus: internal.CreateResolutionRequestStatusWithData(taskAsYAML),
name: "successful task",
kind: "task",
resourceName: exampleTask.Name,
namespace: exampleTask.Namespace,
expectedStatus: &v1beta1.ResolutionRequestStatus{
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(taskAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/task-ns/task/example-task@a123",
Digest: map[string]string{
"sha256": sha256CheckSum(taskSpec),
},
},
},
},
}, {
name: "successful pipeline",
kind: "pipeline",
resourceName: examplePipeline.Name,
namespace: examplePipeline.Namespace,
expectedStatus: internal.CreateResolutionRequestStatusWithData(pipelineAsYAML),
name: "successful pipeline",
kind: "pipeline",
resourceName: examplePipeline.Name,
namespace: examplePipeline.Namespace,
expectedStatus: &v1beta1.ResolutionRequestStatus{
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(pipelineAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/pipeline-ns/pipeline/example-pipeline@b123",
Digest: map[string]string{
"sha256": sha256CheckSum(pipelineSpec),
},
},
},
},
}, {
name: "default namespace",
kind: "pipeline",
resourceName: examplePipeline.Name,
expectedStatus: internal.CreateResolutionRequestStatusWithData(pipelineAsYAML),
name: "default namespace",
kind: "pipeline",
resourceName: examplePipeline.Name,
expectedStatus: &v1beta1.ResolutionRequestStatus{
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(pipelineAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/pipeline-ns/pipeline/example-pipeline@b123",
Digest: map[string]string{
"sha256": sha256CheckSum(pipelineSpec),
},
},
},
},
}, {
name: "default kind",
resourceName: exampleTask.Name,
namespace: exampleTask.Namespace,
expectedStatus: internal.CreateResolutionRequestStatusWithData(taskAsYAML),
name: "default kind",
resourceName: exampleTask.Name,
namespace: exampleTask.Namespace,
expectedStatus: &v1beta1.ResolutionRequestStatus{
Status: duckv1.Status{},
ResolutionRequestStatusFields: v1beta1.ResolutionRequestStatusFields{
Data: base64.StdEncoding.Strict().EncodeToString(taskAsYAML),
Source: &pipelinev1beta1.ConfigSource{
URI: "/apis/tekton.dev/v1beta1/namespaces/task-ns/task/example-task@a123",
Digest: map[string]string{
"sha256": sha256CheckSum(taskSpec),
},
},
},
},
}, {
name: "no such task",
kind: "task",
Expand Down Expand Up @@ -407,3 +465,9 @@ func createRequest(kind, name, namespace string) *v1beta1.ResolutionRequest {
func resolverContext() context.Context {
return frtesting.ContextWithClusterResolverEnabled(context.Background())
}

func sha256CheckSum(input []byte) string {
h := sha256.New()
h.Write(input)
return hex.EncodeToString(h.Sum(nil))
}