-
-
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.yaml
means 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: Wordpress
application custom resource that will always render to exactly onekind: Deployment
and onekind: Service
.Some observations:
one-to-one.yaml
is more succinct by 12 lines of YAML.one-to-one.yaml
has one fewer concept;kind: Composition
.one-to-one.yaml
constrains akind: Wordpress
to rendering akind: Deployment
and akind: Service
. If a use case arose where we instead wanted to render akind: KubernetesApplication
we'd need to introduce akind: RemoteWordpress
or similar to do so.one-to-one.yaml
eliminates the concept ofkind: Composition
from 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.yaml
means that only the person who defines that akind: Wordpress
exists may define how akind: Wordpress
should be composed.