From de64a44a169e3abf38d5d2ae144767ac6532fb59 Mon Sep 17 00:00:00 2001 From: Andrea Frittoli Date: Wed, 17 Jul 2019 03:52:36 +0900 Subject: [PATCH] Cloud Event output resource Define a new type of PipelineResource for cloud events. This resource can be used as an output only. The resource is only defined here, it's not functional yet. Signed-off-by: Andrea Frittoli --- .../pipeline/v1alpha1/cloud_event_resource.go | 105 ++++++++++++++++ .../v1alpha1/cloud_event_resource_test.go | 119 ++++++++++++++++++ pkg/apis/pipeline/v1alpha1/resource_types.go | 7 +- .../v1alpha1/zz_generated.deepcopy.go | 16 +++ 4 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 pkg/apis/pipeline/v1alpha1/cloud_event_resource.go create mode 100644 pkg/apis/pipeline/v1alpha1/cloud_event_resource_test.go diff --git a/pkg/apis/pipeline/v1alpha1/cloud_event_resource.go b/pkg/apis/pipeline/v1alpha1/cloud_event_resource.go new file mode 100644 index 00000000000..5f80075207f --- /dev/null +++ b/pkg/apis/pipeline/v1alpha1/cloud_event_resource.go @@ -0,0 +1,105 @@ +/* +Copyright 2019 The Tekton Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" +) + +// CloudEventResource is an event sink to which events are delivered when a TaskRun has finished +type CloudEventResource struct { + // Name is the name used to reference to the PipelineResource + Name string `json:"name"` + // Type must be `PipelineResourceTypeCloudEvent` + Type PipelineResourceType `json:"type"` + // TargetURI is the URI of the sink which the cloud event is develired to + TargetURI string `json:"targetURI"` +} + +// NewCloudEventResource creates a new CloudEvent resource to pass to a Task +func NewCloudEventResource(r *PipelineResource) (*CloudEventResource, error) { + if r.Spec.Type != PipelineResourceTypeCloudEvent { + return nil, fmt.Errorf("CloudEventResource: Cannot create a Cloud Event resource from a %s Pipeline Resource", r.Spec.Type) + } + var targetURI string + var targetURISpecified bool + + for _, param := range r.Spec.Params { + switch { + case strings.EqualFold(param.Name, "TargetURI"): + targetURI = param.Value + if param.Value != "" { + targetURISpecified = true + } + } + } + + if !targetURISpecified { + return nil, fmt.Errorf("CloudEventResource: Need URI to be specified in order to create a CloudEvent resource %s", r.Name) + } + return &CloudEventResource{ + Name: r.Name, + Type: r.Spec.Type, + TargetURI: targetURI, + }, nil +} + +// GetName returns the name of the resource +func (s CloudEventResource) GetName() string { + return s.Name +} + +// GetType returns the type of the resource, in this case "storage" +func (s CloudEventResource) GetType() PipelineResourceType { + return PipelineResourceTypeCloudEvent +} + +// Replacements is used for template replacement on an CloudEventResource inside of a Taskrun. +func (s *CloudEventResource) Replacements() map[string]string { + return map[string]string{ + "name": s.Name, + "type": string(s.Type), + "target-uri": s.TargetURI, + } +} + +// GetUploadContainerSpec returns nothing as the cloud event is sent by the controller once the POD execution is completed +func (s *CloudEventResource) GetUploadContainerSpec() ([]corev1.Container, error) { + return nil, nil +} + +// GetDownloadContainerSpec returns nothing, cloud events cannot be used as input resource +func (s *CloudEventResource) GetDownloadContainerSpec() ([]corev1.Container, error) { + return nil, nil +} + +// SetDestinationDirectory required by the interface but not really used +func (s *CloudEventResource) SetDestinationDirectory(path string) { +} + +// GetUploadVolumeSpec - no upload from volume for CloudEvent resource +func (s *CloudEventResource) GetUploadVolumeSpec(spec *TaskSpec) ([]corev1.Volume, error) { + return nil, nil +} + +// GetDownloadVolumeSpec - no download from volume for CloudEvent resource +func (s *CloudEventResource) GetDownloadVolumeSpec(spec *TaskSpec) ([]corev1.Volume, error) { + return nil, nil +} diff --git a/pkg/apis/pipeline/v1alpha1/cloud_event_resource_test.go b/pkg/apis/pipeline/v1alpha1/cloud_event_resource_test.go new file mode 100644 index 00000000000..602f287f7de --- /dev/null +++ b/pkg/apis/pipeline/v1alpha1/cloud_event_resource_test.go @@ -0,0 +1,119 @@ +/* +Copyright 2019 The Tekton Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + + tb "github.com/tektoncd/pipeline/test/builder" +) + +func Test_NewCloudEventResource_Invalid(t *testing.T) { + testcases := []struct { + name string + pipelineResource *v1alpha1.PipelineResource + }{{ + name: "create resource with no parameter", + pipelineResource: tb.PipelineResource("cloud-event-resource-no-uri", "default", tb.PipelineResourceSpec( + v1alpha1.PipelineResourceTypeCloudEvent, + )), + }, { + name: "create resource with invalid type", + pipelineResource: tb.PipelineResource("git-resource", "default", tb.PipelineResourceSpec( + v1alpha1.PipelineResourceTypeGit, + tb.PipelineResourceSpecParam("URL", "git://fake/repo"), + tb.PipelineResourceSpecParam("Revision", "fake_rev"), + )), + }} + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + _, err := v1alpha1.NewCloudEventResource(tc.pipelineResource) + if err == nil { + t.Error("Expected error creating CloudEvent resource") + } + }) + } +} + +func Test_NewCloudEventResource_Valid(t *testing.T) { + pr := tb.PipelineResource("cloud-event-resource-uri", "default", tb.PipelineResourceSpec( + v1alpha1.PipelineResourceTypeCloudEvent, + tb.PipelineResourceSpecParam("TargetURI", "http://fake-sink"), + )) + expectedCloudEventResource := &v1alpha1.CloudEventResource{ + Name: "cloud-event-resource-uri", + TargetURI: "http://fake-sink", + Type: v1alpha1.PipelineResourceTypeCloudEvent, + } + + r, err := v1alpha1.NewCloudEventResource(pr) + if err != nil { + t.Fatalf("Unexpected error creating CloudEvent resource: %s", err) + } + if d := cmp.Diff(expectedCloudEventResource, r); d != "" { + t.Errorf("Mismatch of CloudEvent resource: %s", d) + } +} + +func Test_CloudEventGetReplacements(t *testing.T) { + r := &v1alpha1.CloudEventResource{ + Name: "cloud-event-resource", + TargetURI: "http://fake-uri", + Type: v1alpha1.PipelineResourceTypeCloudEvent, + } + expectedReplacementMap := map[string]string{ + "name": "cloud-event-resource", + "type": "cloudEvent", + "target-uri": "http://fake-uri", + } + if d := cmp.Diff(r.Replacements(), expectedReplacementMap); d != "" { + t.Errorf("CloudEvent Replacement map mismatch: %s", d) + } +} + +func Test_CloudEventDownloadContainerSpec(t *testing.T) { + r := &v1alpha1.CloudEventResource{ + Name: "cloud-event-resource", + TargetURI: "http://fake-uri", + Type: v1alpha1.PipelineResourceTypeCloudEvent, + } + d, e := r.GetDownloadContainerSpec(); + if d != nil { + t.Errorf("Did not expect a download container for CloudEventResource") + } + if e != nil { + t.Errorf("Did not expect an error %s when getting a download container for CloudEventResource", e) + } +} + +func Test_CloudEventUploadContainerSpec(t *testing.T) { + r := &v1alpha1.CloudEventResource{ + Name: "cloud-event-resource", + TargetURI: "http://fake-uri", + Type: v1alpha1.PipelineResourceTypeCloudEvent, + } + d, e := r.GetUploadContainerSpec(); + if d != nil { + t.Errorf("Did not expect an upload container for CloudEventResource") + } + if e != nil { + t.Errorf("Did not expect an error %s when getting an upload container for CloudEventResource", e) + } +} diff --git a/pkg/apis/pipeline/v1alpha1/resource_types.go b/pkg/apis/pipeline/v1alpha1/resource_types.go index 215b248b111..d62a9e2ed4e 100644 --- a/pkg/apis/pipeline/v1alpha1/resource_types.go +++ b/pkg/apis/pipeline/v1alpha1/resource_types.go @@ -43,10 +43,13 @@ const ( // PipelineResourceTypePullRequest indicates that this source is a SCM Pull Request. PipelineResourceTypePullRequest PipelineResourceType = "pullRequest" + + // PipelineResourceTypeCloudEvent indicates that this source is a cloud event URI + PipelineResourceTypeCloudEvent PipelineResourceType = "cloudEvent" ) // AllResourceTypes can be used for validation to check if a provided Resource type is one of the known types. -var AllResourceTypes = []PipelineResourceType{PipelineResourceTypeGit, PipelineResourceTypeStorage, PipelineResourceTypeImage, PipelineResourceTypeCluster, PipelineResourceTypePullRequest} +var AllResourceTypes = []PipelineResourceType{PipelineResourceTypeGit, PipelineResourceTypeStorage, PipelineResourceTypeImage, PipelineResourceTypeCluster, PipelineResourceTypePullRequest, PipelineResourceTypeCloudEvent} // PipelineResourceInterface interface to be implemented by different PipelineResource types type PipelineResourceInterface interface { @@ -145,6 +148,8 @@ func ResourceFromType(r *PipelineResource) (PipelineResourceInterface, error) { return NewStorageResource(r) case PipelineResourceTypePullRequest: return NewPullRequestResource(r) + case PipelineResourceTypeCloudEvent: + return NewCloudEventResource(r) } return nil, xerrors.Errorf("%s is an invalid or unimplemented PipelineResource", r.Spec.Type) } diff --git a/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go index 4c68774c347..8600dc6cbe7 100644 --- a/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go @@ -142,6 +142,22 @@ func (in *CloudEventDeliveryState) DeepCopy() *CloudEventDeliveryState { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CloudEventResource) DeepCopyInto(out *CloudEventResource) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CloudEventResource. +func (in *CloudEventResource) DeepCopy() *CloudEventResource { + if in == nil { + return nil + } + out := new(CloudEventResource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterResource) DeepCopyInto(out *ClusterResource) { *out = *in