From 9c5594c41989ed865e251de2bc24acafd4f352fe Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 14 May 2021 12:07:28 -0400 Subject: [PATCH 1/3] Remove mappings from the ServiceBinding resource The existing mappings on the ServiceBinding resource were introduced as a way to make it easier for a user to enrich an existing Secret into a form appropriate for binding to an application workload. This approch had a few issues that can be better addressed by other resources that interoperate with the ServiceBiding resource. The issues include: - the ServiceBinding controller needs to be able to read and write Secrets - Go templates were used to compose new values, which worked for basic templating, but were limited in their capabilities - the capabilities of the Go templates are an implemenation detail of how the controller is built and could change over time independent of the speced behavior. The behavior applied by mappings can be reintroduced as dedicated resources that can themselves expose a Secret as a ProvisionedService, which can be consumed by a ServiceBinding. This change further separates the concerns of provisioning a service from binding a service. Refs #145 Signed-off-by: Scott Andrews --- README.md | 52 +------------------ .../v1alpha2/service_binding.go | 15 ------ service.binding_servicebindings.yaml | 22 -------- 3 files changed, 1 insertion(+), 88 deletions(-) diff --git a/README.md b/README.md index 0f2d992..0c72567 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ Behavior within the project is governed by the [Contributor Covenant Code of Con - [Resource Type Schema](#resource-type-schema-1) - [Minimal Example Resource](#minimal-example-resource) - [Label Selector Example Resource](#label-selector-example-resource) - - [Mappings Example Resource](#mappings-example-resource) - [Environment Variables Example Resource](#environment-variables-example-resource) - [Reconciler Implementation](#reconciler-implementation) - [Ready Condition Status](#ready-condition-status) @@ -263,9 +262,7 @@ The Service Binding resource **MAY** define `.spec.application.containers`, as a - if the value is a string (`${containerString}`), a container or init container matching by name (`.spec.template.spec.containers[?(@.name=='${containerString}')]` or `.spec.template.spec.initContainers[?(@.name=='${containerString}')]`) **MUST** be bound - values that do not match a container or init container **SHOULD** be ignored -A Service Binding Resource **MAY** define a `.spec.mappings` which is an array of `Mapping` objects. A `Mapping` object **MUST** define `name` and `value` entries. The `value` of a `Mapping` **MUST** be handled as a [Go Template][gt] exposing binding `Secret` keys for substitution. The executed output of the template **MUST** be added to the `Secret` exposed to the resource represented by `application` as the key specified by the `name` of the `Mapping`. If the `name` of a `Mapping` matches that of a Provisioned Service `Secret` key, the value from `Mapping` **MUST** be used for binding. - -A Service Binding Resource **MAY** define a `.spec.env` which is an array of `EnvMapping`. An `EnvMapping` object **MUST** define `name` and `key` entries. The `key` of an `EnvMapping` **MUST** refer to a binding `Secret` key name including any key defined by a `Mapping`. The value of this `Secret` entry **MUST** be configured as an environment variable on the resource represented by `application`. +A Service Binding Resource **MAY** define a `.spec.env` which is an array of `EnvMapping`. An `EnvMapping` object **MUST** define `name` and `key` entries. The `key` of an `EnvMapping` **MUST** refer to a binding `Secret` key name. The value of this `Secret` entry **MUST** be configured as an environment variable on the resource represented by `application`. A Service Binding resource **MUST** define `.status.conditions` which is an array of `Condition` objects as defined in [meta/v1 Condition][mv1c]. At least one condition containing a `type` of `Ready` **MUST** be defined. The `Ready` condition **SHOULD** contain appropriate values defined by the implementation. As label selectors are inherently queries that return zero-to-many resources, it is **RECOMMENDED** that `ServiceBinding` authors use a combination of labels that yield a single resource, but implementors **MUST** handle each matching resource as if it was specified by name in a distinct `ServiceBinding` resource. Partial failures **MUST** be aggregated and reported on the binding status's `Ready` condition. A Service Binding resource **SHOULD** reflect the secret projected into the application as `.status.binding.name`. @@ -285,8 +282,6 @@ metadata: ... spec: name: # string, optional, default: .metadata.name - type: # string, optional - provider: # string, optional application: # ObjectReference-like apiVersion: # string @@ -300,10 +295,6 @@ spec: kind: # string name: # string - mappings: # []Mapping, optional - - name: # string - value: # string - env: # []EnvMapping, optional - name: # string key: # string @@ -374,39 +365,6 @@ status: lastTransitionTime: '2021-01-20T17:00:00Z' ``` -## Mappings Example Resource - -```yaml -apiVersion: service.binding/v1alpha2 -kind: ServiceBinding -metadata: - name: account-service -spec: - application: - apiVersion: apps/v1 - kind: Deployment - name: online-banking - - service: - apiVersion: com.example/v1alpha1 - kind: AccountService - name: prod-account-service - - mappings: - - name: accountServiceUri - value: https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}:{{ .port }}/{{ .path }} - -status: - binding: - name: prod-account-service-projection - conditions: - - type: Ready - status: 'True' - reason: 'Projected' - message: '' - lastTransitionTime: '2021-01-20T17:00:00Z' -``` - ## Environment Variables Example Resource ```yaml @@ -425,10 +383,6 @@ spec: kind: AccountService name: prod-account-service - mappings: - - name: accountServiceUri - value: https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}:{{ .port }}/{{ .path }} - env: - name: ACCOUNT_SERVICE_HOST key: host @@ -436,8 +390,6 @@ spec: key: username - name: ACCOUNT_SERVICE_PASSWORD key: password - - name: ACCOUNT_SERVICE_URI - key: accountServiceUri status: binding: @@ -460,8 +412,6 @@ If the `$SERVICE_BINDING_ROOT` environment variable has already been configured The `$SERVICE_BINDING_ROOT` environment variable **MUST NOT** be reset if it is already configured on the resource represented by `application`. -If a `.spec.type` is set, the `type` entry in the binding `Secret` **MUST** be set to its value overriding any existing value. If a `.spec.provider` is set, the `provider` entry in the binding `Secret` **MUST** be set to its value overriding any existing value. - ### Ready Condition Status If the modification of the Application resource is completed successfully, the `Ready` condition status **MUST** be set to `True`. If the modification of the Application resource is not completed successfully the `Ready` condition status **MUST NOT** be set to `True`. diff --git a/internal/service.binding/v1alpha2/service_binding.go b/internal/service.binding/v1alpha2/service_binding.go index f4f371d..9739545 100644 --- a/internal/service.binding/v1alpha2/service_binding.go +++ b/internal/service.binding/v1alpha2/service_binding.go @@ -64,31 +64,16 @@ type EnvMapping struct { Key string `json:"key"` } -// ServiceBindingMapping defines a mapping from the existing collection of Secret values to a new Secret entry. -type ServiceBindingMapping struct { - // Name is the name of the mapped Secret entry - Name string `json:"name"` - // Value is the value of the new Secret entry. Contents may be a Go template and refer to the other secret entries - // by name. - Value string `json:"value"` -} - // ServiceBindingSpec defines the desired state of ServiceBinding type ServiceBindingSpec struct { // Name is the name of the service as projected into the application container. Defaults to .metadata.name. Name string `json:"name,omitempty"` - // Type is the type of the service as projected into the application container - Type string `json:"type,omitempty"` - // Provider is the provider of the service as projected into the application container - Provider string `json:"provider,omitempty"` // Application is a reference to an object Application ServiceBindingApplicationReference `json:"application"` // Service is a reference to an object that fulfills the ProvisionedService duck type Service ServiceBindingServiceReference `json:"service"` // Env is the collection of mappings from Secret entries to environment variables Env []EnvMapping `json:"env,omitempty"` - // Mappings is the collection of mappings from existing Secret entries to new Secret entries - Mappings []ServiceBindingMapping `json:"mappings,omitempty"` } // These are valid conditions of ServiceBinding. diff --git a/service.binding_servicebindings.yaml b/service.binding_servicebindings.yaml index 5057043..79a3ded 100644 --- a/service.binding_servicebindings.yaml +++ b/service.binding_servicebindings.yaml @@ -112,28 +112,9 @@ spec: - name type: object type: array - mappings: - description: Mappings is the collection of mappings from existing Secret entries to new Secret entries - items: - description: ServiceBindingMapping defines a mapping from the existing collection of Secret values to a new Secret entry. - properties: - name: - description: Name is the name of the mapped Secret entry - type: string - value: - description: Value is the value of the new Secret entry. Contents may be a Go template and refer to the other secret entries by name. - type: string - required: - - name - - value - type: object - type: array name: description: Name is the name of the service as projected into the application container. Defaults to .metadata.name. type: string - provider: - description: Provider is the provider of the service as projected into the application container - type: string service: description: Service is a reference to an object that fulfills the ProvisionedService duck type properties: @@ -151,9 +132,6 @@ spec: - kind - name type: object - type: - description: Type is the type of the service as projected into the application container - type: string required: - application - service From bf6c0c04e45ce5c83bdad5f2f30046e93105b149 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Tue, 25 May 2021 11:56:48 -0400 Subject: [PATCH 2/3] Add should/must clarification for .data.type in secrets Signed-off-by: Scott Andrews --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0c72567..f5a2500 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,8 @@ An implementation is not compliant if it fails to satisfy one or more of the MUS A Provisioned Service resource **MUST** define a `.status.binding` which is a `LocalObjectReference`-able (containing a single field `name`) to a `Secret`. The `Secret` **MUST** be in the same namespace as the resource. The `Secret` data **SHOULD** contain a `type` entry with a value that identifies the abstract classification of the binding. The `Secret` type (`.type` verses `.data.type`) **SHOULD** reflect this value as `service.binding/{type}`, replacing `{type}` with the `Secret` data type. It is **RECOMMENDED** that the `Secret` data also contain a `provider` entry with a value that identifies the provider of the binding. The `Secret` data **MAY** contain any other entry. To facilitate discoverability, it is **RECOMMENDED** that a `CustomResourceDefinition` exposing a Provisioned Service add `service.binding/provisioned-service: "true"` as a label. +> Note: While the Provisioned Service referenced `Secret` data should contain a `type` entry, the `type` must be defined before it is projected into an application workload. This allows a mapping to enrich an existing secret. + Extensions and implementations **MAY** define additional mechanisms to consume a Provisioned Service that does not conform to the duck type. ## Resource Type Schema From dbd710590011c571dc1514c75bcf10c99f2b9941 Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Thu, 3 Jun 2021 09:50:05 -0400 Subject: [PATCH 3/3] Restore .spec.type and .spec.provider While bring back these fields and the general capability, I removed references that mandated these fields be added to a Secret. Instead, the requirement is that they are part of the application projection. Implementors can figure out the best way to make that happen, either by creating a derivative Secret, or by using a projected volume. Signed-off-by: Scott Andrews --- README.md | 10 +++++++--- internal/service.binding/v1alpha2/service_binding.go | 4 ++++ service.binding_servicebindings.yaml | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f5a2500..1c4e713 100644 --- a/README.md +++ b/README.md @@ -197,11 +197,11 @@ rules: # Application Projection -A Binding `Secret` **MUST** be volume mounted into a container at `$SERVICE_BINDING_ROOT/` with directory names matching the name of the binding. Binding names **MUST** match `[a-z0-9\-\.]{1,253}`. The `$SERVICE_BINDING_ROOT` environment variable **MUST** be declared and can point to any valid file system location. +A projected binding **MUST** be volume mounted into a container at `$SERVICE_BINDING_ROOT/` with directory names matching the name of the binding. Binding names **MUST** match `[a-z0-9\-\.]{1,253}`. The `$SERVICE_BINDING_ROOT` environment variable **MUST** be declared and can point to any valid file system location. -The `Secret` data **MUST** contain a `type` entry with a value that identifies the abstract classification of the binding. The `Secret` type (`.type` verses `.data.type`) **MUST** reflect this value as `service.binding/{type}`, replacing `{type}` with the `Secret` data type. It is **RECOMMENDED** that the `Secret` data also contain a `provider` entry with a value that identifies the provider of the binding. The `Secret` data **MAY** contain any other entry. +The projected binding **MUST** contain a `type` entry with a value that identifies the abstract classification of the binding. It is **RECOMMENDED** that the projected binding also contain a `provider` entry with a value that identifies the provider of the binding. The projected binding data **MAY** contain any other entry. -The name of a secret entry file name **SHOULD** match `[a-z0-9\-\.]{1,253}`. The contents of a secret entry may be anything representable as bytes on the file system including, but not limited to, a literal string value (e.g. `db-password`), a language-specific binary (e.g. a Java `KeyStore` with a private key and X.509 certificate), or an indirect pointer to another system for value resolution (e.g. `vault://production-database/password`). +The name of a binding entry file name **SHOULD** match `[a-z0-9\-\.]{1,253}`. The contents of a binding entry may be anything representable as bytes on the file system including, but not limited to, a literal string value (e.g. `db-password`), a language-specific binary (e.g. a Java `KeyStore` with a private key and X.509 certificate), or an indirect pointer to another system for value resolution (e.g. `vault://production-database/password`). The collection of files within the directory **MAY** change between container launches. The collection of files within the directory **SHOULD NOT** change during the lifetime of the container. @@ -284,6 +284,8 @@ metadata: ... spec: name: # string, optional, default: .metadata.name + type: # string, optional + provider: # string, optional application: # ObjectReference-like apiVersion: # string @@ -414,6 +416,8 @@ If the `$SERVICE_BINDING_ROOT` environment variable has already been configured The `$SERVICE_BINDING_ROOT` environment variable **MUST NOT** be reset if it is already configured on the resource represented by `application`. +If a `.spec.type` is set, the `type` entry in the application projection **MUST** be set to its value overriding any existing value. If a `.spec.provider` is set, the `provider` entry in the application projection **MUST** be set to its value overriding any existing value. + ### Ready Condition Status If the modification of the Application resource is completed successfully, the `Ready` condition status **MUST** be set to `True`. If the modification of the Application resource is not completed successfully the `Ready` condition status **MUST NOT** be set to `True`. diff --git a/internal/service.binding/v1alpha2/service_binding.go b/internal/service.binding/v1alpha2/service_binding.go index 9739545..9bf62e5 100644 --- a/internal/service.binding/v1alpha2/service_binding.go +++ b/internal/service.binding/v1alpha2/service_binding.go @@ -68,6 +68,10 @@ type EnvMapping struct { type ServiceBindingSpec struct { // Name is the name of the service as projected into the application container. Defaults to .metadata.name. Name string `json:"name,omitempty"` + // Type is the type of the service as projected into the application container + Type string `json:"type,omitempty"` + // Provider is the provider of the service as projected into the application container + Provider string `json:"provider,omitempty"` // Application is a reference to an object Application ServiceBindingApplicationReference `json:"application"` // Service is a reference to an object that fulfills the ProvisionedService duck type diff --git a/service.binding_servicebindings.yaml b/service.binding_servicebindings.yaml index 79a3ded..2920886 100644 --- a/service.binding_servicebindings.yaml +++ b/service.binding_servicebindings.yaml @@ -115,6 +115,9 @@ spec: name: description: Name is the name of the service as projected into the application container. Defaults to .metadata.name. type: string + provider: + description: Provider is the provider of the service as projected into the application container + type: string service: description: Service is a reference to an object that fulfills the ProvisionedService duck type properties: @@ -132,6 +135,9 @@ spec: - kind - name type: object + type: + description: Type is the type of the service as projected into the application container + type: string required: - application - service