diff --git a/cmd/controller/main.go b/cmd/controller/main.go index f2d043665..07f9bff73 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -21,6 +21,7 @@ import ( "github.com/tektoncd/triggers/pkg/reconciler/clusterinterceptor" elresources "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" + "github.com/tektoncd/triggers/pkg/reconciler/interceptor" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -95,5 +96,6 @@ func main() { cfg, eventlistener.NewController(c), clusterinterceptor.NewController(), + interceptor.NewController(), ) } diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index b587fe7e8..8ff1bc428 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -42,6 +42,7 @@ import ( var types = map[schema.GroupVersionKind]resourcesemantics.GenericCRD{ v1alpha1.SchemeGroupVersion.WithKind("ClusterTriggerBinding"): &v1alpha1.ClusterTriggerBinding{}, v1alpha1.SchemeGroupVersion.WithKind("ClusterInterceptor"): &v1alpha1.ClusterInterceptor{}, + v1alpha1.SchemeGroupVersion.WithKind("Interceptor"): &v1alpha1.Interceptor{}, v1alpha1.SchemeGroupVersion.WithKind("EventListener"): &v1alpha1.EventListener{}, v1alpha1.SchemeGroupVersion.WithKind("TriggerBinding"): &v1alpha1.TriggerBinding{}, v1alpha1.SchemeGroupVersion.WithKind("TriggerTemplate"): &v1alpha1.TriggerTemplate{}, diff --git a/config/200-clusterrole.yaml b/config/200-clusterrole.yaml index ec90481c9..378f16ff6 100644 --- a/config/200-clusterrole.yaml +++ b/config/200-clusterrole.yaml @@ -30,10 +30,10 @@ rules: resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - apiGroups: ["triggers.tekton.dev"] - resources: ["clustertriggerbindings", "clusterinterceptors", "eventlisteners", "triggerbindings", "triggertemplates", "triggers", "eventlisteners/finalizers"] + resources: ["clustertriggerbindings", "clusterinterceptors", "interceptors", "eventlisteners", "triggerbindings", "triggertemplates", "triggers", "eventlisteners/finalizers"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] - apiGroups: ["triggers.tekton.dev"] - resources: ["clustertriggerbindings/status", "clusterinterceptors/status", "eventlisteners/status", "triggerbindings/status", "triggertemplates/status", "triggers/status"] + resources: ["clustertriggerbindings/status", "clusterinterceptors/status", "interceptors/status", "eventlisteners/status", "triggerbindings/status", "triggertemplates/status", "triggers/status"] verbs: ["get", "list", "create", "update", "delete", "patch", "watch"] # We uses leases for leaderelection - apiGroups: ["coordination.k8s.io"] @@ -92,7 +92,7 @@ metadata: app.kubernetes.io/part-of: tekton-triggers rules: - apiGroups: ["triggers.tekton.dev"] - resources: ["eventlisteners", "triggerbindings", "triggertemplates", "triggers"] + resources: ["eventlisteners", "triggerbindings", "interceptors", "triggertemplates", "triggers"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps"] diff --git a/config/300-interceptor.yaml b/config/300-interceptor.yaml new file mode 100644 index 000000000..9d9368513 --- /dev/null +++ b/config/300-interceptor.yaml @@ -0,0 +1,54 @@ +# Copyright 2022 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 +# +# https://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. + +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: interceptors.triggers.tekton.dev + labels: + app.kubernetes.io/instance: default + app.kubernetes.io/part-of: tekton-triggers + triggers.tekton.dev/release: "devel" + version: "devel" +spec: + group: triggers.tekton.dev + scope: Namespaced + names: + kind: Interceptor + plural: interceptors + singular: interceptor + shortNames: + - ni + categories: + - tekton + - tekton-triggers + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + # One can use x-kubernetes-preserve-unknown-fields: true + # at the root of the schema (and inside any properties, additionalProperties) + # to get the traditional CRD behaviour that nothing is pruned, despite + # setting spec.preserveUnknownProperties: false. + # + # See https://kubernetes.io/blog/2019/06/20/crd-structural-schema/ + # See issue: https://github.com/knative/serving/issues/912 + x-kubernetes-preserve-unknown-fields: true + # Opt into the status subresource so metadata.generation + # starts to increment + subresources: + status: {} diff --git a/config/clusterrole-aggregate-edit.yaml b/config/clusterrole-aggregate-edit.yaml index 7d4498d61..e95ce9445 100644 --- a/config/clusterrole-aggregate-edit.yaml +++ b/config/clusterrole-aggregate-edit.yaml @@ -28,6 +28,7 @@ rules: - clustertriggerbindings - clusterinterceptors - eventlisteners + - interceptors - triggers - triggerbindings - triggertemplates diff --git a/config/clusterrole-aggregate-view.yaml b/config/clusterrole-aggregate-view.yaml index 44e4248ee..439d31ee9 100644 --- a/config/clusterrole-aggregate-view.yaml +++ b/config/clusterrole-aggregate-view.yaml @@ -27,6 +27,7 @@ rules: - clustertriggerbindings - clusterinterceptors - eventlisteners + - interceptors - triggers - triggerbindings - triggertemplates diff --git a/docs/triggers-api.md b/docs/triggers-api.md index d5ee1153e..cfe843701 100644 --- a/docs/triggers-api.md +++ b/docs/triggers-api.md @@ -600,7 +600,7 @@ string

ClientConfig

-(Appears on:ClusterInterceptorSpec) +(Appears on:ClusterInterceptorSpec, InterceptorSpec)

ClientConfig describes how a client can communicate with the Interceptor

@@ -1182,6 +1182,79 @@ SecretRef +

Interceptor +

+
+

Interceptor describes a pluggable interceptor including configuration +such as the fields it accepts and its deployment address. The type is based on +the Validating/MutatingWebhookConfiguration types for configuring AdmissionWebhooks

+
+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+(Optional) +Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +InterceptorSpec + + +
+
+
+ + + + + +
+clientConfig
+ + +ClientConfig + + +
+
+
+status
+ + +InterceptorStatus + + +
+(Optional) +

InterceptorInterface

@@ -1204,6 +1277,9 @@ SecretRef

"ClusterInterceptor"

ClusterInterceptorKind indicates that Interceptor type has a cluster scope.

+

"NamespacedInterceptor"

+

NamespacedInterceptorKind indicated that interceptor has a namespaced scope

+

InterceptorParams @@ -1282,9 +1358,7 @@ InterceptorKind (Optional) -

InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped. -Currently only InterceptorKind is ClusterInterceptor, so the only valid value -is the default one

+

InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped.

@@ -1427,6 +1501,85 @@ Status +

InterceptorSpec +

+

+(Appears on:Interceptor) +

+
+

InterceptorSpec describes the Spec for an Interceptor

+
+ + + + + + + + + + + + + +
FieldDescription
+clientConfig
+ + +ClientConfig + + +
+
+

InterceptorStatus +

+

+(Appears on:Interceptor) +

+
+

InterceptorStatus holds the status of the Interceptor

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+Status
+ + +knative.dev/pkg/apis/duck/v1.Status + + +
+

+(Members of Status are embedded into this type.) +

+
+AddressStatus
+ + +knative.dev/pkg/apis/duck/v1.AddressStatus + + +
+

+(Members of AddressStatus are embedded into this type.) +

+

Interceptor is Addressable and exposes the URL where the Interceptor is running

+

KubernetesResource

@@ -3612,6 +3765,9 @@ SecretRef

"ClusterInterceptor"

ClusterInterceptorKind indicates that Interceptor type has a cluster scope.

+

"NamespacedInterceptor"

+

NamespacedInterceptorKind indicates that Interceptor type has a namespace scope.

+

InterceptorParams @@ -3690,9 +3846,7 @@ InterceptorKind (Optional) -

InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped. -Currently only InterceptorKind is ClusterInterceptor, so the only valid value -is the default one

+

InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped.

diff --git a/examples/v1alpha1/namespace-selector/01_rbac.yaml b/examples/v1alpha1/namespace-selector/01_rbac.yaml index 09c6ea0f6..2b455d70d 100644 --- a/examples/v1alpha1/namespace-selector/01_rbac.yaml +++ b/examples/v1alpha1/namespace-selector/01_rbac.yaml @@ -6,7 +6,7 @@ metadata: rules: # Permissions for every EventListener deployment to function - apiGroups: ["triggers.tekton.dev"] - resources: ["eventlisteners", "clustertriggerbindings", "clusterinterceptors", "triggerbindings", "triggertemplates", "triggers"] + resources: ["eventlisteners", "clustertriggerbindings", "clusterinterceptors", "interceptors", "triggerbindings", "triggertemplates", "triggers"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps", "secrets"] diff --git a/examples/v1beta1/namespace-selector/01_rbac.yaml b/examples/v1beta1/namespace-selector/01_rbac.yaml index 09c6ea0f6..2b455d70d 100644 --- a/examples/v1beta1/namespace-selector/01_rbac.yaml +++ b/examples/v1beta1/namespace-selector/01_rbac.yaml @@ -6,7 +6,7 @@ metadata: rules: # Permissions for every EventListener deployment to function - apiGroups: ["triggers.tekton.dev"] - resources: ["eventlisteners", "clustertriggerbindings", "clusterinterceptors", "triggerbindings", "triggertemplates", "triggers"] + resources: ["eventlisteners", "clustertriggerbindings", "clusterinterceptors", "interceptors", "triggerbindings", "triggertemplates", "triggers"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["configmaps", "secrets"] diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go index 02a5d619a..fcad0611f 100644 --- a/pkg/adapter/adapter.go +++ b/pkg/adapter/adapter.go @@ -28,6 +28,7 @@ import ( "time" clusterinterceptorsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/clusterinterceptor" + interceptorsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor" clustertriggerbindingsinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/clustertriggerbinding" eventlistenerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/eventlistener" triggersinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/trigger" @@ -108,7 +109,7 @@ func (s *sinker) getHTTPClient() (*http.Client, error) { certPool := x509.NewCertPool() - err := s.getCertFromClusterInterceptor(certPool) + err := s.getCertFromInterceptor(certPool) if err != nil { return &http.Client{}, fmt.Errorf("Timed out waiting on CaBundle to available for clusterInterceptor: %v", err) } @@ -119,7 +120,7 @@ func (s *sinker) getHTTPClient() (*http.Client, error) { go func() { for { <-ticker.C - if err := s.getCertFromClusterInterceptor(certPool); err != nil { + if err := s.getCertFromInterceptor(certPool); err != nil { s.Logger.Fatalf("Timed out waiting on CaBundle to available for clusterInterceptor: %v", err) } } @@ -141,7 +142,7 @@ func (s *sinker) getHTTPClient() (*http.Client, error) { ExpectContinueTimeout: s.Args.ElHTTPClientExpectContinueTimeout * time.Second}}, nil } -func (s *sinker) getCertFromClusterInterceptor(certPool *x509.CertPool) error { +func (s *sinker) getCertFromInterceptor(certPool *x509.CertPool) error { var ( caCert []byte count int @@ -166,12 +167,38 @@ func (s *sinker) getCertFromClusterInterceptor(certPool *x509.CertPool) error { } } } - if httpsCILen != 0 && httpsCILen == count { - return true, nil + + if httpsCILen == 0 || httpsCILen != count { + return false, fmt.Errorf("empty caBundle in clusterInterceptor spec") } - return false, fmt.Errorf("empty caBundle in clusterInterceptor spec") + + httpsCILen = 0 + count = 0 + + interceptorList, err := interceptorsinformer.Get(s.injCtx).Lister().Interceptors(s.Namespace).List(labels.Everything()) + if err != nil { + return false, err + } + + for i := range interceptorList { + if v, k := interceptorList[i].Labels["server/type"]; k && v == "https" { + httpsCILen++ + if !bytes.Equal(interceptorList[i].Spec.ClientConfig.CaBundle, []byte{}) { + caCert = interceptorList[i].Spec.ClientConfig.CaBundle + if ok := certPool.AppendCertsFromPEM(caCert); !ok { + return false, fmt.Errorf("unable to parse cert from %s", caCert) + } + count++ + } + } + } + if httpsCILen != count { + return false, fmt.Errorf("empty caBundle in interceptor spec") + } + + return true, nil }); err != nil { - return fmt.Errorf("Timed out waiting on CaBundle to available for clusterInterceptor: %v", err) + return fmt.Errorf("Timed out waiting on CaBundle to available for Interceptor: %v", err) } return nil } @@ -206,6 +233,7 @@ func (s *sinker) Start(ctx context.Context) error { ClusterTriggerBindingLister: clustertriggerbindingsinformer.Get(s.injCtx).Lister(), TriggerTemplateLister: triggertemplatesinformer.Get(s.injCtx).Lister(), ClusterInterceptorLister: clusterinterceptorsinformer.Get(s.injCtx).Lister(), + InterceptorLister: interceptorsinformer.Get(s.injCtx).Lister(), } mux := http.NewServeMux() diff --git a/pkg/apis/triggers/v1alpha1/interceptor_defaults.go b/pkg/apis/triggers/v1alpha1/interceptor_defaults.go new file mode 100644 index 000000000..d43eac53a --- /dev/null +++ b/pkg/apis/triggers/v1alpha1/interceptor_defaults.go @@ -0,0 +1,36 @@ +/* +Copyright 2022 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 ( + "context" + + "github.com/tektoncd/triggers/pkg/apis/triggers/contexts" +) + +// SetDefaults sets the defaults on the object. +func (it *Interceptor) SetDefaults(ctx context.Context) { + if !contexts.IsUpgradeViaDefaulting(ctx) { + return + } + if _, ok := it.GetLabels()["server/type"]; !ok { + // if server type is not set its assumed that running server is http + it.Labels = map[string]string{ + "server/type": "http", + } + } +} diff --git a/pkg/apis/triggers/v1alpha1/interceptor_types.go b/pkg/apis/triggers/v1alpha1/interceptor_types.go index a87a53e0d..f634dae14 100644 --- a/pkg/apis/triggers/v1alpha1/interceptor_types.go +++ b/pkg/apis/triggers/v1alpha1/interceptor_types.go @@ -1,13 +1,94 @@ package v1alpha1 import ( + "bytes" "context" "fmt" "strings" "google.golang.org/grpc/codes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) +// Check that Interceptor may be validated and defaulted. +var _ apis.Validatable = (*Interceptor)(nil) +var _ apis.Defaultable = (*Interceptor)(nil) + +// +genclient +// +genreconciler:krshapedlogic=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:openapi-gen=true +// Interceptor describes a pluggable interceptor including configuration +// such as the fields it accepts and its deployment address. The type is based on +// the Validating/MutatingWebhookConfiguration types for configuring AdmissionWebhooks +type Interceptor struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec InterceptorSpec `json:"spec"` + // +optional + Status InterceptorStatus `json:"status"` +} + +// InterceptorSpec describes the Spec for an Interceptor +type InterceptorSpec struct { + ClientConfig ClientConfig `json:"clientConfig"` +} + +// InterceptorStatus holds the status of the Interceptor +// +k8s:deepcopy-gen=true +type InterceptorStatus struct { + duckv1.Status `json:",inline"` + + // Interceptor is Addressable and exposes the URL where the Interceptor is running + duckv1.AddressStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// InterceptorList contains a list of Interceptor +// We don't use this but it's required for certain codegen features. +type InterceptorList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + Items []Interceptor `json:"items"` +} + +// ResolveAddress returns the URL where the interceptor is running using its clientConfig +func (it *Interceptor) ResolveAddress() (*apis.URL, error) { + if url := it.Spec.ClientConfig.URL; url != nil { + return url, nil + } + svc := it.Spec.ClientConfig.Service + if svc == nil { + return nil, ErrNilURL + } + var ( + port *int32 + url *apis.URL + ) + + if svc.Port != nil { + port = svc.Port + } + + if bytes.Equal(it.Spec.ClientConfig.CaBundle, []byte{}) { + if port == nil { + port = &defaultHTTPPort + } + url = formURL("http", svc, port) + } else { + if port == nil { + port = &defaultHTTPSPort + } + url = formURL("https", svc, port) + } + return url, nil +} + type InterceptorInterface interface { // Process executes the given InterceptorRequest. Simply getting a non-nil InterceptorResponse back is not sufficient // to determine if the interceptor processing was successful. Instead use the InterceptorResponse.Status.Continue to diff --git a/pkg/apis/triggers/v1alpha1/interceptor_types_test.go b/pkg/apis/triggers/v1alpha1/interceptor_types_test.go index dad66ca75..aa3f09e45 100644 --- a/pkg/apis/triggers/v1alpha1/interceptor_types_test.go +++ b/pkg/apis/triggers/v1alpha1/interceptor_types_test.go @@ -1,10 +1,14 @@ package v1alpha1_test import ( + "errors" "testing" "github.com/google/go-cmp/cmp" "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + "knative.dev/pkg/ptr" ) func TestParseTriggerID(t *testing.T) { @@ -26,3 +30,111 @@ func TestParseTriggerID(t *testing.T) { }) } } + +func TestNSResolveAddress(t *testing.T) { + tests := []struct { + name string + it *v1alpha1.Interceptor + want string + }{{ + name: "clientConfig.url is specified", + it: &v1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: v1alpha1.InterceptorSpec{ + ClientConfig: v1alpha1.ClientConfig{ + URL: &apis.URL{ + Scheme: "http", + Host: "foo.bar.com:8081", + Path: "abc", + }, + }, + }, + }, + want: "http://foo.bar.com:8081/abc", + }, { + name: "clientConfig.service with namespace", + it: &v1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: v1alpha1.InterceptorSpec{ + ClientConfig: v1alpha1.ClientConfig{ + Service: &v1alpha1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "blah", + Port: ptr.Int32(8888), + }, + }, + }, + }, + want: "http://my-svc.default.svc:8888/blah", + }, { + name: "clientConfig.service without port and scheme so it uses defaultHTTPPort", + it: &v1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: v1alpha1.InterceptorSpec{ + ClientConfig: v1alpha1.ClientConfig{ + Service: &v1alpha1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "blah", + }, + }, + }, + }, + want: "http://my-svc.default.svc:80/blah", + }, { + name: "clientConfig with provided caBundle", + it: &v1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: v1alpha1.InterceptorSpec{ + ClientConfig: v1alpha1.ClientConfig{ + CaBundle: []byte("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5ekNDQXB5Z0F3SUJBZ0lRSllLcEFVeXc2dStvY1JhV1VtRVRoREFLQmdncWhrak9QUVFEQWpCWE1SUXcKRWdZRFZRUUtFd3RyYm1GMGFYWmxMbVJsZGpFL01EMEdBMVVFQXhNMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdgpjbVV0YVc1MFpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpNQ0FYRFRJeU1EUXhOVEUyCk1ERTFPRm9ZRHpJeE1qSXdNekl5TVRZd01UVTRXakJYTVJRd0VnWURWUVFLRXd0cmJtRjBhWFpsTG1SbGRqRS8KTUQwR0ExVUVBeE0yZEdWcmRHOXVMWFJ5YVdkblpYSnpMV052Y21VdGFXNTBaWEpqWlhCMGIzSnpMblJsYTNSdgpiaTF3YVhCbGJHbHVaWE11YzNaak1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUFHcHp1RjlQCjY5VnFhN0xIY0tmNGpWY2JqblJNWDAxYWRnakh0Zy9kZFdIaVBWdXVJZER1WnZzVTREaVp5Smh2WnpmaHQ0ZmsKT3FJc3dJeVlmbkpLRnFPQ0FVWXdnZ0ZDTUE0R0ExVWREd0VCL3dRRUF3SUNoREFkQmdOVkhTVUVGakFVQmdncgpCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVQRXFjCnEvRFJHd2FDUTdmOFc0dmlucGN5a09zd2dlQUdBMVVkRVFTQjJEQ0IxWUloZEdWcmRHOXVMWFJ5YVdkblpYSnoKTFdOdmNtVXRhVzUwWlhKalpYQjBiM0p6Z2pKMFpXdDBiMjR0ZEhKcFoyZGxjbk10WTI5eVpTMXBiblJsY21ObApjSFJ2Y25NdWRHVnJkRzl1TFhCcGNHVnNhVzVsYzRJMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdmNtVXRhVzUwClpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpna1IwWld0MGIyNHRkSEpwWjJkbGNuTXQKWTI5eVpTMXBiblJsY21ObGNIUnZjbk11ZEdWcmRHOXVMWEJwY0dWc2FXNWxjeTV6ZG1NdVkyeDFjM1JsY2k1cwpiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkpBREJHQWlFQTlhWFBtUFZzRVA3R0xTbzI0SnNmNnRGTmpyQWJRbEl0CjRCYXllcjBnaU5jQ0lRQ09XSm1NTXQxQkE1RXgwa0FYTWRtZjlFdXV4LzlyUUkzMm9VNjVSYm9mNEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="), + Service: &v1alpha1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "blah", + }, + }, + }, + }, + want: "https://my-svc.default.svc:8443/blah", + }} + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := tc.it.ResolveAddress() + if err != nil { + t.Fatalf("ResolveAddress() unpexpected error: %v", err) + } + if diff := cmp.Diff(tc.want, got.String()); diff != "" { + t.Fatalf("ResolveAddress -want/+got: %s", diff) + } + }) + } + + t.Run("clientConfig with nil url", func(t *testing.T) { + it := &v1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: v1alpha1.InterceptorSpec{ + ClientConfig: v1alpha1.ClientConfig{ + URL: nil, + Service: nil, + }, + }, + } + _, err := it.ResolveAddress() + if !errors.Is(err, v1alpha1.ErrNilURL) { + t.Fatalf("ResolveToURL expected error to be %s but got %s", v1alpha1.ErrNilURL, err) + } + }) +} diff --git a/pkg/apis/triggers/v1alpha1/interceptor_validation.go b/pkg/apis/triggers/v1alpha1/interceptor_validation.go new file mode 100644 index 000000000..b16f9b4ae --- /dev/null +++ b/pkg/apis/triggers/v1alpha1/interceptor_validation.go @@ -0,0 +1,46 @@ +/* +Copyright 2022 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 ( + "context" + + "knative.dev/pkg/apis" +) + +// Validate Interceptor +func (it *Interceptor) Validate(ctx context.Context) *apis.FieldError { + if apis.IsInDelete(ctx) { + return nil + } + return it.Spec.validate(ctx) +} + +func (s *InterceptorSpec) validate(ctx context.Context) (errs *apis.FieldError) { + if s.ClientConfig.URL != nil && s.ClientConfig.Service != nil { + errs = errs.Also(apis.ErrMultipleOneOf("spec.clientConfig.url", "spec.clientConfig.service")) + } + if svc := s.ClientConfig.Service; svc != nil { + if svc.Namespace == "" { + errs = errs.Also(apis.ErrMissingField("spec.clientConfig.service.namespace")) + } + if svc.Name == "" { + errs = errs.Also(apis.ErrMissingField("spec.clientConfig.service.name")) + } + } + return errs +} diff --git a/pkg/apis/triggers/v1alpha1/interceptor_validation_test.go b/pkg/apis/triggers/v1alpha1/interceptor_validation_test.go new file mode 100644 index 000000000..e9b0ace2f --- /dev/null +++ b/pkg/apis/triggers/v1alpha1/interceptor_validation_test.go @@ -0,0 +1,118 @@ +/* +Copyright 2022 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 ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + + triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" +) + +func TestInterceptorValidate_OnDelete(t *testing.T) { + ci := triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Namespace: "", + Name: "github-svc", + }, + }, + }, + } + + err := ci.Validate(apis.WithinDelete(context.Background())) + if err != nil { + t.Errorf("Interceptor.Validate() on Delete expected no error, but got one, Interceptor: %v, error: %v", ci, err) + } +} + +func TestInterceptorValidate(t *testing.T) { + tests := []struct { + name string + namespacedInterceptor triggersv1.Interceptor + want *apis.FieldError + }{{ + name: "both URL and Service specified", + namespacedInterceptor: triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + URL: &apis.URL{ + Scheme: "http", + Host: "some.host", + }, + Service: &triggersv1.ServiceReference{ + Name: "github-svc", + Namespace: "default", + }, + }, + }, + }, + want: apis.ErrMultipleOneOf("spec.clientConfig.url", "spec.clientConfig.service"), + }, { + name: "service missing namespace", + namespacedInterceptor: triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Namespace: "", + Name: "github-svc", + }, + }, + }, + }, + want: apis.ErrMissingField("spec.clientConfig.service.namespace"), + }, { + name: "service missing name", + namespacedInterceptor: triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Namespace: "default", + Name: "", + }, + }, + }, + }, + want: apis.ErrMissingField("spec.clientConfig.service.name"), + }} + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := tc.namespacedInterceptor.Validate(context.Background()) + if diff := cmp.Diff(tc.want.Error(), got.Error()); diff != "" { + t.Fatalf("Interceptor.Validate() error: %s", diff) + } + }) + } +} diff --git a/pkg/apis/triggers/v1alpha1/register.go b/pkg/apis/triggers/v1alpha1/register.go index e9c006a70..ea60ff51a 100644 --- a/pkg/apis/triggers/v1alpha1/register.go +++ b/pkg/apis/triggers/v1alpha1/register.go @@ -52,6 +52,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &ClusterTriggerBindingList{}, &EventListener{}, &EventListenerList{}, + &Interceptor{}, + &InterceptorList{}, &TriggerBinding{}, &TriggerBindingList{}, &TriggerTemplate{}, diff --git a/pkg/apis/triggers/v1alpha1/trigger_types.go b/pkg/apis/triggers/v1alpha1/trigger_types.go index 135133f74..ecf2ff921 100644 --- a/pkg/apis/triggers/v1alpha1/trigger_types.go +++ b/pkg/apis/triggers/v1alpha1/trigger_types.go @@ -118,8 +118,6 @@ type InterceptorRef struct { // Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names Name string `json:"name,omitempty"` // InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped. - // Currently only InterceptorKind is ClusterInterceptor, so the only valid value - // is the default one // +optional Kind InterceptorKind `json:"kind,omitempty"` // API version of the referent @@ -133,6 +131,8 @@ type InterceptorKind string const ( // ClusterInterceptorKind indicates that Interceptor type has a cluster scope. ClusterInterceptorKind InterceptorKind = "ClusterInterceptor" + // NamespacedInterceptorKind indicated that interceptor has a namespaced scope + NamespacedInterceptorKind InterceptorKind = "NamespacedInterceptor" ) func (ti *TriggerInterceptor) defaultInterceptorKind() { diff --git a/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go index 5719284b8..839866033 100644 --- a/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go @@ -518,6 +518,67 @@ func (in *GitLabInterceptor) DeepCopy() *GitLabInterceptor { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Interceptor) DeepCopyInto(out *Interceptor) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Interceptor. +func (in *Interceptor) DeepCopy() *Interceptor { + if in == nil { + return nil + } + out := new(Interceptor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Interceptor) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InterceptorList) DeepCopyInto(out *InterceptorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Interceptor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InterceptorList. +func (in *InterceptorList) DeepCopy() *InterceptorList { + if in == nil { + return nil + } + out := new(InterceptorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InterceptorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InterceptorParams) DeepCopyInto(out *InterceptorParams) { *out = *in @@ -551,6 +612,41 @@ func (in *InterceptorRef) DeepCopy() *InterceptorRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InterceptorSpec) DeepCopyInto(out *InterceptorSpec) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InterceptorSpec. +func (in *InterceptorSpec) DeepCopy() *InterceptorSpec { + if in == nil { + return nil + } + out := new(InterceptorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InterceptorStatus) DeepCopyInto(out *InterceptorStatus) { + *out = *in + in.Status.DeepCopyInto(&out.Status) + in.AddressStatus.DeepCopyInto(&out.AddressStatus) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InterceptorStatus. +func (in *InterceptorStatus) DeepCopy() *InterceptorStatus { + if in == nil { + return nil + } + out := new(InterceptorStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubernetesResource) DeepCopyInto(out *KubernetesResource) { *out = *in diff --git a/pkg/apis/triggers/v1beta1/openapi_generated.go b/pkg/apis/triggers/v1beta1/openapi_generated.go index 313e8c1bf..693fa5669 100644 --- a/pkg/apis/triggers/v1beta1/openapi_generated.go +++ b/pkg/apis/triggers/v1beta1/openapi_generated.go @@ -827,7 +827,7 @@ func schema_pkg_apis_triggers_v1beta1_InterceptorRef(ref common.ReferenceCallbac }, "kind": { SchemaProps: spec.SchemaProps{ - Description: "InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped. Currently only InterceptorKind is ClusterInterceptor, so the only valid value is the default one", + Description: "InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped.", Type: []string{"string"}, Format: "", }, diff --git a/pkg/apis/triggers/v1beta1/trigger_types.go b/pkg/apis/triggers/v1beta1/trigger_types.go index fd0744524..2eb83d6af 100644 --- a/pkg/apis/triggers/v1beta1/trigger_types.go +++ b/pkg/apis/triggers/v1beta1/trigger_types.go @@ -109,8 +109,6 @@ type InterceptorRef struct { // Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names Name string `json:"name,omitempty"` // InterceptorKind indicates the kind of the Interceptor, namespaced or cluster scoped. - // Currently only InterceptorKind is ClusterInterceptor, so the only valid value - // is the default one // +optional Kind InterceptorKind `json:"kind,omitempty"` // API version of the referent @@ -124,6 +122,8 @@ type InterceptorKind string const ( // ClusterInterceptorKind indicates that Interceptor type has a cluster scope. ClusterInterceptorKind InterceptorKind = "ClusterInterceptor" + // NamespacedInterceptorKind indicates that Interceptor type has a namespace scope. + NamespacedInterceptorKind InterceptorKind = "NamespacedInterceptor" ) func (ti *TriggerInterceptor) defaultInterceptorKind() { diff --git a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_interceptor.go b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_interceptor.go new file mode 100644 index 000000000..4347eba98 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_interceptor.go @@ -0,0 +1,142 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeInterceptors implements InterceptorInterface +type FakeInterceptors struct { + Fake *FakeTriggersV1alpha1 + ns string +} + +var interceptorsResource = schema.GroupVersionResource{Group: "triggers.tekton.dev", Version: "v1alpha1", Resource: "interceptors"} + +var interceptorsKind = schema.GroupVersionKind{Group: "triggers.tekton.dev", Version: "v1alpha1", Kind: "Interceptor"} + +// Get takes name of the interceptor, and returns the corresponding interceptor object, and an error if there is any. +func (c *FakeInterceptors) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Interceptor, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(interceptorsResource, c.ns, name), &v1alpha1.Interceptor{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Interceptor), err +} + +// List takes label and field selectors, and returns the list of Interceptors that match those selectors. +func (c *FakeInterceptors) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.InterceptorList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(interceptorsResource, interceptorsKind, c.ns, opts), &v1alpha1.InterceptorList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.InterceptorList{ListMeta: obj.(*v1alpha1.InterceptorList).ListMeta} + for _, item := range obj.(*v1alpha1.InterceptorList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested interceptors. +func (c *FakeInterceptors) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(interceptorsResource, c.ns, opts)) + +} + +// Create takes the representation of a interceptor and creates it. Returns the server's representation of the interceptor, and an error, if there is any. +func (c *FakeInterceptors) Create(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.CreateOptions) (result *v1alpha1.Interceptor, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(interceptorsResource, c.ns, interceptor), &v1alpha1.Interceptor{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Interceptor), err +} + +// Update takes the representation of a interceptor and updates it. Returns the server's representation of the interceptor, and an error, if there is any. +func (c *FakeInterceptors) Update(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (result *v1alpha1.Interceptor, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(interceptorsResource, c.ns, interceptor), &v1alpha1.Interceptor{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Interceptor), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeInterceptors) UpdateStatus(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (*v1alpha1.Interceptor, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(interceptorsResource, "status", c.ns, interceptor), &v1alpha1.Interceptor{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Interceptor), err +} + +// Delete takes name of the interceptor and deletes it. Returns an error if one occurs. +func (c *FakeInterceptors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(interceptorsResource, c.ns, name, opts), &v1alpha1.Interceptor{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeInterceptors) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(interceptorsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.InterceptorList{}) + return err +} + +// Patch applies the patch and returns the patched interceptor. +func (c *FakeInterceptors) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Interceptor, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(interceptorsResource, c.ns, name, pt, data, subresources...), &v1alpha1.Interceptor{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.Interceptor), err +} diff --git a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_triggers_client.go b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_triggers_client.go index f766031ca..5419d6b96 100644 --- a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_triggers_client.go +++ b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/fake/fake_triggers_client.go @@ -40,6 +40,10 @@ func (c *FakeTriggersV1alpha1) EventListeners(namespace string) v1alpha1.EventLi return &FakeEventListeners{c, namespace} } +func (c *FakeTriggersV1alpha1) Interceptors(namespace string) v1alpha1.InterceptorInterface { + return &FakeInterceptors{c, namespace} +} + func (c *FakeTriggersV1alpha1) Triggers(namespace string) v1alpha1.TriggerInterface { return &FakeTriggers{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/generated_expansion.go index c7ab02c6f..8a615d1ca 100644 --- a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/generated_expansion.go @@ -24,6 +24,8 @@ type ClusterTriggerBindingExpansion interface{} type EventListenerExpansion interface{} +type InterceptorExpansion interface{} + type TriggerExpansion interface{} type TriggerBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/interceptor.go b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/interceptor.go new file mode 100644 index 000000000..7231dba9a --- /dev/null +++ b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/interceptor.go @@ -0,0 +1,195 @@ +/* +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. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + "time" + + v1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + scheme "github.com/tektoncd/triggers/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// InterceptorsGetter has a method to return a InterceptorInterface. +// A group's client should implement this interface. +type InterceptorsGetter interface { + Interceptors(namespace string) InterceptorInterface +} + +// InterceptorInterface has methods to work with Interceptor resources. +type InterceptorInterface interface { + Create(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.CreateOptions) (*v1alpha1.Interceptor, error) + Update(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (*v1alpha1.Interceptor, error) + UpdateStatus(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (*v1alpha1.Interceptor, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Interceptor, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.InterceptorList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Interceptor, err error) + InterceptorExpansion +} + +// interceptors implements InterceptorInterface +type interceptors struct { + client rest.Interface + ns string +} + +// newInterceptors returns a Interceptors +func newInterceptors(c *TriggersV1alpha1Client, namespace string) *interceptors { + return &interceptors{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the interceptor, and returns the corresponding interceptor object, and an error if there is any. +func (c *interceptors) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Interceptor, err error) { + result = &v1alpha1.Interceptor{} + err = c.client.Get(). + Namespace(c.ns). + Resource("interceptors"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Interceptors that match those selectors. +func (c *interceptors) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.InterceptorList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.InterceptorList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("interceptors"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested interceptors. +func (c *interceptors) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("interceptors"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a interceptor and creates it. Returns the server's representation of the interceptor, and an error, if there is any. +func (c *interceptors) Create(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.CreateOptions) (result *v1alpha1.Interceptor, err error) { + result = &v1alpha1.Interceptor{} + err = c.client.Post(). + Namespace(c.ns). + Resource("interceptors"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(interceptor). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a interceptor and updates it. Returns the server's representation of the interceptor, and an error, if there is any. +func (c *interceptors) Update(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (result *v1alpha1.Interceptor, err error) { + result = &v1alpha1.Interceptor{} + err = c.client.Put(). + Namespace(c.ns). + Resource("interceptors"). + Name(interceptor.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(interceptor). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *interceptors) UpdateStatus(ctx context.Context, interceptor *v1alpha1.Interceptor, opts v1.UpdateOptions) (result *v1alpha1.Interceptor, err error) { + result = &v1alpha1.Interceptor{} + err = c.client.Put(). + Namespace(c.ns). + Resource("interceptors"). + Name(interceptor.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(interceptor). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the interceptor and deletes it. Returns an error if one occurs. +func (c *interceptors) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("interceptors"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *interceptors) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("interceptors"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched interceptor. +func (c *interceptors) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Interceptor, err error) { + result = &v1alpha1.Interceptor{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("interceptors"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/triggers_client.go b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/triggers_client.go index 7f8fd64c4..28f9ba423 100644 --- a/pkg/client/clientset/versioned/typed/triggers/v1alpha1/triggers_client.go +++ b/pkg/client/clientset/versioned/typed/triggers/v1alpha1/triggers_client.go @@ -31,6 +31,7 @@ type TriggersV1alpha1Interface interface { ClusterInterceptorsGetter ClusterTriggerBindingsGetter EventListenersGetter + InterceptorsGetter TriggersGetter TriggerBindingsGetter TriggerTemplatesGetter @@ -53,6 +54,10 @@ func (c *TriggersV1alpha1Client) EventListeners(namespace string) EventListenerI return newEventListeners(c, namespace) } +func (c *TriggersV1alpha1Client) Interceptors(namespace string) InterceptorInterface { + return newInterceptors(c, namespace) +} + func (c *TriggersV1alpha1Client) Triggers(namespace string) TriggerInterface { return newTriggers(c, namespace) } diff --git a/pkg/client/dynamic/clientset/tekton/tekton.go b/pkg/client/dynamic/clientset/tekton/tekton.go index 0b1d501a8..8d88d6fe8 100644 --- a/pkg/client/dynamic/clientset/tekton/tekton.go +++ b/pkg/client/dynamic/clientset/tekton/tekton.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 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 tekton import ( @@ -14,7 +30,7 @@ var ( "v1beta1": {"pipelineruns", "taskruns", "pipelines", "clustertasks", "tasks"}, } allowedTriggersTypes = map[string][]string{ - "v1alpha1": {"clusterinterceptors"}, + "v1alpha1": {"clusterinterceptors", "interceptors"}, "v1beta1": {"clustertriggerbindings", "eventlisteners", "triggerbindings", "triggers", "triggertemplates"}, } ) diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 58ad7f48a..d13e72184 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -60,6 +60,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Triggers().V1alpha1().ClusterTriggerBindings().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("eventlisteners"): return &genericInformer{resource: resource.GroupResource(), informer: f.Triggers().V1alpha1().EventListeners().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("interceptors"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Triggers().V1alpha1().Interceptors().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("triggers"): return &genericInformer{resource: resource.GroupResource(), informer: f.Triggers().V1alpha1().Triggers().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("triggerbindings"): diff --git a/pkg/client/informers/externalversions/triggers/v1alpha1/interceptor.go b/pkg/client/informers/externalversions/triggers/v1alpha1/interceptor.go new file mode 100644 index 000000000..05ff98ac6 --- /dev/null +++ b/pkg/client/informers/externalversions/triggers/v1alpha1/interceptor.go @@ -0,0 +1,90 @@ +/* +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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + versioned "github.com/tektoncd/triggers/pkg/client/clientset/versioned" + internalinterfaces "github.com/tektoncd/triggers/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/tektoncd/triggers/pkg/client/listers/triggers/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// InterceptorInformer provides access to a shared informer and lister for +// Interceptors. +type InterceptorInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.InterceptorLister +} + +type interceptorInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewInterceptorInformer constructs a new informer for Interceptor type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewInterceptorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredInterceptorInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredInterceptorInformer constructs a new informer for Interceptor type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredInterceptorInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TriggersV1alpha1().Interceptors(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TriggersV1alpha1().Interceptors(namespace).Watch(context.TODO(), options) + }, + }, + &triggersv1alpha1.Interceptor{}, + resyncPeriod, + indexers, + ) +} + +func (f *interceptorInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredInterceptorInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *interceptorInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&triggersv1alpha1.Interceptor{}, f.defaultInformer) +} + +func (f *interceptorInformer) Lister() v1alpha1.InterceptorLister { + return v1alpha1.NewInterceptorLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/triggers/v1alpha1/interface.go b/pkg/client/informers/externalversions/triggers/v1alpha1/interface.go index d993befd9..d1904cc2a 100644 --- a/pkg/client/informers/externalversions/triggers/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/triggers/v1alpha1/interface.go @@ -30,6 +30,8 @@ type Interface interface { ClusterTriggerBindings() ClusterTriggerBindingInformer // EventListeners returns a EventListenerInformer. EventListeners() EventListenerInformer + // Interceptors returns a InterceptorInformer. + Interceptors() InterceptorInformer // Triggers returns a TriggerInformer. Triggers() TriggerInformer // TriggerBindings returns a TriggerBindingInformer. @@ -64,6 +66,11 @@ func (v *version) EventListeners() EventListenerInformer { return &eventListenerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// Interceptors returns a InterceptorInformer. +func (v *version) Interceptors() InterceptorInformer { + return &interceptorInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Triggers returns a TriggerInformer. func (v *version) Triggers() TriggerInformer { return &triggerInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/injection/client/client.go b/pkg/client/injection/client/client.go index abfe618bf..4db863625 100644 --- a/pkg/client/injection/client/client.go +++ b/pkg/client/injection/client/client.go @@ -498,6 +498,137 @@ func (w *wrapTriggersV1alpha1EventListenerImpl) Watch(ctx context.Context, opts return nil, errors.New("NYI: Watch") } +func (w *wrapTriggersV1alpha1) Interceptors(namespace string) typedtriggersv1alpha1.InterceptorInterface { + return &wrapTriggersV1alpha1InterceptorImpl{ + dyn: w.dyn.Resource(schema.GroupVersionResource{ + Group: "triggers.tekton.dev", + Version: "v1alpha1", + Resource: "interceptors", + }), + + namespace: namespace, + } +} + +type wrapTriggersV1alpha1InterceptorImpl struct { + dyn dynamic.NamespaceableResourceInterface + + namespace string +} + +var _ typedtriggersv1alpha1.InterceptorInterface = (*wrapTriggersV1alpha1InterceptorImpl)(nil) + +func (w *wrapTriggersV1alpha1InterceptorImpl) Create(ctx context.Context, in *v1alpha1.Interceptor, opts v1.CreateOptions) (*v1alpha1.Interceptor, error) { + in.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "triggers.tekton.dev", + Version: "v1alpha1", + Kind: "Interceptor", + }) + uo := &unstructured.Unstructured{} + if err := convert(in, uo); err != nil { + return nil, err + } + uo, err := w.dyn.Namespace(w.namespace).Create(ctx, uo, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.Interceptor{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return w.dyn.Namespace(w.namespace).Delete(ctx, name, opts) +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + return w.dyn.Namespace(w.namespace).DeleteCollection(ctx, opts, listOpts) +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Interceptor, error) { + uo, err := w.dyn.Namespace(w.namespace).Get(ctx, name, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.Interceptor{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.InterceptorList, error) { + uo, err := w.dyn.Namespace(w.namespace).List(ctx, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.InterceptorList{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Interceptor, err error) { + uo, err := w.dyn.Namespace(w.namespace).Patch(ctx, name, pt, data, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.Interceptor{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) Update(ctx context.Context, in *v1alpha1.Interceptor, opts v1.UpdateOptions) (*v1alpha1.Interceptor, error) { + in.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "triggers.tekton.dev", + Version: "v1alpha1", + Kind: "Interceptor", + }) + uo := &unstructured.Unstructured{} + if err := convert(in, uo); err != nil { + return nil, err + } + uo, err := w.dyn.Namespace(w.namespace).Update(ctx, uo, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.Interceptor{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) UpdateStatus(ctx context.Context, in *v1alpha1.Interceptor, opts v1.UpdateOptions) (*v1alpha1.Interceptor, error) { + in.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "triggers.tekton.dev", + Version: "v1alpha1", + Kind: "Interceptor", + }) + uo := &unstructured.Unstructured{} + if err := convert(in, uo); err != nil { + return nil, err + } + uo, err := w.dyn.Namespace(w.namespace).UpdateStatus(ctx, uo, opts) + if err != nil { + return nil, err + } + out := &v1alpha1.Interceptor{} + if err := convert(uo, out); err != nil { + return nil, err + } + return out, nil +} + +func (w *wrapTriggersV1alpha1InterceptorImpl) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return nil, errors.New("NYI: Watch") +} + func (w *wrapTriggersV1alpha1) Triggers(namespace string) typedtriggersv1alpha1.TriggerInterface { return &wrapTriggersV1alpha1TriggerImpl{ dyn: w.dyn.Resource(schema.GroupVersionResource{ diff --git a/pkg/client/injection/informers/triggers/v1alpha1/interceptor/fake/fake.go b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/fake/fake.go new file mode 100644 index 000000000..558119a53 --- /dev/null +++ b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/fake/fake.go @@ -0,0 +1,40 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + fake "github.com/tektoncd/triggers/pkg/client/injection/informers/factory/fake" + interceptor "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" +) + +var Get = interceptor.Get + +func init() { + injection.Fake.RegisterInformer(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := fake.Get(ctx) + inf := f.Triggers().V1alpha1().Interceptors() + return context.WithValue(ctx, interceptor.Key{}, inf), inf.Informer() +} diff --git a/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/fake/fake.go b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/fake/fake.go new file mode 100644 index 000000000..822b2d869 --- /dev/null +++ b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/fake/fake.go @@ -0,0 +1,52 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package fake + +import ( + context "context" + + factoryfiltered "github.com/tektoncd/triggers/pkg/client/injection/informers/factory/filtered" + filtered "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +var Get = filtered.Get + +func init() { + injection.Fake.RegisterFilteredInformers(withInformer) +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(factoryfiltered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := factoryfiltered.Get(ctx, selector) + inf := f.Triggers().V1alpha1().Interceptors() + ctx = context.WithValue(ctx, filtered.Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} diff --git a/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/interceptor.go b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/interceptor.go new file mode 100644 index 000000000..177eb7fb7 --- /dev/null +++ b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/filtered/interceptor.go @@ -0,0 +1,136 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package filtered + +import ( + context "context" + + apistriggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + versioned "github.com/tektoncd/triggers/pkg/client/clientset/versioned" + v1alpha1 "github.com/tektoncd/triggers/pkg/client/informers/externalversions/triggers/v1alpha1" + client "github.com/tektoncd/triggers/pkg/client/injection/client" + filtered "github.com/tektoncd/triggers/pkg/client/injection/informers/factory/filtered" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/client/listers/triggers/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + cache "k8s.io/client-go/tools/cache" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterFilteredInformers(withInformer) + injection.Dynamic.RegisterDynamicInformer(withDynamicInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct { + Selector string +} + +func withInformer(ctx context.Context) (context.Context, []controller.Informer) { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + infs := []controller.Informer{} + for _, selector := range labelSelectors { + f := filtered.Get(ctx, selector) + inf := f.Triggers().V1alpha1().Interceptors() + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + infs = append(infs, inf.Informer()) + } + return ctx, infs +} + +func withDynamicInformer(ctx context.Context) context.Context { + untyped := ctx.Value(filtered.LabelKey{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch labelkey from context.") + } + labelSelectors := untyped.([]string) + for _, selector := range labelSelectors { + inf := &wrapper{client: client.Get(ctx), selector: selector} + ctx = context.WithValue(ctx, Key{Selector: selector}, inf) + } + return ctx +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context, selector string) v1alpha1.InterceptorInformer { + untyped := ctx.Value(Key{Selector: selector}) + if untyped == nil { + logging.FromContext(ctx).Panicf( + "Unable to fetch github.com/tektoncd/triggers/pkg/client/informers/externalversions/triggers/v1alpha1.InterceptorInformer with selector %s from context.", selector) + } + return untyped.(v1alpha1.InterceptorInformer) +} + +type wrapper struct { + client versioned.Interface + + namespace string + + selector string +} + +var _ v1alpha1.InterceptorInformer = (*wrapper)(nil) +var _ triggersv1alpha1.InterceptorLister = (*wrapper)(nil) + +func (w *wrapper) Informer() cache.SharedIndexInformer { + return cache.NewSharedIndexInformer(nil, &apistriggersv1alpha1.Interceptor{}, 0, nil) +} + +func (w *wrapper) Lister() triggersv1alpha1.InterceptorLister { + return w +} + +func (w *wrapper) Interceptors(namespace string) triggersv1alpha1.InterceptorNamespaceLister { + return &wrapper{client: w.client, namespace: namespace, selector: w.selector} +} + +func (w *wrapper) List(selector labels.Selector) (ret []*apistriggersv1alpha1.Interceptor, err error) { + reqs, err := labels.ParseToRequirements(w.selector) + if err != nil { + return nil, err + } + selector = selector.Add(reqs...) + lo, err := w.client.TriggersV1alpha1().Interceptors(w.namespace).List(context.TODO(), v1.ListOptions{ + LabelSelector: selector.String(), + // TODO(mattmoor): Incorporate resourceVersion bounds based on staleness criteria. + }) + if err != nil { + return nil, err + } + for idx := range lo.Items { + ret = append(ret, &lo.Items[idx]) + } + return ret, nil +} + +func (w *wrapper) Get(name string) (*apistriggersv1alpha1.Interceptor, error) { + // TODO(mattmoor): Check that the fetched object matches the selector. + return w.client.TriggersV1alpha1().Interceptors(w.namespace).Get(context.TODO(), name, v1.GetOptions{ + // TODO(mattmoor): Incorporate resourceVersion bounds based on staleness criteria. + }) +} diff --git a/pkg/client/injection/informers/triggers/v1alpha1/interceptor/interceptor.go b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/interceptor.go new file mode 100644 index 000000000..eabc4d565 --- /dev/null +++ b/pkg/client/injection/informers/triggers/v1alpha1/interceptor/interceptor.go @@ -0,0 +1,116 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package interceptor + +import ( + context "context" + + apistriggersv1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + versioned "github.com/tektoncd/triggers/pkg/client/clientset/versioned" + v1alpha1 "github.com/tektoncd/triggers/pkg/client/informers/externalversions/triggers/v1alpha1" + client "github.com/tektoncd/triggers/pkg/client/injection/client" + factory "github.com/tektoncd/triggers/pkg/client/injection/informers/factory" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/client/listers/triggers/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + cache "k8s.io/client-go/tools/cache" + controller "knative.dev/pkg/controller" + injection "knative.dev/pkg/injection" + logging "knative.dev/pkg/logging" +) + +func init() { + injection.Default.RegisterInformer(withInformer) + injection.Dynamic.RegisterDynamicInformer(withDynamicInformer) +} + +// Key is used for associating the Informer inside the context.Context. +type Key struct{} + +func withInformer(ctx context.Context) (context.Context, controller.Informer) { + f := factory.Get(ctx) + inf := f.Triggers().V1alpha1().Interceptors() + return context.WithValue(ctx, Key{}, inf), inf.Informer() +} + +func withDynamicInformer(ctx context.Context) context.Context { + inf := &wrapper{client: client.Get(ctx), resourceVersion: injection.GetResourceVersion(ctx)} + return context.WithValue(ctx, Key{}, inf) +} + +// Get extracts the typed informer from the context. +func Get(ctx context.Context) v1alpha1.InterceptorInformer { + untyped := ctx.Value(Key{}) + if untyped == nil { + logging.FromContext(ctx).Panic( + "Unable to fetch github.com/tektoncd/triggers/pkg/client/informers/externalversions/triggers/v1alpha1.InterceptorInformer from context.") + } + return untyped.(v1alpha1.InterceptorInformer) +} + +type wrapper struct { + client versioned.Interface + + namespace string + + resourceVersion string +} + +var _ v1alpha1.InterceptorInformer = (*wrapper)(nil) +var _ triggersv1alpha1.InterceptorLister = (*wrapper)(nil) + +func (w *wrapper) Informer() cache.SharedIndexInformer { + return cache.NewSharedIndexInformer(nil, &apistriggersv1alpha1.Interceptor{}, 0, nil) +} + +func (w *wrapper) Lister() triggersv1alpha1.InterceptorLister { + return w +} + +func (w *wrapper) Interceptors(namespace string) triggersv1alpha1.InterceptorNamespaceLister { + return &wrapper{client: w.client, namespace: namespace, resourceVersion: w.resourceVersion} +} + +// SetResourceVersion allows consumers to adjust the minimum resourceVersion +// used by the underlying client. It is not accessible via the standard +// lister interface, but can be accessed through a user-defined interface and +// an implementation check e.g. rvs, ok := foo.(ResourceVersionSetter) +func (w *wrapper) SetResourceVersion(resourceVersion string) { + w.resourceVersion = resourceVersion +} + +func (w *wrapper) List(selector labels.Selector) (ret []*apistriggersv1alpha1.Interceptor, err error) { + lo, err := w.client.TriggersV1alpha1().Interceptors(w.namespace).List(context.TODO(), v1.ListOptions{ + LabelSelector: selector.String(), + ResourceVersion: w.resourceVersion, + }) + if err != nil { + return nil, err + } + for idx := range lo.Items { + ret = append(ret, &lo.Items[idx]) + } + return ret, nil +} + +func (w *wrapper) Get(name string) (*apistriggersv1alpha1.Interceptor, error) { + return w.client.TriggersV1alpha1().Interceptors(w.namespace).Get(context.TODO(), name, v1.GetOptions{ + ResourceVersion: w.resourceVersion, + }) +} diff --git a/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/controller.go b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/controller.go new file mode 100644 index 000000000..f98d05f48 --- /dev/null +++ b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/controller.go @@ -0,0 +1,162 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package interceptor + +import ( + context "context" + fmt "fmt" + reflect "reflect" + strings "strings" + + versionedscheme "github.com/tektoncd/triggers/pkg/client/clientset/versioned/scheme" + client "github.com/tektoncd/triggers/pkg/client/injection/client" + interceptor "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor" + zap "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + kubeclient "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + logkey "knative.dev/pkg/logging/logkey" + reconciler "knative.dev/pkg/reconciler" +) + +const ( + defaultControllerAgentName = "interceptor-controller" + defaultFinalizerName = "interceptors.triggers.tekton.dev" +) + +// NewImpl returns a controller.Impl that handles queuing and feeding work from +// the queue through an implementation of controller.Reconciler, delegating to +// the provided Interface and optional Finalizer methods. OptionsFn is used to return +// controller.ControllerOptions to be used by the internal reconciler. +func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl { + logger := logging.FromContext(ctx) + + // Check the options function input. It should be 0 or 1. + if len(optionsFns) > 1 { + logger.Fatal("Up to one options function is supported, found: ", len(optionsFns)) + } + + interceptorInformer := interceptor.Get(ctx) + + lister := interceptorInformer.Lister() + + var promoteFilterFunc func(obj interface{}) bool + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + if promoteFilterFunc != nil { + if ok := promoteFilterFunc(elt); !ok { + continue + } + } + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client.Get(ctx), + Lister: lister, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + ctrType := reflect.TypeOf(r).Elem() + ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name()) + ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".") + + logger = logger.With( + zap.String(logkey.ControllerType, ctrTypeName), + zap.String(logkey.Kind, "triggers.tekton.dev.Interceptor"), + ) + + impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger}) + agentName := defaultControllerAgentName + + // Pass impl to the options. Save any optional results. + for _, fn := range optionsFns { + opts := fn(impl) + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.AgentName != "" { + agentName = opts.AgentName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + if opts.PromoteFilterFunc != nil { + promoteFilterFunc = opts.PromoteFilterFunc + } + } + + rec.Recorder = createRecorder(ctx, agentName) + + return impl +} + +func createRecorder(ctx context.Context, agentName string) record.EventRecorder { + logger := logging.FromContext(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + return recorder +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/reconciler.go b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/reconciler.go new file mode 100644 index 000000000..6a5677aea --- /dev/null +++ b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/reconciler.go @@ -0,0 +1,429 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package interceptor + +import ( + context "context" + json "encoding/json" + fmt "fmt" + + v1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + versioned "github.com/tektoncd/triggers/pkg/client/clientset/versioned" + triggersv1alpha1 "github.com/tektoncd/triggers/pkg/client/listers/triggers/v1alpha1" + zap "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + equality "k8s.io/apimachinery/pkg/api/equality" + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + record "k8s.io/client-go/tools/record" + controller "knative.dev/pkg/controller" + kmp "knative.dev/pkg/kmp" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.Interceptor. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha1.Interceptor. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1alpha1.Interceptor) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.Interceptor. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha1.Interceptor. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1alpha1.Interceptor) reconciler.Event +} + +// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.Interceptor if they want to process resources for which +// they are not the leader. +type ReadOnlyInterface interface { + // ObserveKind implements logic to observe v1alpha1.Interceptor. + // This method should not write to the API. + ObserveKind(ctx context.Context, o *v1alpha1.Interceptor) reconciler.Event +} + +type doReconcile func(ctx context.Context, o *v1alpha1.Interceptor) reconciler.Event + +// reconcilerImpl implements controller.Reconciler for v1alpha1.Interceptor resources. +type reconcilerImpl struct { + // LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware. + reconciler.LeaderAwareFuncs + + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources. + Lister triggersv1alpha1.InterceptorLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface + + // finalizerName is the name of the finalizer to reconcile. + finalizerName string + + // skipStatusUpdates configures whether or not this reconciler automatically updates + // the status of the reconciled resource. + skipStatusUpdates bool +} + +// Check that our Reconciler implements controller.Reconciler. +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +// Check that our generated Reconciler is always LeaderAware. +var _ reconciler.LeaderAware = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister triggersv1alpha1.InterceptorLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.Fatal("Up to one options struct is supported, found: ", len(options)) + } + + // Fail fast when users inadvertently implement the other LeaderAware interface. + // For the typed reconcilers, Promote shouldn't take any arguments. + if _, ok := r.(reconciler.LeaderAware); ok { + logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r) + } + + rec := &reconcilerImpl{ + LeaderAwareFuncs: reconciler.LeaderAwareFuncs{ + PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error { + all, err := lister.List(labels.Everything()) + if err != nil { + return err + } + for _, elt := range all { + // TODO: Consider letting users specify a filter in options. + enq(bkt, types.NamespacedName{ + Namespace: elt.GetNamespace(), + Name: elt.GetName(), + }) + } + return nil + }, + }, + Client: client, + Lister: lister, + Recorder: recorder, + reconciler: r, + finalizerName: defaultFinalizerName, + } + + for _, opts := range options { + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + if opts.FinalizerName != "" { + rec.finalizerName = opts.FinalizerName + } + if opts.SkipStatusUpdates { + rec.skipStatusUpdates = true + } + if opts.DemoteFunc != nil { + rec.DemoteFunc = opts.DemoteFunc + } + } + + return rec +} + +// Reconcile implements controller.Reconciler +func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // Initialize the reconciler state. This will convert the namespace/name + // string into a distinct namespace and name, determine if this instance of + // the reconciler is the leader, and any additional interfaces implemented + // by the reconciler. Returns an error is the resource key is invalid. + s, err := newState(key, r) + if err != nil { + logger.Error("Invalid resource key: ", key) + return nil + } + + // If we are not the leader, and we don't implement either ReadOnly + // observer interfaces, then take a fast-path out. + if s.isNotLeaderNorObserver() { + return controller.NewSkipKey(key) + } + + // If configStore is set, attach the frozen configuration to the context. + if r.configStore != nil { + ctx = r.configStore.ToContext(ctx) + } + + // Add the recorder to context. + ctx = controller.WithEventRecorder(ctx, r.Recorder) + + // Get the resource with this namespace/name. + + getter := r.Lister.Interceptors(s.namespace) + + original, err := getter.Get(s.name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing and call + // the ObserveDeletion handler if appropriate. + logger.Debugf("Resource %q no longer exists", key) + if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok { + return del.ObserveDeletion(ctx, types.NamespacedName{ + Namespace: s.namespace, + Name: s.name, + }) + } + return nil + } else if err != nil { + return err + } + + // Don't modify the informers copy. + resource := original.DeepCopy() + + var reconcileEvent reconciler.Event + + name, do := s.reconcileMethodFor(resource) + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", name)) + switch name { + case reconciler.DoReconcileKind: + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + return fmt.Errorf("failed to set finalizers: %w", err) + } + + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = do(ctx, resource) + + case reconciler.DoFinalizeKind: + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = do(ctx, resource) + + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + return fmt.Errorf("failed to clear finalizers: %w", err) + } + + case reconciler.DoObserveKind: + // Observe any changes to this resource, since we are not the leader. + reconcileEvent = do(ctx, resource) + + } + + // Synchronize the status. + switch { + case r.skipStatusUpdates: + // This reconciler implementation is configured to skip resource updates. + // This may mean this reconciler does not observe spec, but reconciles external changes. + case equality.Semantic.DeepEqual(original.Status, resource.Status): + // If we didn't change anything then don't call updateStatus. + // This is important because the copy we loaded from the injectionInformer's + // cache may be stale and we don't want to overwrite a prior update + // to status with this stale state. + case !s.isLeader: + // High-availability reconcilers may have many replicas watching the resource, but only + // the elected leader is expected to write modifications. + logger.Warn("Saw status changes when we aren't the leader!") + default: + if err = r.updateStatus(ctx, original, resource); err != nil { + logger.Warnw("Failed to update resource status", zap.Error(err)) + r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", + "Failed to update status for %q: %v", resource.Name, err) + return err + } + } + + // Report the reconciler event, if any. + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + logger.Infow("Returned an event", zap.Any("event", reconcileEvent)) + r.Recorder.Event(resource, event.EventType, event.Reason, event.Error()) + + // the event was wrapped inside an error, consider the reconciliation as failed + if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent { + return reconcileEvent + } + return nil + } + + if controller.IsSkipKey(reconcileEvent) { + // This is a wrapped error, don't emit an event. + } else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok { + // This is a wrapped error, don't emit an event. + } else { + logger.Errorw("Returned an error", zap.Error(reconcileEvent)) + r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error()) + } + return reconcileEvent + } + + return nil +} + +func (r *reconcilerImpl) updateStatus(ctx context.Context, existing *v1alpha1.Interceptor, desired *v1alpha1.Interceptor) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.TriggersV1alpha1().Interceptors(desired.Namespace) + + existing, err = getter.Get(ctx, desired.Name, metav1.GetOptions{}) + if err != nil { + return err + } + } + + // If there's nothing to update, just return. + if equality.Semantic.DeepEqual(existing.Status, desired.Status) { + return nil + } + + if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" { + logging.FromContext(ctx).Debug("Updating status with: ", diff) + } + + existing.Status = desired.Status + + updater := r.Client.TriggersV1alpha1().Interceptors(existing.Namespace) + + _, err = updater.UpdateStatus(ctx, existing, metav1.UpdateOptions{}) + return err + }) +} + +// updateFinalizersFiltered will update the Finalizers of the resource. +// TODO: this method could be generic and sync all finalizers. For now it only +// updates defaultFinalizerName or its override. +func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1alpha1.Interceptor, desiredFinalizers sets.String) (*v1alpha1.Interceptor, error) { + // Don't modify the informers copy. + existing := resource.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + + if desiredFinalizers.Has(r.finalizerName) { + if existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, r.finalizerName) + } else { + if !existingFinalizers.Has(r.finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(r.finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.TriggersV1alpha1().Interceptors(resource.Namespace) + + resourceName := resource.Name + updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.Recorder.Eventf(existing, v1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resourceName, err) + } else { + r.Recorder.Eventf(updated, v1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return updated, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1alpha1.Interceptor) (*v1alpha1.Interceptor, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1alpha1.Interceptor, reconcileEvent reconciler.Event) (*v1alpha1.Interceptor, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(r.finalizerName) + } + } + } else { + finalizers.Delete(r.finalizerName) + } + + // Synchronize the finalizers filtered by r.finalizerName. + return r.updateFinalizersFiltered(ctx, resource, finalizers) +} diff --git a/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/state.go b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/state.go new file mode 100644 index 000000000..d5542df3a --- /dev/null +++ b/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor/state.go @@ -0,0 +1,97 @@ +/* +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. +*/ + +// Code generated by injection-gen. DO NOT EDIT. + +package interceptor + +import ( + fmt "fmt" + + v1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + types "k8s.io/apimachinery/pkg/types" + cache "k8s.io/client-go/tools/cache" + reconciler "knative.dev/pkg/reconciler" +) + +// state is used to track the state of a reconciler in a single run. +type state struct { + // key is the original reconciliation key from the queue. + key string + // namespace is the namespace split from the reconciliation key. + namespace string + // name is the name split from the reconciliation key. + name string + // reconciler is the reconciler. + reconciler Interface + // roi is the read only interface cast of the reconciler. + roi ReadOnlyInterface + // isROI (Read Only Interface) the reconciler only observes reconciliation. + isROI bool + // isLeader the instance of the reconciler is the elected leader. + isLeader bool +} + +func newState(key string, r *reconcilerImpl) (*state, error) { + // Convert the namespace/name string into a distinct namespace and name. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + return nil, fmt.Errorf("invalid resource key: %s", key) + } + + roi, isROI := r.reconciler.(ReadOnlyInterface) + + isLeader := r.IsLeaderFor(types.NamespacedName{ + Namespace: namespace, + Name: name, + }) + + return &state{ + key: key, + namespace: namespace, + name: name, + reconciler: r.reconciler, + roi: roi, + isROI: isROI, + isLeader: isLeader, + }, nil +} + +// isNotLeaderNorObserver checks to see if this reconciler with the current +// state is enabled to do any work or not. +// isNotLeaderNorObserver returns true when there is no work possible for the +// reconciler. +func (s *state) isNotLeaderNorObserver() bool { + if !s.isLeader && !s.isROI { + // If we are not the leader, and we don't implement the ReadOnly + // interface, then take a fast-path out. + return true + } + return false +} + +func (s *state) reconcileMethodFor(o *v1alpha1.Interceptor) (string, doReconcile) { + if o.GetDeletionTimestamp().IsZero() { + if s.isLeader { + return reconciler.DoReconcileKind, s.reconciler.ReconcileKind + } else if s.isROI { + return reconciler.DoObserveKind, s.roi.ObserveKind + } + } else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok { + return reconciler.DoFinalizeKind, fin.FinalizeKind + } + return "unknown", nil +} diff --git a/pkg/client/listers/triggers/v1alpha1/expansion_generated.go b/pkg/client/listers/triggers/v1alpha1/expansion_generated.go index 9581afc7c..6f1ebae38 100644 --- a/pkg/client/listers/triggers/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/triggers/v1alpha1/expansion_generated.go @@ -34,6 +34,14 @@ type EventListenerListerExpansion interface{} // EventListenerNamespaceLister. type EventListenerNamespaceListerExpansion interface{} +// InterceptorListerExpansion allows custom methods to be added to +// InterceptorLister. +type InterceptorListerExpansion interface{} + +// InterceptorNamespaceListerExpansion allows custom methods to be added to +// InterceptorNamespaceLister. +type InterceptorNamespaceListerExpansion interface{} + // TriggerListerExpansion allows custom methods to be added to // TriggerLister. type TriggerListerExpansion interface{} diff --git a/pkg/client/listers/triggers/v1alpha1/interceptor.go b/pkg/client/listers/triggers/v1alpha1/interceptor.go new file mode 100644 index 000000000..235b62a32 --- /dev/null +++ b/pkg/client/listers/triggers/v1alpha1/interceptor.go @@ -0,0 +1,99 @@ +/* +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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// InterceptorLister helps list Interceptors. +// All objects returned here must be treated as read-only. +type InterceptorLister interface { + // List lists all Interceptors in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Interceptor, err error) + // Interceptors returns an object that can list and get Interceptors. + Interceptors(namespace string) InterceptorNamespaceLister + InterceptorListerExpansion +} + +// interceptorLister implements the InterceptorLister interface. +type interceptorLister struct { + indexer cache.Indexer +} + +// NewInterceptorLister returns a new InterceptorLister. +func NewInterceptorLister(indexer cache.Indexer) InterceptorLister { + return &interceptorLister{indexer: indexer} +} + +// List lists all Interceptors in the indexer. +func (s *interceptorLister) List(selector labels.Selector) (ret []*v1alpha1.Interceptor, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Interceptor)) + }) + return ret, err +} + +// Interceptors returns an object that can list and get Interceptors. +func (s *interceptorLister) Interceptors(namespace string) InterceptorNamespaceLister { + return interceptorNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// InterceptorNamespaceLister helps list and get Interceptors. +// All objects returned here must be treated as read-only. +type InterceptorNamespaceLister interface { + // List lists all Interceptors in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.Interceptor, err error) + // Get retrieves the Interceptor from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.Interceptor, error) + InterceptorNamespaceListerExpansion +} + +// interceptorNamespaceLister implements the InterceptorNamespaceLister +// interface. +type interceptorNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Interceptors in the indexer for a given namespace. +func (s interceptorNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Interceptor, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.Interceptor)) + }) + return ret, err +} + +// Get retrieves the Interceptor from the indexer for a given namespace and name. +func (s interceptorNamespaceLister) Get(name string) (*v1alpha1.Interceptor, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("interceptor"), name) + } + return obj.(*v1alpha1.Interceptor), nil +} diff --git a/pkg/reconciler/interceptor/controller.go b/pkg/reconciler/interceptor/controller.go new file mode 100644 index 000000000..c98270f2e --- /dev/null +++ b/pkg/reconciler/interceptor/controller.go @@ -0,0 +1,43 @@ +/* +Copyright 2021 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 interceptor + +import ( + "context" + + interceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor" + interceptorreconciler "github.com/tektoncd/triggers/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor" + "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" +) + +func NewController() func(context.Context, configmap.Watcher) *controller.Impl { + return func(ctx context.Context, cmw configmap.Watcher) *controller.Impl { + interceptorInformer := interceptorinformer.Get(ctx) + reconciler := &Reconciler{} + + impl := interceptorreconciler.NewImpl(ctx, reconciler, func(impl *controller.Impl) controller.Options { + return controller.Options{ + AgentName: ControllerName, + } + }) + + interceptorInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + + return impl + } +} diff --git a/pkg/reconciler/interceptor/interceptor.go b/pkg/reconciler/interceptor/interceptor.go new file mode 100644 index 000000000..fec0a892d --- /dev/null +++ b/pkg/reconciler/interceptor/interceptor.go @@ -0,0 +1,56 @@ +/* +Copyright 2021 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 interceptor + +import ( + "context" + + "github.com/tektoncd/triggers/pkg/apis/triggers/contexts" + "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + interceptorreconciler "github.com/tektoncd/triggers/pkg/client/injection/reconciler/triggers/v1alpha1/interceptor" + v1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/logging" + pkgreconciler "knative.dev/pkg/reconciler" +) + +const ControllerName = "Interceptor" + +// Reconciler implements controller.Reconciler for Configuration resources. +type Reconciler struct { +} + +var ( + // Check that our Reconciler implements interceptorreconciler.Interface + _ interceptorreconciler.Interface = (*Reconciler)(nil) +) + +func (r *Reconciler) ReconcileKind(ctx context.Context, it *v1alpha1.Interceptor) pkgreconciler.Event { + logger := logging.FromContext(ctx) + if it.Status.Address == nil { // Initialize Address if needed + it.Status.Address = &v1.Addressable{} + } + if contexts.IsUpgradeViaDefaulting(ctx) { // Set defaults + it.SetDefaults(ctx) + } + url, err := it.ResolveAddress() + logger.Debugf("Resolved Address is %s", url) + if err != nil { + return err + } + it.Status.Address.URL = url + return nil +} diff --git a/pkg/reconciler/interceptor/interceptor_test.go b/pkg/reconciler/interceptor/interceptor_test.go new file mode 100644 index 000000000..248385967 --- /dev/null +++ b/pkg/reconciler/interceptor/interceptor_test.go @@ -0,0 +1,179 @@ +/* +Copyright 2021 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 interceptor + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/triggers/pkg/apis/triggers/contexts" + triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + logtesting "knative.dev/pkg/logging/testing" + "knative.dev/pkg/ptr" +) + +func TestReconcileKind(t *testing.T) { + tests := []struct { + name string + initial *triggersv1.Interceptor // State of the world before we call Reconcile + want *triggersv1.Interceptor // Expected State of the world after calling Reconcile + }{{ + name: "initial status is nil", + initial: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + Port: ptr.Int32(80), + }, + }}, + Status: triggersv1.InterceptorStatus{}, + }, + want: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + Labels: map[string]string{"server/type": "http"}, + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + Port: ptr.Int32(80), + }, + }}, + Status: triggersv1.InterceptorStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "my-svc.default.svc:80", + Path: "path", + }, + }, + }, + }, + }, + }, { + name: "defaults are applied", + initial: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + }, + }}, + Status: triggersv1.InterceptorStatus{}, + }, + want: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + Labels: map[string]string{"server/type": "http"}, + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + }, + }}, + Status: triggersv1.InterceptorStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: "my-svc.default.svc:80", + Path: "path", + }, + }, + }, + }, + }, + }, { + name: "when provided caBundle", + initial: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + Labels: map[string]string{"server/type": "https"}, + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + CaBundle: []byte("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5ekNDQXB5Z0F3SUJBZ0lRSllLcEFVeXc2dStvY1JhV1VtRVRoREFLQmdncWhrak9QUVFEQWpCWE1SUXcKRWdZRFZRUUtFd3RyYm1GMGFYWmxMbVJsZGpFL01EMEdBMVVFQXhNMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdgpjbVV0YVc1MFpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpNQ0FYRFRJeU1EUXhOVEUyCk1ERTFPRm9ZRHpJeE1qSXdNekl5TVRZd01UVTRXakJYTVJRd0VnWURWUVFLRXd0cmJtRjBhWFpsTG1SbGRqRS8KTUQwR0ExVUVBeE0yZEdWcmRHOXVMWFJ5YVdkblpYSnpMV052Y21VdGFXNTBaWEpqWlhCMGIzSnpMblJsYTNSdgpiaTF3YVhCbGJHbHVaWE11YzNaak1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUFHcHp1RjlQCjY5VnFhN0xIY0tmNGpWY2JqblJNWDAxYWRnakh0Zy9kZFdIaVBWdXVJZER1WnZzVTREaVp5Smh2WnpmaHQ0ZmsKT3FJc3dJeVlmbkpLRnFPQ0FVWXdnZ0ZDTUE0R0ExVWREd0VCL3dRRUF3SUNoREFkQmdOVkhTVUVGakFVQmdncgpCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVQRXFjCnEvRFJHd2FDUTdmOFc0dmlucGN5a09zd2dlQUdBMVVkRVFTQjJEQ0IxWUloZEdWcmRHOXVMWFJ5YVdkblpYSnoKTFdOdmNtVXRhVzUwWlhKalpYQjBiM0p6Z2pKMFpXdDBiMjR0ZEhKcFoyZGxjbk10WTI5eVpTMXBiblJsY21ObApjSFJ2Y25NdWRHVnJkRzl1TFhCcGNHVnNhVzVsYzRJMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdmNtVXRhVzUwClpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpna1IwWld0MGIyNHRkSEpwWjJkbGNuTXQKWTI5eVpTMXBiblJsY21ObGNIUnZjbk11ZEdWcmRHOXVMWEJwY0dWc2FXNWxjeTV6ZG1NdVkyeDFjM1JsY2k1cwpiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkpBREJHQWlFQTlhWFBtUFZzRVA3R0xTbzI0SnNmNnRGTmpyQWJRbEl0CjRCYXllcjBnaU5jQ0lRQ09XSm1NTXQxQkE1RXgwa0FYTWRtZjlFdXV4LzlyUUkzMm9VNjVSYm9mNEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="), + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + }, + }}, + Status: triggersv1.InterceptorStatus{}, + }, + want: &triggersv1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-interceptor", + Labels: map[string]string{"server/type": "https"}, + }, + Spec: triggersv1.InterceptorSpec{ + ClientConfig: triggersv1.ClientConfig{ + CaBundle: []byte("LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5ekNDQXB5Z0F3SUJBZ0lRSllLcEFVeXc2dStvY1JhV1VtRVRoREFLQmdncWhrak9QUVFEQWpCWE1SUXcKRWdZRFZRUUtFd3RyYm1GMGFYWmxMbVJsZGpFL01EMEdBMVVFQXhNMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdgpjbVV0YVc1MFpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpNQ0FYRFRJeU1EUXhOVEUyCk1ERTFPRm9ZRHpJeE1qSXdNekl5TVRZd01UVTRXakJYTVJRd0VnWURWUVFLRXd0cmJtRjBhWFpsTG1SbGRqRS8KTUQwR0ExVUVBeE0yZEdWcmRHOXVMWFJ5YVdkblpYSnpMV052Y21VdGFXNTBaWEpqWlhCMGIzSnpMblJsYTNSdgpiaTF3YVhCbGJHbHVaWE11YzNaak1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUFHcHp1RjlQCjY5VnFhN0xIY0tmNGpWY2JqblJNWDAxYWRnakh0Zy9kZFdIaVBWdXVJZER1WnZzVTREaVp5Smh2WnpmaHQ0ZmsKT3FJc3dJeVlmbkpLRnFPQ0FVWXdnZ0ZDTUE0R0ExVWREd0VCL3dRRUF3SUNoREFkQmdOVkhTVUVGakFVQmdncgpCZ0VGQlFjREFRWUlLd1lCQlFVSEF3SXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVQRXFjCnEvRFJHd2FDUTdmOFc0dmlucGN5a09zd2dlQUdBMVVkRVFTQjJEQ0IxWUloZEdWcmRHOXVMWFJ5YVdkblpYSnoKTFdOdmNtVXRhVzUwWlhKalpYQjBiM0p6Z2pKMFpXdDBiMjR0ZEhKcFoyZGxjbk10WTI5eVpTMXBiblJsY21ObApjSFJ2Y25NdWRHVnJkRzl1TFhCcGNHVnNhVzVsYzRJMmRHVnJkRzl1TFhSeWFXZG5aWEp6TFdOdmNtVXRhVzUwClpYSmpaWEIwYjNKekxuUmxhM1J2Ymkxd2FYQmxiR2x1WlhNdWMzWmpna1IwWld0MGIyNHRkSEpwWjJkbGNuTXQKWTI5eVpTMXBiblJsY21ObGNIUnZjbk11ZEdWcmRHOXVMWEJwY0dWc2FXNWxjeTV6ZG1NdVkyeDFjM1JsY2k1cwpiMk5oYkRBS0JnZ3Foa2pPUFFRREFnTkpBREJHQWlFQTlhWFBtUFZzRVA3R0xTbzI0SnNmNnRGTmpyQWJRbEl0CjRCYXllcjBnaU5jQ0lRQ09XSm1NTXQxQkE1RXgwa0FYTWRtZjlFdXV4LzlyUUkzMm9VNjVSYm9mNEE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="), + Service: &triggersv1.ServiceReference{ + Name: "my-svc", + Namespace: "default", + Path: "path", + }, + }}, + Status: triggersv1.InterceptorStatus{ + AddressStatus: duckv1.AddressStatus{ + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "https", + Host: "my-svc.default.svc:8443", + Path: "path", + }, + }, + }, + }, + }, + }} + + for _, tc := range tests { + r := Reconciler{} + context := contexts.WithUpgradeViaDefaulting(logtesting.TestContextWithLogger(t)) + err := r.ReconcileKind(context, tc.initial) + if err != nil { + t.Fatalf("ReconcileKind() unexpected error: %v", err) + } + got := tc.initial + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Fatalf("ReconcileKind() diff -want/+got: %s", diff) + } + } +} diff --git a/pkg/sink/sink.go b/pkg/sink/sink.go index 14424c3f1..82ddf5c26 100644 --- a/pkg/sink/sink.go +++ b/pkg/sink/sink.go @@ -51,6 +51,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/record" + "knative.dev/pkg/apis" v1 "knative.dev/pkg/apis/duck/v1" ) @@ -82,6 +83,7 @@ type Sink struct { ClusterTriggerBindingLister listers.ClusterTriggerBindingLister TriggerTemplateLister listers.TriggerTemplateLister ClusterInterceptorLister listersv1alpha1.ClusterInterceptorLister + InterceptorLister listersv1alpha1.InterceptorLister } // Response defines the HTTP body that the Sink responds to events with. @@ -503,9 +505,37 @@ func (r Sink) ExecuteInterceptors(trInt []*triggersv1.TriggerInterceptor, in *ht continue } request.InterceptorParams = interceptors.GetInterceptorParams(i) - url, err := interceptors.ResolveToURL(r.ClusterInterceptorLister.Get, i.GetName()) - if err != nil { - return nil, nil, nil, fmt.Errorf("could not resolve interceptor URL: %w", err) + + var url *apis.URL + if i.Ref.Kind == triggersv1.ClusterInterceptorKind { + ic, err := r.ClusterInterceptorLister.Get(i.GetName()) + if err != nil { + return nil, nil, nil, fmt.Errorf("url resolution failed for interceptor %s with: %w", i.GetName(), err) + } + if ic.Status.Address != nil && ic.Status.Address.URL != nil { + url = ic.Status.Address.URL + } else if url, err = ic.ResolveAddress(); err != nil { + return nil, nil, nil, fmt.Errorf("url resolution failed for interceptor %s with: %w", i.GetName(), err) + } + if err != nil { + return nil, nil, nil, fmt.Errorf("could not resolve clusterinterceptor URL: %w", err) + } + } else if i.Ref.Kind == triggersv1.NamespacedInterceptorKind { + if r.InterceptorLister == nil { + r.Logger.Debugf("nil lister") + } + ic, err := r.InterceptorLister.Interceptors(r.EventListenerNamespace).Get(i.GetName()) + if err != nil { + return nil, nil, nil, fmt.Errorf("url resolution failed for interceptor %s with: %w", i.GetName(), err) + } + if addr := ic.Status.Address; addr != nil && addr.URL != nil { + url = addr.URL + } else if url, err = ic.ResolveAddress(); err != nil { + return nil, nil, nil, fmt.Errorf("url resolution failed for interceptor %s with: %w", i.GetName(), err) + } + if err != nil { + return nil, nil, nil, fmt.Errorf("could not resolve clusterinterceptor URL: %w", err) + } } interceptorResponse, err := interceptors.Execute(context.Background(), r.HTTPClient, &request, url.String()) diff --git a/pkg/sink/sink_test.go b/pkg/sink/sink_test.go index a1e0fb10a..21122a493 100644 --- a/pkg/sink/sink_test.go +++ b/pkg/sink/sink_test.go @@ -41,7 +41,8 @@ import ( triggersv1beta1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" dynamicclientset "github.com/tektoncd/triggers/pkg/client/dynamic/clientset" "github.com/tektoncd/triggers/pkg/client/dynamic/clientset/tekton" - interceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/clusterinterceptor" + clusterinterceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/clusterinterceptor" + interceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor" clustertriggerbindinginformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/clustertriggerbinding" eventlistenerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/eventlistener" triggerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/trigger" @@ -123,6 +124,21 @@ var ( }, }, } + nsInterceptor = &triggersv1alpha1.Interceptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bitbucket", + Namespace: namespace, + }, + Spec: triggersv1alpha1.InterceptorSpec{ + ClientConfig: triggersv1alpha1.ClientConfig{ + URL: &apis.URL{ + Scheme: "http", + Host: "tekton-triggers-core-interceptors", + Path: "/bitbucket", + }, + }, + }, + } ) // getSinkAssets seeds test resources and returns a testable Sink and a dynamic client. The returned client is used to @@ -162,7 +178,8 @@ func getSinkAssets(t *testing.T, res test.Resources, elName string, webhookInter TriggerBindingLister: triggerbindinginformer.Get(ctx).Lister(), ClusterTriggerBindingLister: clustertriggerbindinginformer.Get(ctx).Lister(), TriggerTemplateLister: triggertemplateinformer.Get(ctx).Lister(), - ClusterInterceptorLister: interceptorinformer.Get(ctx).Lister(), + ClusterInterceptorLister: clusterinterceptorinformer.Get(ctx).Lister(), + InterceptorLister: interceptorinformer.Get(ctx).Lister(), PayloadValidation: true, } return r, dynamicClient @@ -685,7 +702,7 @@ func TestHandleEvent(t *testing.T) { }, Spec: triggersv1beta1.TriggerSpec{ Interceptors: []*triggersv1beta1.EventInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "github"}, + Ref: triggersv1beta1.InterceptorRef{Name: "github", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "secretRef", Value: test.ToV1JSON(t, &triggersv1beta1.SecretRef{ @@ -740,7 +757,62 @@ func TestHandleEvent(t *testing.T) { }, Spec: triggersv1beta1.TriggerSpec{ Interceptors: []*triggersv1beta1.EventInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "bitbucket"}, + Ref: triggersv1beta1.InterceptorRef{Name: "bitbucket", Kind: triggersv1beta1.ClusterInterceptorKind}, + Params: []triggersv1beta1.InterceptorParams{{ + Name: "secretRef", + Value: test.ToV1JSON(t, &triggersv1beta1.SecretRef{ + SecretKey: "secretKey", + SecretName: "secret", + }), + }, { + Name: "eventTypes", + Value: test.ToV1JSON(t, []string{"repo:refs_changed"}), + }}, + }}, + Bindings: gitCloneTBSpec, + Template: triggersv1beta1.TriggerSpecTemplate{Spec: makeGitCloneTTSpec(t, "git-clone-test-run")}, + }, + }}, + EventListeners: []*triggersv1beta1.EventListener{{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: namespace, + UID: types.UID(elUID), + }, + Spec: triggersv1beta1.EventListenerSpec{ + Triggers: []triggersv1beta1.EventListenerTrigger{{ + TriggerRef: "git-clone-trigger", + }}, + }, + }}, + }, + eventBody: eventBody, + headers: map[string][]string{ + "X-Event-Key": {"repo:refs_changed"}, + "X-Hub-Signature": {test.HMACHeader(t, "secret", eventBody, "sha1")}, + }, + want: []pipelinev1.TaskRun{gitCloneTaskRun}, + }, { + name: "with namespaced interceptor", + resources: test.Resources{ + Secrets: []*corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: namespace, + }, + Data: map[string][]byte{ + "secretKey": []byte("secret"), + }, + }}, + Interceptors: []*triggersv1alpha1.Interceptor{nsInterceptor}, + Triggers: []*triggersv1beta1.Trigger{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "git-clone-trigger", + Namespace: namespace, + }, + Spec: triggersv1beta1.TriggerSpec{ + Interceptors: []*triggersv1beta1.EventInterceptor{{ + Ref: triggersv1beta1.InterceptorRef{Name: "bitbucket", Kind: triggersv1beta1.NamespacedInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "secretRef", Value: test.ToV1JSON(t, &triggersv1beta1.SecretRef{ @@ -898,6 +970,7 @@ func TestHandleEvent(t *testing.T) { Interceptors: []*triggersv1beta1.EventInterceptor{{ Ref: triggersv1beta1.InterceptorRef{ Name: "cel", + Kind: triggersv1beta1.ClusterInterceptorKind, }, Params: []triggersv1beta1.InterceptorParams{ {Name: "filter", Value: test.ToV1JSON(t, "has(body.head_commit)")}, @@ -924,6 +997,7 @@ func TestHandleEvent(t *testing.T) { Interceptors: []*triggersv1beta1.EventInterceptor{{ Ref: triggersv1beta1.InterceptorRef{ Name: "cel", + Kind: triggersv1beta1.ClusterInterceptorKind, }, Params: []triggersv1beta1.InterceptorParams{ {Name: "filter", Value: test.ToV1JSON(t, "has(body.head_commit)")}, @@ -953,7 +1027,7 @@ func TestHandleEvent(t *testing.T) { { TriggerRef: "git-clone-trigger", Interceptors: []*triggersv1beta1.TriggerInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "filter", Value: test.ToV1JSON(t, "has(body.head_commit)"), @@ -963,7 +1037,7 @@ func TestHandleEvent(t *testing.T) { { TriggerRef: "git-clone-trigger-2", Interceptors: []*triggersv1beta1.TriggerInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "filter", Value: test.ToV1JSON(t, "has(body.head_commit)"), @@ -1078,7 +1152,7 @@ func TestHandleEvent(t *testing.T) { TriggerGroups: []triggersv1beta1.EventListenerTriggerGroup{{ Name: "filter-event", Interceptors: []*triggersv1beta1.TriggerInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "filter", Value: test.ToV1JSON(t, "has(body.head_commit)"), @@ -1392,7 +1466,7 @@ func TestExecuteInterceptor_NotContinue(t *testing.T) { trigger := triggersv1beta1.Trigger{ Spec: triggersv1beta1.TriggerSpec{ Interceptors: []*triggersv1beta1.EventInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "filter", Value: test.ToV1JSON(t, `body.head == "abcde"`), @@ -1446,7 +1520,7 @@ func TestExecuteInterceptor_ExtensionChaining(t *testing.T) { trigger := triggersv1beta1.Trigger{ Spec: triggersv1beta1.TriggerSpec{ Interceptors: []*triggersv1beta1.EventInterceptor{{ - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "overlays", Value: test.ToV1JSON(t, []triggersv1beta1.CELOverlay{{ @@ -1463,7 +1537,7 @@ func TestExecuteInterceptor_ExtensionChaining(t *testing.T) { }, }, }, { - Ref: triggersv1beta1.InterceptorRef{Name: "cel"}, + Ref: triggersv1beta1.InterceptorRef{Name: "cel", Kind: triggersv1beta1.ClusterInterceptorKind}, Params: []triggersv1beta1.InterceptorParams{{ Name: "filter", Value: test.ToV1JSON(t, "body.extensions.truncated_sha == \"abcde\" && extensions.truncated_sha == \"abcde\""), diff --git a/test/controller.go b/test/controller.go index b3e3cd15f..3f46a125e 100644 --- a/test/controller.go +++ b/test/controller.go @@ -32,6 +32,7 @@ import ( faketriggersclientset "github.com/tektoncd/triggers/pkg/client/clientset/versioned/fake" faketriggersclient "github.com/tektoncd/triggers/pkg/client/injection/client/fake" fakeClusterInterceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/clusterinterceptor/fake" + fakeInterceptorinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1alpha1/interceptor/fake" fakeclustertriggerbindinginformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/clustertriggerbinding/fake" fakeeventlistenerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/eventlistener/fake" faketriggerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/trigger/fake" @@ -75,6 +76,7 @@ type Resources struct { ClusterTriggerBindings []*v1beta1.ClusterTriggerBinding EventListeners []*v1beta1.EventListener ClusterInterceptors []*v1alpha1.ClusterInterceptor + Interceptors []*v1alpha1.Interceptor TriggerBindings []*v1beta1.TriggerBinding TriggerTemplates []*v1beta1.TriggerTemplate Triggers []*v1beta1.Trigger @@ -139,6 +141,7 @@ func SeedResources(t *testing.T, ctx context.Context, r Resources) Clients { ctbInformer := fakeclustertriggerbindinginformer.Get(ctx) elInformer := fakeeventlistenerinformer.Get(ctx) icInformer := fakeClusterInterceptorinformer.Get(ctx) + nsicInformer := fakeInterceptorinformer.Get(ctx) ttInformer := faketriggertemplateinformer.Get(ctx) tbInformer := faketriggerbindinginformer.Get(ctx) trInformer := faketriggerinformer.Get(ctx) @@ -180,6 +183,14 @@ func SeedResources(t *testing.T, ctx context.Context, r Resources) Clients { t.Fatal(err) } } + for _, ic := range r.Interceptors { + if err := nsicInformer.Informer().GetIndexer().Add(ic); err != nil { + t.Fatal(err) + } + if _, err := c.Triggers.TriggersV1alpha1().Interceptors(ic.Namespace).Create(context.Background(), ic, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + } for _, tb := range r.TriggerBindings { if err := tbInformer.Informer().GetIndexer().Add(tb); err != nil { t.Fatal(err) diff --git a/test/eventlistener_scale_test.go b/test/eventlistener_scale_test.go index f4d717bff..8dc4da3cc 100644 --- a/test/eventlistener_scale_test.go +++ b/test/eventlistener_scale_test.go @@ -120,7 +120,7 @@ func createServiceAccount(t *testing.T, c *clients, namespace, name string) { ObjectMeta: metav1.ObjectMeta{Name: "sa-role"}, Rules: []rbacv1.PolicyRule{{ APIGroups: []string{triggers.GroupName}, - Resources: []string{"eventlisteners", "triggerbindings", "triggertemplates", "triggers"}, + Resources: []string{"eventlisteners", "interceptors", "triggerbindings", "triggertemplates", "triggers"}, Verbs: []string{"get", "list", "watch"}, }, { APIGroups: []string{""}, diff --git a/test/eventlistener_test.go b/test/eventlistener_test.go index c524a1d71..f974fadce 100644 --- a/test/eventlistener_test.go +++ b/test/eventlistener_test.go @@ -290,7 +290,7 @@ func TestEventListenerCreate(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "my-role"}, Rules: []rbacv1.PolicyRule{{ APIGroups: []string{triggers.GroupName}, - Resources: []string{"clustertriggerbindings", "eventlisteners", "clusterinterceptors", "triggerbindings", "triggertemplates", "triggers"}, + Resources: []string{"clustertriggerbindings", "eventlisteners", "clusterinterceptors", "interceptors", "triggerbindings", "triggertemplates", "triggers"}, Verbs: []string{"get", "list", "watch"}, }, { APIGroups: []string{"tekton.dev"},