-
-
Save negz/cb9ee4236e0f1dfff514617584a1c8c6 to your computer and use it in GitHub Desktop.
| # In this YAML an ApplicationDefinition defines the schema of a | |
| # Wordpress, while a Composition specifies what resources the | |
| # Wordpress will be composed of, and how they'll be created. | |
| --- | |
| apiVersion: crossplane.io/v1alpha1 | |
| kind: ApplicationDefinition | |
| metadata: | |
| name: wordpresses.apps.example.org | |
| spec: | |
| crdSpecTemplate: | |
| group: apps.example.org | |
| version: v1alpha1 | |
| names: | |
| kind: Wordpress | |
| listKind: WordpressList | |
| plural: wordpresses | |
| singular: wordpress | |
| validation: | |
| openAPIV3Schema: | |
| properties: | |
| image: | |
| type: string | |
| type: object | |
| serviceAccountRef: | |
| namespace: crossplane-system | |
| name: wordpresses.apps.example.org | |
| --- | |
| apiVersion: crossplane.io/v1alpha1 | |
| kind: Composition | |
| metadata: | |
| name: wordpresses.apps.example.org | |
| annotations: | |
| crossplane.io/default: "true" | |
| spec: | |
| from: | |
| apiVersion: apps.example.org/v1alpha1 | |
| kind: Wordpress | |
| to: | |
| - base: | |
| apiVersion: apps/v1 | |
| kind: Deployment | |
| spec: | |
| template: | |
| spec: | |
| containers: | |
| - name: wordpress | |
| image: wordpress:4.6.1-apache | |
| env: | |
| - name: WORDPRESS_DB_HOST | |
| valueFrom: | |
| secretKeyRef: | |
| key: endpoint | |
| - name: WORDPRESS_DB_USER | |
| valueFrom: | |
| secretKeyRef: | |
| key: username | |
| - name: WORDPRESS_DB_PASSWORD | |
| valueFrom: | |
| secretKeyRef: | |
| key: password | |
| ports: | |
| - containerPort: 80 | |
| name: wordpress | |
| patches: | |
| - fromFieldPath: "spec.image" | |
| toFieldPath: "spec.containers[0].image" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.selector.matchLabels[wordpress]" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.template.metadata.labels[wordpress]" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[0].valueFrom.secretKeyRef.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[1].valueFrom.secretKeyRef.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[2].valueFrom.secretKeyRef.name" | |
| - base: | |
| apiVersion: v1 | |
| kind: Service | |
| spec: | |
| ports: | |
| - port: 80 | |
| type: LoadBalancer | |
| patches: | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.selector[wordpress]" | |
| - base: | |
| apiVersion: database.example.org/v1alpha1 | |
| kind: MySQLInstanceBinding | |
| spec: | |
| engineVersion: "5.7" | |
| patches: | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.infrastructure.writeConnectionSecretToRef.name" |
| # In this YAML an ApplicationDefinition declares the resources it | |
| # should produce inline. This implies that a Wordpress can only | |
| # ever render to a Deployment and a Service. It also implies that | |
| # the person who defines the schema of a Wordpress must always be | |
| # the same person who defines the template used to render it. | |
| --- | |
| apiVersion: crossplane.io/v1alpha1 | |
| kind: ApplicationDefinition | |
| metadata: | |
| name: wordpresses.apps.example.org | |
| spec: | |
| crdSpecTemplate: | |
| group: apps.example.org | |
| version: v1alpha1 | |
| names: | |
| kind: Wordpress | |
| listKind: WordpressList | |
| plural: wordpresses | |
| singular: wordpress | |
| validation: | |
| openAPIV3Schema: | |
| properties: | |
| image: | |
| type: string | |
| type: object | |
| serviceAccountRef: | |
| namespace: crossplane-system | |
| name: wordpresses.apps.example.org | |
| resources: | |
| - base: | |
| apiVersion: apps/v1 | |
| kind: Deployment | |
| spec: | |
| template: | |
| spec: | |
| containers: | |
| - name: wordpress | |
| image: wordpress:4.6.1-apache | |
| env: | |
| - name: WORDPRESS_DB_HOST | |
| valueFrom: | |
| secretKeyRef: | |
| key: endpoint | |
| - name: WORDPRESS_DB_USER | |
| valueFrom: | |
| secretKeyRef: | |
| key: username | |
| - name: WORDPRESS_DB_PASSWORD | |
| valueFrom: | |
| secretKeyRef: | |
| key: password | |
| ports: | |
| - containerPort: 80 | |
| name: wordpress | |
| patches: | |
| - fromFieldPath: "spec.image" | |
| toFieldPath: "spec.containers[0].image" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.selector.matchLabels[wordpress]" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.template.metadata.labels[wordpress]" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[0].valueFrom.secretKeyRef.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[1].valueFrom.secretKeyRef.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.containers[0].env[2].valueFrom.secretKeyRef.name" | |
| - base: | |
| apiVersion: v1 | |
| kind: Service | |
| spec: | |
| ports: | |
| - port: 80 | |
| type: LoadBalancer | |
| patches: | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.selector[wordpress]" | |
| - base: | |
| apiVersion: database.example.org/v1alpha1 | |
| kind: MySQLInstanceBinding | |
| spec: | |
| engineVersion: "5.7" | |
| patches: | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "metadata.name" | |
| - fromFieldPath: "metadata.name" | |
| toFieldPath: "spec.infrastructure.writeConnectionSecretToRef.name" |
| # The application operator authors a wordpress CR | |
| # for either of the above cases. When a composition | |
| # is annotated as the default the app operator need | |
| # not be aware of the concept of compositions. | |
| --- | |
| apiVersion: apps.example.org/v1alpha1 | |
| kind: Wordpress | |
| metadata: | |
| namespace: default | |
| name: my-cool-wordpress | |
| spec: | |
| image: 5.3.2-php7.2-apache |
https://github.com/crossplane/app-wordpress/blob/b51da8/helm-chart/templates/app.yaml#L13
I derived this example scenario from our app-wordpress example template stack. While doing so I noticed that even our basic template stack example has a use case for supporting multiple application compositions; its Helm template branches based on the setting of spec.provisionPolicy and will either provision a new KubernetesCluster to deploy Wordpress to, or try to use an existing one.
one-to-one.yamlmeans that only the person who defines that a kind: Wordpress exists may define how a kind: Wordpress should be composed.
This constraint has interesting implications around how an application consumes infrastructure.
In our current app-wordpress template stack kind: Wordpress provisions a kind: MySQLInstance claim for Wordpress to use as its database. If we presume an ApplicationDefinition will usually be written by an application developer (the developers of Wordpress presumably author the Wordpress Helm chart), then in one-to-one.yaml those application developers must define how a kind: Wordpress should be composed. Presumably this means they'll be limited to rendering well known (core?) resource claim kinds? If ExampleCorp Ltd would rather their kind: Wordpress produced a kind: VerySecureMySQLInstanceBinding they must fork and maintain a variant of the upstream ApplicationDefinition. Presumably they'd have to change the API group or kind to avoid conflicts with upstream. Maybe they now have kind: VerySecureWordpress, or kind: Wordpress, apiVersion: example.corp.org/v1alpha.
Conversely in one-to-many.yaml, the infrastructure operator at ExampleCorp could introduce a new kind: Composition that was mostly the same as the official upstream one, but rendered a kind: VerySecureMySQLInstanceBinding instead of a kind: MySQLInstanceBinding and annotate it as the default. The application operators wouldn't need to care about any of this; they'd author a kind: Wordpress just as they would have if their infrastructure operators hadn't provided a new default composition to override the database binding.
This gist models the case in which an application developer wants to define a
kind: Wordpressapplication custom resource that will always render to exactly onekind: Deploymentand onekind: Service.Some observations:
one-to-one.yamlis more succinct by 12 lines of YAML.one-to-one.yamlhas one fewer concept;kind: Composition.one-to-one.yamlconstrains akind: Wordpressto rendering akind: Deploymentand akind: Service. If a use case arose where we instead wanted to render akind: KubernetesApplicationwe'd need to introduce akind: RemoteWordpressor similar to do so.one-to-one.yamleliminates the concept ofkind: Compositionfrom applications, but not from Crossplane. The concept (or something like it) must still be learned by infrastructure operators who wish to publish infrastructure, and application operators who wish to consume that infrastructure.one-to-one.yamlmeans that only the person who defines that akind: Wordpressexists may define how akind: Wordpressshould be composed.