diff --git a/CHANGELOG.md b/CHANGELOG.md index 5636adb..b0c16ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All notable changes to this project will be documented in this file. +## [0.5.0] + +### Added + +- Added Ingress (vanilla) support ([#79](https://github.com/application-stacks/runtime-component-operator/pull/79)) +- Added support for external service bindings ([#76](https://github.com/application-stacks/runtime-component-operator/pull/76)) +- Added additional service ports support ([#80](https://github.com/application-stacks/runtime-component-operator/pull/80)) +- Added support to specify NodePort on service ([#60](https://github.com/application-stacks/runtime-component-operator/pull/60)) + ## [0.4.1] ### Fixed @@ -89,7 +98,8 @@ All notable changes to this project will be documented in this file. The initial release of the Appsody Operator 🎉🥳 -[Unreleased]: https://github.com/appsody/appsody-operator/compare/v0.4.1...HEAD +[Unreleased]: https://github.com/appsody/appsody-operator/compare/v0.5.0...HEAD +[0.5.0]: https://github.com/appsody/appsody-operator/compare/v0.4.1...v0.5.0 [0.4.1]: https://github.com/appsody/appsody-operator/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/appsody/appsody-operator/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/appsody/appsody-operator/compare/v0.2.2...v0.3.0 diff --git a/build/Dockerfile b/build/Dockerfile index 03cea8d..4c6bc15 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -2,7 +2,7 @@ FROM registry.access.redhat.com/ubi8/ubi-minimal:latest LABEL vendor="Appsody" \ name="Appsody Application Operator" \ - version="0.4.1" \ + version="0.5.0" \ summary="Image for Appsody Application Operator" \ description="This image contains the controller for Appsody Application Operator. See https://github.com/appsody/appsody-operator#appsody-application-operator" diff --git a/deploy/cluster_role.yaml b/deploy/cluster_role.yaml index 3948d3b..c2a765e 100644 --- a/deploy/cluster_role.yaml +++ b/deploy/cluster_role.yaml @@ -83,4 +83,17 @@ rules: resources: - applications verbs: + - '*' +- apiGroups: + - apps.openshift.io + resources: + - servicebindingrequests + verbs: + - '*' +- apiGroups: + - networking.k8s.io + - extensions + resources: + - ingresses + verbs: - '*' \ No newline at end of file diff --git a/deploy/crds/appsody.dev_appsodyapplications_crd.yaml b/deploy/crds/appsody.dev_appsodyapplications_crd.yaml index a7a972d..514386b 100644 --- a/deploy/crds/appsody.dev_appsodyapplications_crd.yaml +++ b/deploy/crds/appsody.dev_appsodyapplications_crd.yaml @@ -88,6 +88,14 @@ spec: format: int32 type: integer type: object + bindings: + description: AppsodyBindings represents service binding related parameters + properties: + autoDetect: + type: boolean + resourceRef: + type: string + type: object createAppDefinition: type: boolean createKnativeService: @@ -2154,6 +2162,11 @@ spec: - name type: object type: array + nodePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer port: format: int32 maximum: 65535 @@ -2161,6 +2174,51 @@ spec: type: integer portName: type: string + ports: + items: + description: ServicePort contains information on service's port. + properties: + name: + description: The name of this port within the service. This + must be a DNS_LABEL. All ports within a ServiceSpec must + have unique names. When considering the endpoints for a + Service, this must match the 'name' field in the EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: 'The port on each node on which this service + is exposed when type=NodePort or LoadBalancer. Usually assigned + by the system. If specified, it will be allocated to the + service if unused or else creation of the service will fail. + Default is to auto-allocate a port if the ServiceType of + this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + description: The IP protocol for this port. Supports "TCP", + "UDP", and "SCTP". Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: 'Number or name of the port to access on the + pods targeted by the service. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. If this is a + string, it will be looked up as a named port in the target + Pod''s container ports. If this is not specified, the value + of the ''port'' field is used (an identity map). This field + is ignored for services with clusterIP=None, and should + be omitted or set equal to the ''port'' field. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service' + required: + - port + type: object + type: array provides: description: ServiceBindingProvides represents information about properties: @@ -4711,6 +4769,10 @@ spec: type: object imageReference: type: string + resolvedBindings: + items: + type: string + type: array type: object version: v1beta1 versions: diff --git a/deploy/releases/daily/appsody-app-cluster-rbac.yaml b/deploy/releases/daily/appsody-app-cluster-rbac.yaml index 05ec611..f24c3ef 100644 --- a/deploy/releases/daily/appsody-app-cluster-rbac.yaml +++ b/deploy/releases/daily/appsody-app-cluster-rbac.yaml @@ -85,6 +85,19 @@ rules: - applications verbs: - '*' +- apiGroups: + - apps.openshift.io + resources: + - servicebindingrequests + verbs: + - '*' +- apiGroups: + - networking.k8s.io + - extensions + resources: + - ingresses + verbs: + - '*' --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/releases/daily/appsody-app-crd.yaml b/deploy/releases/daily/appsody-app-crd.yaml index a7a972d..514386b 100644 --- a/deploy/releases/daily/appsody-app-crd.yaml +++ b/deploy/releases/daily/appsody-app-crd.yaml @@ -88,6 +88,14 @@ spec: format: int32 type: integer type: object + bindings: + description: AppsodyBindings represents service binding related parameters + properties: + autoDetect: + type: boolean + resourceRef: + type: string + type: object createAppDefinition: type: boolean createKnativeService: @@ -2154,6 +2162,11 @@ spec: - name type: object type: array + nodePort: + format: int32 + maximum: 65535 + minimum: 0 + type: integer port: format: int32 maximum: 65535 @@ -2161,6 +2174,51 @@ spec: type: integer portName: type: string + ports: + items: + description: ServicePort contains information on service's port. + properties: + name: + description: The name of this port within the service. This + must be a DNS_LABEL. All ports within a ServiceSpec must + have unique names. When considering the endpoints for a + Service, this must match the 'name' field in the EndpointPort. + Optional if only one ServicePort is defined on this service. + type: string + nodePort: + description: 'The port on each node on which this service + is exposed when type=NodePort or LoadBalancer. Usually assigned + by the system. If specified, it will be allocated to the + service if unused or else creation of the service will fail. + Default is to auto-allocate a port if the ServiceType of + this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + description: The IP protocol for this port. Supports "TCP", + "UDP", and "SCTP". Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: 'Number or name of the port to access on the + pods targeted by the service. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. If this is a + string, it will be looked up as a named port in the target + Pod''s container ports. If this is not specified, the value + of the ''port'' field is used (an identity map). This field + is ignored for services with clusterIP=None, and should + be omitted or set equal to the ''port'' field. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service' + required: + - port + type: object + type: array provides: description: ServiceBindingProvides represents information about properties: @@ -4711,6 +4769,10 @@ spec: type: object imageReference: type: string + resolvedBindings: + items: + type: string + type: array type: object version: v1beta1 versions: diff --git a/deploy/releases/daily/appsody-app-operator.yaml b/deploy/releases/daily/appsody-app-operator.yaml index 8a81397..122af30 100644 --- a/deploy/releases/daily/appsody-app-operator.yaml +++ b/deploy/releases/daily/appsody-app-operator.yaml @@ -88,6 +88,19 @@ rules: - applications verbs: - '*' +- apiGroups: + - apps.openshift.io + resources: + - servicebindingrequests + verbs: + - '*' +- apiGroups: + - networking.k8s.io + - extensions + resources: + - ingresses + verbs: + - '*' --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/role.yaml b/deploy/role.yaml index dc04b85..1753eaf 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -82,4 +82,17 @@ rules: resources: - applications verbs: + - '*' +- apiGroups: + - apps.openshift.io + resources: + - servicebindingrequests + verbs: + - '*' +- apiGroups: + - networking.k8s.io + - extensions + resources: + - ingresses + verbs: - '*' \ No newline at end of file diff --git a/doc/user-guide.md b/doc/user-guide.md index 31f22ac..3bfbd6b 100644 --- a/doc/user-guide.md +++ b/doc/user-guide.md @@ -64,7 +64,9 @@ Each `AppsodyApplication` CR must at least specify the `applicationImage` parame | `service.port` | The port exposed by the container, directing traffic to the application. | | `service.portName` | The name for the port exposed by the container. | | `service.targetPort` | The port that the appsody application uses within the container. Defaults to the value of `service.port`. | +| `service.ports` | An array consisting of service ports. | | `service.type` | The Kubernetes [Service Type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types). | +| `service.nodePort` | Node proxies this port into your service. Please note once this port is set to a non-zero value it cannot be reset to zero. | | `service.annotations` | Annotations to be added to the service. | | `service.certificate` | A YAML object representing a [Certificate](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1alpha2.CertificateSpec). | | `service.certificateSecretRef` | A name of a secret that already contains TLS key, certificate and CA to be mounted in the pod. | diff --git a/go.mod b/go.mod index 71b408d..c12fa05 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/appsody/appsody-operator go 1.13 require ( - github.com/application-stacks/runtime-component-operator v0.4.2 + github.com/application-stacks/runtime-component-operator v0.5.0 github.com/coreos/prometheus-operator v0.34.0 github.com/go-openapi/spec v0.19.4 github.com/jetstack/cert-manager v0.12.0 diff --git a/go.sum b/go.sum index 717f47f..f24562c 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/ant31/crd-validation v0.0.0-20180702145049-30f8a35d0ac2/go.mod h1:X0noFIik9YqfhGYBLEHg8LJKEwy7QIitLQuFMpKLcPk= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/application-stacks/runtime-component-operator v0.4.2 h1:itFM372355B1Ka4TT1uMOmgLLndF9qxUq1p76HSNX7k= -github.com/application-stacks/runtime-component-operator v0.4.2/go.mod h1:3TJa36rzYElnHB7MRA/2GCpEq4SbGmBVw5O7ueN/JeM= +github.com/application-stacks/runtime-component-operator v0.5.0 h1:m06rPIUi5Btgn3QMmMA79U0r7/IJztPgJ3maV3KMIGQ= +github.com/application-stacks/runtime-component-operator v0.5.0/go.mod h1:fcbZ5gYAnxyl+LANAlOqNO09R/e6jdCXIlSgvHVae9s= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/pkg/apis/appsody/v1beta1/appsodyapplication_types.go b/pkg/apis/appsody/v1beta1/appsodyapplication_types.go index d3a7c8c..9ee249c 100644 --- a/pkg/apis/appsody/v1beta1/appsodyapplication_types.go +++ b/pkg/apis/appsody/v1beta1/appsodyapplication_types.go @@ -47,9 +47,12 @@ type AppsodyApplicationSpec struct { ApplicationName string `json:"applicationName,omitempty"` // +listType=map // +listMapKey=name - InitContainers []corev1.Container `json:"initContainers,omitempty"` + InitContainers []corev1.Container `json:"initContainers,omitempty"` + // +listType=map + // +listMapKey=name SidecarContainers []corev1.Container `json:"sidecarContainers,omitempty"` Route *AppsodyRoute `json:"route,omitempty"` + Bindings *AppsodyBindings `json:"bindings,omitempty"` } // AppsodyApplicationAutoScaling ... @@ -74,8 +77,14 @@ type AppsodyApplicationService struct { // +kubebuilder:validation:Minimum=1 TargetPort *int32 `json:"targetPort,omitempty"` + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:validation:Minimum=0 + NodePort *int32 `json:"nodePort,omitempty"` + PortName string `json:"portName,omitempty"` + Ports []corev1.ServicePort `json:"ports,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` // +listType=atomic Consumes []ServiceBindingConsumes `json:"consumes,omitempty"` @@ -138,13 +147,21 @@ type ServiceBindingAuth struct { Password corev1.SecretKeySelector `json:"password,omitempty"` } +// AppsodyBindings represents service binding related parameters +type AppsodyBindings struct { + AutoDetect *bool `json:"autoDetect,omitempty"` + ResourceRef string `json:"resourceRef,omitempty"` +} + // AppsodyApplicationStatus defines the observed state of AppsodyApplication // +k8s:openapi-gen=true type AppsodyApplicationStatus struct { // +listType=atomic Conditions []StatusCondition `json:"conditions,omitempty"` ConsumedServices common.ConsumedServices `json:"consumedServices,omitempty"` - ImageReference string `json:"imageReference,omitempty"` + // +listType=set + ResolvedBindings []string `json:"resolvedBindings,omitempty"` + ImageReference string `json:"imageReference,omitempty"` } // StatusCondition ... @@ -353,6 +370,24 @@ func (cr *AppsodyApplication) GetRoute() common.BaseComponentRoute { return cr.Spec.Route } +// GetBindings returns route configuration for RuntimeComponent +func (cr *AppsodyApplication) GetBindings() common.BaseComponentBindings { + if cr.Spec.Bindings == nil { + return nil + } + return cr.Spec.Bindings +} + +// GetResolvedBindings returns a map of all the service names to be consumed by the application +func (s *AppsodyApplicationStatus) GetResolvedBindings() []string { + return s.ResolvedBindings +} + +// SetResolvedBindings sets ConsumedServices +func (s *AppsodyApplicationStatus) SetResolvedBindings(rb []string) { + s.ResolvedBindings = rb +} + // GetConsumedServices returns a map of all the service names to be consumed by the application func (s *AppsodyApplicationStatus) GetConsumedServices() common.ConsumedServices { if s.ConsumedServices == nil { @@ -401,7 +436,7 @@ func (s *AppsodyApplicationStorage) GetMountPath() string { return s.MountPath } -// GetVolumeClaimTemplate returns a template representing requested persitent volume +// GetVolumeClaimTemplate returns a template representing requested persistent volume func (s *AppsodyApplicationStorage) GetVolumeClaimTemplate() *corev1.PersistentVolumeClaim { return s.VolumeClaimTemplate } @@ -416,12 +451,15 @@ func (s *AppsodyApplicationService) GetPort() int32 { return s.Port } -// GetPortName return the name of the service port -func (s *AppsodyApplicationService) GetPortName() string { - return s.PortName +// GetNodePort returns service nodePort +func (s *AppsodyApplicationService) GetNodePort() *int32 { + if s.NodePort == nil { + return nil + } + return s.NodePort } -// GetTargetPort returns the internal container port to be targetted +// GetTargetPort returns the internal target port for containers func (s *AppsodyApplicationService) GetTargetPort() *int32 { if s.TargetPort == nil { return nil @@ -429,11 +467,21 @@ func (s *AppsodyApplicationService) GetTargetPort() *int32 { return s.TargetPort } +// GetPortName returns name of service port +func (s *AppsodyApplicationService) GetPortName() string { + return s.PortName +} + // GetType returns service type func (s *AppsodyApplicationService) GetType() *corev1.ServiceType { return s.Type } +// GetPorts returns a list of service ports +func (s *AppsodyApplicationService) GetPorts() []corev1.ServicePort { + return s.Ports +} + // GetProvides returns service provider configuration func (s *AppsodyApplicationService) GetProvides() common.ServiceBindingProvides { if s.Provides == nil { @@ -544,6 +592,14 @@ func (r *AppsodyRoute) GetCertificate() common.Certificate { return r.Certificate } +// GetCertificateSecretRef returns the secret ref for route certificate +func (r *AppsodyRoute) GetCertificateSecretRef() *string { + if r.CertificateSecretRef == nil { + return nil + } + return r.CertificateSecretRef +} + // GetTermination returns terminatation of the route's TLS func (r *AppsodyRoute) GetTermination() *routev1.TLSTerminationType { return r.Termination @@ -564,12 +620,14 @@ func (r *AppsodyRoute) GetPath() string { return r.Path } -// GetCertificateSecretRef returns the secret ref for route certificate -func (r *AppsodyRoute) GetCertificateSecretRef() *string { - if r.CertificateSecretRef == nil { - return nil - } - return r.CertificateSecretRef +// GetAutoDetect returns a boolean to specify if the operator should auto-detect ServiceBinding CRs with the same name as the RuntimeComponent CR +func (r *AppsodyBindings) GetAutoDetect() *bool { + return r.AutoDetect +} + +// GetResourceRef returns name of ServiceBinding CRs created manually in the same namespace as the RuntimeComponent CR +func (r *AppsodyBindings) GetResourceRef() string { + return r.ResourceRef } // Initialize the AppsodyApplication instance with values from the default and constant ConfigMap @@ -619,6 +677,7 @@ func (cr *AppsodyApplication) Initialize(defaults AppsodyApplicationSpec, consta } } + // Default applicationName to cr.Name, if a user sets createAppDefinition to true but doesn't set applicationName if cr.Spec.ApplicationName == "" { if cr.Labels != nil && cr.Labels["app.kubernetes.io/part-of"] != "" { cr.Spec.ApplicationName = cr.Labels["app.kubernetes.io/part-of"] diff --git a/pkg/apis/appsody/v1beta1/zz_generated.deepcopy.go b/pkg/apis/appsody/v1beta1/zz_generated.deepcopy.go index 26350b9..733925f 100644 --- a/pkg/apis/appsody/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/appsody/v1beta1/zz_generated.deepcopy.go @@ -144,6 +144,16 @@ func (in *AppsodyApplicationService) DeepCopyInto(out *AppsodyApplicationService *out = new(int32) **out = **in } + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(int32) + **out = **in + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]corev1.ServicePort, len(*in)) + copy(*out, *in) + } if in.Annotations != nil { in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) @@ -309,6 +319,11 @@ func (in *AppsodyApplicationSpec) DeepCopyInto(out *AppsodyApplicationSpec) { *out = new(AppsodyRoute) (*in).DeepCopyInto(*out) } + if in.Bindings != nil { + in, out := &in.Bindings, &out.Bindings + *out = new(AppsodyBindings) + (*in).DeepCopyInto(*out) + } return } @@ -347,6 +362,11 @@ func (in *AppsodyApplicationStatus) DeepCopyInto(out *AppsodyApplicationStatus) (*out)[key] = outVal } } + if in.ResolvedBindings != nil { + in, out := &in.ResolvedBindings, &out.ResolvedBindings + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -381,6 +401,27 @@ func (in *AppsodyApplicationStorage) DeepCopy() *AppsodyApplicationStorage { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AppsodyBindings) DeepCopyInto(out *AppsodyBindings) { + *out = *in + if in.AutoDetect != nil { + in, out := &in.AutoDetect, &out.AutoDetect + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppsodyBindings. +func (in *AppsodyBindings) DeepCopy() *AppsodyBindings { + if in == nil { + return nil + } + out := new(AppsodyBindings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppsodyRoute) DeepCopyInto(out *AppsodyRoute) { *out = *in diff --git a/pkg/apis/appsody/v1beta1/zz_generated.openapi.go b/pkg/apis/appsody/v1beta1/zz_generated.openapi.go index 69e860a..ebe840a 100644 --- a/pkg/apis/appsody/v1beta1/zz_generated.openapi.go +++ b/pkg/apis/appsody/v1beta1/zz_generated.openapi.go @@ -123,12 +123,30 @@ func schema_pkg_apis_appsody_v1beta1_AppsodyApplicationService(ref common.Refere Format: "int32", }, }, + "nodePort": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, "portName": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, + "ports": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.ServicePort"), + }, + }, + }, + }, + }, "annotations": { SchemaProps: spec.SchemaProps{ Type: []string{"object"}, @@ -180,7 +198,7 @@ func schema_pkg_apis_appsody_v1beta1_AppsodyApplicationService(ref common.Refere }, }, Dependencies: []string{ - "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.Certificate", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.ServiceBindingConsumes", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.ServiceBindingProvides"}, + "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.Certificate", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.ServiceBindingConsumes", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.ServiceBindingProvides", "k8s.io/api/core/v1.ServicePort"}, } } @@ -399,6 +417,12 @@ func schema_pkg_apis_appsody_v1beta1_AppsodyApplicationSpec(ref common.Reference }, }, "sidecarContainers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": "name", + "x-kubernetes-list-type": "map", + }, + }, SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ @@ -415,12 +439,17 @@ func schema_pkg_apis_appsody_v1beta1_AppsodyApplicationSpec(ref common.Reference Ref: ref("github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyRoute"), }, }, + "bindings": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyBindings"), + }, + }, }, Required: []string{"applicationImage"}, }, }, Dependencies: []string{ - "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationAutoScaling", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationMonitoring", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationService", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationStorage", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyRoute", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, + "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationAutoScaling", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationMonitoring", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationService", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyApplicationStorage", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyBindings", "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1.AppsodyRoute", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -469,6 +498,24 @@ func schema_pkg_apis_appsody_v1beta1_AppsodyApplicationStatus(ref common.Referen }, }, }, + "resolvedBindings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, "imageReference": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, diff --git a/pkg/controller/appsodyapplication/appsodyapplication_controller.go b/pkg/controller/appsodyapplication/appsodyapplication_controller.go index cfc56b5..8a9c9fe 100644 --- a/pkg/controller/appsodyapplication/appsodyapplication_controller.go +++ b/pkg/controller/appsodyapplication/appsodyapplication_controller.go @@ -8,6 +8,7 @@ import ( "github.com/application-stacks/runtime-component-operator/pkg/common" "github.com/operator-framework/operator-sdk/pkg/k8sutil" + networkingv1beta1 "k8s.io/api/networking/v1beta1" oputils "github.com/application-stacks/runtime-component-operator/pkg/utils" appsodyv1beta1 "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1" @@ -27,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/types" applicationsv1beta1 "sigs.k8s.io/application/pkg/apis/app/v1beta1" "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -120,15 +122,6 @@ func newReconciler(mgr manager.Manager) reconcile.Reconciler { return reconciler } -func getAppsodyOpConfigMap(name string, ns string, r *ReconcileAppsodyApplication) (*corev1.ConfigMap, error) { - configMap := &corev1.ConfigMap{} - err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: name, Namespace: ns}, configMap) - if err != nil { - return nil, err - } - return configMap, nil -} - func setup(mgr manager.Manager) { mgr.GetFieldIndexer().IndexField(&appsodyv1beta1.AppsodyApplication{}, indexFieldImageStreamName, func(obj runtime.Object) []string { instance := obj.(*appsodyv1beta1.AppsodyApplication) @@ -143,6 +136,14 @@ func setup(mgr manager.Manager) { } return nil }) + mgr.GetFieldIndexer().IndexField(&appsodyv1beta1.AppsodyApplication{}, indexFieldBindingsResourceRef, func(obj runtime.Object) []string { + instance := obj.(*appsodyv1beta1.AppsodyApplication) + + if instance.Spec.Bindings != nil && instance.Spec.Bindings.ResourceRef != "" { + return []string{instance.Spec.Bindings.ResourceRef} + } + return nil + }) } // add adds a new Controller to mgr with r as the reconcile.Reconciler @@ -274,13 +275,21 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return err } + err = c.Watch( + &source.Kind{Type: &corev1.Secret{}}, + &EnqueueRequestsForCustomIndexField{ + Matcher: CreateBindingSecretMatcher(mgr.GetClient()), + }) + if err != nil { + return err + } + ok, _ := reconciler.IsGroupVersionSupported(imagev1.SchemeGroupVersion.String(), "ImageStream") if ok { c.Watch( &source.Kind{Type: &imagev1.ImageStream{}}, - &EnqueueRequestsForImageStream{ - Client: mgr.GetClient(), - WatchNamespaces: watchNamespaces, + &EnqueueRequestsForCustomIndexField{ + Matcher: CreateImageStreamMatcher(mgr.GetClient(), watchNamespaces), }) } @@ -357,7 +366,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco ns = watchNamespaces[0] } - configMap, err := getAppsodyOpConfigMap("appsody-operator-defaults", ns, r) + configMap, err := r.GetOpConfigMap("appsody-operator-defaults", ns) if err != nil { log.Info("Failed to find config map defaults in namespace " + ns) } else { @@ -378,7 +387,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco r.lastDefautsRV = configMap.ResourceVersion } - configMap, err = getAppsodyOpConfigMap("appsody-operator-constants", ns, r) + configMap, err = r.GetOpConfigMap("appsody-operator-constants", ns) if err != nil { log.Info("Failed to find config map constants") } else { @@ -399,13 +408,25 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco r.lastConstantsRV = configMap.ResourceVersion } - configMap, err = getAppsodyOpConfigMap("appsody-operator", ns, r) + configMap, err = r.GetOpConfigMap("appsody-operator", ns) if err != nil { - log.Info("Failed to find appsody operator config map") + log.Info("Failed to find appsody-operator config map") + common.Config = common.DefaultOpConfig() + configMap = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "appsody-operator", Namespace: ns}} + configMap.Data = common.Config } else { common.Config.LoadFromConfigMap(configMap) } + _, err = controllerutil.CreateOrUpdate(context.TODO(), r.GetClient(), configMap, func() error { + configMap.Data = common.Config + return nil + }) + + if err != nil { + log.Info("Failed to update appsody-operator config map") + } + // Fetch the AppsodyApplication instance instance := &appsodyv1beta1.AppsodyApplication{} var ba common.BaseComponent @@ -527,6 +548,19 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco return result, nil } + if r.IsSeriveBindingSupported() { + result, err = r.ReconcileBindings(instance) + if err != nil || result != (reconcile.Result{}) { + return result, err + } + } else if instance.Spec.Bindings != nil { + return r.ManageError(errors.New("failed to reconcile as the operator failed to find Service Binding CRDs"), common.StatusConditionTypeReconciled, instance) + } + resolvedBindingSecret, err := r.GetResolvedBindingSecret(ba) + if err != nil { + return r.ManageError(err, common.StatusConditionTypeReconciled, instance) + } + if instance.Spec.ServiceAccountName == nil || *instance.Spec.ServiceAccountName == "" { serviceAccount := &corev1.ServiceAccount{ObjectMeta: defaultMeta} err = r.CreateOrUpdate(serviceAccount, instance, func() error { @@ -568,6 +602,9 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } + if ok, _ := r.IsGroupVersionSupported(networkingv1beta1.SchemeGroupVersion.String(), "Ingress"); ok { + r.DeleteResource(&networkingv1beta1.Ingress{ObjectMeta: defaultMeta}) + } if r.IsOpenShift() { route := &routev1.Route{ObjectMeta: defaultMeta} err = r.DeleteResource(route) @@ -581,6 +618,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco ksvc := &servingv1alpha1.Service{ObjectMeta: defaultMeta} err = r.CreateOrUpdate(ksvc, instance, func() error { oputils.CustomizeKnativeService(ksvc, instance) + oputils.CustomizeServiceBinding(resolvedBindingSecret, &ksvc.Spec.Template.Spec.PodSpec, instance) return nil }) @@ -606,12 +644,12 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco err = r.CreateOrUpdate(svc, instance, func() error { oputils.CustomizeService(svc, ba) svc.Annotations = oputils.MergeMaps(svc.Annotations, instance.Spec.Service.Annotations) - + monitoringEnabledLabelName := getMonitoringEnabledLabelName(ba) if instance.Spec.Monitoring != nil { - svc.Labels["monitor."+ba.GetGroupName()+"/enabled"] = "true" + svc.Labels[monitoringEnabledLabelName] = "true" } else { - if _, ok := svc.Labels["monitor."+ba.GetGroupName()+"/enabled"]; ok { - delete(svc.Labels, "monitor."+ba.GetGroupName()+"/enabled") + if _, ok := svc.Labels[monitoringEnabledLabelName]; ok { + delete(svc.Labels, monitoringEnabledLabelName) } } return nil @@ -647,6 +685,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco oputils.CustomizeStatefulSet(statefulSet, instance) oputils.CustomizePodSpec(&statefulSet.Spec.Template, instance) oputils.CustomizePersistence(statefulSet, instance) + oputils.CustomizeServiceBinding(resolvedBindingSecret, &statefulSet.Spec.Template.Spec, instance) return nil }) if err != nil { @@ -675,6 +714,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco err = r.CreateOrUpdate(deploy, instance, func() error { oputils.CustomizeDeployment(deploy, instance) oputils.CustomizePodSpec(&deploy.Spec.Template, instance) + oputils.CustomizeServiceBinding(resolvedBindingSecret, &deploy.Spec.Template.Spec, instance) return nil }) if err != nil { @@ -731,7 +771,30 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco } } } else { - reqLogger.V(1).Info(fmt.Sprintf("%s is not supported", routev1.SchemeGroupVersion.String())) + + if ok, err := r.IsGroupVersionSupported(networkingv1beta1.SchemeGroupVersion.String(), "Ingress"); err != nil { + reqLogger.Error(err, fmt.Sprintf("Failed to check if %s is supported", networkingv1beta1.SchemeGroupVersion.String())) + r.ManageError(err, common.StatusConditionTypeReconciled, instance) + } else if ok { + if instance.Spec.Expose != nil && *instance.Spec.Expose { + ing := &networkingv1beta1.Ingress{ObjectMeta: defaultMeta} + err = r.CreateOrUpdate(ing, instance, func() error { + oputils.CustomizeIngress(ing, instance) + return nil + }) + if err != nil { + reqLogger.Error(err, "Failed to reconcile Ingress") + return r.ManageError(err, common.StatusConditionTypeReconciled, instance) + } + } else { + ing := &networkingv1beta1.Ingress{ObjectMeta: defaultMeta} + err = r.DeleteResource(ing) + if err != nil { + reqLogger.Error(err, "Failed to delete Ingress") + return r.ManageError(err, common.StatusConditionTypeReconciled, instance) + } + } + } } if ok, err = r.IsGroupVersionSupported(prometheusv1.SchemeGroupVersion.String(), "ServiceMonitor"); err != nil { @@ -763,3 +826,7 @@ func (r *ReconcileAppsodyApplication) Reconcile(request reconcile.Request) (reco return r.ManageSuccess(common.StatusConditionTypeReconciled, instance) } + +func getMonitoringEnabledLabelName(ba common.BaseComponent) string { + return "monitor." + ba.GetGroupName() + "/enabled" +} diff --git a/pkg/controller/appsodyapplication/enqueue_image_stream.go b/pkg/controller/appsodyapplication/enqueue_image_stream.go deleted file mode 100644 index 238613c..0000000 --- a/pkg/controller/appsodyapplication/enqueue_image_stream.go +++ /dev/null @@ -1,86 +0,0 @@ -package appsodyapplication - -import ( - "context" - - oputils "github.com/application-stacks/runtime-component-operator/pkg/utils" - appsodyv1beta1 "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/workqueue" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -var _ handler.EventHandler = &EnqueueRequestsForImageStream{} - -const ( - indexFieldImageStreamName = "spec.applicationImage" -) - -// EnqueueRequestsForImageStream enqueues reconcile Requests Appsody Applications if the app is relying on -// the image stream -type EnqueueRequestsForImageStream struct { - handler.Funcs - WatchNamespaces []string - Client client.Client -} - -// Update implements EventHandler -func (e *EnqueueRequestsForImageStream) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { - e.handle(evt.MetaNew, q) -} - -// Delete implements EventHandler -func (e *EnqueueRequestsForImageStream) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { - e.handle(evt.Meta, q) -} - -// Generic implements EventHandler -func (e *EnqueueRequestsForImageStream) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { - e.handle(evt.Meta, q) -} - -// handle common implementation to enqueue reconcile Requests for applications -func (e *EnqueueRequestsForImageStream) handle(evtMeta metav1.Object, q workqueue.RateLimitingInterface) { - apps, _ := e.matchApplication(evtMeta) - for _, app := range apps { - q.Add(reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: app.Namespace, - Name: app.Name, - }}) - } -} - -// matchApplication returns the NamespacedName of all applications using the input ImageStreamTag -func (e *EnqueueRequestsForImageStream) matchApplication(imageStreamTag metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) { - apps := []appsodyv1beta1.AppsodyApplication{} - var namespaces []string - if oputils.IsClusterWide(e.WatchNamespaces) { - nsList := &corev1.NamespaceList{} - if err := e.Client.List(context.Background(), nsList, client.InNamespace("")); err != nil { - return nil, err - } - for _, ns := range nsList.Items { - namespaces = append(namespaces, ns.Name) - } - } else { - namespaces = e.WatchNamespaces - } - for _, ns := range namespaces { - appList := &appsodyv1beta1.AppsodyApplicationList{} - err := e.Client.List(context.Background(), - appList, - client.InNamespace(ns), - client.MatchingFields{indexFieldImageStreamName: imageStreamTag.GetNamespace() + "/" + imageStreamTag.GetName()}) - if err != nil { - return nil, err - } - apps = append(apps, appList.Items...) - } - return apps, nil -} diff --git a/pkg/controller/appsodyapplication/enqueue_with_matcher.go b/pkg/controller/appsodyapplication/enqueue_with_matcher.go new file mode 100644 index 0000000..00e5076 --- /dev/null +++ b/pkg/controller/appsodyapplication/enqueue_with_matcher.go @@ -0,0 +1,121 @@ +package appsodyapplication + +import ( + "context" + + appstacksutils "github.com/application-stacks/runtime-component-operator/pkg/utils" + appsodyv1beta1 "github.com/appsody/appsody-operator/pkg/apis/appsody/v1beta1" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var _ handler.EventHandler = &EnqueueRequestsForCustomIndexField{} + +const ( + indexFieldImageStreamName = "spec.applicationImage" + indexFieldBindingsResourceRef = "spec.bindings.resourceRef" +) + +// EnqueueRequestsForCustomIndexField enqueues reconcile Requests Runtime Components if the app is relying on +// the modified resource +type EnqueueRequestsForCustomIndexField struct { + handler.Funcs + Matcher func(metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) +} + +// Update implements EventHandler +func (e *EnqueueRequestsForCustomIndexField) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { + e.handle(evt.MetaNew, evt.ObjectNew, q) +} + +// Delete implements EventHandler +func (e *EnqueueRequestsForCustomIndexField) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { + e.handle(evt.Meta, evt.Object, q) +} + +// Generic implements EventHandler +func (e *EnqueueRequestsForCustomIndexField) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { + e.handle(evt.Meta, evt.Object, q) +} + +// handle common implementation to enqueue reconcile Requests for applications +func (e *EnqueueRequestsForCustomIndexField) handle(evtMeta metav1.Object, evtObj runtime.Object, q workqueue.RateLimitingInterface) { + apps, _ := e.Matcher(evtMeta) + for _, app := range apps { + q.Add(reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: app.Namespace, + Name: app.Name, + }}) + } +} + +// CreateImageStreamMatcher return a func that matches all applications using the input ImageStreamTag +func CreateImageStreamMatcher(clnt client.Client, watchNamespaces []string) func(metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) { + matcher := func(imageStreamTag metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) { + apps := []appsodyv1beta1.AppsodyApplication{} + var namespaces []string + if appstacksutils.IsClusterWide(watchNamespaces) { + nsList := &corev1.NamespaceList{} + if err := clnt.List(context.Background(), nsList, client.InNamespace("")); err != nil { + return nil, err + } + for _, ns := range nsList.Items { + namespaces = append(namespaces, ns.Name) + } + } else { + namespaces = watchNamespaces + } + for _, ns := range namespaces { + appList := &appsodyv1beta1.AppsodyApplicationList{} + err := clnt.List(context.Background(), + appList, + client.InNamespace(ns), + client.MatchingFields{indexFieldImageStreamName: imageStreamTag.GetNamespace() + "/" + imageStreamTag.GetName()}) + if err != nil { + return nil, err + } + apps = append(apps, appList.Items...) + } + return apps, nil + } + return matcher +} + +//CreateBindingSecretMatcher return a func that matches all applications that "could" rely on the secret as a secret binding +func CreateBindingSecretMatcher(clnt client.Client) func(metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) { + matcher := func(secret metav1.Object) ([]appsodyv1beta1.AppsodyApplication, error) { + apps := []appsodyv1beta1.AppsodyApplication{} + + // Adding apps which have this secret defined in the spec.bindings.resourceRef + appList := &appsodyv1beta1.AppsodyApplicationList{} + err := clnt.List(context.Background(), + appList, + client.InNamespace(secret.GetNamespace()), + client.MatchingFields{indexFieldBindingsResourceRef: secret.GetName()}) + if err != nil { + return nil, err + } + apps = append(apps, appList.Items...) + + // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario + app := &appsodyv1beta1.AppsodyApplication{} + err = clnt.Get(context.Background(), types.NamespacedName{Name: secret.GetName(), Namespace: secret.GetNamespace()}, app) + if err != nil { + if !kerrors.IsNotFound(err) { + return nil, err + } + } + apps = append(apps, *app) + return apps, nil + } + return matcher +} diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go index dd69a32..48b6a35 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go @@ -49,8 +49,9 @@ type RuntimeComponentSpec struct { InitContainers []corev1.Container `json:"initContainers,omitempty"` // +listType=map // +listMapKey=name - SidecarContainers []corev1.Container `json:"sidecarContainers,omitempty"` - Route *RuntimeComponentRoute `json:"route,omitempty"` + SidecarContainers []corev1.Container `json:"sidecarContainers,omitempty"` + Route *RuntimeComponentRoute `json:"route,omitempty"` + Bindings *RuntimeComponentBindings `json:"bindings,omitempty"` } // RuntimeComponentAutoScaling ... @@ -75,8 +76,14 @@ type RuntimeComponentService struct { // +kubebuilder:validation:Minimum=1 TargetPort *int32 `json:"targetPort,omitempty"` + // +kubebuilder:validation:Maximum=65535 + // +kubebuilder:validation:Minimum=0 + NodePort *int32 `json:"nodePort,omitempty"` + PortName string `json:"portName,omitempty"` + Ports []corev1.ServicePort `json:"ports,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` // +listType=atomic Consumes []ServiceBindingConsumes `json:"consumes,omitempty"` @@ -139,13 +146,21 @@ type ServiceBindingAuth struct { Password corev1.SecretKeySelector `json:"password,omitempty"` } +// RuntimeComponentBindings represents service binding related parameters +type RuntimeComponentBindings struct { + AutoDetect *bool `json:"autoDetect,omitempty"` + ResourceRef string `json:"resourceRef,omitempty"` +} + // RuntimeComponentStatus defines the observed state of RuntimeComponent // +k8s:openapi-gen=true type RuntimeComponentStatus struct { // +listType=atomic Conditions []StatusCondition `json:"conditions,omitempty"` ConsumedServices common.ConsumedServices `json:"consumedServices,omitempty"` - ImageReference string `json:"imageReference,omitempty"` + // +listType=set + ResolvedBindings []string `json:"resolvedBindings,omitempty"` + ImageReference string `json:"imageReference,omitempty"` } // StatusCondition ... @@ -354,6 +369,24 @@ func (cr *RuntimeComponent) GetRoute() common.BaseComponentRoute { return cr.Spec.Route } +// GetBindings returns route configuration for RuntimeComponent +func (cr *RuntimeComponent) GetBindings() common.BaseComponentBindings { + if cr.Spec.Bindings == nil { + return nil + } + return cr.Spec.Bindings +} + +// GetResolvedBindings returns a map of all the service names to be consumed by the application +func (s *RuntimeComponentStatus) GetResolvedBindings() []string { + return s.ResolvedBindings +} + +// SetResolvedBindings sets ConsumedServices +func (s *RuntimeComponentStatus) SetResolvedBindings(rb []string) { + s.ResolvedBindings = rb +} + // GetConsumedServices returns a map of all the service names to be consumed by the application func (s *RuntimeComponentStatus) GetConsumedServices() common.ConsumedServices { if s.ConsumedServices == nil { @@ -402,7 +435,7 @@ func (s *RuntimeComponentStorage) GetMountPath() string { return s.MountPath } -// GetVolumeClaimTemplate returns a template representing requested persitent volume +// GetVolumeClaimTemplate returns a template representing requested persistent volume func (s *RuntimeComponentStorage) GetVolumeClaimTemplate() *corev1.PersistentVolumeClaim { return s.VolumeClaimTemplate } @@ -417,6 +450,14 @@ func (s *RuntimeComponentService) GetPort() int32 { return s.Port } +// GetNodePort returns service nodePort +func (s *RuntimeComponentService) GetNodePort() *int32 { + if s.NodePort == nil { + return nil + } + return s.NodePort +} + // GetTargetPort returns the internal target port for containers func (s *RuntimeComponentService) GetTargetPort() *int32 { if s.TargetPort == nil { @@ -436,6 +477,11 @@ func (s *RuntimeComponentService) GetType() *corev1.ServiceType { return s.Type } +// GetPorts returns a list of service ports +func (s *RuntimeComponentService) GetPorts() []corev1.ServicePort { + return s.Ports +} + // GetProvides returns service provider configuration func (s *RuntimeComponentService) GetProvides() common.ServiceBindingProvides { if s.Provides == nil { @@ -534,7 +580,7 @@ func (r *RuntimeComponentRoute) GetAnnotations() map[string]string { return r.Annotations } -// GetCertificate returns certficate spec for route +// GetCertificate returns certificate spec for route func (r *RuntimeComponentRoute) GetCertificate() common.Certificate { if r.Certificate == nil { return nil @@ -567,6 +613,16 @@ func (r *RuntimeComponentRoute) GetPath() string { return r.Path } +// GetAutoDetect returns a boolean to specify if the operator should auto-detect ServiceBinding CRs with the same name as the RuntimeComponent CR +func (r *RuntimeComponentBindings) GetAutoDetect() *bool { + return r.AutoDetect +} + +// GetResourceRef returns name of ServiceBinding CRs created manually in the same namespace as the RuntimeComponent CR +func (r *RuntimeComponentBindings) GetResourceRef() string { + return r.ResourceRef +} + // Initialize the RuntimeComponent instance func (cr *RuntimeComponent) Initialize() { if cr.Spec.PullPolicy == nil { diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go index beb748c..f26be71 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go @@ -120,6 +120,27 @@ func (in *RuntimeComponentAutoScaling) DeepCopy() *RuntimeComponentAutoScaling { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuntimeComponentBindings) DeepCopyInto(out *RuntimeComponentBindings) { + *out = *in + if in.AutoDetect != nil { + in, out := &in.AutoDetect, &out.AutoDetect + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuntimeComponentBindings. +func (in *RuntimeComponentBindings) DeepCopy() *RuntimeComponentBindings { + if in == nil { + return nil + } + out := new(RuntimeComponentBindings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RuntimeComponentList) DeepCopyInto(out *RuntimeComponentList) { *out = *in @@ -239,6 +260,16 @@ func (in *RuntimeComponentService) DeepCopyInto(out *RuntimeComponentService) { *out = new(int32) **out = **in } + if in.NodePort != nil { + in, out := &in.NodePort, &out.NodePort + *out = new(int32) + **out = **in + } + if in.Ports != nil { + in, out := &in.Ports, &out.Ports + *out = make([]corev1.ServicePort, len(*in)) + copy(*out, *in) + } if in.Annotations != nil { in, out := &in.Annotations, &out.Annotations *out = make(map[string]string, len(*in)) @@ -404,6 +435,11 @@ func (in *RuntimeComponentSpec) DeepCopyInto(out *RuntimeComponentSpec) { *out = new(RuntimeComponentRoute) (*in).DeepCopyInto(*out) } + if in.Bindings != nil { + in, out := &in.Bindings, &out.Bindings + *out = new(RuntimeComponentBindings) + (*in).DeepCopyInto(*out) + } return } @@ -442,6 +478,11 @@ func (in *RuntimeComponentStatus) DeepCopyInto(out *RuntimeComponentStatus) { (*out)[key] = outVal } } + if in.ResolvedBindings != nil { + in, out := &in.ResolvedBindings, &out.ResolvedBindings + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.openapi.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.openapi.go index 58dc817..6a88a6f 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.openapi.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1/zz_generated.openapi.go @@ -187,12 +187,30 @@ func schema_pkg_apis_appstacks_v1beta1_RuntimeComponentService(ref common.Refere Format: "int32", }, }, + "nodePort": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, "portName": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", }, }, + "ports": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/api/core/v1.ServicePort"), + }, + }, + }, + }, + }, "annotations": { SchemaProps: spec.SchemaProps{ Type: []string{"object"}, @@ -244,7 +262,7 @@ func schema_pkg_apis_appstacks_v1beta1_RuntimeComponentService(ref common.Refere }, }, Dependencies: []string{ - "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.Certificate", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.ServiceBindingConsumes", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.ServiceBindingProvides"}, + "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.Certificate", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.ServiceBindingConsumes", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.ServiceBindingProvides", "k8s.io/api/core/v1.ServicePort"}, } } @@ -479,12 +497,17 @@ func schema_pkg_apis_appstacks_v1beta1_RuntimeComponentSpec(ref common.Reference Ref: ref("github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentRoute"), }, }, + "bindings": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentBindings"), + }, + }, }, Required: []string{"applicationImage"}, }, }, Dependencies: []string{ - "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentAutoScaling", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentMonitoring", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentRoute", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentService", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentStorage", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, + "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentAutoScaling", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentBindings", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentMonitoring", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentRoute", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentService", "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1.RuntimeComponentStorage", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -533,6 +556,24 @@ func schema_pkg_apis_appstacks_v1beta1_RuntimeComponentStatus(ref common.Referen }, }, }, + "resolvedBindings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, "imageReference": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/config.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/config.go index fe60b3a..ff334cf 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/config.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/config.go @@ -13,6 +13,13 @@ const ( // OpConfigPropUseClusterIssuer whether to use ClusterIssuer for cert-manager certificate OpConfigPropUseClusterIssuer = "useClusterIssuer" + + // OpConfigSvcBindingGVKs a comma-seperated list of GroupVersionKind values in ".." format. + // e.g. "CronTab.v1.stable.example.com" + OpConfigSvcBindingGVKs = "serviceBinding.groupVersionKinds" + + // OpConfigDefaultHostname a DNS name to be used for hostname generation. + OpConfigDefaultHostname = "defaultHostname" ) // Config stores operator configuration @@ -20,9 +27,10 @@ var Config = OpConfig{} // LoadFromConfigMap creates a config out of kubernetes config map func (oc OpConfig) LoadFromConfigMap(cm *corev1.ConfigMap) { - for k := range oc { - delete(oc, k) + for k, v := range DefaultOpConfig() { + oc[k] = v } + for k, v := range cm.Data { oc[k] = v } @@ -33,5 +41,8 @@ func DefaultOpConfig() OpConfig { cfg := OpConfig{} cfg[OpConfigPropDefaultIssuer] = "self-signed" cfg[OpConfigPropUseClusterIssuer] = "true" + cfg[OpConfigSvcBindingGVKs] = "ServiceBindingRequest.v1alpha1.apps.openshift.io" + cfg[OpConfigDefaultHostname] = "" + return cfg } diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/types.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/types.go index 56e1fda..13bbc27 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/types.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/common/types.go @@ -40,6 +40,8 @@ type BaseComponentStatus interface { NewCondition() StatusCondition GetConsumedServices() ConsumedServices SetConsumedServices(ConsumedServices) + GetResolvedBindings() []string + SetResolvedBindings([]string) GetImageReference() string SetImageReference(string) } @@ -75,6 +77,8 @@ type BaseComponentService interface { GetTargetPort() *int32 GetPortName() string GetType() *corev1.ServiceType + GetNodePort() *int32 + GetPorts() []corev1.ServicePort GetAnnotations() map[string]string GetProvides() ServiceBindingProvides GetConsumes() []ServiceBindingConsumes @@ -121,6 +125,12 @@ type ServiceBindingAuth interface { GetPassword() corev1.SecretKeySelector } +// BaseComponentBindings represents Service Binding information +type BaseComponentBindings interface { + GetAutoDetect() *bool + GetResourceRef() string +} + // ServiceBindingCategory ... type ServiceBindingCategory string @@ -160,6 +170,7 @@ type BaseComponent interface { GetSidecarContainers() []corev1.Container GetGroupName() string GetRoute() BaseComponentRoute + GetBindings() BaseComponentBindings } // Certificate returns cert-manager CertificateSpec diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/reconciler.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/reconciler.go index 4379f03..bd44238 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/reconciler.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/reconciler.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math" - "reflect" "strings" "time" @@ -284,253 +283,6 @@ func (r *ReconcilerBase) UpdateStatus(obj runtime.Object) error { return r.GetClient().Status().Update(context.Background(), obj) } -// SyncSecretAcrossNamespace syncs up the secret data across a namespace -func (r *ReconcilerBase) SyncSecretAcrossNamespace(fromSecret *corev1.Secret, namespace string) error { - toSecret := &corev1.Secret{} - err := r.client.Get(context.TODO(), types.NamespacedName{Name: fromSecret.Name, Namespace: namespace}, toSecret) - if err != nil { - return err - } - toSecret.Data = fromSecret.Data - return r.client.Update(context.TODO(), toSecret) -} - -// AsOwner returns an owner reference object based on the input object. Also can set controller field on the owner ref. -func (r *ReconcilerBase) AsOwner(rObj runtime.Object, controller bool) (metav1.OwnerReference, error) { - mObj, ok := rObj.(metav1.Object) - if !ok { - err := errors.Errorf("%T is not a metav1.Object", rObj) - log.Error(err, "failed to convert into metav1.Object") - return metav1.OwnerReference{}, err - } - - gvk, err := apiutil.GVKForObject(rObj, r.scheme) - if err != nil { - log.Error(err, "failed to get GroupVersionKind associated with the runtime.Object", mObj) - return metav1.OwnerReference{}, err - } - - return metav1.OwnerReference{ - APIVersion: gvk.Group + "/" + gvk.Version, - Kind: gvk.Kind, - Name: mObj.GetName(), - UID: mObj.GetUID(), - Controller: &controller, - }, nil -} - -// GetServiceBindingCreds returns a map containing username/password string values based on 'cr.spec.service.provides.auth' -func (r *ReconcilerBase) GetServiceBindingCreds(ba common.BaseComponent) (map[string]string, error) { - if ba.GetService() == nil || ba.GetService().GetProvides() == nil || ba.GetService().GetProvides().GetAuth() == nil { - return nil, errors.Errorf("auth is not set on the object %s", ba) - } - metaObj := ba.(metav1.Object) - authMap := map[string]string{} - - auth := ba.GetService().GetProvides().GetAuth() - getCred := func(key string, getCredF func() corev1.SecretKeySelector) error { - if getCredF() != (corev1.SecretKeySelector{}) { - cred, err := getCredFromSecret(metaObj.GetNamespace(), getCredF(), key, r.client) - if err != nil { - return err - } - authMap[key] = cred - } - return nil - } - err := getCred("username", auth.GetUsername) - err = getCred("password", auth.GetPassword) - if err != nil { - return nil, err - } - return authMap, nil -} - -func getCredFromSecret(namespace string, sel corev1.SecretKeySelector, cred string, client client.Client) (string, error) { - secret := &corev1.Secret{} - err := client.Get(context.TODO(), types.NamespacedName{Name: sel.Name, Namespace: namespace}, secret) - if err != nil { - return "", errors.Wrapf(err, "unable to fetch credential %q from secret %q", cred, sel.Name) - } - - if s, ok := secret.Data[sel.Key]; ok { - return string(s), nil - } - return "", errors.Errorf("unable to find credential %q in secret %q using key %q", cred, sel.Name, sel.Key) -} - -// ReconcileProvides ... -func (r *ReconcilerBase) ReconcileProvides(ba common.BaseComponent) (_ reconcile.Result, err error) { - mObj := ba.(metav1.Object) - logger := log.WithValues("ba.Namespace", mObj.GetNamespace(), "ba.Name", mObj.GetName()) - - secretName := BuildServiceBindingSecretName(mObj.GetName(), mObj.GetNamespace()) - provides := ba.GetService().GetProvides() - if provides != nil && provides.GetCategory() == common.ServiceBindingCategoryOpenAPI { - var creds map[string]string - if provides.GetAuth() != nil { - if creds, err = r.GetServiceBindingCreds(ba); err != nil { - r.ManageError(errors.Wrapf(err, "service binding dependency not satisfied"), common.StatusConditionTypeDependenciesSatisfied, ba) - return r.ManageError(errors.New("failed to get authentication info"), common.StatusConditionTypeReconciled, ba) - } - } - - secretMeta := metav1.ObjectMeta{ - Name: secretName, - Namespace: mObj.GetNamespace(), - } - providerSecret := &corev1.Secret{ObjectMeta: secretMeta} - err = r.CreateOrUpdate(providerSecret, mObj, func() error { - CustomizeServiceBindingSecret(providerSecret, creds, ba) - return nil - }) - if err != nil { - logger.Error(err, "Failed to reconcile provider secret") - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - - r.ManageSuccess(common.StatusConditionTypeDependenciesSatisfied, ba) - } else { - providerSecret := &corev1.Secret{} - err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: mObj.GetNamespace()}, providerSecret) - if err != nil { - if kerrors.IsNotFound(err) { - logger.V(4).Info(fmt.Sprintf("Unable to find secret %q in namespace %q", secretName, mObj.GetNamespace())) - } else { - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - } else { - // Delete all copies of this secret in other namespaces - copiedToNamespacesKey := getCopiedToNamespacesAnnotationKey(ba) - if providerSecret.Annotations[copiedToNamespacesKey] != "" { - namespaces := strings.Split(providerSecret.Annotations[copiedToNamespacesKey], ",") - for _, ns := range namespaces { - err = r.GetClient().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: ns}}) - if err != nil { - if kerrors.IsNotFound(err) { - logger.V(4).Info(fmt.Sprintf("unable to find secret %q in namespace %q", secretName, mObj.GetNamespace())) - } else { - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - } - } - } - - // Delete the secret itself - err = r.GetClient().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: mObj.GetNamespace()}}) - if err != nil { - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - } - } - - return r.ManageSuccess(common.StatusConditionTypeDependenciesSatisfied, ba) -} - -// ReconcileConsumes ... -func (r *ReconcilerBase) ReconcileConsumes(ba common.BaseComponent) (reconcile.Result, error) { - rObj := ba.(runtime.Object) - mObj := ba.(metav1.Object) - for _, con := range ba.GetService().GetConsumes() { - if con.GetCategory() == common.ServiceBindingCategoryOpenAPI { - conNamespace := con.GetNamespace() - if conNamespace == "" { - conNamespace = mObj.GetNamespace() - } - secretName := BuildServiceBindingSecretName(con.GetName(), conNamespace) - existingSecret := &corev1.Secret{} - err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: conNamespace}, existingSecret) - if err != nil { - if kerrors.IsNotFound(err) { - delErr := r.DeleteResource(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: mObj.GetNamespace()}}) - if delErr != nil && !kerrors.IsNotFound(delErr) { - delErr = errors.Wrapf(delErr, "unable to delete orphaned secret %q from namespace %q", secretName, mObj.GetNamespace()) - err = errors.Wrapf(delErr, "unable to find service binding secret %q for service %q in namespace %q", secretName, con.GetName(), conNamespace) - } else { - err = errors.Wrapf(err, "unable to find service binding secret %q for service %q in namespace %q", secretName, con.GetName(), conNamespace) - } - } - r.ManageError(errors.Wrapf(err, "service binding dependency not satisfied"), common.StatusConditionTypeDependenciesSatisfied, ba) - return r.ManageError(errors.New("dependency not satisfied"), common.StatusConditionTypeReconciled, ba) - } - - if existingSecret.Annotations == nil { - existingSecret.Annotations = map[string]string{} - } - copiedToNamespacesKey := getCopiedToNamespacesAnnotationKey(ba) - existingSecret.Annotations[copiedToNamespacesKey] = AppendIfNotSubstring(mObj.GetNamespace(), existingSecret.Annotations[copiedToNamespacesKey]) - err = r.GetClient().Update(context.TODO(), existingSecret) - if err != nil { - r.ManageError(errors.Wrapf(err, "failed to update service provider secret"), common.StatusConditionTypeDependenciesSatisfied, ba) - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - - copiedSecret := &corev1.Secret{} - err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: mObj.GetNamespace()}, copiedSecret) - consumedByKey := getConsumedByAnnotationKey(ba) - if kerrors.IsNotFound(err) { - owner, _ := r.AsOwner(rObj, false) - copiedSecret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName, - Namespace: mObj.GetNamespace(), - Labels: existingSecret.Labels, - OwnerReferences: []metav1.OwnerReference{owner}, - Annotations: map[string]string{consumedByKey: mObj.GetName()}, - }, - Data: existingSecret.Data, - } - err = r.GetClient().Create(context.TODO(), copiedSecret) - } else if err == nil { - existingCopiedSecret := copiedSecret.DeepCopyObject() - if copiedSecret.Annotations == nil { - copiedSecret.Annotations = map[string]string{} - } - copiedSecret.Annotations[consumedByKey] = AppendIfNotSubstring(mObj.GetName(), copiedSecret.Annotations[consumedByKey]) - copiedSecret.Data = existingSecret.Data - // Skip setting the owner on the copiedSecret if the consumer and provider are in the same namespace - // This is because we want the secret to be deleted if the provider is deleted - if conNamespace != copiedSecret.Namespace { - owner, _ := r.AsOwner(rObj, false) - EnsureOwnerRef(copiedSecret, owner) - } - if !reflect.DeepEqual(existingCopiedSecret, copiedSecret) { - err = r.GetClient().Update(context.TODO(), copiedSecret) - } - } - - if err != nil { - r.ManageError(errors.Wrapf(err, "failed to create or update secret for consumes"), common.StatusConditionTypeDependenciesSatisfied, ba) - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - - consumedServices := ba.GetStatus().GetConsumedServices() - if consumedServices == nil { - consumedServices = common.ConsumedServices{} - } - if !ContainsString(consumedServices[common.ServiceBindingCategoryOpenAPI], secretName) { - consumedServices[common.ServiceBindingCategoryOpenAPI] = - append(consumedServices[common.ServiceBindingCategoryOpenAPI], secretName) - ba.GetStatus().SetConsumedServices(consumedServices) - err := r.UpdateStatus(rObj) - if err != nil { - r.ManageError(errors.Wrapf(err, "unable to update status with service binding secret information"), common.StatusConditionTypeDependenciesSatisfied, ba) - return r.ManageError(err, common.StatusConditionTypeReconciled, ba) - } - } - } - } - return r.ManageSuccess(common.StatusConditionTypeDependenciesSatisfied, ba) -} - -func getCopiedToNamespacesAnnotationKey(ba common.BaseComponent) string { - return "service." + ba.GetGroupName() + "/copied-to-namespaces" -} - -func getConsumedByAnnotationKey(ba common.BaseComponent) string { - return "service." + ba.GetGroupName() + "/consumed-by" -} - // ReconcileCertificate used to manage cert-manager integration func (r *ReconcilerBase) ReconcileCertificate(ba common.BaseComponent) (reconcile.Result, error) { owner := ba.(metav1.Object) @@ -609,6 +361,9 @@ func (r *ReconcilerBase) ReconcileCertificate(ba common.BaseComponent) (reconcil // use routes host if no DNS information provided on certificate if crt.Spec.CommonName == "" { crt.Spec.CommonName = ba.GetRoute().GetHost() + if crt.Spec.CommonName == "" && common.Config[common.OpConfigDefaultHostname] != "" { + crt.Spec.CommonName = obj.GetName() + "-" + obj.GetNamespace() + "." + common.Config[common.OpConfigDefaultHostname] + } } if len(crt.Spec.DNSNames) == 0 { crt.Spec.DNSNames = append(crt.Spec.DNSNames, crt.Spec.CommonName) diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/service_binding_reconciler.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/service_binding_reconciler.go new file mode 100644 index 0000000..951ee6c --- /dev/null +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/service_binding_reconciler.go @@ -0,0 +1,397 @@ +package utils + +import ( + "context" + "fmt" + "reflect" + "sort" + "strings" + + "github.com/application-stacks/runtime-component-operator/pkg/common" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// SyncSecretAcrossNamespace syncs up the secret data across a namespace +func (r *ReconcilerBase) SyncSecretAcrossNamespace(fromSecret *corev1.Secret, namespace string) error { + toSecret := &corev1.Secret{} + err := r.client.Get(context.TODO(), types.NamespacedName{Name: fromSecret.Name, Namespace: namespace}, toSecret) + if err != nil { + return err + } + toSecret.Data = fromSecret.Data + return r.client.Update(context.TODO(), toSecret) +} + +// AsOwner returns an owner reference object based on the input object. Also can set controller field on the owner ref. +func (r *ReconcilerBase) AsOwner(rObj runtime.Object, controller bool) (metav1.OwnerReference, error) { + mObj, ok := rObj.(metav1.Object) + if !ok { + err := errors.Errorf("%T is not a metav1.Object", rObj) + log.Error(err, "failed to convert into metav1.Object") + return metav1.OwnerReference{}, err + } + + gvk, err := apiutil.GVKForObject(rObj, r.scheme) + if err != nil { + log.Error(err, "failed to get GroupVersionKind associated with the runtime.Object", mObj) + return metav1.OwnerReference{}, err + } + + return metav1.OwnerReference{ + APIVersion: gvk.Group + "/" + gvk.Version, + Kind: gvk.Kind, + Name: mObj.GetName(), + UID: mObj.GetUID(), + Controller: &controller, + }, nil +} + +// GetServiceBindingCreds returns a map containing username/password string values based on 'cr.spec.service.provides.auth' +func (r *ReconcilerBase) GetServiceBindingCreds(ba common.BaseComponent) (map[string]string, error) { + if ba.GetService() == nil || ba.GetService().GetProvides() == nil || ba.GetService().GetProvides().GetAuth() == nil { + return nil, errors.Errorf("auth is not set on the object %s", ba) + } + metaObj := ba.(metav1.Object) + authMap := map[string]string{} + + auth := ba.GetService().GetProvides().GetAuth() + getCred := func(key string, getCredF func() corev1.SecretKeySelector) error { + if getCredF() != (corev1.SecretKeySelector{}) { + cred, err := getCredFromSecret(metaObj.GetNamespace(), getCredF(), key, r.client) + if err != nil { + return err + } + authMap[key] = cred + } + return nil + } + err := getCred("username", auth.GetUsername) + err = getCred("password", auth.GetPassword) + if err != nil { + return nil, err + } + return authMap, nil +} + +func getCredFromSecret(namespace string, sel corev1.SecretKeySelector, cred string, client client.Client) (string, error) { + secret := &corev1.Secret{} + err := client.Get(context.TODO(), types.NamespacedName{Name: sel.Name, Namespace: namespace}, secret) + if err != nil { + return "", errors.Wrapf(err, "unable to fetch credential %q from secret %q", cred, sel.Name) + } + + if s, ok := secret.Data[sel.Key]; ok { + return string(s), nil + } + return "", errors.Errorf("unable to find credential %q in secret %q using key %q", cred, sel.Name, sel.Key) +} + +// ReconcileProvides ... +func (r *ReconcilerBase) ReconcileProvides(ba common.BaseComponent) (_ reconcile.Result, err error) { + mObj := ba.(metav1.Object) + logger := log.WithValues("ba.Namespace", mObj.GetNamespace(), "ba.Name", mObj.GetName()) + + secretName := BuildServiceBindingSecretName(mObj.GetName(), mObj.GetNamespace()) + provides := ba.GetService().GetProvides() + if provides != nil && provides.GetCategory() == common.ServiceBindingCategoryOpenAPI { + var creds map[string]string + if provides.GetAuth() != nil { + if creds, err = r.GetServiceBindingCreds(ba); err != nil { + r.requeueError(ba, errors.Wrapf(err, "service binding dependency not satisfied")) + } + } + + secretMeta := metav1.ObjectMeta{ + Name: secretName, + Namespace: mObj.GetNamespace(), + } + providerSecret := &corev1.Secret{ObjectMeta: secretMeta} + err = r.CreateOrUpdate(providerSecret, mObj, func() error { + CustomizeServiceBindingSecret(providerSecret, creds, ba) + return nil + }) + if err != nil { + logger.Error(err, "Failed to reconcile provider secret") + return r.ManageError(err, common.StatusConditionTypeReconciled, ba) + } + + r.done(ba) + } else { + providerSecret := &corev1.Secret{} + err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: mObj.GetNamespace()}, providerSecret) + if err != nil { + if kerrors.IsNotFound(err) { + logger.V(4).Info(fmt.Sprintf("Unable to find secret %q in namespace %q", secretName, mObj.GetNamespace())) + } else { + return r.ManageError(err, common.StatusConditionTypeReconciled, ba) + } + } else { + // Delete all copies of this secret in other namespaces + copiedToNamespacesKey := getCopiedToNamespacesAnnotationKey(ba) + if providerSecret.Annotations[copiedToNamespacesKey] != "" { + namespaces := strings.Split(providerSecret.Annotations[copiedToNamespacesKey], ",") + for _, ns := range namespaces { + err = r.GetClient().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: ns}}) + if err != nil { + if kerrors.IsNotFound(err) { + logger.V(4).Info(fmt.Sprintf("unable to find secret %q in namespace %q", secretName, mObj.GetNamespace())) + } else { + return r.ManageError(err, common.StatusConditionTypeReconciled, ba) + } + } + } + } + + // Delete the secret itself + err = r.GetClient().Delete(context.TODO(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: mObj.GetNamespace()}}) + if err != nil { + return r.ManageError(err, common.StatusConditionTypeReconciled, ba) + } + } + } + + return r.done(ba) +} + +// ReconcileConsumes ... +func (r *ReconcilerBase) ReconcileConsumes(ba common.BaseComponent) (reconcile.Result, error) { + rObj := ba.(runtime.Object) + mObj := ba.(metav1.Object) + for _, con := range ba.GetService().GetConsumes() { + if con.GetCategory() == common.ServiceBindingCategoryOpenAPI { + conNamespace := con.GetNamespace() + if conNamespace == "" { + conNamespace = mObj.GetNamespace() + } + secretName := BuildServiceBindingSecretName(con.GetName(), conNamespace) + existingSecret := &corev1.Secret{} + err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: conNamespace}, existingSecret) + if err != nil { + if kerrors.IsNotFound(err) { + delErr := r.DeleteResource(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: mObj.GetNamespace()}}) + if delErr != nil && !kerrors.IsNotFound(delErr) { + delErr = errors.Wrapf(delErr, "unable to delete orphaned secret %q from namespace %q", secretName, mObj.GetNamespace()) + err = errors.Wrapf(delErr, "unable to find service binding secret %q for service %q in namespace %q", secretName, con.GetName(), conNamespace) + } else { + err = errors.Wrapf(err, "unable to find service binding secret %q for service %q in namespace %q", secretName, con.GetName(), conNamespace) + } + } + return r.requeueError(ba, errors.Wrapf(err, "service binding dependency not satisfied")) + } + + if existingSecret.Annotations == nil { + existingSecret.Annotations = map[string]string{} + } + copiedToNamespacesKey := getCopiedToNamespacesAnnotationKey(ba) + existingSecret.Annotations[copiedToNamespacesKey] = AppendIfNotSubstring(mObj.GetNamespace(), existingSecret.Annotations[copiedToNamespacesKey]) + err = r.GetClient().Update(context.TODO(), existingSecret) + if err != nil { + r.requeueError(ba, errors.Wrapf(err, "failed to update service provider secret")) + } + + copiedSecret := &corev1.Secret{} + err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: mObj.GetNamespace()}, copiedSecret) + consumedByKey := getConsumedByAnnotationKey(ba) + if kerrors.IsNotFound(err) { + owner, _ := r.AsOwner(rObj, false) + copiedSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: mObj.GetNamespace(), + Labels: existingSecret.Labels, + OwnerReferences: []metav1.OwnerReference{owner}, + Annotations: map[string]string{consumedByKey: mObj.GetName()}, + }, + Data: existingSecret.Data, + } + err = r.GetClient().Create(context.TODO(), copiedSecret) + } else if err == nil { + existingCopiedSecret := copiedSecret.DeepCopyObject() + if copiedSecret.Annotations == nil { + copiedSecret.Annotations = map[string]string{} + } + copiedSecret.Annotations[consumedByKey] = AppendIfNotSubstring(mObj.GetName(), copiedSecret.Annotations[consumedByKey]) + copiedSecret.Data = existingSecret.Data + // Skip setting the owner on the copiedSecret if the consumer and provider are in the same namespace + // This is because we want the secret to be deleted if the provider is deleted + if conNamespace != copiedSecret.Namespace { + owner, _ := r.AsOwner(rObj, false) + EnsureOwnerRef(copiedSecret, owner) + } + if !reflect.DeepEqual(existingCopiedSecret, copiedSecret) { + err = r.GetClient().Update(context.TODO(), copiedSecret) + } + } + + if err != nil { + return r.requeueError(ba, errors.Wrapf(err, "failed to create or update secret for consumes")) + } + + consumedServices := ba.GetStatus().GetConsumedServices() + if consumedServices == nil { + consumedServices = common.ConsumedServices{} + } + if !ContainsString(consumedServices[common.ServiceBindingCategoryOpenAPI], secretName) { + consumedServices[common.ServiceBindingCategoryOpenAPI] = + append(consumedServices[common.ServiceBindingCategoryOpenAPI], secretName) + ba.GetStatus().SetConsumedServices(consumedServices) + err := r.UpdateStatus(rObj) + if err != nil { + return r.requeueError(ba, errors.Wrapf(err, "unable to update status with service binding secret information")) + } + } + } + } + return r.done(ba) +} + +// ReconcileBindings goes through the reconcile logic for service binding +func (r *ReconcilerBase) ReconcileBindings(ba common.BaseComponent) (reconcile.Result, error) { + + if res, err := r.reconcileExternals(ba); isRequeue(res, err) { + return res, err + } + return r.done(ba) +} + +func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (reconcile.Result, error) { + mObj := ba.(metav1.Object) + var resolvedBindings []string + + if ba.GetBindings() != nil && ba.GetBindings().GetResourceRef() != "" { + bindingName := ba.GetBindings().GetResourceRef() + key := types.NamespacedName{Name: bindingName, Namespace: mObj.GetNamespace()} + bindingSecret := &corev1.Secret{} + err := r.GetClient().Get(context.TODO(), key, bindingSecret) + if err == nil { + resolvedBindings = append(resolvedBindings, bindingName) + } else { + err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for external binding %q in namespace %q", bindingName, mObj.GetNamespace()) + return r.requeueError(ba, err) + } + } else if ba.GetBindings() == nil || ba.GetBindings().GetAutoDetect() == nil || *ba.GetBindings().GetAutoDetect() { + bindingName := mObj.GetName() + key := types.NamespacedName{Name: bindingName, Namespace: mObj.GetNamespace()} + + for _, gvk := range getServiceBindingGVK() { + // Using a unstructured object to find ServiceBinding CR since GVK might change + bindingObj := &unstructured.Unstructured{} + bindingObj.SetGroupVersionKind(gvk) + err := r.client.Get(context.Background(), key, bindingObj) + if err != nil { + if !kerrors.IsNotFound(err) { + log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get ServiceBinding CR") + } + continue + } + + bindingSecret := &corev1.Secret{} + err = r.GetClient().Get(context.TODO(), key, bindingSecret) + if err == nil { + resolvedBindings = append(resolvedBindings, bindingName) + break + } else if err != nil && kerrors.IsNotFound(err) { + err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for external binding %q in namespace %q", bindingName, mObj.GetNamespace()) + return r.requeueError(ba, err) + } + } + } + + if !equals(resolvedBindings, ba.GetStatus().GetResolvedBindings()) { + sort.Strings(resolvedBindings) + ba.GetStatus().SetResolvedBindings(resolvedBindings) + if err := r.UpdateStatus(ba.(runtime.Object)); err != nil { + return r.requeueError(ba, errors.Wrapf(err, "unable to update status with resolved service binding information")) + } + } + + return r.done(ba) +} + +//GetResolvedBindingSecret returns the secret referenced in .status.resolvedBindings +func (r *ReconcilerBase) GetResolvedBindingSecret(ba common.BaseComponent) (*corev1.Secret, error) { + if len(ba.GetStatus().GetResolvedBindings()) == 0 { + return nil, nil + } + mObj := ba.(metav1.Object) + secret := &corev1.Secret{} + key := types.NamespacedName{Name: ba.GetStatus().GetResolvedBindings()[0], Namespace: mObj.GetNamespace()} + err := r.client.Get(context.TODO(), key, secret) + if err != nil { + return nil, err + } + return secret, nil +} + +// done when no error happens +func (r *ReconcilerBase) done(ba common.BaseComponent) (reconcile.Result, error) { + return r.ManageSuccess(common.StatusConditionTypeDependenciesSatisfied, ba) +} + +// requeueError simply calls ManageError when dependency is not fulfilled +func (r *ReconcilerBase) requeueError(ba common.BaseComponent, err error) (reconcile.Result, error) { + r.ManageError(err, common.StatusConditionTypeDependenciesSatisfied, ba) + return r.ManageError(errors.New("dependency not satisfied"), common.StatusConditionTypeReconciled, ba) +} + +func isRequeue(res reconcile.Result, err error) bool { + return err != nil || res.Requeue +} + +func getCopiedToNamespacesAnnotationKey(ba common.BaseComponent) string { + return "service." + ba.GetGroupName() + "/copied-to-namespaces" +} + +func getConsumedByAnnotationKey(ba common.BaseComponent) string { + return "service." + ba.GetGroupName() + "/consumed-by" +} + +func equals(sl1, sl2 []string) bool { + if len(sl1) != len(sl2) { + return false + } + for i, v := range sl1 { + if v != sl2[i] { + return false + } + } + return true +} + +func getServiceBindingGVK() []schema.GroupVersionKind { + gvkStringList := strings.Split(common.Config[common.OpConfigSvcBindingGVKs], ",") + for i := range gvkStringList { + gvkStringList[i] = strings.TrimSpace(gvkStringList[i]) + } + + gvkList := []schema.GroupVersionKind{} + for i := range gvkStringList { + gvk, _ := schema.ParseKindArg(gvkStringList[i]) + if gvk == nil { + log.Error(errors.Errorf("failed to parse %q to a valid GroupVersionKind", gvkStringList[i]), "Invalid GroupVersionKind from operator ConfigMap") + continue + } + gvkList = append(gvkList, *gvk) + } + return gvkList +} + +// IsSeriveBindingSupported returns true if at least one GVK in the operator ConfigMap's serviceBinding.groupVersionKinds is installed +func (r *ReconcilerBase) IsSeriveBindingSupported() bool { + for _, gvk := range getServiceBindingGVK() { + if ok, _ := r.IsGroupVersionSupported(gvk.GroupVersion().String(), gvk.Kind); ok { + return true + } + } + return false +} diff --git a/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/utils.go b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/utils.go index a9aebdb..4d9fe83 100644 --- a/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/utils.go +++ b/vendor/github.com/application-stacks/runtime-component-operator/pkg/utils/utils.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + "github.com/application-stacks/runtime-component-operator/pkg/common" prometheusv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" @@ -95,7 +97,12 @@ func CustomizeRoute(route *routev1.Route, ba common.BaseComponent, key string, c if ba.GetRoute() != nil { rt := ba.GetRoute() route.Annotations = MergeMaps(route.Annotations, rt.GetAnnotations()) - route.Spec.Host = rt.GetHost() + + host := rt.GetHost() + if host == "" && common.Config[common.OpConfigDefaultHostname] != "" { + host = obj.GetName() + "-" + obj.GetNamespace() + "." + common.Config[common.OpConfigDefaultHostname] + } + route.Spec.Host = host route.Spec.Path = rt.GetPath() if ba.GetRoute().GetTermination() != nil { if route.Spec.TLS == nil { @@ -168,6 +175,15 @@ func CustomizeService(svc *corev1.Service, ba common.BaseComponent) { } else { svc.Spec.Ports[0].Name = strconv.Itoa(int(ba.GetService().GetPort())) + "-tcp" } + + if *ba.GetService().GetType() == corev1.ServiceTypeNodePort && ba.GetService().GetNodePort() != nil { + svc.Spec.Ports[0].NodePort = *ba.GetService().GetNodePort() + } + + if *ba.GetService().GetType() == corev1.ServiceTypeClusterIP { + svc.Spec.Ports[0].NodePort = 0 + } + svc.Spec.Type = *ba.GetService().GetType() svc.Spec.Selector = map[string]string{ "app.kubernetes.io/instance": obj.GetName(), @@ -176,6 +192,45 @@ func CustomizeService(svc *corev1.Service, ba common.BaseComponent) { if ba.GetService().GetTargetPort() != nil { svc.Spec.Ports[0].TargetPort = intstr.FromInt(int(*ba.GetService().GetTargetPort())) } + + numOfAdditionalPorts := len(ba.GetService().GetPorts()) + numOfCurrentPorts := len(svc.Spec.Ports) - 1 + for i := 0; i < numOfAdditionalPorts; i++ { + for numOfCurrentPorts < numOfAdditionalPorts { + svc.Spec.Ports = append(svc.Spec.Ports, corev1.ServicePort{}) + numOfCurrentPorts++ + } + for numOfCurrentPorts > numOfAdditionalPorts && len(svc.Spec.Ports) != 0 { + svc.Spec.Ports = svc.Spec.Ports[:len(svc.Spec.Ports)-1] + numOfCurrentPorts-- + } + svc.Spec.Ports[i+1].Port = ba.GetService().GetPorts()[i].Port + svc.Spec.Ports[i+1].TargetPort = intstr.FromInt(int(ba.GetService().GetPorts()[i].Port)) + + if ba.GetService().GetPorts()[i].Name != "" { + svc.Spec.Ports[i+1].Name = ba.GetService().GetPorts()[i].Name + } else { + svc.Spec.Ports[i+1].Name = strconv.Itoa(int(ba.GetService().GetPorts()[i].Port)) + "-tcp" + } + + if ba.GetService().GetPorts()[i].TargetPort.String() != "" { + svc.Spec.Ports[i+1].TargetPort = intstr.FromInt(ba.GetService().GetPorts()[i].TargetPort.IntValue()) + } + + if *ba.GetService().GetType() == corev1.ServiceTypeNodePort && ba.GetService().GetPorts()[i].NodePort != 0 { + svc.Spec.Ports[i+1].NodePort = ba.GetService().GetPorts()[i].NodePort + } + + if *ba.GetService().GetType() == corev1.ServiceTypeClusterIP { + svc.Spec.Ports[i+1].NodePort = 0 + } + } + if len(ba.GetService().GetPorts()) == 0 { + for numOfCurrentPorts > 0 { + svc.Spec.Ports = svc.Spec.Ports[:len(svc.Spec.Ports)-1] + numOfCurrentPorts-- + } + } } // CustomizeServiceBindingSecret ... @@ -302,6 +357,26 @@ func CustomizePodSpec(pts *corev1.PodTemplateSpec, ba common.BaseComponent) { } } +// CustomizeServiceBinding ... +func CustomizeServiceBinding(secret *corev1.Secret, podSpec *corev1.PodSpec, ba common.BaseComponent) { + if len(ba.GetStatus().GetResolvedBindings()) != 0 { + appContainer := GetAppContainer(podSpec.Containers) + binding := ba.GetStatus().GetResolvedBindings()[0] + bindingSecret := corev1.EnvFromSource{ + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: binding, + }}, + } + appContainer.EnvFrom = append(appContainer.EnvFrom, bindingSecret) + + secretRev := corev1.EnvVar{ + Name: "RESOLVED_BINDING_SECRET_REV", + Value: secret.ResourceVersion} + appContainer.Env = append(appContainer.Env, secretRev) + } +} + // CustomizeConsumedServices ... func CustomizeConsumedServices(podSpec *corev1.PodSpec, ba common.BaseComponent) { if ba.GetStatus().GetConsumedServices() != nil { @@ -594,10 +669,27 @@ func CustomizeServiceMonitor(sm *prometheusv1.ServiceMonitor, ba common.BaseComp if len(sm.Spec.Endpoints) == 0 { sm.Spec.Endpoints = append(sm.Spec.Endpoints, prometheusv1.Endpoint{}) } - if ba.GetService().GetPortName() != "" { - sm.Spec.Endpoints[0].Port = ba.GetService().GetPortName() - } else { - sm.Spec.Endpoints[0].Port = strconv.Itoa(int(ba.GetService().GetPort())) + "-tcp" + sm.Spec.Endpoints[0].Port = "" + sm.Spec.Endpoints[0].TargetPort = nil + if len(ba.GetMonitoring().GetEndpoints()) > 0 { + port := ba.GetMonitoring().GetEndpoints()[0].Port + targetPort := ba.GetMonitoring().GetEndpoints()[0].TargetPort + if port != "" { + sm.Spec.Endpoints[0].Port = port + } + if targetPort != nil { + sm.Spec.Endpoints[0].TargetPort = targetPort + } + if port != "" && targetPort != nil { + sm.Spec.Endpoints[0].TargetPort = nil + } + } + if sm.Spec.Endpoints[0].Port == "" && sm.Spec.Endpoints[0].TargetPort == nil { + if ba.GetService().GetPortName() != "" { + sm.Spec.Endpoints[0].Port = ba.GetService().GetPortName() + } else { + sm.Spec.Endpoints[0].Port = strconv.Itoa(int(ba.GetService().GetPort())) + "-tcp" + } } if len(ba.GetMonitoring().GetLabels()) > 0 { for k, v := range ba.GetMonitoring().GetLabels() { @@ -827,3 +919,58 @@ func GetAppContainer(containerList []corev1.Container) *corev1.Container { } return &containerList[0] } + +// CustomizeIngress customizes ingress resource +func CustomizeIngress(ing *networkingv1beta1.Ingress, ba common.BaseComponent) { + obj := ba.(metav1.Object) + ing.Labels = ba.GetLabels() + ing.Annotations = MergeMaps(ing.Annotations, ba.GetAnnotations(), ba.GetRoute().GetAnnotations()) + servicePort := strconv.Itoa(int(ba.GetService().GetPort())) + "-tcp" + rt := ba.GetRoute() + if ba.GetService().GetPortName() != "" { + servicePort = ba.GetService().GetPortName() + } + host := rt.GetHost() + if host == "" && common.Config[common.OpConfigDefaultHostname] != "" { + host = obj.GetName() + "-" + obj.GetNamespace() + "." + common.Config[common.OpConfigDefaultHostname] + } + ing.Spec.Rules = []networkingv1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: rt.GetPath(), + Backend: networkingv1beta1.IngressBackend{ + ServiceName: obj.GetName(), + ServicePort: intstr.IntOrString{Type: intstr.String, StrVal: servicePort}, + }, + }, + }, + }, + }, + }, + } + + tlsSecretName := "" + if rt.GetCertificate() != nil { + tlsSecretName = obj.GetName() + "-route-tls" + if rt.GetCertificate().GetSpec().SecretName != "" { + tlsSecretName = obj.GetName() + rt.GetCertificate().GetSpec().SecretName + } + } + if rt.GetCertificateSecretRef() != nil && *rt.GetCertificateSecretRef() != "" { + tlsSecretName = *rt.GetCertificateSecretRef() + } + if tlsSecretName != "" { + ing.Spec.TLS = []networkingv1beta1.IngressTLS{ + { + Hosts: []string{host}, + SecretName: tlsSecretName, + }, + } + } else { + ing.Spec.TLS = nil + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5cef187..2c190a8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,7 +15,7 @@ github.com/Azure/go-autorest/tracing github.com/PuerkitoBio/purell # github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 github.com/PuerkitoBio/urlesc -# github.com/application-stacks/runtime-component-operator v0.4.2 +# github.com/application-stacks/runtime-component-operator v0.5.0 github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1 github.com/application-stacks/runtime-component-operator/pkg/common github.com/application-stacks/runtime-component-operator/pkg/utils diff --git a/version/version.go b/version/version.go index 64c7cd2..0ede0cb 100644 --- a/version/version.go +++ b/version/version.go @@ -1,5 +1,5 @@ package version var ( - Version = "0.4.1" + Version = "0.5.0" )