diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 15339172ac..826a8974b1 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -20,6 +20,7 @@ import ( "os" "github.com/tektoncd/triggers/pkg/reconciler/clusterinterceptor" + elresources "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" corev1 "k8s.io/api/core/v1" "knative.dev/pkg/injection" @@ -35,24 +36,24 @@ const ( ) var ( - image = flag.String("el-image", eventlistener.DefaultImage, "The container image for the EventListener Pod.") - port = flag.Int("el-port", eventlistener.DefaultPort, "The container port for the EventListener to listen on.") - setSecurityContext = flag.Bool("el-security-context", eventlistener.DefaultSetSecurityContext, "Add a security context to the event listener deployment.") - readTimeOut = flag.Int64("el-readtimeout", eventlistener.DefaultReadTimeout, "The read timeout for EventListener Server.") - writeTimeOut = flag.Int64("el-writetimeout", eventlistener.DefaultWriteTimeout, "The write timeout for EventListener Server.") - idleTimeOut = flag.Int64("el-idletimeout", eventlistener.DefaultIdleTimeout, "The idle timeout for EventListener Server.") - timeOutHandler = flag.Int64("el-timeouthandler", eventlistener.DefaultTimeOutHandler, "The timeout for Timeout Handler of EventListener Server.") - periodSeconds = flag.Int("period-seconds", eventlistener.DefaultPeriodSeconds, "The Period Seconds for the EventListener Liveness and Readiness Probes.") - failureThreshold = flag.Int("failure-threshold", eventlistener.DefaultFailureThreshold, "The Failure Threshold for the EventListener Liveness and Readiness Probes.") + image = flag.String("el-image", elresources.DefaultImage, "The container image for the EventListener Pod.") + port = flag.Int("el-port", elresources.DefaultPort, "The container port for the EventListener to listen on.") + setSecurityContext = flag.Bool("el-security-context", elresources.DefaultSetSecurityContext, "Add a security context to the event listener deployment.") + readTimeOut = flag.Int64("el-readtimeout", elresources.DefaultReadTimeout, "The read timeout for EventListener Server.") + writeTimeOut = flag.Int64("el-writetimeout", elresources.DefaultWriteTimeout, "The write timeout for EventListener Server.") + idleTimeOut = flag.Int64("el-idletimeout", elresources.DefaultIdleTimeout, "The idle timeout for EventListener Server.") + timeOutHandler = flag.Int64("el-timeouthandler", elresources.DefaultTimeOutHandler, "The timeout for Timeout Handler of EventListener Server.") + periodSeconds = flag.Int("period-seconds", elresources.DefaultPeriodSeconds, "The Period Seconds for the EventListener Liveness and Readiness Probes.") + failureThreshold = flag.Int("failure-threshold", elresources.DefaultFailureThreshold, "The Failure Threshold for the EventListener Liveness and Readiness Probes.") - staticResourceLabels = eventlistener.DefaultStaticResourceLabels + staticResourceLabels = elresources.DefaultStaticResourceLabels systemNamespace = os.Getenv("SYSTEM_NAMESPACE") ) func main() { cfg := injection.ParseAndGetRESTConfigOrDie() - c := eventlistener.Config{ + c := elresources.Config{ Image: image, Port: port, SetSecurityContext: setSecurityContext, diff --git a/pkg/reconciler/eventlistener/controller.go b/pkg/reconciler/eventlistener/controller.go index 3ed80ba3a5..0c7e1fcddc 100644 --- a/pkg/reconciler/eventlistener/controller.go +++ b/pkg/reconciler/eventlistener/controller.go @@ -25,6 +25,7 @@ import ( eventlistenerinformer "github.com/tektoncd/triggers/pkg/client/injection/informers/triggers/v1beta1/eventlistener" eventlistenerreconciler "github.com/tektoncd/triggers/pkg/client/injection/reconciler/triggers/v1beta1/eventlistener" dynamicduck "github.com/tektoncd/triggers/pkg/dynamic" + "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" "k8s.io/client-go/tools/cache" duckinformer "knative.dev/pkg/client/injection/ducks/duck/v1/podspecable" kubeclient "knative.dev/pkg/client/injection/kube/client" @@ -38,7 +39,7 @@ import ( ) // NewController creates a new instance of an EventListener controller. -func NewController(config Config) func(context.Context, configmap.Watcher) *controller.Impl { +func NewController(config resources.Config) func(context.Context, configmap.Watcher) *controller.Impl { return func(ctx context.Context, cmw configmap.Watcher) *controller.Impl { logger := logging.FromContext(ctx) dynamicclientset := dynamicclient.Get(ctx) diff --git a/pkg/reconciler/eventlistener/eventlistener.go b/pkg/reconciler/eventlistener/eventlistener.go index 72f51f5131..72a0b3fd43 100644 --- a/pkg/reconciler/eventlistener/eventlistener.go +++ b/pkg/reconciler/eventlistener/eventlistener.go @@ -37,6 +37,7 @@ import ( eventlistenerreconciler "github.com/tektoncd/triggers/pkg/client/injection/reconciler/triggers/v1beta1/eventlistener" listers "github.com/tektoncd/triggers/pkg/client/listers/triggers/v1beta1" dynamicduck "github.com/tektoncd/triggers/pkg/dynamic" + "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" "golang.org/x/xerrors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -55,7 +56,6 @@ import ( "knative.dev/pkg/kmeta" "knative.dev/pkg/logging" "knative.dev/pkg/metrics" - "knative.dev/pkg/network" "knative.dev/pkg/ptr" pkgreconciler "knative.dev/pkg/reconciler" ) @@ -101,7 +101,7 @@ type Reconciler struct { serviceLister corev1lister.ServiceLister // config is the configuration options that the Reconciler accepts. - config Config + config resources.Config podspecableTracker dynamicduck.ListableTracker onlyOnce sync.Once } @@ -183,36 +183,15 @@ func reconcileObjectMeta(existing *metav1.ObjectMeta, desired metav1.ObjectMeta) } func (r *Reconciler) reconcileService(ctx context.Context, el *v1beta1.EventListener) error { - // for backward compatibility with original behavior - var serviceType corev1.ServiceType - if el.Spec.Resources.KubernetesResource != nil && el.Spec.Resources.KubernetesResource.ServiceType != "" { - serviceType = el.Spec.Resources.KubernetesResource.ServiceType - } - - servicePort := getServicePort(el, r.config) - metricsPort := corev1.ServicePort{ - Name: eventListenerMetricsPortName, - Protocol: corev1.ProtocolTCP, - Port: int32(9000), - TargetPort: intstr.IntOrString{ - IntVal: int32(eventListenerMetricsPort), - }, - } + service := resources.MakeService(el, r.config) - service := &corev1.Service{ - ObjectMeta: generateObjectMeta(el, r.config.StaticResourceLabels), - Spec: corev1.ServiceSpec{ - Selector: GenerateResourceLabels(el.Name, r.config.StaticResourceLabels), - Type: serviceType, - Ports: []corev1.ServicePort{servicePort, metricsPort}}, - } existingService, err := r.serviceLister.Services(el.Namespace).Get(el.Status.Configuration.GeneratedResourceName) switch { case err == nil: // Determine if reconciliation has to occur updated := reconcileObjectMeta(&existingService.ObjectMeta, service.ObjectMeta) el.Status.SetExistsCondition(v1beta1.ServiceExists, nil) - el.Status.SetAddress(listenerHostname(service.Name, el.Namespace, int(servicePort.Port))) + el.Status.SetAddress(resources.ListenerHostname(el, r.config)) if !reflect.DeepEqual(existingService.Spec.Selector, service.Spec.Selector) { existingService.Spec.Selector = service.Spec.Selector updated = true @@ -243,7 +222,7 @@ func (r *Reconciler) reconcileService(ctx context.Context, el *v1beta1.EventList logging.FromContext(ctx).Errorf("Error creating EventListener Service: %s", err) return err } - el.Status.SetAddress(listenerHostname(service.Name, el.Namespace, int(servicePort.Port))) + el.Status.SetAddress(resources.ListenerHostname(el, r.config)) logging.FromContext(ctx).Infof("Created EventListener Service %s in Namespace %s", service.Name, el.Namespace) default: logging.FromContext(ctx).Error(err) @@ -510,7 +489,7 @@ func (r *Reconciler) reconcileCustomObject(ctx context.Context, el *v1beta1.Even SuccessThreshold: 1, } - podlabels := kmeta.UnionMaps(el.Labels, GenerateResourceLabels(el.Name, r.config.StaticResourceLabels)) + podlabels := kmeta.UnionMaps(el.Labels, resources.GenerateLabels(el.Name, r.config.StaticResourceLabels)) podlabels = kmeta.UnionMaps(podlabels, customObjectData.Labels) @@ -720,14 +699,14 @@ func (r *Reconciler) reconcileCustomObject(ctx context.Context, el *v1beta1.Even return nil } -func getDeployment(el *v1beta1.EventListener, container corev1.Container, c Config) *appsv1.Deployment { +func getDeployment(el *v1beta1.EventListener, container corev1.Container, c resources.Config) *appsv1.Deployment { var ( tolerations []corev1.Toleration nodeSelector, annotations, podlabels map[string]string serviceAccountName string securityContext corev1.PodSecurityContext ) - podlabels = kmeta.UnionMaps(el.Labels, GenerateResourceLabels(el.Name, c.StaticResourceLabels)) + podlabels = kmeta.UnionMaps(el.Labels, resources.GenerateLabels(el.Name, c.StaticResourceLabels)) serviceAccountName = el.Spec.ServiceAccountName @@ -781,11 +760,11 @@ func getDeployment(el *v1beta1.EventListener, container corev1.Container, c Conf } return &appsv1.Deployment{ - ObjectMeta: generateObjectMeta(el, c.StaticResourceLabels), + ObjectMeta: resources.ObjectMeta(el, c.StaticResourceLabels), Spec: appsv1.DeploymentSpec{ Replicas: replicas, Selector: &metav1.LabelSelector{ - MatchLabels: GenerateResourceLabels(el.Name, c.StaticResourceLabels), + MatchLabels: resources.GenerateLabels(el.Name, c.StaticResourceLabels), }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -805,7 +784,7 @@ func getDeployment(el *v1beta1.EventListener, container corev1.Container, c Conf } } -func addCertsForSecureConnection(container corev1.Container, c Config) corev1.Container { +func addCertsForSecureConnection(container corev1.Container, c resources.Config) corev1.Container { var elCert, elKey string certEnv := map[string]*corev1.EnvVarSource{} for i := range container.Env { @@ -859,7 +838,7 @@ func addCertsForSecureConnection(container corev1.Container, c Config) corev1.Co return container } -func getContainer(el *v1beta1.EventListener, c Config, pod *duckv1.WithPod) corev1.Container { +func getContainer(el *v1beta1.EventListener, c resources.Config, pod *duckv1.WithPod) corev1.Container { var resources corev1.ResourceRequirements env := []corev1.EnvVar{} if el.Spec.Resources.KubernetesResource != nil { @@ -913,74 +892,6 @@ func getContainer(el *v1beta1.EventListener, c Config, pod *duckv1.WithPod) core } } -func getServicePort(el *v1beta1.EventListener, c Config) corev1.ServicePort { - var elCert, elKey string - - servicePortName := eventListenerServicePortName - servicePortPort := *c.Port - - certEnv := map[string]*corev1.EnvVarSource{} - if el.Spec.Resources.KubernetesResource != nil { - if len(el.Spec.Resources.KubernetesResource.Template.Spec.Containers) != 0 { - for i := range el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env { - certEnv[el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env[i].Name] = - el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env[i].ValueFrom - } - } - } - - if v, ok := certEnv["TLS_CERT"]; ok { - elCert = v.SecretKeyRef.Key - } else { - elCert = "" - } - if v, ok := certEnv["TLS_KEY"]; ok { - elKey = v.SecretKeyRef.Key - } else { - elKey = "" - } - - if elCert != "" && elKey != "" { - servicePortName = eventListenerServiceTLSPortName - if *c.Port == DefaultPort { - // We return port 8443 if TLS is enabled and the default HTTP port is set. - // This effectively makes 8443 the default HTTPS port unless a user explicitly sets a different port. - servicePortPort = 8443 - } - } - - return corev1.ServicePort{ - Name: servicePortName, - Protocol: corev1.ProtocolTCP, - Port: int32(servicePortPort), - TargetPort: intstr.IntOrString{ - IntVal: int32(eventListenerContainerPort), - }, - } -} - -// GenerateResourceLabels generates the labels to be used on all generated resources. -func GenerateResourceLabels(eventListenerName string, staticResourceLabels map[string]string) map[string]string { - resourceLabels := make(map[string]string, len(staticResourceLabels)+1) - for k, v := range staticResourceLabels { - resourceLabels[k] = v - } - resourceLabels["eventlistener"] = eventListenerName - return resourceLabels -} - -// generateObjectMeta generates the object meta that should be used by all -// resources generated by the EventListener reconciler -func generateObjectMeta(el *v1beta1.EventListener, staticResourceLabels map[string]string) metav1.ObjectMeta { - return metav1.ObjectMeta{ - Namespace: el.Namespace, - Name: el.Status.Configuration.GeneratedResourceName, - OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(el)}, - Labels: kmeta.UnionMaps(el.Labels, GenerateResourceLabels(el.Name, staticResourceLabels)), - Annotations: el.Annotations, - } -} - // wrapError wraps errors together. If one of the errors is nil, the other is // returned. func wrapError(err1, err2 error) error { @@ -993,11 +904,6 @@ func wrapError(err1, err2 error) error { return xerrors.Errorf("%s : %s", err1.Error(), err2.Error()) } -// listenerHostname returns the intended hostname for the EventListener service. -func listenerHostname(name, namespace string, port int) string { - return network.GetServiceHostname(name, namespace) + fmt.Sprintf(":%d", port) -} - func defaultLoggingConfigMap() *corev1.ConfigMap { return &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{Name: eventListenerConfigMapName}, diff --git a/pkg/reconciler/eventlistener/eventlistener_test.go b/pkg/reconciler/eventlistener/eventlistener_test.go index 8ab91b36a3..f06fcd68ab 100644 --- a/pkg/reconciler/eventlistener/eventlistener_test.go +++ b/pkg/reconciler/eventlistener/eventlistener_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -108,7 +109,7 @@ func compareCondition(x, y apis.Condition) bool { // getEventListenerTestAssets returns TestAssets that have been seeded with the // given test.Resources r where r represents the state of the system -func getEventListenerTestAssets(t *testing.T, r test.Resources, c *Config) (test.Assets, context.CancelFunc) { +func getEventListenerTestAssets(t *testing.T, r test.Resources, c *resources.Config) (test.Assets, context.CancelFunc) { t.Helper() ctx, _ := rtesting.SetupFakeContext(t) ctx, cancel := context.WithCancel(ctx) @@ -131,7 +132,7 @@ func getEventListenerTestAssets(t *testing.T, r test.Resources, c *Config) (test clients := test.SeedResources(t, ctx, r) cmw := cminformer.NewInformedWatcher(clients.Kube, system.GetNamespace()) if c == nil { - c = makeConfig() + c = resources.MakeConfig() } testAssets := test.Assets{ Controller: NewController(*c)(ctx, cmw), @@ -143,30 +144,6 @@ func getEventListenerTestAssets(t *testing.T, r test.Resources, c *Config) (test return testAssets, cancel } -// makeConfig is a helper to build a config that is consumed by an EventListener. -// It generates a default Config for the EventListener without any flags set and accepts functions for modification. -func makeConfig(ops ...func(d *Config)) *Config { - c := Config{ - Image: &DefaultImage, - Port: &DefaultPort, - SetSecurityContext: &DefaultSetSecurityContext, - ReadTimeOut: &DefaultReadTimeout, - WriteTimeOut: &DefaultWriteTimeout, - IdleTimeOut: &DefaultIdleTimeout, - TimeOutHandler: &DefaultTimeOutHandler, - PeriodSeconds: &DefaultPeriodSeconds, - FailureThreshold: &DefaultFailureThreshold, - - StaticResourceLabels: DefaultStaticResourceLabels, - SystemNamespace: DefaultSystemNamespace, - } - - for _, op := range ops { - op(&c) - } - return &c -} - // makeEL is a helper to build an EventListener for tests. // It generates a base EventListener that can then be modified by the passed in op function // If no ops are specified, it generates a base EventListener with no triggers and no Status @@ -213,7 +190,7 @@ func makeDeployment(ops ...func(d *appsv1.Deployment)) *appsv1.Deployment { ServiceAccountName: "sa", Containers: []corev1.Container{{ Name: "event-listener", - Image: DefaultImage, + Image: resources.DefaultImage, Ports: []corev1.ContainerPort{{ ContainerPort: int32(eventListenerContainerPort), Protocol: corev1.ProtocolTCP, @@ -229,8 +206,8 @@ func makeDeployment(ops ...func(d *appsv1.Deployment)) *appsv1.Deployment { Port: intstr.FromInt(eventListenerContainerPort), }, }, - PeriodSeconds: int32(DefaultPeriodSeconds), - FailureThreshold: int32(DefaultFailureThreshold), + PeriodSeconds: int32(resources.DefaultPeriodSeconds), + FailureThreshold: int32(resources.DefaultFailureThreshold), }, ReadinessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -240,17 +217,17 @@ func makeDeployment(ops ...func(d *appsv1.Deployment)) *appsv1.Deployment { Port: intstr.FromInt(eventListenerContainerPort), }, }, - PeriodSeconds: int32(DefaultPeriodSeconds), - FailureThreshold: int32(DefaultFailureThreshold), + PeriodSeconds: int32(resources.DefaultPeriodSeconds), + FailureThreshold: int32(resources.DefaultFailureThreshold), }, Args: []string{ "-el-name", eventListenerName, "-el-namespace", namespace, "-port", strconv.Itoa(eventListenerContainerPort), - "readtimeout", strconv.FormatInt(DefaultReadTimeout, 10), - "writetimeout", strconv.FormatInt(DefaultWriteTimeout, 10), - "idletimeout", strconv.FormatInt(DefaultIdleTimeout, 10), - "timeouthandler", strconv.FormatInt(DefaultTimeOutHandler, 10), + "readtimeout", strconv.FormatInt(resources.DefaultReadTimeout, 10), + "writetimeout", strconv.FormatInt(resources.DefaultWriteTimeout, 10), + "idletimeout", strconv.FormatInt(resources.DefaultIdleTimeout, 10), + "timeouthandler", strconv.FormatInt(resources.DefaultTimeOutHandler, 10), }, VolumeMounts: []corev1.VolumeMount{{ Name: "config-logging", @@ -308,7 +285,7 @@ func makeDeployment(ops ...func(d *appsv1.Deployment)) *appsv1.Deployment { var withTLSConfig = func(d *appsv1.Deployment) { d.Spec.Template.Spec.Containers = []corev1.Container{{ Name: "event-listener", - Image: DefaultImage, + Image: resources.DefaultImage, Ports: []corev1.ContainerPort{{ ContainerPort: int32(eventListenerContainerPort), Protocol: corev1.ProtocolTCP, @@ -324,8 +301,8 @@ var withTLSConfig = func(d *appsv1.Deployment) { Port: intstr.FromInt(eventListenerContainerPort), }, }, - PeriodSeconds: int32(DefaultPeriodSeconds), - FailureThreshold: int32(DefaultFailureThreshold), + PeriodSeconds: int32(resources.DefaultPeriodSeconds), + FailureThreshold: int32(resources.DefaultFailureThreshold), }, ReadinessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -335,8 +312,8 @@ var withTLSConfig = func(d *appsv1.Deployment) { Port: intstr.FromInt(eventListenerContainerPort), }, }, - PeriodSeconds: int32(DefaultPeriodSeconds), - FailureThreshold: int32(DefaultFailureThreshold), + PeriodSeconds: int32(resources.DefaultPeriodSeconds), + FailureThreshold: int32(resources.DefaultFailureThreshold), }, Args: []string{ "-el-name", eventListenerName, @@ -433,7 +410,7 @@ func makeWithPod(ops ...func(d *duckv1.WithPod)) *duckv1.WithPod { Spec: corev1.PodSpec{ Containers: []corev1.Container{{ Name: "event-listener", - Image: DefaultImage, + Image: resources.DefaultImage, Ports: []corev1.ContainerPort{{ ContainerPort: int32(eventListenerContainerPort), Protocol: corev1.ProtocolTCP, @@ -442,10 +419,10 @@ func makeWithPod(ops ...func(d *duckv1.WithPod)) *duckv1.WithPod { "--el-name=" + eventListenerName, "--el-namespace=" + namespace, "--port=" + strconv.Itoa(eventListenerContainerPort), - "--readtimeout=" + strconv.FormatInt(DefaultReadTimeout, 10), - "--writetimeout=" + strconv.FormatInt(DefaultWriteTimeout, 10), - "--idletimeout=" + strconv.FormatInt(DefaultIdleTimeout, 10), - "--timeouthandler=" + strconv.FormatInt(DefaultTimeOutHandler, 10), + "--readtimeout=" + strconv.FormatInt(resources.DefaultReadTimeout, 10), + "--writetimeout=" + strconv.FormatInt(resources.DefaultWriteTimeout, 10), + "--idletimeout=" + strconv.FormatInt(resources.DefaultIdleTimeout, 10), + "--timeouthandler=" + strconv.FormatInt(resources.DefaultTimeOutHandler, 10), "--is-multi-ns=" + strconv.FormatBool(false), }, VolumeMounts: []corev1.VolumeMount{{ @@ -514,7 +491,7 @@ func makeService(ops ...func(*corev1.Service)) *corev1.Service { Ports: []corev1.ServicePort{{ Name: eventListenerServicePortName, Protocol: corev1.ProtocolTCP, - Port: int32(DefaultPort), + Port: int32(resources.DefaultPort), TargetPort: intstr.IntOrString{ IntVal: int32(eventListenerContainerPort), }, @@ -543,12 +520,10 @@ func logConfig(ns string) *corev1.ConfigMap { } func withTLSPort(el *v1beta1.EventListener) { - el.Status.Address = &duckv1beta1.Addressable{ - URL: &apis.URL{ - Scheme: "http", - Host: listenerHostname(generatedResourceName, namespace, 8443), - }, - } + el.Status.SetAddress(resources.ListenerHostname(el, *resources.MakeConfig(func(c *resources.Config) { + x := 8443 + c.Port = &x + }))) } func withKnativeStatus(el *v1beta1.EventListener) { @@ -593,20 +568,14 @@ func withStatus(el *v1beta1.EventListener) { Message: "Service exists", }}, }, - AddressStatus: duckv1beta1.AddressStatus{ - Address: &duckv1beta1.Addressable{ - URL: &apis.URL{ - Scheme: "http", - Host: listenerHostname(generatedResourceName, namespace, DefaultPort), - }, - }, - }, Configuration: v1beta1.EventListenerConfig{ GeneratedResourceName: generatedResourceName, }, } + el.Status.SetAddress(resources.ListenerHostname(el, *resources.MakeConfig())) } + func withAddedLabels(el *v1beta1.EventListener) { el.Labels = updateLabel } @@ -643,11 +612,11 @@ func TestReconcile(t *testing.T) { customPort := 80 - configWithSetSecurityContextFalse := makeConfig(func(c *Config) { + configWithSetSecurityContextFalse := resources.MakeConfig(func(c *resources.Config) { c.SetSecurityContext = ptr.Bool(false) }) - configWithPortSet := makeConfig(func(c *Config) { + configWithPortSet := resources.MakeConfig(func(c *resources.Config) { c.Port = &customPort }) @@ -887,7 +856,9 @@ func TestReconcile(t *testing.T) { el.Status.Address = &duckv1beta1.Addressable{ URL: &apis.URL{ Scheme: "http", - Host: listenerHostname(generatedResourceName, namespace, customPort), + Host: resources.ListenerHostname(el, *resources.MakeConfig(func(c *resources.Config) { + c.Port = &customPort + })), }, } }) @@ -971,7 +942,7 @@ func TestReconcile(t *testing.T) { }) imageForCustomResource := makeWithPod(func(data *duckv1.WithPod) { - data.Spec.Template.Spec.Containers[0].Image = DefaultImage + data.Spec.Template.Spec.Containers[0].Image = resources.DefaultImage }) annotationForCustomResource := makeWithPod(func(data *duckv1.WithPod) { data.Annotations = map[string]string{ @@ -1041,9 +1012,9 @@ func TestReconcile(t *testing.T) { tests := []struct { name string key string - config *Config // Config of the reconciler - startResources test.Resources // State of the world before we call Reconcile - endResources test.Resources // Expected State of the world after calling Reconcile + config *resources.Config // Config of the reconciler + startResources test.Resources // State of the world before we call Reconcile + endResources test.Resources // Expected State of the world after calling Reconcile }{{ name: "eventlistener creation", key: reconcileKey, @@ -1492,8 +1463,8 @@ func TestReconcile(t *testing.T) { ConfigMaps: []*corev1.ConfigMap{loggingConfigMap, observabilityConfigMap}, WithPod: []*duckv1.WithPod{nodeSelectorForCustomResource}, }, - }, - } + }} + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Setup with startResources @@ -1639,9 +1610,9 @@ func TestReconcile_InvalidForCustomResource(t *testing.T) { tests := []struct { name string key string - config *Config // Config of the reconciler - startResources test.Resources // State of the world before we call Reconcile - endResources test.Resources // Expected State of the world after calling Reconcile + config *resources.Config // Config of the reconciler + startResources test.Resources // State of the world before we call Reconcile + endResources test.Resources // Expected State of the world after calling Reconcile }{ { name: "eventlistener with custome resource", @@ -1690,9 +1661,9 @@ func TestReconcile_Delete(t *testing.T) { tests := []struct { name string key string - config *Config // Config of the reconciler - startResources test.Resources // State of the world before we call Reconcile - endResources test.Resources // Expected State of the world after calling Reconcile + config *resources.Config // Config of the reconciler + startResources test.Resources // State of the world before we call Reconcile + endResources test.Resources // Expected State of the world after calling Reconcile }{{ name: "delete eventlistener with remaining eventlisteners", key: fmt.Sprintf("%s/%s", namespace, "el-2"), @@ -1773,80 +1744,6 @@ func TestReconcile_Delete(t *testing.T) { } } -func Test_getServicePort(t *testing.T) { - tests := []struct { - name string - el *v1beta1.EventListener - config Config - expectedServicePort corev1.ServicePort - }{{ - name: "EventListener with status", - el: makeEL(withStatus), - config: *makeConfig(), - expectedServicePort: corev1.ServicePort{ - Name: eventListenerServicePortName, - Protocol: corev1.ProtocolTCP, - Port: int32(DefaultPort), - TargetPort: intstr.IntOrString{ - IntVal: int32(eventListenerContainerPort), - }, - }, - }, { - name: "EventListener with TLS configuration", - el: makeEL(withStatus, withTLSPort, func(el *v1beta1.EventListener) { - el.Spec.Resources.KubernetesResource = &v1beta1.KubernetesResource{ - WithPodSpec: duckv1.WithPodSpec{ - Template: duckv1.PodSpecable{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{ - Env: []corev1.EnvVar{{ - Name: "TLS_CERT", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "tls-secret-key", - }, - Key: "tls.crt", - }, - }, - }, { - Name: "TLS_KEY", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "tls-secret-key", - }, - Key: "tls.key", - }, - }, - }}, - }}, - }, - }, - }, - } - }), - config: *makeConfig(), - expectedServicePort: corev1.ServicePort{ - Name: eventListenerServiceTLSPortName, - Protocol: corev1.ProtocolTCP, - Port: int32(8443), - TargetPort: intstr.IntOrString{ - IntVal: int32(eventListenerContainerPort), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - actualPort := getServicePort(tt.el, tt.config) - if diff := cmp.Diff(tt.expectedServicePort, actualPort); diff != "" { - t.Errorf("getServicePort() did not return expected. -want, +got: %s", diff) - } - }) - } -} - func Test_wrapError(t *testing.T) { tests := []struct { name string @@ -1891,131 +1788,3 @@ func Test_wrapError(t *testing.T) { }) } } - -func TestGenerateResourceLabels(t *testing.T) { - staticResourceLabels := map[string]string{ - "app.kubernetes.io/managed-by": "EventListener", - "app.kubernetes.io/part-of": "Triggers", - } - - expectedLabels := kmeta.UnionMaps(staticResourceLabels, map[string]string{"eventlistener": eventListenerName}) - actualLabels := GenerateResourceLabels(eventListenerName, staticResourceLabels) - if diff := cmp.Diff(expectedLabels, actualLabels); diff != "" { - t.Errorf("mergeLabels() did not return expected. -want, +got: %s", diff) - } -} - -func Test_generateObjectMeta(t *testing.T) { - blockOwnerDeletion := true - isController := true - tests := []struct { - name string - el *v1beta1.EventListener - expectedObjectMeta metav1.ObjectMeta - }{{ - name: "Empty EventListener", - el: &v1beta1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventListenerName, - Namespace: "", - }, - }, - expectedObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "triggers.tekton.dev/v1beta1", - Kind: "EventListener", - Name: eventListenerName, - UID: "", - Controller: &isController, - BlockOwnerDeletion: &blockOwnerDeletion, - }}, - Labels: generatedLabels, - }, - }, { - name: "EventListener with Configuration", - el: &v1beta1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventListenerName, - Namespace: "", - }, - Status: v1beta1.EventListenerStatus{ - Configuration: v1beta1.EventListenerConfig{ - GeneratedResourceName: "generatedName", - }, - }, - }, - - expectedObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "generatedName", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "triggers.tekton.dev/v1beta1", - Kind: "EventListener", - Name: eventListenerName, - UID: "", - Controller: &isController, - BlockOwnerDeletion: &blockOwnerDeletion, - }}, - Labels: generatedLabels, - }, - }, { - name: "EventListener with Labels", - el: &v1beta1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventListenerName, - Namespace: "", - Labels: map[string]string{ - "k": "v", - }, - }, - }, - expectedObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "triggers.tekton.dev/v1beta1", - Kind: "EventListener", - Name: eventListenerName, - UID: "", - Controller: &isController, - BlockOwnerDeletion: &blockOwnerDeletion, - }}, - Labels: kmeta.UnionMaps(map[string]string{"k": "v"}, generatedLabels), - }, - }, { - name: "EventListener with Annotation", - el: &v1beta1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: eventListenerName, - Namespace: "", - Annotations: map[string]string{ - "k": "v", - }, - }, - }, - expectedObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: "", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: "triggers.tekton.dev/v1beta1", - Kind: "EventListener", - Name: eventListenerName, - UID: "", - Controller: &isController, - BlockOwnerDeletion: &blockOwnerDeletion, - }}, - Labels: generatedLabels, - Annotations: map[string]string{"k": "v"}, - }, - }} - for i := range tests { - t.Run(tests[i].name, func(t *testing.T) { - actualObjectMeta := generateObjectMeta(tests[i].el, DefaultStaticResourceLabels) - if diff := cmp.Diff(tests[i].expectedObjectMeta, actualObjectMeta); diff != "" { - t.Errorf("generateObjectMeta() did not return expected. -want, +got: %s", diff) - } - }) - } -} diff --git a/pkg/reconciler/eventlistener/resources/common_test.go b/pkg/reconciler/eventlistener/resources/common_test.go new file mode 100644 index 0000000000..5a5010a929 --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/common_test.go @@ -0,0 +1,89 @@ +/* +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 resources + +import ( + "fmt" + + "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" +) + +// makeEL is a helper to build an EventListener for tests. +// It generates a base EventListener that can then be modified by the passed in op function +// If no ops are specified, it generates a base EventListener with no triggers and no Status +func makeEL(ops ...func(el *v1beta1.EventListener)) *v1beta1.EventListener { + e := &v1beta1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: namespace, + }, + Spec: v1beta1.EventListenerSpec{ + ServiceAccountName: "sa", + }, + } + for _, op := range ops { + op(e) + } + return e +} + +func withStatus(el *v1beta1.EventListener) { + el.Status = v1beta1.EventListenerStatus{ + Status: duckv1.Status{ + Conditions: []apis.Condition{{ + Type: apis.ConditionType(appsv1.DeploymentAvailable), + Status: corev1.ConditionTrue, + Message: "Deployment has minimum availability", + Reason: "MinimumReplicasAvailable", + }, { + Type: v1beta1.DeploymentExists, + Status: corev1.ConditionTrue, + Message: "Deployment exists", + }, { + Type: apis.ConditionType(appsv1.DeploymentProgressing), + Status: corev1.ConditionTrue, + Message: fmt.Sprintf("ReplicaSet \"%s\" has successfully progressed.", eventListenerName), + Reason: "NewReplicaSetAvailable", + }, { + Type: apis.ConditionReady, + Status: corev1.ConditionTrue, + Message: "EventListener is ready", + }, { + Type: v1alpha1.ServiceExists, + Status: corev1.ConditionTrue, + Message: "Service exists", + }}, + }, + Configuration: v1beta1.EventListenerConfig{ + GeneratedResourceName: generatedResourceName, + }, + } + el.Status.SetAddress(ListenerHostname(el, *MakeConfig())) +} + +func withTLSPort(el *v1beta1.EventListener) { + el.Status.SetAddress(ListenerHostname(el, *MakeConfig(func(c *Config) { + x := 8443 + c.Port = &x + }))) +} diff --git a/pkg/reconciler/eventlistener/config.go b/pkg/reconciler/eventlistener/resources/config.go similarity index 77% rename from pkg/reconciler/eventlistener/config.go rename to pkg/reconciler/eventlistener/resources/config.go index 469e6b40bf..20b4bfc643 100644 --- a/pkg/reconciler/eventlistener/config.go +++ b/pkg/reconciler/eventlistener/resources/config.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package eventlistener +package resources var ( // DefaultImage is the image used by default. @@ -68,3 +68,29 @@ type Config struct { // SystemNamespace is the namespace where the reconciler is deployed. SystemNamespace string } + +type ConfigOption func(d *Config) + +// MakeConfig is a helper to build a config that is consumed by an EventListener. +// It generates a default Config for the EventListener without any flags set and accepts functions for modification. +func MakeConfig(ops ...ConfigOption) *Config { + c := &Config{ + Image: &DefaultImage, + Port: &DefaultPort, + SetSecurityContext: &DefaultSetSecurityContext, + ReadTimeOut: &DefaultReadTimeout, + WriteTimeOut: &DefaultWriteTimeout, + IdleTimeOut: &DefaultIdleTimeout, + TimeOutHandler: &DefaultTimeOutHandler, + PeriodSeconds: &DefaultPeriodSeconds, + FailureThreshold: &DefaultFailureThreshold, + + StaticResourceLabels: DefaultStaticResourceLabels, + SystemNamespace: DefaultSystemNamespace, + } + + for _, op := range ops { + op(c) + } + return c +} diff --git a/pkg/reconciler/eventlistener/resources/config_test.go b/pkg/reconciler/eventlistener/resources/config_test.go new file mode 100644 index 0000000000..b3cf7161d6 --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/config_test.go @@ -0,0 +1,37 @@ +/* +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 resources + +import ( + "testing" +) + +func TestMakeConfig(t *testing.T) { + cfg := MakeConfig() + if got, want := *cfg.Image, DefaultImage; got != want { + t.Errorf("MakeConfig().Image = %s, wanted %s", got, want) + } + + want := "ghcr.io/mattmoor/something:awesome" + cfg = MakeConfig(func(c *Config) { + c.Image = &want + }) + if got := *cfg.Image; got != want { + t.Errorf("MakeConfig().Image = %s, wanted %s", got, want) + } + +} diff --git a/pkg/reconciler/eventlistener/resources/meta.go b/pkg/reconciler/eventlistener/resources/meta.go new file mode 100644 index 0000000000..ef03815681 --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/meta.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 resources + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/kmeta" + + "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" +) + +// ObjectMeta generates the object meta that should be used by all +// resources generated by the EventListener reconciler +func ObjectMeta(el *v1beta1.EventListener, staticResourceLabels map[string]string) metav1.ObjectMeta { + return metav1.ObjectMeta{ + Namespace: el.Namespace, + Name: el.Status.Configuration.GeneratedResourceName, + OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(el)}, + Labels: kmeta.UnionMaps(el.Labels, GenerateLabels(el.Name, staticResourceLabels)), + Annotations: el.Annotations, + } +} + +// GenerateLabels generates the labels to be used on all generated resources. +func GenerateLabels(eventListenerName string, staticResourceLabels map[string]string) map[string]string { + resourceLabels := kmeta.CopyMap(staticResourceLabels) + resourceLabels["eventlistener"] = eventListenerName + return resourceLabels +} diff --git a/pkg/reconciler/eventlistener/resources/meta_test.go b/pkg/reconciler/eventlistener/resources/meta_test.go new file mode 100644 index 0000000000..d550dd0712 --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/meta_test.go @@ -0,0 +1,169 @@ +/* +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 resources + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/kmeta" +) + +var ( + eventListenerName = "my-eventlistener" + generatedResourceName = fmt.Sprintf("el-%s", eventListenerName) + + namespace = "test-pipelines" + + // Standard labels added by EL reconciler to the underlying el-deployments/services + generatedLabels = map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + "eventlistener": eventListenerName, + } +) + +func TestGenerateLabels(t *testing.T) { + staticResourceLabels := map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + } + + expectedLabels := kmeta.UnionMaps(staticResourceLabels, map[string]string{"eventlistener": eventListenerName}) + actualLabels := GenerateLabels(eventListenerName, staticResourceLabels) + if diff := cmp.Diff(expectedLabels, actualLabels); diff != "" { + t.Errorf("mergeLabels() did not return expected. -want, +got: %s", diff) + } +} + +func TestObjectMeta(t *testing.T) { + blockOwnerDeletion := true + isController := true + tests := []struct { + name string + el *v1beta1.EventListener + expectedObjectMeta metav1.ObjectMeta + }{{ + name: "Empty EventListener", + el: &v1beta1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: "", + }, + }, + expectedObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "triggers.tekton.dev/v1beta1", + Kind: "EventListener", + Name: eventListenerName, + UID: "", + Controller: &isController, + BlockOwnerDeletion: &blockOwnerDeletion, + }}, + Labels: generatedLabels, + }, + }, { + name: "EventListener with Configuration", + el: &v1beta1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: "", + }, + Status: v1beta1.EventListenerStatus{ + Configuration: v1beta1.EventListenerConfig{ + GeneratedResourceName: "generatedName", + }, + }, + }, + + expectedObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "generatedName", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "triggers.tekton.dev/v1beta1", + Kind: "EventListener", + Name: eventListenerName, + UID: "", + Controller: &isController, + BlockOwnerDeletion: &blockOwnerDeletion, + }}, + Labels: generatedLabels, + }, + }, { + name: "EventListener with Labels", + el: &v1beta1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: "", + Labels: map[string]string{ + "k": "v", + }, + }, + }, + expectedObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "triggers.tekton.dev/v1beta1", + Kind: "EventListener", + Name: eventListenerName, + UID: "", + Controller: &isController, + BlockOwnerDeletion: &blockOwnerDeletion, + }}, + Labels: kmeta.UnionMaps(map[string]string{"k": "v"}, generatedLabels), + }, + }, { + name: "EventListener with Annotation", + el: &v1beta1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: eventListenerName, + Namespace: "", + Annotations: map[string]string{ + "k": "v", + }, + }, + }, + expectedObjectMeta: metav1.ObjectMeta{ + Namespace: "", + Name: "", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: "triggers.tekton.dev/v1beta1", + Kind: "EventListener", + Name: eventListenerName, + UID: "", + Controller: &isController, + BlockOwnerDeletion: &blockOwnerDeletion, + }}, + Labels: generatedLabels, + Annotations: map[string]string{"k": "v"}, + }, + }} + for i := range tests { + t.Run(tests[i].name, func(t *testing.T) { + actualObjectMeta := ObjectMeta(tests[i].el, DefaultStaticResourceLabels) + if diff := cmp.Diff(tests[i].expectedObjectMeta, actualObjectMeta); diff != "" { + t.Errorf("generateObjectMeta() did not return expected. -want, +got: %s", diff) + } + }) + } +} diff --git a/pkg/reconciler/eventlistener/resources/service.go b/pkg/reconciler/eventlistener/resources/service.go new file mode 100644 index 0000000000..9f1993460b --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/service.go @@ -0,0 +1,118 @@ +/* +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 resources + +import ( + "fmt" + + "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "knative.dev/pkg/network" +) + +const ( + // eventListenerServicePortName defines service port name for EventListener Service + eventListenerServicePortName = "http-listener" + // eventListenerServiceTLSPortName defines service TLS port name for EventListener Service + eventListenerServiceTLSPortName = "https-listener" + // eventListenerMetricsPortName defines the metrics port name by the EventListener Container + eventListenerMetricsPortName = "http-metrics" + // eventListenerContainerPort defines service port for EventListener Service + eventListenerContainerPort = 8080 + // eventListenerMetricsPort defines metrics port for EventListener Service + eventListenerMetricsPort = 9000 +) + +var metricsPort = corev1.ServicePort{ + Name: eventListenerMetricsPortName, + Protocol: corev1.ProtocolTCP, + Port: int32(9000), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerMetricsPort), + }, +} + +func MakeService(el *v1beta1.EventListener, c Config) *corev1.Service { + // for backward compatibility with original behavior + var serviceType corev1.ServiceType + if el.Spec.Resources.KubernetesResource != nil && el.Spec.Resources.KubernetesResource.ServiceType != "" { + serviceType = el.Spec.Resources.KubernetesResource.ServiceType + } + + servicePort := ServicePort(el, c) + + return &corev1.Service{ + ObjectMeta: ObjectMeta(el, c.StaticResourceLabels), + Spec: corev1.ServiceSpec{ + Selector: GenerateLabels(el.Name, c.StaticResourceLabels), + Type: serviceType, + Ports: []corev1.ServicePort{servicePort, metricsPort}}, + } +} + +func ServicePort(el *v1beta1.EventListener, c Config) corev1.ServicePort { + var elCert, elKey string + + servicePortName := eventListenerServicePortName + servicePortPort := *c.Port + + certEnv := map[string]*corev1.EnvVarSource{} + if el.Spec.Resources.KubernetesResource != nil { + if len(el.Spec.Resources.KubernetesResource.Template.Spec.Containers) != 0 { + for i := range el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env { + certEnv[el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env[i].Name] = + el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Env[i].ValueFrom + } + } + } + + if v, ok := certEnv["TLS_CERT"]; ok { + elCert = v.SecretKeyRef.Key + } else { + elCert = "" + } + if v, ok := certEnv["TLS_KEY"]; ok { + elKey = v.SecretKeyRef.Key + } else { + elKey = "" + } + + if elCert != "" && elKey != "" { + servicePortName = eventListenerServiceTLSPortName + if *c.Port == DefaultPort { + // We return port 8443 if TLS is enabled and the default HTTP port is set. + // This effectively makes 8443 the default HTTPS port unless a user explicitly sets a different port. + servicePortPort = 8443 + } + } + + return corev1.ServicePort{ + Name: servicePortName, + Protocol: corev1.ProtocolTCP, + Port: int32(servicePortPort), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerContainerPort), + }, + } +} + +// ListenerHostname returns the intended hostname for the EventListener service. +func ListenerHostname(el *v1beta1.EventListener, c Config) string { + sp := ServicePort(el, c) + return network.GetServiceHostname(el.Status.Configuration.GeneratedResourceName, el.Namespace) + fmt.Sprintf(":%d", sp.Port) +} diff --git a/pkg/reconciler/eventlistener/resources/service_test.go b/pkg/reconciler/eventlistener/resources/service_test.go new file mode 100644 index 0000000000..0ded7645c8 --- /dev/null +++ b/pkg/reconciler/eventlistener/resources/service_test.go @@ -0,0 +1,186 @@ +/* +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 resources + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/tektoncd/triggers/pkg/apis/triggers/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" +) + +func TestService(t *testing.T) { + config := *MakeConfig() + + tests := []struct { + name string + el *v1beta1.EventListener + want *corev1.Service + }{{ + name: "EventListener with status", + el: makeEL(withStatus), + want: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: generatedResourceName, + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + "eventlistener": eventListenerName, + }, + OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(makeEL())}, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{ + Name: eventListenerServicePortName, + Protocol: corev1.ProtocolTCP, + Port: int32(*config.Port), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerContainerPort), + }, + }, metricsPort}, + Selector: map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + "eventlistener": eventListenerName, + }, + }, + }, + }, { + name: "EventListener with type: LoadBalancer", + el: makeEL(func(el *v1beta1.EventListener) { + el.Spec.Resources.KubernetesResource = &v1beta1.KubernetesResource{ + ServiceType: "LoadBalancer", + } + }, withStatus), + want: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: generatedResourceName, + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + "eventlistener": eventListenerName, + }, + OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(makeEL())}, + }, + Spec: corev1.ServiceSpec{ + Type: "LoadBalancer", + Ports: []corev1.ServicePort{{ + Name: eventListenerServicePortName, + Protocol: corev1.ProtocolTCP, + Port: int32(*config.Port), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerContainerPort), + }, + }, metricsPort}, + Selector: map[string]string{ + "app.kubernetes.io/managed-by": "EventListener", + "app.kubernetes.io/part-of": "Triggers", + "eventlistener": eventListenerName, + }, + }, + }}} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := MakeService(tt.el, config) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("MakeService() did not return expected. -want, +got: %s", diff) + } + }) + } +} + +func TestServicePort(t *testing.T) { + tests := []struct { + name string + el *v1beta1.EventListener + config Config + expectedServicePort corev1.ServicePort + }{{ + name: "EventListener with status", + el: makeEL(withStatus), + config: *MakeConfig(), + expectedServicePort: corev1.ServicePort{ + Name: eventListenerServicePortName, + Protocol: corev1.ProtocolTCP, + Port: int32(DefaultPort), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerContainerPort), + }, + }, + }, { + name: "EventListener with TLS configuration", + el: makeEL(withStatus, withTLSPort, func(el *v1beta1.EventListener) { + el.Spec.Resources.KubernetesResource = &v1beta1.KubernetesResource{ + WithPodSpec: duckv1.WithPodSpec{ + Template: duckv1.PodSpecable{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Env: []corev1.EnvVar{{ + Name: "TLS_CERT", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tls-secret-key", + }, + Key: "tls.crt", + }, + }, + }, { + Name: "TLS_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tls-secret-key", + }, + Key: "tls.key", + }, + }, + }}, + }}, + }, + }, + }, + } + }), + config: *MakeConfig(), + expectedServicePort: corev1.ServicePort{ + Name: eventListenerServiceTLSPortName, + Protocol: corev1.ProtocolTCP, + Port: int32(8443), + TargetPort: intstr.IntOrString{ + IntVal: int32(eventListenerContainerPort), + }, + }, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualPort := ServicePort(tt.el, tt.config) + if diff := cmp.Diff(tt.expectedServicePort, actualPort); diff != "" { + t.Errorf("ServicePort() did not return expected. -want, +got: %s", diff) + } + }) + } +} diff --git a/test/eventlistener_test.go b/test/eventlistener_test.go index c33276f53e..2f6895e482 100644 --- a/test/eventlistener_test.go +++ b/test/eventlistener_test.go @@ -41,6 +41,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" eventReconciler "github.com/tektoncd/triggers/pkg/reconciler/eventlistener" + "github.com/tektoncd/triggers/pkg/reconciler/eventlistener/resources" "github.com/tektoncd/triggers/pkg/sink" bldr "github.com/tektoncd/triggers/test/builder" corev1 "k8s.io/api/core/v1" @@ -380,7 +381,7 @@ func TestEventListenerCreate(t *testing.T) { }, } - labelSelector := fields.SelectorFromSet(eventReconciler.GenerateResourceLabels(el.Name, eventReconciler.DefaultStaticResourceLabels)).String() + labelSelector := fields.SelectorFromSet(resources.GenerateLabels(el.Name, resources.DefaultStaticResourceLabels)).String() // Grab EventListener sink pods sinkPods, err := c.KubeClient.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector}) if err != nil {