Remove knowledge base section (#755)

Remove KB and add redirects
This commit is contained in:
Pete Lumbis 2024-04-22 15:44:32 -04:00 committed by GitHub
parent e2603a9cc0
commit 2b598cb79f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
115 changed files with 16243 additions and 403 deletions

View File

@ -54,43 +54,6 @@ To create new content create a new markdown file in the appropriate location.
To create a new section, create a new directory and an `_index.md` file in the To create a new section, create a new directory and an `_index.md` file in the
root. root.
### Types of content
Crossplane documentation has three content sections:
* The [Contributing Guide]({{<ref "/contribute/_index.md">}}) with details on
how to contribute to the Crossplane documentation.
* The [Knowledge Base]({{<ref "/knowledge-base" >}}) is for content related to
Crossplane integrations, in-depth articles or how-to guides.
* [User documentation]({{<ref "/master" >}}) are for generic documentation,
commonly version-specific.
#### User documentation vs knowledge base articles
User documentation includes both _conceptual_ and _procedural_ instructions.
_Conceptual_ content describes the background and theory behind the technology.
Conceptual documents are helpful to explain the "why" of the technology.
An example of _Conceptual_ content would be describing the role
of a Crossplane Provider.
_Procedural_ content is the step-by-step instructions to do something.
Procedural content details the "how" of a piece of technology.
An example of a _Procedural_ document would be a step-by-step Crossplane
installation guide.
User documentation is more narrowly focused on a single piece or
related pieces of technology. For example, installing a Provider and creating a
ProviderConfig.
Knowledge base articles are more "free-form" and can describe more than one
piece of technology or provide more opinionated examples.
{{< hint "tip" >}}
Not sure if the content would be better as a knowledge base article or user
document? Ask in the `#documentation` channel of the
[Crossplane Slack](https://slack.crossplane.io/).
{{< /hint >}}
### Front matter ### Front matter
Each page contains metadata called Each page contains metadata called
[front matter](https://gohugo.io/content-management/front-matter/). Each page [front matter](https://gohugo.io/content-management/front-matter/). Each page

View File

@ -1,32 +0,0 @@
---
title: Knowledge Base
weight: -1
cascade:
version: "0"
docs: false
product: "Knowledge Base"
---
{{< img src="media/banner.png" alt="Crossplane Popsicle Truck" eager=true >}}
<br />
The Crossplane Knowledge Base is a collection of documents covering recommended
practices, third-party integrations and Crossplane-related how-tos.
## Topics
* [Configuration Guides]({{<ref "guides" >}}) covers topics related to operating
and using Crossplane.
* [Integrations]({{<ref "integrations" >}}) are topics using Crossplane with
third-party tools like [Vault](https://www.vaultproject.io/).
## Contribute an article
Contributions to the Knowledge Base are always welcome. Knowledge Base articles
cover topics in detail or describe using Crossplane with third-party software.
Some examples of Knowledge Base articles could include:
* Troubleshooting Crossplane providers
* Migrating to Crossplane (from another tool)
* Using Crossplane with [ArgoCD]({{< ref "integrations/argo-cd-crossplane" >}})
Find more information in the Crossplane
[documentation contributor's guide]({{< ref "/contribute" >}}).

View File

@ -1,12 +0,0 @@
---
title: Configuration Guides
weight: 10
description: Guides on using, operating and configuring Crossplane
---
Crossplane Configuration Guides cover topics about using, operating and
configuring Crossplane.
These guides cover specific administrative tasks,
complex scenarios or more in-depth examples of Crossplane components.

View File

@ -1,136 +0,0 @@
---
title: Composition Revisions
---
This guide discusses the use of "Composition Revisions" to safely make and roll
back changes to a Crossplane [`Composition`][composition-type]. It assumes
familiarity with Crossplane, and particularly with
[Compositions].
A `Composition` configures how Crossplane should reconcile a Composite Resource
(XR). Put otherwise, when you create an XR the selected `Composition` determines
what managed resources Crossplane will create in response. Let's say for example
that you define a `PlatformDB` XR, which represents your organisation's common
database configuration of an Azure MySQL Server and a few firewall rules. The
`Composition` contains the 'base' configuration for the MySQL server and the
firewall rules that is extended by the configuration for the `PlatformDB`.
There is a one-to-many relationship between a `Composition` and the XRs that use
it. You might define a `Composition` named `big-platform-db` that is used by ten
different `PlatformDB` XRs. Usually, in the interest of self-service, the
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
For example the `Composition` may be written and maintained by a platform team
member, while individual application teams create `PlatformDB` XRs that use said
`Composition`.
Each `Composition` is mutable - you can update it as your organisation's needs
change. However, without Composition Revisions updating a `Composition` can be a
risky process. Crossplane constantly uses the `Composition` to ensure that your
actual infrastructure - your MySQL Servers and firewall rules - match your
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
`Composition`, all 10 of those XRs will be instantly updated in accordance with
any updates you make to the `big-platform-db` `Composition`.
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
update your XRs to leverage the latest `Composition` settings at your own pace.
This enables you to [canary] changes to your infrastructure, or to roll back
some XRs to previous `Composition` settings without rolling back all XRs.
## Using Composition Revisions
When you enable Composition Revisions three things happen:
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
which `CompositionRevision` they use.
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
specifies how they should be updated to new Composition Revisions.
Each time you edit a `Composition` Crossplane will automatically create a
`CompositionRevision` that represents that 'revision' of the `Composition` -
that unique state. Each revision is allocated an increasing revision number.
This gives `CompositionRevision` consumers an idea about which revision is
'newest'.
Crossplane distinguishes between the 'newest' and the 'current' revision of a
`Composition`. That is, if you revert a `Composition` to a previous state that
corresponds to an existing `CompositionRevision` that revision will become
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
`Composition` configuration).
You can discover which revisions exist using `kubectl`:
```console
# Find all revisions of the Composition named 'example'
kubectl get compositionrevision -l crossplane.io/composition-name=example
```
This should produce output something like:
```console
NAME REVISION CURRENT AGE
example-18pdg 1 False 4m36s
example-2bgdr 2 True 73s
example-xjrdm 3 False 61s
```
> A `Composition` is a mutable resource that you can update as your needs
> change over time. Each `CompositionRevision` is an immutable snapshot of those
> needs at a particular point in time.
Crossplane behaves the same way by default whether Composition Revisions are
enabled or not. This is because when you enable Composition Revisions all XRs
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
policies:
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
* `Manual`: Require manual intervention to change `CompositionRevision`.
The below XR uses the `Manual` policy. When this policy is used the XR will
select the current `CompositionRevision` when it is first created, but must
manually be updated when you wish it to use another `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
# The Manual policy specifies that you do not want this XR to update to the
# current CompositionRevision automatically.
compositionUpdatePolicy: Manual
compositionRef:
name: example
writeConnectionSecretToRef:
name: db-conn
```
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
policy you must edit the `compositionRevisionRef` field when you want your XR to
use a different `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
compositionUpdatePolicy: Manual
compositionRef:
name: example
# Update the referenced CompositionRevision if and when you are ready.
compositionRevisionRef:
name: example-18pdg
writeConnectionSecretToRef:
name: db-conn
```
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
[install-guide]: {{<ref "../../master/software/install" >}}

View File

@ -1,7 +0,0 @@
---
title: Integrations
weight: 20
description: Integrate Crossplane with third-party software
---
This section contains guides for using Crossplane alongside other technologies.

View File

@ -1,5 +1,5 @@
--- ---
title: Crossplane API title: API Reference
weight: 400 weight: 400
description: "API details for Crossplane's core types" description: "API details for Crossplane's core types"
cascade: cascade:

View File

@ -1,6 +1,6 @@
--- ---
weight: 400 weight: 200
title: Crossplane CLI title: CLI Reference
description: "Documentation for the Crossplane command-line interface" description: "Documentation for the Crossplane command-line interface"
--- ---

View File

@ -1,6 +1,6 @@
--- ---
title: Concepts title: Concepts
weight: 100 weight: 50
description: Understand Crossplane's core components description: Understand Crossplane's core components
--- ---

View File

@ -204,4 +204,4 @@ spec:
name: my-claim-secret name: my-claim-secret
``` ```
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).

View File

@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
{{</hint >}} {{</hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Set composite resource defaults ### Set composite resource defaults
XRDs can set default parameters for composite resources and Claims. XRDs can set default parameters for composite resources and Claims.

View File

@ -189,7 +189,7 @@ spec:
### Composition revision policy ### Composition revision policy
Crossplane tracks changes to Compositions as Crossplane tracks changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) . [Composition revisions]({{<ref "composition-revisions">}}) .
A composite resource can use A composite resource can use
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
@ -217,7 +217,7 @@ spec:
### Composition revision selection ### Composition revision selection
Crossplane records changes to Compositions as Crossplane records changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}). [Composition revisions]({{<ref "composition-revisions">}}).
A composite resource can A composite resource can
select a specific Composition revision. select a specific Composition revision.
@ -309,7 +309,7 @@ spec:
``` ```
Composite resources can write connection secrets to an Composite resources can write connection secrets to an
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}), [external secret store]({{<ref "../guides/vault-as-secret-store">}}),
like HashiCorp Vault. like HashiCorp Vault.
{{<hint "important" >}} {{<hint "important" >}}
@ -332,10 +332,10 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
external secret stores. external secret stores.
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Pausing composite resources ### Pausing composite resources

View File

@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
{{<hint "tip">}} {{<hint "tip">}}
Crossplane has Crossplane has
[language specific guides]({{<ref "../../knowledge-base/guides">}}) to writing [language specific guides]({{<ref "../guides">}}) to writing
a composition function. Refer to the guide for your preferred language for a a composition function. Refer to the guide for your preferred language for a
more detailed guide to writing a function. more detailed guide to writing a function.
{{</hint>}} {{</hint>}}

View File

@ -0,0 +1,445 @@
---
title: Composition Revisions
weight: 35
---
This guide discusses the use of "Composition Revisions" to safely make and roll
back changes to a Crossplane [`Composition`][composition-type]. It assumes
familiarity with Crossplane, and particularly with
[Compositions].
A `Composition` configures how Crossplane should reconcile a Composite Resource
(XR). Put otherwise, when you create an XR the selected `Composition` determines
what managed resources Crossplane will create in response. Let's say for example
that you define a `PlatformDB` XR, which represents your organisation's common
database configuration of an Azure MySQL Server and a few firewall rules. The
`Composition` contains the 'base' configuration for the MySQL server and the
firewall rules that is extended by the configuration for the `PlatformDB`.
There is a one-to-many relationship between a `Composition` and the XRs that use
it. You might define a `Composition` named `big-platform-db` that is used by ten
different `PlatformDB` XRs. Usually, in the interest of self-service, the
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
For example the `Composition` may be written and maintained by a platform team
member, while individual application teams create `PlatformDB` XRs that use said
`Composition`.
Each `Composition` is mutable - you can update it as your organisation's needs
change. However, without Composition Revisions updating a `Composition` can be a
risky process. Crossplane constantly uses the `Composition` to ensure that your
actual infrastructure - your MySQL Servers and firewall rules - match your
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
`Composition`, all 10 of those XRs will be instantly updated in accordance with
any updates you make to the `big-platform-db` `Composition`.
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
update your XRs to leverage the latest `Composition` settings at your own pace.
This enables you to [canary] changes to your infrastructure, or to roll back
some XRs to previous `Composition` settings without rolling back all XRs.
## Using Composition Revisions
When you enable Composition Revisions three things happen:
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
which `CompositionRevision` they use.
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
specifies how they should be updated to new Composition Revisions.
Each time you edit a `Composition` Crossplane will automatically create a
`CompositionRevision` that represents that 'revision' of the `Composition` -
that unique state. Each revision is allocated an increasing revision number.
This gives `CompositionRevision` consumers an idea about which revision is
'newest'.
Crossplane distinguishes between the 'newest' and the 'current' revision of a
`Composition`. That is, if you revert a `Composition` to a previous state that
corresponds to an existing `CompositionRevision` that revision will become
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
`Composition` configuration).
You can discover which revisions exist using `kubectl`:
```console
# Find all revisions of the Composition named 'example'
kubectl get compositionrevision -l crossplane.io/composition-name=example
```
This should produce output something like:
```console
NAME REVISION CURRENT AGE
example-18pdg 1 False 4m36s
example-2bgdr 2 True 73s
example-xjrdm 3 False 61s
```
> A `Composition` is a mutable resource that you can update as your needs
> change over time. Each `CompositionRevision` is an immutable snapshot of those
> needs at a particular point in time.
Crossplane behaves the same way by default whether Composition Revisions are
enabled or not. This is because when you enable Composition Revisions all XRs
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
policies:
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
* `Manual`: Require manual intervention to change `CompositionRevision`.
The below XR uses the `Manual` policy. When this policy is used the XR will
select the current `CompositionRevision` when it is first created, but must
manually be updated when you wish it to use another `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
# The Manual policy specifies that you do not want this XR to update to the
# current CompositionRevision automatically.
compositionUpdatePolicy: Manual
compositionRef:
name: example
writeConnectionSecretToRef:
name: db-conn
```
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
policy you must edit the `compositionRevisionRef` field when you want your XR to
use a different `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
compositionUpdatePolicy: Manual
compositionRef:
name: example
# Update the referenced CompositionRevision if and when you are ready.
compositionRevisionRef:
name: example-18pdg
writeConnectionSecretToRef:
name: db-conn
```
## Complete example
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
assign different CompositionRevisions to the created composite resources each time the composition is updated.
### Preparation
##### Install Crossplane
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
```shell
kubectl create namespace crossplane-system
helm repo add crossplane-master https://charts.crossplane.io/master/
helm repo update
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
kubectl get pods -n crossplane-system
```
Expected Output:
```shell
NAME READY STATUS RESTARTS AGE
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
```
#### Deploy Composition and XRD Examples
Apply the example Composition.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: true
enableDnsHostnames: true
name: my-vcp
```
Apply the example XRD.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: myvpcs.aws.example.upbound.io
spec:
group: aws.example.upbound.io
names:
kind: MyVPC
plural: myvpcs
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
id:
type: string
description: ID of this VPC that other objects will use to refer to it.
required:
- id
```
Verify that Crossplane created the Composition revision
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
{{< hint "note" >}}
The label `dev` is automatically created from the Composition.
{{< /hint >}}
### Create Composite Resources
This tutorial has four composite resources to cover different update policies and composition selection options.
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
#### Default update policy
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-auto
spec:
id: vpc-auto
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-auto created
```
#### Manual update policy
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-man
spec:
id: vpc-man
compositionUpdatePolicy: Manual
compositionRevisionRef:
name: myvpcs.aws.example.upbound.io-ad265bc
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-man created
```
#### Using a selector
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-dev
spec:
id: vpc-dev
compositionRevisionSelector:
matchLabels:
channel: dev
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-dev created
```
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-staging
spec:
id: vpc-staging
compositionRevisionSelector:
matchLabels:
channel: staging
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-staging created
```
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
All other XRs have a `REVISION` matching the created Composition Revision.
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging False <none> Automatic map[channel:staging]
```
{{< hint "note" >}}
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
{{< /hint >}}
### Create new Composition revisions
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
also trigger a new CompositionRevision.
#### Update the Composition label
Update the `Composition` label to `channel: staging`:
```shell
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-auto` always use the latest Revision.
`vpc-staging` now matches the label applied to Revision revision:2.
{{< /hint >}}
#### Update Composition Spec and Label
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
Apply the following changes to update the `Composition` spec and label:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: false
enableDnsHostnames: true
name: my-vcp
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
myvpcs.aws.example.upbound.io-f81c553 3 dev
```
{{< hint "note" >}}
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
{{< /hint >}}
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-dev` matches the updated label applied to Revision revision:3.
`vpc-staging` matches the label applied to Revision revision:2.
{{< /hint >}}
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
[install-guide]: {{<ref "../../master/software/install" >}}

View File

@ -748,7 +748,7 @@ details.
This section discusses creating Kubernetes secrets. This section discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store. with an external secret store.
{{</hint >}} {{</hint >}}
@ -958,7 +958,7 @@ for more information on restricting secret keys.
{{< /hint >}} {{< /hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
{{<hint "warning">}} {{<hint "warning">}}
You can't change the You can't change the
@ -973,7 +973,7 @@ recreate the Composition to change the
#### Save connection details to an external secret store #### Save connection details to an external secret store
Crossplane Crossplane
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
write secrets and connection details to external secret stores like HashiCorp write secrets and connection details to external secret stores like HashiCorp
Vault. Vault.
@ -1018,7 +1018,7 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
integration guide. integration guide.
### Resource readiness checks ### Resource readiness checks

View File

@ -0,0 +1,607 @@
---
title: Connection Details
weight: 110
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
---
Using connection details in Crossplane requires the following components:
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
* Optionally, define the `connectionSecretKeys` in a
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
{{<hint "note">}}
This guide discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store.
{{</hint >}}
## Background
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
resource, the resource may generate resource-specific details. These details can include
usernames, passwords or connection details like an IP address.
Crossplane refers to this information as the _connection details_ or
_connection secrets_.
The Provider
defines what information to present as a _connection
detail_ from a managed resource.
<!-- vale gitlab.SentenceLength = NO -->
<!-- wordy because of type names -->
When a managed resource is part of a
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
and optionally, the
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
and where they're stored.
<!-- vale gitlab.SentenceLength = YES -->
{{<hint "note">}}
All the following examples use the same set of Compositions,
CompositeResourceDefinitions and Claims.
All examples rely on
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
to create resources.
{{<expand "Reference Composition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xsecrettest.example.org
spec:
writeConnectionSecretsToNamespace: other-namespace
compositeTypeRef:
apiVersion: example.org/v1alpha1
kind: XSecretTest
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchControllerRef: true
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret1"
- name: user
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
spec:
forProvider: {}
- name: user2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
metadata:
labels:
docs.crossplane.io: user
spec:
forProvider: {}
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
- name: key2-password
fromConnectionSecretKey: password
- name: key2-secret
fromConnectionSecretKey: attribute.secret
- name: key2-smtp
fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret2"
```
{{</expand >}}
{{<expand "Reference CompositeResourceDefinition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xsecrettests.example.org
spec:
group: example.org
connectionSecretKeys:
- username
- password
- attribute.secret
- attribute.ses_smtp_password_v4
- key2-user
- key2-pass
- key2-secret
- key2-smtp
names:
kind: XSecretTest
plural: xsecrettests
claimNames:
kind: SecretTest
plural: secrettests
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
```
{{</ expand >}}
{{<expand "Reference Claim" >}}
```yaml
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
{{</expand >}}
{{</hint >}}
## Connection secrets in a managed resource
<!-- vale gitlab.Substitutions = NO -->
<!-- vale gitlab.SentenceLength = NO -->
<!-- under 25 words -->
When a managed resource creates connection secrets, Crossplane can write the
secrets to a
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
or an
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
<!-- vale gitlab.SentenceLength = YES -->
<!-- vale gitlab.Substitutions = YES -->
Creating an individual managed resource shows the connection secrets the
resource creates.
{{<hint "note" >}}
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
documentation for more information on configuring resources and storing
connection secrets for individual resources.
{{< /hint >}}
For example, create an
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
connection secrets in a Kubernetes secret named
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
in the
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
```yaml {label="mr"}
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
metadata:
name: test-accesskey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: default
name: my-accesskey-secret
```
View the Kubernetes secret to see the connection details from the managed
resource.
This includes an
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
```yaml {label="mrSecret",copy-lines="1"}
kubectl describe secret my-accesskey-secret
Name: my-accesskey-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: connection.crossplane.io/v1alpha1
Data
====
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
username: 20 bytes
```
Compositions and CompositeResourceDefinitions require the exact names of the
secrets generated by a resource.
## Connection secrets in Compositions
Resources in a Composition that create connection details still create a
secret object containing their connection details.
Crossplane also generates
another secret object for each composite resource,
containing the secrets from all the defined resources.
For example, a Composition defines two
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
objects.
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
the resource
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane also creates a secret object for the entire Composition
saved in the namespace defined by
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
with a Crossplane generated name.
```yaml {label="comp1",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key1
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1-secret
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2-secret
# Removed for brevity
```
After applying a Claim, view the Kubernetes secrets to see three secret objects
created.
The secret
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="6">}}key1{{</hover>}},
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
Crossplane creates another secret in the namespace
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
secrets from resource in the Composition.
```shell {label="compGetSec",copy-lines="1"}
kubectl get secrets -A
NAMESPACE NAME TYPE DATA AGE
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
```
Although Crossplane creates a secret object, by default, Crossplane doesn't add
any data to the object.
```yaml {copy-lines="none"}
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
```
The Composition must list the connection secrets to store for each resource.
Use the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
each resource and define the secret keys the resource creates.
{{<hint "warning">}}
You can't change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
of a Composition.
You must delete and
recreate the Composition to change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
{{</hint >}}
```yaml {label="comp2",copy-lines="16-20"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
# Removed for brevity
```
After applying a Claim the composite resource secret object contains the list of
keys listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
```shell {copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
```
{{<hint "important">}}
If a key isn't listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
it isn't stored in the secret object.
{{< /hint >}}
### Managing conflicting secret keys
If resources produce conflicting keys, create a unique name with a connection
details
{{<hover label="comp3" line="25">}}name{{</hover>}}.
```yaml {label="comp3",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
The secret object contains both keys,
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
and
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
```shell {label="comp3Sec",copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
key2-user: 20 bytes
# Removed for brevity.
```
## Connection secrets in Composite Resource Definitions
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
put in the combined secret and provided to a Claim.
By default an XRD writes all secret keys listed in the composed resource
`connectionDetails` to the combined secret object.
Limit the keys passed to the combined secret object and Claims with a
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
the secret key names to create. Crossplane only adds the keys listed to the
combined secret.
{{<hint "warning">}}
You can't change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
You must delete and
recreate the XRD to change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
{{</hint >}}
For example, an XRD may restrict the secrets to only the
{{<hover label="xrd" line="5">}}username{{</hover>}},
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
```yaml {label="xrd",copy-lines="4-12"}
kind: CompositeResourceDefinition
spec:
# Removed for brevity.
connectionSecretKeys:
- username
- password
- key2-user
```
The secret from an individual resource contains all the resources detailed in
the Composition's `connectionDetails`.
```shell {label="xrdSec",copy-lines="1"}
kubectl describe secret key1 -n docs
Name: key1
Namespace: docs
Data
====
password: 40 bytes
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
```
The Claim's secret only contains the
keys allowed by the XRD
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
fields.
```shell {label="xrdSec2",copy-lines="2"}
kubectl describe secret my-access-key-secret
Name: my-access-key-secret
Data
====
key2-user: 20 bytes
password: 40 bytes
username: 20 bytes
```
## Secret objects
Compositions create a secret object for each resource and an extra secret
containing all the secrets from all resources.
Crossplane saves the resource secret objects in the location defined by the
resource's
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane saves the combined secret with a Crossplane generated name in the
namespace defined in the Composition's
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
```yaml {label="comp4",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
If a Claim uses a secret, it's stored in the same namespace as the Claim with
the name defined in the Claim's
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
```yaml {label="claim3",copy-lines="none"}
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
After applying the Claim Crossplane creates the following secrets:
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
* The composite resource secret object in the
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
Composition's `writeConnectionSecretsToNamespace`.
```shell {label="allSec",copy-lines="none"}
kubectl get secret -A
NAMESPACE NAME TYPE DATA AGE
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
docs key1 connection.crossplane.io/v1alpha1 4 31m
docs key2 connection.crossplane.io/v1alpha1 4 31m
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
```

View File

@ -50,7 +50,7 @@ kind: Instance
A managed resource's `deletionPolicy` tells the Provider what to do after A managed resource's `deletionPolicy` tells the Provider what to do after
deleting the managed resource. If the `deletionPolicy` is `Delete` the Provider deleting the managed resource. If the `deletionPolicy` is `Delete` the Provider
deletes the external resource as well. If the `deletionPolicy` is `Orphan` the deletes the external resource as well. If the `deletionPolicy` is `orphan` the
Provider deletes the managed resource but doesn't delete the external resource. Provider deletes the managed resource but doesn't delete the external resource.
#### Options #### Options
@ -357,7 +357,7 @@ Crossplane supports the following policies:
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. | | `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. | | `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. | | `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| `Update` | Crossplane changes the external resource when changing the managed resource. | | `Update` | Crossplane changes the external resource when changing the managed resource. |
{{</table >}} {{</table >}}
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | | {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
{{< /table >}} {{< /table >}}
@ -567,7 +567,7 @@ metadata:
{{<hint "tip" >}} {{<hint "tip" >}}
Read the Read the
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}}) [Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
guide for details on using StoreConfig objects. guide for details on using StoreConfig objects.
{{< /hint >}} {{< /hint >}}

View File

@ -404,7 +404,7 @@ If you remove the Provider first, you must manually delete external resources
through your cloud provider. Managed resources must be manually deleted by through your cloud provider. Managed resources must be manually deleted by
removing their finalizers. removing their finalizers.
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}). For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
{{< /hint >}} {{< /hint >}}
## Verify a Provider ## Verify a Provider
@ -593,12 +593,12 @@ replacement for Controller configuration and is available in v1.14+.
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
the Provider's pod. The the Provider's pod. The
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1) [Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
defines the supported set of ControllerConfig settings. defines the supported set of ControllerConfig settings.
The most common use case for ControllerConfigs are providing `args` to a The most common use case for ControllerConfigs are providing `args` to a
Provider's pod enabling optional services. For example, enabling Provider's pod enabling optional services. For example, enabling
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider) [external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
for a Provider. for a Provider.
Each Provider determines their supported set of `args`. Each Provider determines their supported set of `args`.

View File

@ -236,4 +236,4 @@ virtualnetwork.network.azure.upbound.io "crossplane-quickstart-network" deleted
* Explore Azure resources that Crossplane can configure in the * Explore Azure resources that Crossplane can configure in the
[Provider CRD reference](https://marketplace.upbound.io/providers/upbound/provider-family-azure/). [Provider CRD reference](https://marketplace.upbound.io/providers/upbound/provider-family-azure/).
* Join the [Crossplane Slack](https://slack.crossplane.io/) and connect with * Join the [Crossplane Slack](https://slack.crossplane.io/) and connect with
Crossplane users and contributors. Crossplane users and contributors.

View File

@ -0,0 +1,5 @@
---
title: Guides
weight: 100
description: Crossplane integrations and detailed examples.
---

View File

@ -0,0 +1,443 @@
---
title: Troubleshoot Crossplane
weight: 306
---
## Requested Resource Not Found
If you use the Crossplane CLI to install a `Provider` or
`Configuration` (e.g. `crossplane install provider
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
could not find the requested resource` error, more often than not, that is an
indicator that the Crossplane CLI you're using is outdated. In other words
some Crossplane API has been graduated from alpha to beta or stable and the old
plugin is not aware of this change.
## Resource Status and Conditions
Most Crossplane resources have a `status` section that can represent the current
state of that particular resource. Running `kubectl describe` against a
Crossplane resource will frequently give insightful information about its
condition. For example, to determine the status of a GCP `CloudSQLInstance`
managed resource use `kubectl describe` for the resource.
```shell {copy-lines="1"}
kubectl describe cloudsqlinstance my-db
Status:
Conditions:
Last Transition Time: 2019-09-16T13:46:42Z
Reason: Creating
Status: False
Type: Ready
```
Most Crossplane resources set the `Ready` condition. `Ready` represents the
availability of the resource - whether it is creating, deleting, available,
unavailable, binding, etc.
## Resource Events
Most Crossplane resources emit _events_ when something interesting happens. You
can see the events associated with a resource by running `kubectl describe` -
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
particular namespace by running `kubectl get events`.
```console
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
```
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
> are cluster scoped. Crossplane emits events for cluster scoped resources to
> the 'default' namespace.
## Crossplane Logs
The next place to look to get more information or investigate a failure would be
in the Crossplane pod logs, which should be running in the `crossplane-system`
namespace. To get the current Crossplane logs, run the following:
```shell
kubectl -n crossplane-system logs -lapp=crossplane
```
> Note that Crossplane emits few logs by default - events are typically the best
> place to look for information about what Crossplane is doing. You may need to
> restart Crossplane with the `--debug` flag if you can't find what you're
> looking for.
## Provider Logs
Remember that much of Crossplane's functionality is provided by providers. You
can use `kubectl logs` to view provider logs too. By convention, they also emit
few logs by default.
```shell
kubectl -n crossplane-system logs <name-of-provider-pod>
```
All providers maintained by the Crossplane community mirror Crossplane's support
of the `--debug` flag. The easiest way to set flags on a provider is to create a
`ControllerConfig` and reference it from the `Provider`:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: debug-config
spec:
args:
- --debug
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: debug-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Compositions and composite resource definition
### General troubleshooting steps
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
2. If there are errors in the events, address them.
3. If there are no errors, follow its sub-resources.
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
4. Repeat this process for each resource returned.
{{< hint "note" >}}
The rest of this section show you how to debug issues related to compositions without using external tooling.
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
{{< /hint >}}
### Examples
#### Composition
<!-- vale Google.WordList = NO -->
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
The example application never reaches available state as shown below.
1. View the claim.
```bash
kubectl describe exampleapp example-application
Status:
Conditions:
Last Transition Time: 2022-03-01T22:57:38Z
Reason: Composite resource claim is waiting for composite resource to become Ready
Status: False
Type: Ready
Events: <none>
```
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
```bash
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XExampleApp",
"name": "example-application-xqlsz"
}
```
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
4. View the cluster scoped resource's events.
```bash
kubectl describe xexampleapp example-application-xqlsz
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
```
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
```bash
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
[
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XDynamoDBTable",
"name": "example-application-xqlsz-6j9nm"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-lp9wt"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-btwkn"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "IRSA"
}
]
```
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
#### Composite resource definition
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
1. Get the XRD
```bash
kubectl get xrd testing.awsblueprints.io
NAME ESTABLISHED OFFERED AGE
testing.awsblueprints.io 66s
```
2. Notice its status it not established. You describe this XRD to get its events.
```bash
kubectl describe xrd testing.awsblueprints.io
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
```
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
#### Providers
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
documentation]({{<ref "/master/concepts/packages">}})
for more information about Crossplane Packages.
If you are experiencing provider issues, steps below are a good starting point.
1. Check the status of provider object.
```bash
kubectl describe provider.pkg.crossplane.io provider-aws
Status:
Conditions:
Last Transition Time: 2022-08-04T16:19:44Z
Reason: HealthyPackageRevision
Status: True
Type: Healthy
Last Transition Time: 2022-08-04T16:14:29Z
Reason: ActivePackageRevision
Status: True
Type: Installed
Current Identifier: crossplane/provider-aws:v0.29.0
Current Revision: provider-aws-a2e16ca2fc1a
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
```
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
```bash
kubectl get providerrevision provider-aws-a2e16ca2fc1a
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
```
When you describe the object, you find all CRDs managed by this object.
```bash
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
Status:
Controller Ref:
Name: provider-aws-a2e16ca2fc1a
Object Refs:
API Version: apiextensions.k8s.io/v1
Kind: CustomResourceDefinition
Name: natgateways.ec2.aws.crossplane.io
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
```
The event field also indicates any issues that may have occurred during this process.
<!-- vale Google.WordList = YES -->
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
```bash
kubectl get deployment -n crossplane-system
NAME READY UP-TO-DATE AVAILABLE AGE
crossplane 1/1 1 1 105d
crossplane-rbac-manager 1/1 1 1 105d
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
kubectl get pods -n crossplane-system
NAME READY STATUS RESTARTS AGE
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
```
If there are any pods failing, check its logs and remedy the problem.
## Pausing Crossplane
Sometimes, for example when you encounter a bug, it can be useful to pause
Crossplane if you want to stop it from actively attempting to manage your
resources. To pause Crossplane without deleting all of its resources, run the
following command to simply scale down its deployment:
```bash
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
```
Once you have been able to rectify the problem or smooth things out, you can
unpause Crossplane simply by scaling its deployment back up:
```bash
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
```
## Pausing Providers
Providers can also be paused when troubleshooting an issue or orchestrating a
complex migration of resources. Creating and referencing a `ControllerConfig` is
the easiest way to scale down a provider, and the `ControllerConfig` can be
modified or the reference can be removed to scale it back up:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: scale-config
spec:
replicas: 0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: scale-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Deleting When a Resource Hangs
The resources that Crossplane manages will automatically be cleaned up so as not
to leave anything running behind. This is accomplished by using finalizers, but
in certain scenarios the finalizer can prevent the Kubernetes object from
getting deleted.
To deal with this, we essentially want to patch the object to remove its
finalizer, which will then allow it to be deleted completely. Note that this
won't necessarily delete the external resource that Crossplane was managing, so
you will want to go to your cloud provider's console and look there for any
lingering resources to clean up.
In general, a finalizer can be removed from an object with this command:
```shell
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
```
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
`my-db`, you can remove its finalizer with:
```shell
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
```
## Tips, Tricks, and Troubleshooting
In this section we'll cover some common tips, tricks, and troubleshooting steps
for working with Composite Resources. If you're trying to track down why your
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
has some useful information.
### Troubleshooting Claims and XRs
Crossplane relies heavily on status conditions and events for troubleshooting.
You can see both using `kubectl describe` - for example:
```console
# Describe the PostgreSQLInstance claim named my-db
kubectl describe postgresqlinstance.database.example.org my-db
```
Per Kubernetes convention, Crossplane keeps errors close to the place they
happen. This means that if your claim is not becoming ready due to an issue with
your `Composition` or with a composed resource you'll need to "follow the
references" to find out why. Your claim will only tell you that the XR is not
yet ready.
To follow the references:
1. Find your XR by running `kubectl describe` on your claim and looking for its
"Resource Ref" (aka `spec.resourceRef`).
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
with the `Composition` you're using, if any.
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
resources.
1. Run `kubectl describe` on each referenced composed resource to determine
whether it is ready and what issues, if any, it is encountering.
<!-- Named Links -->
[Requested Resource Not Found]: #requested-resource-not-found
[install Crossplane CLI]: "../getting-started/install-configure"
[Resource Status and Conditions]: #resource-status-and-conditions
[Resource Events]: #resource-events
[Crossplane Logs]: #crossplane-logs
[Provider Logs]: #provider-logs
[Pausing Crossplane]: #pausing-crossplane
[Pausing Providers]: #pausing-providers
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
[Installing Crossplane Package]: #installing-crossplane-package
[Crossplane package]: /master/concepts/packages/
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons

View File

@ -0,0 +1,838 @@
---
title: Write a Composition Function in Go
state: beta
alphaVersion: "1.11"
betaVersion: "1.14"
weight: 80
description: "Composition functions allow you to template resources using Go"
---
Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource
(XR). Read the
[concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions.
You can write a function to template resources using a general purpose
programming language. Using a general purpose programming language allows a
function to use advanced logic to template resources, like loops and
conditionals. This guide explains how to write a composition function in
[Go](https://go.dev).
{{< hint "important" >}}
It helps to be familiar with
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide.
{{< /hint >}}
## Understand the steps
This guide covers writing a composition function for an
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
```yaml {label="xr"}
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<!-- vale gitlab.FutureTense = NO -->
<!--
This section is setting the stage for future sections. It doesn't make sense to
refer to the function in the present tense, because it doesn't exist yet.
-->
An `XBuckets` XR has a region and an array of bucket names. The function will
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
<!-- vale gitlab.FutureTense = YES -->
To write a function in Go:
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
1. [Test the function end-to-end](#test-the-function-end-to-end)
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
This guide covers each of these steps in detail.
## Install the tools you need to write the function
To write a function in Go you need:
* [Go](https://go.dev/dl/) v1.21 or newer. The guide uses Go v1.21.
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
CLI v1.14.
{{<hint "note">}}
You don't need access to a Kubernetes cluster or a Crossplane control plane to
build or test a composition function.
{{</hint>}}
## Initialize the function from a template
Use the `crossplane beta xpkg init` command to initialize a new function. When
you run this command it initializes your function using
[a GitHub repository](https://github.com/crossplane/function-template-go)
as a template.
```shell {copy-lines=1}
crossplane beta xpkg init function-xbuckets function-template-go -d function-xbuckets
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
```
The `crossplane beta init xpkg` command creates a directory named
`function-xbuckets`. When you run the command the new directory should look like
this:
```shell {copy-lines=1}
ls function-xbuckets
Dockerfile fn.go fn_test.go go.mod go.sum input/ LICENSE main.go package/ README.md renovate.json
```
The `fn.go` file is where you add the function's code. It's useful to know about
some other files in the template:
* `main.go` runs the function. You don't need to edit `main.go`.
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
* The `input` directory defines the function's input type.
* The `package` directory contains metadata used to build the function package.
{{<hint "tip">}}
<!-- vale gitlab.FutureTense = NO -->
<!--
This tip talks about future plans for Crossplane.
-->
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
template GitHub repository. A future CLI release will automate tasks like
replacing the template name with the new function's name. See Crossplane issue
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
<!-- vale gitlab.FutureTense = YES -->
{{</hint>}}
You must make some changes before you start adding code:
* Edit `package/crossplane.yaml` to change the package's name.
* Edit `go.mod` to change the Go module's name.
Name your package `function-xbuckets`.
The name of your module depends on where you want to keep your function code. If
you push Go code to GitHub, you can use your GitHub username. For example
`module github.com/negz/function-xbuckets`.
The function in this guide doesn't use an input type. For this function you
should delete the `input` and `package/input` directories.
The `input` directory defines a Go struct that a function can use to take input,
using the `input` field from a Composition. The
[composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains how to pass an input to a composition function.
The `package/input` directory contains an OpenAPI schema generated from the
structs in the `input` directory.
{{<hint "tip">}}
If you're writing a function that uses an input, edit the input to meet your
function's requirements.
Change the input's kind and API group. Don't use `Input` and
`template.fn.crossplane.io`. Instead use something meaningful to your function.
When you edit files under the `input` directory you must update some generated
files by running `go generate`. See `input/generate.go` for details.
```shell
go generate ./...
```
{{</hint>}}
## Edit the template to add the function's logic
You add your function's logic to the
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
method in `fn.go`. When you first open the file it contains a "hello world"
function.
```go {label="hello-world"}
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
rsp := response.To(req, response.DefaultTTL)
in := &v1beta1.Input{}
if err := request.GetInput(req, in); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
return rsp, nil
}
response.Normalf(rsp, "I was run with input %q", in.Example)
return rsp, nil
}
```
All Go composition functions have a `RunFunction` method. Crossplane passes
everything the function needs to run in a
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} struct.
The function tells Crossplane what resources it should compose by returning a
{{<hover label="hello-world" line="13">}}RunFunctionResponse{{</hover>}} struct.
{{<hint "tip">}}
Crossplane generates the `RunFunctionRequest` and `RunFunctionResponse` structs
using [Protocol Buffers](http://protobuf.dev). You can find detailed schemas for
`RunFunctionRequest` and `RunFunctionResponse` in the
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
{{</hint>}}
Edit the `RunFunction` method to replace it with this code.
```go {hl_lines="4-56"}
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
rsp := response.To(req, response.DefaultTTL)
xr, err := request.GetObservedCompositeResource(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
return rsp, nil
}
region, err := xr.Resource.GetString("spec.region")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
return rsp, nil
}
names, err := xr.Resource.GetStringArray("spec.names")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
return rsp, nil
}
desired, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
return rsp, nil
}
_ = v1beta1.AddToScheme(composed.Scheme)
for _, name := range names {
b := &v1beta1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"crossplane.io/external-name": name,
},
},
Spec: v1beta1.BucketSpec{
ForProvider: v1beta1.BucketParameters{
Region: ptr.To[string](region),
},
},
}
cd, err := composed.From(b)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
return rsp, nil
}
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
}
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
return rsp, nil
}
return rsp, nil
}
```
Expand the below block to view the full `fn.go`, including imports and
commentary explaining the function's logic.
{{<expand "The full fn.go file" >}}
```go
package main
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"github.com/upbound/provider-aws/apis/s3/v1beta1"
"github.com/crossplane/function-sdk-go/errors"
"github.com/crossplane/function-sdk-go/logging"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/resource/composed"
"github.com/crossplane/function-sdk-go/response"
)
// Function returns whatever response you ask it to.
type Function struct {
fnv1beta1.UnimplementedFunctionRunnerServiceServer
log logging.Logger
}
// RunFunction observes an XBuckets composite resource (XR). It adds an S3
// bucket to the desired state for every entry in the XR's spec.names array.
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
// Create a response to the request. This copies the desired state and
// pipeline context from the request to the response.
rsp := response.To(req, response.DefaultTTL)
// Read the observed XR from the request. Most functions use the observed XR
// to add desired managed resources.
xr, err := request.GetObservedCompositeResource(req)
if err != nil {
// If the function can't read the XR, the request is malformed. This
// should never happen. The function returns a fatal result. This tells
// Crossplane to stop running functions and return an error.
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
return rsp, nil
}
// Create an updated logger with useful information about the XR.
log := f.log.WithValues(
"xr-version", xr.Resource.GetAPIVersion(),
"xr-kind", xr.Resource.GetKind(),
"xr-name", xr.Resource.GetName(),
)
// Get the region from the XR. The XR has getter methods like GetString,
// GetBool, etc. You can use them to get values by their field path.
region, err := xr.Resource.GetString("spec.region")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
return rsp, nil
}
// Get the array of bucket names from the XR.
names, err := xr.Resource.GetStringArray("spec.names")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
return rsp, nil
}
// Get all desired composed resources from the request. The function will
// update this map of resources, then save it. This get, update, set pattern
// ensures the function keeps any resources added by other functions.
desired, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
return rsp, nil
}
// Add v1beta1 types (including Bucket) to the composed resource scheme.
// composed.From uses this to automatically set apiVersion and kind.
_ = v1beta1.AddToScheme(composed.Scheme)
// Add a desired S3 bucket for each name.
for _, name := range names {
// One advantage of writing a function in Go is strong typing. The
// function can import and use managed resource types from the provider.
b := &v1beta1.Bucket{
ObjectMeta: metav1.ObjectMeta{
// Set the external name annotation to the desired bucket name.
// This controls what the bucket will be named in AWS.
Annotations: map[string]string{
"crossplane.io/external-name": name,
},
},
Spec: v1beta1.BucketSpec{
ForProvider: v1beta1.BucketParameters{
// Set the bucket's region to the value read from the XR.
Region: ptr.To[string](region),
},
},
}
// Convert the bucket to the unstructured resource data format the SDK
// uses to store desired composed resources.
cd, err := composed.From(b)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
return rsp, nil
}
// Add the bucket to the map of desired composed resources. It's
// important that the function adds the same bucket every time it's
// called. It's also important that the bucket is added with the same
// resource.Name every time it's called. The function prefixes the name
// with "xbuckets-" to avoid collisions with any other composed
// resources that might be in the desired resources map.
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
}
// Finally, save the updated desired composed resources to the response.
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
return rsp, nil
}
// Log what the function did. This will only appear in the function's pod
// logs. A function can use response.Normal and response.Warning to emit
// Kubernetes events associated with the XR it's operating on.
log.Info("Added desired buckets", "region", region, "count", len(names))
return rsp, nil
}
```
{{</expand>}}
This code:
1. Gets the observed composite resource from the `RunFunctionRequest`.
1. Gets the region and bucket names from the observed composite resource.
1. Adds one desired S3 bucket for each bucket name.
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
The code uses the `v1beta1.Bucket` type from
[Upbound's AWS S3 provider](https://github.com/upbound/provider-aws). One
advantage of writing a function in Go is that you can compose resources using
the same strongly typed structs Crossplane uses in its providers.
You must get the AWS Provider Go module to use this type:
```shell
go get github.com/upbound/provider-aws@v0.43.0
```
Crossplane provides a
[software development kit](https://github.com/crossplane/function-sdk-go) (SDK)
for writing composition functions in [Go](https://go.dev). This function uses
utilities from the SDK. In particular the `request` and `response` packages make
working with the `RunFunctionRequest` and `RunFunctionResponse` types easier.
{{<hint "tip">}}
Read the
[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go)
for the SDK.
{{</hint>}}
## Test the function end-to-end
Test your function by adding unit tests, and by using the `crossplane beta
render` command.
Go has rich support for unit testing. When you initialize a function from the
template it adds some unit tests to `fn_test.go`. These tests follow Go's
[recommendations](https://github.com/golang/go/wiki/TestComments). They use only
[`pkg/testing`](https://pkg.go.dev/testing) from the Go standard library and
[`google/go-cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp).
To add test cases, update the `cases` map in `TestRunFunction`. Expand the below
block to view the full `fn_test.go` file for the function.
{{<expand "The full fn_test.go file" >}}
```go
package main
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/crossplane/crossplane-runtime/pkg/logging"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/resource"
)
func TestRunFunction(t *testing.T) {
type args struct {
ctx context.Context
req *fnv1beta1.RunFunctionRequest
}
type want struct {
rsp *fnv1beta1.RunFunctionResponse
err error
}
cases := map[string]struct {
reason string
args args
want want
}{
"AddTwoBuckets": {
reason: "The Function should add two buckets to the desired composed resources",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
// MustStructJSON is a handy way to provide mock
// resources.
Resource: resource.MustStructJSON(`{
"apiVersion": "example.crossplane.io/v1alpha1",
"kind": "XBuckets",
"metadata": {
"name": "test"
},
"spec": {
"region": "us-east-2",
"names": [
"test-bucket-a",
"test-bucket-b"
]
}
}`),
},
},
},
},
want: want{
rsp: &fnv1beta1.RunFunctionResponse{
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(60 * time.Second)},
Desired: &fnv1beta1.State{
Resources: map[string]*fnv1beta1.Resource{
"xbuckets-test-bucket-a": {Resource: resource.MustStructJSON(`{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-a"
}
},
"spec": {
"forProvider": {
"region": "us-east-2"
}
}
}`)},
"xbuckets-test-bucket-b": {Resource: resource.MustStructJSON(`{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-b"
}
},
"spec": {
"forProvider": {
"region": "us-east-2"
}
}
}`)},
},
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{log: logging.NewNopLogger()}
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)
if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff)
}
})
}
}
```
{{</expand>}}
Run the unit tests using the `go test` command:
```shell
go test -v -cover .
=== RUN TestRunFunction
=== RUN TestRunFunction/AddTwoBuckets
--- PASS: TestRunFunction (0.00s)
--- PASS: TestRunFunction/AddTwoBuckets (0.00s)
PASS
coverage: 52.6% of statements
ok github.com/negz/function-xbuckets 0.016s coverage: 52.6% of statements
```
You can preview the output of a Composition that uses this function using
the Crossplane CLI. You don't need a Crossplane control plane to do this.
Create a directory under `function-xbuckets` named `example` and create
Composite Resource, Composition and Function YAML files.
Expand the following block to see example files.
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
You can recreate the output below using by running `crossplane beta render` with
these files.
The `xr.yaml` file contains the composite resource to render:
```yaml
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<br />
The `composition.yaml` file contains the Composition to use to render the
composite resource:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: create-buckets
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: XBuckets
mode: Pipeline
pipeline:
- step: create-buckets
functionRef:
name: function-xbuckets
```
<br />
The `functions.yaml` file contains the Functions the Composition references in
its pipeline steps:
```yaml
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
spec:
# The CLI ignores this package when using the Development runtime.
# You can set it to any value.
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
```
{{</expand>}}
The Function in `functions.yaml` uses the
{{<hover label="development" line="6">}}Development{{</hover>}}
runtime. This tells `crossplane beta render` that your function is running
locally. It connects to your locally running function instead of using Docker to
pull and run the function.
```yaml {label="development"}
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
```
Use `go run` to run your function locally.
```shell {label="run"}
go run . --insecure --debug
```
{{<hint "warning">}}
The {{<hover label="run" line="1">}}insecure{{</hover>}} flag tells the function
to run without encryption or authentication. Only use it during testing and
development.
{{</hint>}}
In a separate terminal, run `crossplane beta render`.
```shell
crossplane beta render xr.yaml composition.yaml functions.yaml
```
This command calls your function. In the terminal where your function is running
you should now see log output:
```shell
go run . --insecure --debug
2023-10-31T16:17:32.158-0700 INFO function-xbuckets/fn.go:29 Running Function {"tag": ""}
2023-10-31T16:17:32.159-0700 INFO function-xbuckets/fn.go:125 Added desired buckets {"xr-version": "example.crossplane.io/v1", "xr-kind": "XBuckets", "xr-name": "example-buckets", "region": "us-east-2", "count": 3}
```
The `crossplane beta render` command prints the desired resources the function
returns.
```yaml
---
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
crossplane.io/external-name: crossplane-functions-example-b
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
crossplane.io/external-name: crossplane-functions-example-c
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
crossplane.io/external-name: crossplane-functions-example-a
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
```
{{<hint "tip">}}
Read the composition functions documentation to learn more about
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
{{</hint>}}
## Build and push the function to a package registry
You build a function in two stages. First you build the function's runtime. This
is the Open Container Initiative (OCI) image Crossplane uses to run your
function. You then embed that runtime in a package, and push it to a package
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
registry.
A function supports a single platform, like `linux/amd64`, by default. You can
support multiple platforms by building a runtime and package for each platform,
then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform.
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
```
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
```
{{<hint "tip">}}
You can use whatever tag you want. There's no need to push the runtime images to
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
embed.
{{</hint>}}
Use the Crossplane CLI to build a package for each platform. Each package embeds
a runtime image.
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
the `package` directory, which contains `crossplane.yaml`. This includes
metadata about the package.
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
specifies the runtime image tag built using Docker.
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
specifies where to write the package file to disk. Crossplane package files use
the extension `.xpkg`.
```shell {label="build"}
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-amd64 \
--package-file=function-amd64.xpkg
```
```shell
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-arm64 \
--package-file=function-arm64.xpkg
```
{{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the
[packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the
registry creates a
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
package that runs on both `linux/arm64` and `linux/amd64` hosts.
```shell
crossplane xpkg push \
--package-files=function-amd64.xpkg,function-arm64.xpkg \
negz/function-xbuckets:v0.1.0
```
{{<hint "tip">}}
If you push the function to a GitHub repository the template automatically sets
up continuous integration (CI) using
[GitHub Actions](https://github.com/features/actions). The CI workflow will
lint, test, and build your function. You can see how the template configures CI
by reading `.github/workflows/ci.yaml`.
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
to work you must create a repository at https://marketplace.upbound.io. Give the
CI workflow access to push to the Marketplace by creating an API token and
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
token as a secret named `XPKG_TOKEN`.
{{</hint>}}

View File

@ -0,0 +1,745 @@
---
title: Write a Composition Function in Python
state: beta
alphaVersion: "1.11"
betaVersion: "1.14"
weight: 81
description: "Composition functions allow you to template resources using Python"
---
Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource
(XR). Read the
[concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions.
You can write a function to template resources using a general purpose
programming language. Using a general purpose programming language allows a
function to use advanced logic to template resources, like loops and
conditionals. This guide explains how to write a composition function in
[Python](https://python.org).
{{< hint "important" >}}
It helps to be familiar with
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide.
{{< /hint >}}
## Understand the steps
This guide covers writing a composition function for an
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
```yaml {label="xr"}
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<!-- vale gitlab.FutureTense = NO -->
<!--
This section is setting the stage for future sections. It doesn't make sense to
refer to the function in the present tense, because it doesn't exist yet.
-->
An `XBuckets` XR has a region and an array of bucket names. The function will
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
<!-- vale gitlab.FutureTense = YES -->
To write a function in Python:
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
1. [Test the function end-to-end](#test-the-function-end-to-end)
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
This guide covers each of these steps in detail.
## Install the tools you need to write the function
To write a function in Python you need:
* [Python](https://www.python.org/downloads/) v3.11.
* [Hatch](https://hatch.pypa.io/), a Python build tool. This guide uses v1.7.
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
CLI v1.14.
{{<hint "note">}}
You don't need access to a Kubernetes cluster or a Crossplane control plane to
build or test a composition function.
{{</hint>}}
## Initialize the function from a template
Use the `crossplane beta xpkg init` command to initialize a new function. When
you run this command it initializes your function using
[a GitHub repository](https://github.com/crossplane/function-template-python)
as a template.
```shell {copy-lines=1}
crossplane beta xpkg init function-xbuckets https://github.com/crossplane/function-template-python -d function-xbuckets
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-python/tree/bfed6923ab4c8e7adeed70f41138645fc7d38111 (main)
```
The `crossplane beta init xpkg` command creates a directory named
`function-xbuckets`. When you run the command the new directory should look like
this:
```shell {copy-lines=1}
ls function-xbuckets
Dockerfile example/ function/ LICENSE package/ pyproject.toml README.md renovate.json tests/
```
Your function's code lives in the `function` directory:
```shell {copy-lines=1}
ls function/
__version__.py fn.py main.py
```
The `function/fn.py` file is where you add the function's code. It's useful to
know about some other files in the template:
* `function/main.py` runs the function. You don't need to edit `main.py`.
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
* The `package` directory contains metadata used to build the function package.
{{<hint "tip">}}
<!-- vale gitlab.FutureTense = NO -->
<!--
This tip talks about future plans for Crossplane.
-->
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
template GitHub repository. A future CLI release will automate tasks like
replacing the template name with the new function's name. See Crossplane issue
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
<!-- vale gitlab.FutureTense = YES -->
{{</hint>}}
Edit `package/crossplane.yaml` to change the package's name before you start
adding code. Name your package `function-xbuckets`.
The `package/input` directory defines the OpenAPI schema for the a function's
input. The function in this guide doesn't accept an input. Delete the
`package/input` directory.
The [composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains composition function inputs.
{{<hint "tip">}}
If you're writing a function that uses an input, edit the input YAML file to
meet your function's requirements.
Change the input's kind and API group. Don't use `Input` and
`template.fn.crossplane.io`. Instead use something meaningful to your function.
{{</hint>}}
## Edit the template to add the function's logic
You add your function's logic to the
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
method in `function/fn.py`. When you first open the file it contains a "hello
world" function.
```python {label="hello-world"}
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
rsp = response.to(req)
example = ""
if "example" in req.input:
example = req.input["example"]
# TODO: Add your function logic here!
response.normal(rsp, f"I was run with input {example}!")
log.info("I was run!", input=example)
return rsp
```
All Python composition functions have a `RunFunction` method. Crossplane passes
everything the function needs to run in a
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} object.
The function tells Crossplane what resources it should compose by returning a
{{<hover label="hello-world" line="15">}}RunFunctionResponse{{</hover>}} object.
Edit the `RunFunction` method to replace it with this code.
```python {hl_lines="7-28"}
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
rsp = response.to(req)
region = req.observed.composite.resource["spec"]["region"]
names = req.observed.composite.resource["spec"]["names"]
for name in names:
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": name,
},
},
"spec": {
"forProvider": {
"region": region,
},
},
}
)
log.info("Added desired buckets", region=region, count=len(names))
return rsp
```
Expand the below block to view the full `fn.py`, including imports and
commentary explaining the function's logic.
{{<expand "The full fn.py file" >}}
```python
"""A Crossplane composition function."""
import grpc
from crossplane.function import logging, response
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
from crossplane.function.proto.v1beta1 import run_function_pb2_grpc as grpcv1beta1
class FunctionRunner(grpcv1beta1.FunctionRunnerService):
"""A FunctionRunner handles gRPC RunFunctionRequests."""
def __init__(self):
"""Create a new FunctionRunner."""
self.log = logging.get_logger()
async def RunFunction(
self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext
) -> fnv1beta1.RunFunctionResponse:
"""Run the function."""
# Create a logger for this request.
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
# Create a response to the request. This copies the desired state and
# pipeline context from the request to the response.
rsp = response.to(req)
# Get the region and a list of bucket names from the observed composite
# resource (XR). Crossplane represents resources using the Struct
# well-known protobuf type. The Struct Python object can be accessed
# like a dictionary.
region = req.observed.composite.resource["spec"]["region"]
names = req.observed.composite.resource["spec"]["names"]
# Add a desired S3 bucket for each name.
for name in names:
# Crossplane represents desired composed resources using a protobuf
# map of messages. This works a little like a Python defaultdict.
# Instead of assigning to a new key in the dict-like map, you access
# the key and mutate its value as if it did exist.
#
# The below code works because accessing the xbuckets-{name} key
# automatically creates a new, empty fnv1beta1.Resource message. The
# Resource message has a resource field containing an empty Struct
# object that can be populated from a dictionary by calling update.
#
# https://protobuf.dev/reference/python/python-generated/#map-fields
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": name,
},
},
"spec": {
"forProvider": {
"region": region,
},
},
}
)
# Log what the function did. This will only appear in the function's pod
# logs. A function can use response.normal() and response.warning() to
# emit Kubernetes events associated with the XR it's operating on.
log.info("Added desired buckets", region=region, count=len(names))
return rsp
```
{{</expand>}}
This code:
1. Gets the observed composite resource from the `RunFunctionRequest`.
1. Gets the region and bucket names from the observed composite resource.
1. Adds one desired S3 bucket for each bucket name.
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
Crossplane provides a
[software development kit](https://github.com/crossplane/function-sdk-python)
(SDK) for writing composition functions in Python. This function uses utilities
from the SDK.
{{<hint "tip">}}
Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python).
{{</hint>}}
{{<hint "important">}}
The Python SDK automatically generates the `RunFunctionRequest` and
`RunFunctionResponse` Python objects from a
[Protocol Buffers](https://protobuf.dev) schema. You can see the schema in the
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
The fields of the generated Python objects behave similarly to builtin Python
types like dictionaries and lists. Be aware that there are some differences.
Notably, you access the map of observed and desired resources like a dictionary
but you can't add a new desired resource by assigning to a map key. Instead,
access and mutate the map key as if it already exists.
Instead of adding a new resource like this:
```python
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
rsp.desired.resources["new-resource"] = fnv1beta1.Resource(resource=resource)
```
Pretend it already exists and mutate it, like this:
```python
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
rsp.desired.resources["new-resource"].resource.update(resource)
```
Refer to the Protocol Buffers
[Python Generated Code Guide](https://protobuf.dev/reference/python/python-generated/#fields)
for further details.
{{</hint>}}
## Test the function end-to-end
Test your function by adding unit tests, and by using the `crossplane beta
render` command.
When you initialize a function from the
template it adds some unit tests to `tests/test_fn.py`. These tests use the
[`unittest`](https://docs.python.org/3/library/unittest.html) module from the
Python standard library.
To add test cases, update the `cases` list in `test_run_function`. Expand the
below block to view the full `tests/test_fn.py` file for the function.
{{<expand "The full test_fn.py file" >}}
```python
import dataclasses
import unittest
from crossplane.function import logging, resource
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
from google.protobuf import duration_pb2 as durationpb
from google.protobuf import json_format
from google.protobuf import struct_pb2 as structpb
from function import fn
class TestFunctionRunner(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
logging.configure(level=logging.Level.DISABLED)
self.maxDiff = 2000
async def test_run_function(self) -> None:
@dataclasses.dataclass
class TestCase:
reason: str
req: fnv1beta1.RunFunctionRequest
want: fnv1beta1.RunFunctionResponse
cases = [
TestCase(
reason="The function should compose two S3 buckets.",
req=fnv1beta1.RunFunctionRequest(
observed=fnv1beta1.State(
composite=fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "example.crossplane.io/v1alpha1",
"kind": "XBuckets",
"metadata": {"name": "test"},
"spec": {
"region": "us-east-2",
"names": ["test-bucket-a", "test-bucket-b"],
},
}
)
)
)
),
want=fnv1beta1.RunFunctionResponse(
meta=fnv1beta1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
desired=fnv1beta1.State(
resources={
"xbuckets-test-bucket-a": fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-a"
},
},
"spec": {
"forProvider": {"region": "us-east-2"}
},
}
)
),
"xbuckets-test-bucket-b": fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-b"
},
},
"spec": {
"forProvider": {"region": "us-east-2"}
},
}
)
),
},
),
context=structpb.Struct(),
),
),
]
runner = fn.FunctionRunner()
for case in cases:
got = await runner.RunFunction(case.req, None)
self.assertEqual(
json_format.MessageToDict(got),
json_format.MessageToDict(case.want),
"-want, +got",
)
if __name__ == "__main__":
unittest.main()
```
{{</expand>}}
Run the unit tests using `hatch run`:
```shell {copy-lines="1"}
hatch run test:unit
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
```
{{<hint "tip">}}
[Hatch](https://hatch.pypa.io/) is a Python build tool. It builds Python
artifacts like wheels. It also manages virtual environments, similar
to `virtualenv` or `venv`. The `hatch run` command creates a virtual environment
and runs a command in that environment.
{{</hint>}}
You can preview the output of a Composition that uses this function using
the Crossplane CLI. You don't need a Crossplane control plane to do this.
Create a directory under `function-xbuckets` named `example` and create
Composite Resource, Composition and Function YAML files.
Expand the following block to see example files.
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
You can recreate the output below using by running `crossplane beta render` with
these files.
The `xr.yaml` file contains the composite resource to render:
```yaml
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<br />
The `composition.yaml` file contains the Composition to use to render the
composite resource:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: create-buckets
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: XBuckets
mode: Pipeline
pipeline:
- step: create-buckets
functionRef:
name: function-xbuckets
```
<br />
The `functions.yaml` file contains the Functions the Composition references in
its pipeline steps:
```yaml
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
spec:
# The CLI ignores this package when using the Development runtime.
# You can set it to any value.
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
```
{{</expand>}}
The Function in `functions.yaml` uses the
{{<hover label="development" line="6">}}Development{{</hover>}}
runtime. This tells `crossplane beta render` that your function is running
locally. It connects to your locally running function instead of using Docker to
pull and run the function.
```yaml {label="development"}
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
```
Use `hatch run development` to run your function locally.
```shell {label="run"}
hatch run development
```
{{<hint "warning">}}
`hatch run development` runs the function without encryption or authentication.
Only use it during testing and development.
{{</hint>}}
In a separate terminal, run `crossplane beta render`.
```shell
crossplane beta render xr.yaml composition.yaml functions.yaml
```
This command calls your function. In the terminal where your function is running
you should now see log output:
```shell
hatch run development
2024-01-11T22:12:58.153572Z [info ] Running function filename=fn.py lineno=22 tag=
2024-01-11T22:12:58.153792Z [info ] Added desired buckets count=3 filename=fn.py lineno=68 region=us-east-2 tag=
```
The `crossplane beta render` command prints the desired resources the function
returns.
```yaml
---
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
crossplane.io/external-name: crossplane-functions-example-b
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
crossplane.io/external-name: crossplane-functions-example-c
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
crossplane.io/external-name: crossplane-functions-example-a
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
```
{{<hint "tip">}}
Read the composition functions documentation to learn more about
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
{{</hint>}}
## Build and push the function to a package registry
You build a function in two stages. First you build the function's runtime. This
is the Open Container Initiative (OCI) image Crossplane uses to run your
function. You then embed that runtime in a package, and push it to a package
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
registry.
A function supports a single platform, like `linux/amd64`, by default. You can
support multiple platforms by building a runtime and package for each platform,
then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform.
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
```
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
```
{{<hint "tip">}}
You can use whatever tag you want. There's no need to push the runtime images to
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
embed.
{{</hint>}}
{{<hint "important">}}
Docker uses emulation to create images for different platforms. If building an
image for a different platform fails, make sure you have installed `binfmt`. See
the
[Docker documentation](https://docs.docker.com/build/building/multi-platform/#qemu)
for instructions.
{{</hint>}}
Use the Crossplane CLI to build a package for each platform. Each package embeds
a runtime image.
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
the `package` directory, which contains `crossplane.yaml`. This includes
metadata about the package.
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
specifies the runtime image tag built using Docker.
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
specifies where to write the package file to disk. Crossplane package files use
the extension `.xpkg`.
```shell {label="build"}
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-amd64 \
--package-file=function-amd64.xpkg
```
```shell
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-arm64 \
--package-file=function-arm64.xpkg
```
{{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the
[packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the
registry creates a
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
package that runs on both `linux/arm64` and `linux/amd64` hosts.
```shell
crossplane xpkg push \
--package-files=function-amd64.xpkg,function-arm64.xpkg \
negz/function-xbuckets:v0.1.0
```
{{<hint "tip">}}
If you push the function to a GitHub repository the template automatically sets
up continuous integration (CI) using
[GitHub Actions](https://github.com/features/actions). The CI workflow will
lint, test, and build your function. You can see how the template configures CI
by reading `.github/workflows/ci.yaml`.
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
to work you must create a repository at https://marketplace.upbound.io. Give the
CI workflow access to push to the Marketplace by creating an API token and
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
token as a secret named `XPKG_TOKEN`.
{{</hint>}}

View File

@ -1,6 +1,7 @@
--- ---
title: Learn More title: Learn More
weight: 307 description: Learn more about Crossplane.
weight: 500
--- ---
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]! If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!

View File

@ -1,6 +1,6 @@
--- ---
title: Release Notes title: Release Notes
weight: 20 weight: 600
description: "Crossplane release notes" description: "Crossplane release notes"
product: "Release Notes" product: "Release Notes"
cascade: cascade:

View File

@ -1,6 +1,6 @@
--- ---
title: Install and Uninstall Crossplane title: Install, Upgrade and Uninstall
weight: 300 weight: 10
description: Manage Crossplane installations description: Manage Crossplane installations
--- ---

View File

@ -51,7 +51,7 @@ For example, in v1.15.0 Crossplane changed the default image registry from
before v1.15.0 updates the default package registry. before v1.15.0 updates the default package registry.
Override new defaults by Override new defaults by
[customizing the Helm chart](https://docs.crossplane.io/v1.15/software/install/#customize-the-crossplane-helm-chart) [customizing the Helm chart]({{<ref "install#customize-the-crossplane-helm-chart" >}})
with the upgrade command. with the upgrade command.
For example, to maintain the original image registry use For example, to maintain the original image registry use

View File

@ -205,4 +205,4 @@ spec:
``` ```
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).

View File

@ -623,7 +623,7 @@ recreate the XRD to change the `connectionSecretKeys`.
{{</hint >}} {{</hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Set composite resource defaults ### Set composite resource defaults
XRDs can set default parameters for composite resources and Claims. XRDs can set default parameters for composite resources and Claims.

View File

@ -190,7 +190,7 @@ spec:
### Composition revision policy ### Composition revision policy
Crossplane tracks changes to Compositions as Crossplane tracks changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) . [Composition revisions]({{<ref "composition-revisions">}}) .
A composite resource can use A composite resource can use
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
@ -218,7 +218,7 @@ spec:
### Composition revision selection ### Composition revision selection
Crossplane records changes to Compositions as Crossplane records changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}). [Composition revisions]({{<ref "composition-revisions">}}).
A composite resource can A composite resource can
select a specific Composition revision. select a specific Composition revision.
@ -311,7 +311,7 @@ spec:
``` ```
Composite resources can write connection secrets to an Composite resources can write connection secrets to an
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}), [external secret store]({{<ref "../guides/vault-as-secret-store">}}),
like HashiCorp Vault. like HashiCorp Vault.
{{<hint "important" >}} {{<hint "important" >}}
@ -334,11 +334,11 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
external secret stores. external secret stores.
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Pausing composite resources ### Pausing composite resources

View File

@ -1,13 +1,144 @@
--- ---
title: Composition Revision Example title: Composition Revisions
--- ---
This guide discusses the use of "Composition Revisions" to safely make and roll
back changes to a Crossplane [`Composition`][composition-type]. It assumes
familiarity with Crossplane, and particularly with
[Compositions].
A `Composition` configures how Crossplane should reconcile a Composite Resource
(XR). Put otherwise, when you create an XR the selected `Composition` determines
what managed resources Crossplane will create in response. Let's say for example
that you define a `PlatformDB` XR, which represents your organisation's common
database configuration of an Azure MySQL Server and a few firewall rules. The
`Composition` contains the 'base' configuration for the MySQL server and the
firewall rules that is extended by the configuration for the `PlatformDB`.
There is a one-to-many relationship between a `Composition` and the XRs that use
it. You might define a `Composition` named `big-platform-db` that is used by ten
different `PlatformDB` XRs. Usually, in the interest of self-service, the
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
For example the `Composition` may be written and maintained by a platform team
member, while individual application teams create `PlatformDB` XRs that use said
`Composition`.
Each `Composition` is mutable - you can update it as your organisation's needs
change. However, without Composition Revisions updating a `Composition` can be a
risky process. Crossplane constantly uses the `Composition` to ensure that your
actual infrastructure - your MySQL Servers and firewall rules - match your
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
`Composition`, all 10 of those XRs will be instantly updated in accordance with
any updates you make to the `big-platform-db` `Composition`.
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
update your XRs to leverage the latest `Composition` settings at your own pace.
This enables you to [canary] changes to your infrastructure, or to roll back
some XRs to previous `Composition` settings without rolling back all XRs.
## Using Composition Revisions
When you enable Composition Revisions three things happen:
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
which `CompositionRevision` they use.
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
specifies how they should be updated to new Composition Revisions.
Each time you edit a `Composition` Crossplane will automatically create a
`CompositionRevision` that represents that 'revision' of the `Composition` -
that unique state. Each revision is allocated an increasing revision number.
This gives `CompositionRevision` consumers an idea about which revision is
'newest'.
Crossplane distinguishes between the 'newest' and the 'current' revision of a
`Composition`. That is, if you revert a `Composition` to a previous state that
corresponds to an existing `CompositionRevision` that revision will become
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
`Composition` configuration).
You can discover which revisions exist using `kubectl`:
```console
# Find all revisions of the Composition named 'example'
kubectl get compositionrevision -l crossplane.io/composition-name=example
```
This should produce output something like:
```console
NAME REVISION CURRENT AGE
example-18pdg 1 False 4m36s
example-2bgdr 2 True 73s
example-xjrdm 3 False 61s
```
> A `Composition` is a mutable resource that you can update as your needs
> change over time. Each `CompositionRevision` is an immutable snapshot of those
> needs at a particular point in time.
Crossplane behaves the same way by default whether Composition Revisions are
enabled or not. This is because when you enable Composition Revisions all XRs
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
policies:
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
* `Manual`: Require manual intervention to change `CompositionRevision`.
The below XR uses the `Manual` policy. When this policy is used the XR will
select the current `CompositionRevision` when it is first created, but must
manually be updated when you wish it to use another `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
# The Manual policy specifies that you do not want this XR to update to the
# current CompositionRevision automatically.
compositionUpdatePolicy: Manual
compositionRef:
name: example
writeConnectionSecretToRef:
name: db-conn
```
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
policy you must edit the `compositionRevisionRef` field when you want your XR to
use a different `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
compositionUpdatePolicy: Manual
compositionRef:
name: example
# Update the referenced CompositionRevision if and when you are ready.
compositionRevisionRef:
name: example-18pdg
writeConnectionSecretToRef:
name: db-conn
```
## Complete example
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC` (XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
assign different CompositionRevisions to the created composite resources each time the composition is updated. assign different CompositionRevisions to the created composite resources each time the composition is updated.
## Preparation ### Preparation
### Install Crossplane ##### Install Crossplane
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running. Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
```shell ```shell
kubectl create namespace crossplane-system kubectl create namespace crossplane-system
@ -23,7 +154,7 @@ crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
``` ```
### Deploy Composition and XRD Examples #### Deploy Composition and XRD Examples
Apply the example Composition. Apply the example Composition.
```yaml ```yaml
@ -95,13 +226,13 @@ The label `dev` is automatically created from the Composition.
{{< /hint >}} {{< /hint >}}
## Create Composite Resources ### Create Composite Resources
This tutorial has four composite resources to cover different update policies and composition selection options. This tutorial has four composite resources to cover different update policies and composition selection options.
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label `compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`. with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
### Default update policy #### Default update policy
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default: Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
```yaml ```yaml
apiVersion: aws.example.upbound.io/v1alpha1 apiVersion: aws.example.upbound.io/v1alpha1
@ -116,7 +247,7 @@ Expected Output:
myvpc.aws.example.upbound.io/vpc-auto created myvpc.aws.example.upbound.io/vpc-auto created
``` ```
### Manual update policy #### Manual update policy
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`. Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
```yaml ```yaml
apiVersion: aws.example.upbound.io/v1alpha1 apiVersion: aws.example.upbound.io/v1alpha1
@ -135,7 +266,7 @@ Expected Output:
myvpc.aws.example.upbound.io/vpc-man created myvpc.aws.example.upbound.io/vpc-man created
``` ```
### Using a selector #### Using a selector
Create an XR with a `compositionRevisionSelector` of `channel: dev`: Create an XR with a `compositionRevisionSelector` of `channel: dev`:
```yaml ```yaml
apiVersion: aws.example.upbound.io/v1alpha1 apiVersion: aws.example.upbound.io/v1alpha1
@ -189,11 +320,11 @@ vpc-staging False <none> Automatic map[c
The `vpc-staging` XR label doesn't match any existing Composition Revisions. The `vpc-staging` XR label doesn't match any existing Composition Revisions.
{{< /hint >}} {{< /hint >}}
## Create new Composition revisions ### Create new Composition revisions
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
also trigger a new CompositionRevision. also trigger a new CompositionRevision.
### Update the Composition label #### Update the Composition label
Update the `Composition` label to `channel: staging`: Update the `Composition` label to `channel: staging`:
```shell ```shell
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
@ -234,7 +365,7 @@ vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[c
`vpc-staging` now matches the label applied to Revision revision:2. `vpc-staging` now matches the label applied to Revision revision:2.
{{< /hint >}} {{< /hint >}}
### Update Composition Spec and Label #### Update Composition Spec and Label
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`. Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
Apply the following changes to update the `Composition` spec and label: Apply the following changes to update the `Composition` spec and label:
@ -304,4 +435,10 @@ vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[c
{{< hint "note" >}} {{< hint "note" >}}
`vpc-dev` matches the updated label applied to Revision revision:3. `vpc-dev` matches the updated label applied to Revision revision:3.
`vpc-staging` matches the label applied to Revision revision:2. `vpc-staging` matches the label applied to Revision revision:2.
{{< /hint >}} {{< /hint >}}
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
[install-guide]: {{<ref "../../master/software/install" >}}

View File

@ -716,7 +716,7 @@ details.
This section discusses creating Kubernetes secrets. This section discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store. with an external secret store.
{{</hint >}} {{</hint >}}
@ -926,7 +926,7 @@ for more information on restricting secret keys.
{{< /hint >}} {{< /hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
{{<hint "warning">}} {{<hint "warning">}}
You can't change the You can't change the
@ -941,7 +941,7 @@ recreate the Composition to change the
#### Save connection details to an external secret store #### Save connection details to an external secret store
Crossplane Crossplane
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
write secrets and connection details to external secret stores like HashiCorp write secrets and connection details to external secret stores like HashiCorp
Vault. Vault.
@ -986,7 +986,7 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
integration guide. integration guide.
### Resource readiness checks ### Resource readiness checks

View File

@ -18,7 +18,7 @@ Using connection details in Crossplane requires the following components:
This guide discusses creating Kubernetes secrets. This guide discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store. with an external secret store.
{{</hint >}} {{</hint >}}

View File

@ -372,7 +372,7 @@ Crossplane supports the following policies:
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. | | `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. | | `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. | | `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| `Update` | Crossplane changes the external resource when changing the managed resource. | | `Update` | Crossplane changes the external resource when changing the managed resource. |
{{</table >}} {{</table >}}
@ -388,7 +388,7 @@ The following is a list of common policy combinations:
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | | {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
{{< /table >}} {{< /table >}}
@ -583,7 +583,7 @@ metadata:
{{<hint "tip" >}} {{<hint "tip" >}}
Read the Read the
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}}) [Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
guide for details on using StoreConfig objects. guide for details on using StoreConfig objects.
{{< /hint >}} {{< /hint >}}

View File

@ -133,7 +133,7 @@ If you remove the Provider first, you must manually delete external resources
through your cloud provider. Managed resources must be manually deleted by through your cloud provider. Managed resources must be manually deleted by
removing their finalizers. removing their finalizers.
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}). For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
{{< /hint >}} {{< /hint >}}
## Verify a Provider ## Verify a Provider
@ -324,7 +324,7 @@ defines the supported set of ControllerConfig settings.
The most common use case for ControllerConfigs are providing `args` to a The most common use case for ControllerConfigs are providing `args` to a
Provider's pod enabling optional services. For example, enabling Provider's pod enabling optional services. For example, enabling
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider) [external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
for a Provider. for a Provider.
Each Provider determines their supported set of `args`. Each Provider determines their supported set of `args`.

View File

@ -0,0 +1,5 @@
---
title: Guides
weight: 400
description: Crossplane integrations and detailed examples.
---

View File

@ -0,0 +1,216 @@
---
title: Configuring Crossplane with Argo CD
weight: 270
---
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
cluster into a Universal Control Plane for all of your resources. There are
configuration details required in order for the two to work together properly.
This doc will help you understand these requirements. It is recommended to use
Argo CD version 2.4.8 or later with Crossplane.
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
### Configuring Argo CD with Crossplane
#### Set Resource Tracking Method
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
to be configured to use the annotation mechanism.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
```
#### Set Health Status
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
of Provider's CRDs.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
{{<hint "note">}}
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
{{</hint>}}
```yaml {label="argocfg"}
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
resource.customizations: |
"*.upbound.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Ready" then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
"*.crossplane.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"Composition",
"CompositionRevision",
"DeploymentRuntimeConfig",
"ControllerConfig",
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
```
#### Set Resource Exclusion
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
resource.exclusions: |
- apiGroups:
- "*"
kinds:
- ProviderConfigUsage
```
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
#### Increase K8s Client QPS
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.

View File

@ -0,0 +1,10 @@
---
title: Disaster Recovery with Crossplane
weight: 10
---
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
using Crossplane to provision resources and Velero for Kubernetes backup and
recovery.
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).

View File

@ -0,0 +1,285 @@
---
title: Import Existing Resources
weight: 200
---
If you have resources that are already provisioned in a Provider,
you can import them as managed resources and let Crossplane manage them.
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
field enables importing external resources into Crossplane.
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
or [automatically]({{<ref "#import-resources-automatically">}}).
## Import resources manually
Crossplane can discover and import existing Provider resources by matching the
`crossplane.io/external-name` annotation in a managed resource.
To import an existing external resource in a Provider, create a new managed
resource with the `crossplane.io/external-name` annotation. Set the annotation
value to the name of the resource in the Provider.
For example, to import an existing GCP Network named
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
create a new managed resource and use the
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
annotation.
```yaml {label="annotation",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
annotations:
crossplane.io/external-name: my-existing-network
```
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
field can be anything you want. For example,
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
{{< hint "note" >}}
This name is the
name of the Kubernetes object. It's not related to the resource name inside the
Provider.
{{< /hint >}}
```yaml {label="name",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
```
Leave the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
Crossplane imports the settings and automatically applies them to the managed
resource.
{{< hint "important" >}}
If the managed resource has _required_ fields in the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
the `forProvider` field.
The values of those fields must match what's inside the Provider or Crossplane
overwrites the existing values.
{{< /hint >}}
```yaml {label="fp",copy-lines="all"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
spec:
forProvider: {}
```
Crossplane now controls and manages this imported resource. Any changes to the
managed resource `spec` changes the external resource.
## Import resources automatically
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
Crossplane imports observe only resources but never changes or deletes the
resources.
{{<hint "important" >}}
The managed resource `managementPolicies` option is a beta feature.
The Provider determines support for management policies.
Refer to the Provider's documentation to see if the Provider supports
management policies.
{{< /hint >}}
<!-- vale off -->
### Apply the Observe management policy
<!-- vale on -->
Create a new managed resource matching the
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
to import and add
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
For example, to import a GCP SQL DatabaseInstance, create a new resource with
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
set.
```yaml {label="oo-policy",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
spec:
managementPolicies: ["Observe"]
```
### Add the external-name annotation
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation for the resource. This name must match the name inside the Provider.
For example, for a GCP database named
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
the
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation with the value
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
```yaml {label="oo-ex-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Create a Kubernetes object name
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
Kubernetes object.
For example, name the Kubernetes object
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
```yaml {label="oo-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Identify a specific external resource
If more than one resource inside the Provider shares the same name, identify the
specific resource with a unique
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
For example, only import the GCP SQL database in the
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
```yaml {label="oo-region"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: "us-central1"
```
### Apply the managed resource
Apply the new managed resource. Crossplane syncs the status of the external
resource in the cloud with the newly created managed resource.
### View the discovered resource
Crossplane discovers the managed resource and populates the
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
fields with the values from the external resource.
```yaml {label="ooPopulated",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: us-central1
status:
atProvider:
connectionName: crossplane-playground:us-central1:my-external-database
databaseVersion: POSTGRES_14
deletionProtection: true
firstIpAddress: 35.184.74.79
id: my-external-database
publicIpAddress: 35.184.74.79
region: us-central1
# Removed for brevity
settings:
- activationPolicy: ALWAYS
availabilityType: REGIONAL
diskSize: 100
# Removed for brevity
pricingPlan: PER_USE
tier: db-custom-4-26624
version: 4
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
<!-- vale off -->
## Control imported ObserveOnly resources
<!-- vale on -->
Crossplane can take active control of observe only imported resources by
changing the `managementPolicies` after import.
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
of the managed resource to
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
Copy any required parameter values from
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
{{< hint "tip" >}}
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
{{< /hint >}}
```yaml {label="fc"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["*"]
forProvider:
databaseVersion: POSTGRES_14
region: us-central1
settings:
- diskSize: 100
tier: db-custom-4-26624
status:
atProvider:
databaseVersion: POSTGRES_14
region: us-central1
# Removed for brevity
settings:
- diskSize: 100
tier: db-custom-4-26624
# Removed for brevity
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T11:16:45Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
Crossplane now fully manages the imported resource. Crossplane applies any
changes to the managed resource in the Provider's external resource.

View File

@ -0,0 +1,323 @@
---
title: Multi-Tenant Crossplane
weight: 240
---
This guide describes how to use Crossplane effectively in multi-tenant
environments by utilizing Kubernetes primitives and compatible policy
enforcement projects in the cloud-native ecosystem.
## TL;DR
Infrastructure operators in multi-tenant Crossplane environments typically
utilize composition and Kubernetes RBAC to define lightweight, standardized
policies that dictate what level of self-service developers are given when
requesting infrastructure. This is primarily achieved through exposing abstract
resource types at the namespace scope, defining `Roles` for teams and
individuals within that namespace, and patching the `spec.providerConfigRef` of
the underlying managed resources so that they use a specific `ProviderConfig`
and credentials when provisioned from each namespace. Larger organizations, or
those with more complex environments, may choose to incorporate third-party
policy engines, or scale to multiple Crossplane clusters. The following sections
describe each of these scenarios in greater detail.
- [TL;DR](#tldr)
- [Background](#background)
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
- [Namespace Scoped Claims](#namespace-scoped-claims)
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
- [Control Plane of Control Planes](#control-plane-of-control-planes)
## Background
Crossplane is designed to run in multi-tenant environments where many teams are
consuming the services and abstractions provided by infrastructure operators in
the cluster. This functionality is facilitated by two major design patterns in
the Crossplane ecosystem.
### Cluster-Scoped Managed Resources
Typically, Crossplane providers, which supply granular [managed resources] that
reflect an external API, authenticate by using a `ProviderConfig` object that
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
filesystem, or an environment variable). Then, every managed resource references
a `ProviderConfig` that points to credentials with sufficient permissions to
manage that resource type.
For example, the following `ProviderConfig` for `provider-aws` points to a
Kubernetes `Secret` with AWS credentials.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: cool-aws-creds
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: creds
```
If a user desired for these credentials to be used to provision an
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
```yaml
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
name: rdsmysql
spec:
forProvider:
region: us-east-1
dbInstanceClass: db.t3.medium
masterUsername: masteruser
allocatedStorage: 20
engine: mysql
engineVersion: "5.6.35"
skipFinalSnapshotBeforeDeletion: true
providerConfigRef:
name: cool-aws-creds # name of ProviderConfig above
writeConnectionSecretToRef:
namespace: crossplane-system
name: aws-rdsmysql-conn
```
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
the RDS controller in `provider-aws` will resolve this reference by fetching the
`ProviderConfig`, obtaining the credentials it points to, and using those
credentials to reconcile the `RDSInstance`. This means that anyone who has been
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
In practice, Crossplane assumes that only folks acting as infrastructure
administrators or platform builders will interact directly with cluster-scoped
resources.
### Namespace Scoped Claims
While managed resources exist at the cluster scope, composite resources, which
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
the cluster or namespace scope. Platform builders define XRDs and
**Compositions** that specify what granular managed resources should be created
in response to the creation of an instance of the XRD. More information about
this architecture can be found in the [Composition] documentation.
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
defined will have a namespace-scoped variant.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xmysqlinstances.example.org
spec:
group: example.org
names:
kind: XMySQLInstance
plural: xmysqlinstances
claimNames:
kind: MySQLInstance
plural: mysqlinstances
...
```
When the example above is created, Crossplane will produce two
[CustomResourceDefinitions]:
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
**Composite Resource (XR)**.
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
**Claim (XRC)**.
Platform builders may choose to define an arbitrary number of Compositions that
map to these types, meaning that creating a `MySQLInstance` in a given namespace
can result in the creations of any set of managed resources at the cluster
scope. For instance, creating a `MySQLInstance` could result in the creation of
the `RDSInstance` defined above.
## Single Cluster Multi-Tenancy
Depending on the size and scope of an organization, platform teams may choose to
run one central Crossplane control plane, or many different ones for each team
or business unit. This section will focus on servicing multiple teams within a
single cluster, which may or may not be one of many other Crossplane clusters in
the organization.
### Composition as an Isolation Mechanism
While managed resources always reflect every field that the underlying provider
API exposes, XRDs can have any schema that a platform builder chooses. The
fields in the XRD schema can then be patched onto fields in the underlying
managed resource defined in a Composition, essentially exposing those fields as
configurable to the consumer of the XR or XRC.
This feature serves as a lightweight policy mechanism by only giving the
consumer the ability to customize the underlying resources to the extent the
platform builder desires. For instance, in the examples above, a platform
builder may choose to define a `spec.location` field in the schema of the
`XMySQLInstance` that is an enum with options `east` and `west`. In the
Composition, those fields could map to the `RDSInstance` `spec.region` field,
making the value either `us-east-1` or `us-west-1`. If no other patches were
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
create a very specifically configured `RDSInstance`, where they can only decide
the region where it lives and they are restricted to two options.
This model is in contrast to many infrastructure as code tools where the end
user must have provider credentials to create the underlying resources that are
rendered from the abstraction. Crossplane takes a different approach, defining
various credentials in the cluster (using the `ProviderConfig`), then giving
only the provider controllers the ability to utilize those credentials and
provision infrastructure on the users behalf. This creates a consistent
permission model, even when using many providers with differing IAM models, by
standardizing on Kubernetes RBAC.
### Namespaces as an Isolation Mechanism
While the ability to define abstract schemas and patches to concrete resource
types using composition is powerful, the ability to define Claim types at the
namespace scope enhances the functionality further by enabling RBAC to be
applied with namespace restrictions. Most users in a cluster do not have access
to cluster-scoped resources as they are considered only relevant to
infrastructure admins by both Kubernetes and Crossplane.
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
builder may choose to define permissions on `MySQLInstance` at the namespace
scope using a `Role`. This allows for giving users the ability to create and
manage `MySQLInstances` in their given namespace, but not the ability to see
those defined in other namespaces.
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
be utilized to configure managed resources based on the namespace in which the
corresponding XRC was defined. This is especially useful if a platform builder
wants to designate specific credentials or a set of credentials that users in a
given namespace can utilize when provisioning infrastructure using an XRC. This
can be accomplished today by creating one or more `ProviderConfig` objects that
include the name of the namespace in the `ProviderConfig` name. For example, if
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
credentials when the provider controller creates the underlying `RDSInstance`,
the platform builder could:
1. Define a `ProviderConfig` with name `team-1`.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: team-1
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: team-1-creds
key: creds
```
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
to the `providerConfigRef` of the `RDSInstance`.
```yaml
...
resources:
- base:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
spec:
forProvider:
...
patches:
- fromFieldPath: spec.claimRef.namespace
toFieldPath: spec.providerConfigRef.name
policy:
fromFieldPath: Required
```
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
namespace the corresponding `MySQLInstance` was created in.
> Note that this model currently only allows for a single `ProviderConfig` per
> namespace. However, future Crossplane releases should allow for defining a set
> of `ProviderConfig` that can be selected from using [Multiple Source Field
> patching].
### Policy Enforcement with Open Policy Agent
In some Crossplane deployment models, only using composition and RBAC to define
policy will not be flexible enough. However, because Crossplane brings
management of external infrastructure to the Kubernetes API, it is well suited
to integrate with other projects in the cloud-native ecosystem. Organizations
and individuals that need a more robust policy engine, or just prefer a more
general language for defining policy, often turn to [Open Policy Agent] (OPA).
OPA allows platform builders to write custom logic in [Rego], a domain-specific
language. Writing policy in this manner allows for not only incorporating the
information available in the specific resource being evaluated, but also using
other state represented in the cluster. Crossplane users typically install OPA's
[Gatekeeper] to make policy management as streamlined as possible.
> A live demo of using OPA with Crossplane can be viewed [here].
## Multi-Cluster Multi-Tenancy
Organizations that deploy Crossplane across many clusters typically take
advantage of two major features that make managing multiple control planes much
simpler.
### Reproducible Platforms with Configuration Packages
[Configuration packages] allow platform builders to package their XRDs and
Compositions into [OCI images] that can be distributed via any OCI-compliant
image registry. These packages can also declare dependencies on providers,
meaning that a single package can declare all of the granular managed resources,
the controllers that must be deployed to reconcile them, and the abstract types
that expose the underlying resources using composition.
Organizations with many Crossplane deployments utilize Configuration packages to
reproduce their platform in each cluster. This can be as simple as installing
Crossplane with the flag to automatically install a Configuration package
alongside it.
```
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
```
### Control Plane of Control Planes
Taking the multi-cluster multi-tenancy model one step further, some
organizations opt to manage their many Crossplane clusters using a single
central Crossplane control plane. This requires setting up the central cluster,
then using a provider to spin up new clusters (such as an [EKS Cluster] using
[provider-aws]), then using [provider-helm] to install Crossplane into the new
remote cluster, potentially bundling a common Configuration package into each
install using the method described above.
This advanced pattern allows for full management of Crossplane clusters using
Crossplane itself, and when done properly, is a scalable solution to providing
dedicated control planes to many tenants within a single organization.
<!-- Named Links -->
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
[Composition]: {{<ref "../../master/concepts/compositions" >}}
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
[Open Policy Agent]: https://www.openpolicyagent.org/
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
[here]: https://youtu.be/TaF0_syejXc
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
[OCI images]: https://github.com/opencontainers/image-spec
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
[Cloudfoundry]: https://www.cloudfoundry.org/
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo

View File

@ -0,0 +1,49 @@
---
title: Self-Signed CA Certs
weight: 270
---
> Using self-signed certificates is not advised in production, it is
recommended to only use self-signed certificates for testing.
When Crossplane loads Configuration and Provider Packages from private
registries, it must be configured to trust the CA and Intermediate certs.
Crossplane needs to be installed via the Helm chart with the
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
## Configure
1. Create a CA Bundle (A file containing your Root and Intermediate
certificates in a specific order). This can be done with any text editor or
from the command line, so long as the resulting file contains all required crt
files in the proper order. In many cases, this will be either a single
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
order of the crt files should be from lowest to highest in signing order.
For example, if you have a chain of two certificates below your Root
certificate, you place the bottom level Intermediate cert at the beginning of
the file, then the Intermediate cert that singed that cert, then the Root cert
that signed that cert.
2. Save the files as `[yourdomain].ca-bundle`.
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
```
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
--from-file=ca-bundle=./[yourdomain].ca-bundle
```
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
`ca-bundle`.
> Providing Helm with parameter values is convered in the Helm docs,
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
in an `override.yaml` file would look like this:
```
registryCaBundleConfig:
name: ca-bundle-config
key: ca-bundle
```

View File

@ -0,0 +1,627 @@
---
title: Vault as an External Secret Store
weight: 230
---
This guide walks through the steps required to configure Crossplane and
its Providers to use [Vault] as an [External Secret Store] (`ESS`) with [ESS Plugin Vault].
{{<hint "warning" >}}
External Secret Stores are an alpha feature.
They're not recommended for production use. Crossplane disables External Secret
Stores by default.
{{< /hint >}}
Crossplane uses sensitive information including Provider credentials, inputs to
managed resources and connection details.
The [Vault credential injection guide]({{<ref "vault-injection" >}}) details
using Vault and Crossplane for Provider credentials.
Crossplane doesn't support for using Vault for managed resources input.
[Crossplane issue #2985](https://github.com/crossplane/crossplane/issues/2985)
tracks support for this feature.
Supporting connection details with Vault requires a Crossplane external secret
store.
## Prerequisites
This guide requires [Helm](https://helm.sh) version 3.11 or later.
## Install Vault
{{<hint "note" >}}
Detailed instructions on [installing
Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/helm)
are available from the Vault documentation.
{{< /hint >}}
### Add the Vault Helm chart
Add the Helm repository for `hashicorp`.
```shell
helm repo add hashicorp https://helm.releases.hashicorp.com --force-update
```
Install Vault using Helm.
```shell
helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace
```
### Unseal Vault
If Vault is [sealed](https://developer.hashicorp.com/vault/docs/concepts/seal)
unseal Vault using the unseal keys.
Get the Vault keys.
```shell
kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
```
Unseal the vault using the keys.
```shell {copy-lines="1"}
kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.13.1
Build Date 2023-03-23T12:51:35Z
Storage Type file
Cluster Name vault-cluster-df884357
Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
HA Enabled false
```
## Configure Vault Kubernetes authentication
Enable the [Kubernetes auth method] for Vault to authenticate requests based on
Kubernetes service accounts.
### Get the Vault root token
The Vault root token is inside the JSON file created when
[unsealing Vault](#unseal-vault).
```shell
cat cluster-keys.json | jq -r ".root_token"
```
### Enable Kubernetes authentication
Connect to a shell in the Vault pod.
```shell {copy-lines="1"}
kubectl -n vault-system exec -it vault-0 -- /bin/sh
/ $
```
From the Vault shell, login to Vault using the _root token_.
```shell {copy-lines="1"}
vault login # use the root token from above
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.TSN4SssfMBM0HAtwGrxgARgn
token_accessor qodxHrINVlRXKyrGeeDkxnih
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
```
Enable the Kubernetes authentication method in Vault.
```shell {copy-lines="1"}
vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/
```
Configure Vault to communicate with Kubernetes and exit the Vault shell
```shell {copy-lines="1-4"}
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config
/ $ exit
```
## Configure Vault for Crossplane integration
Crossplane relies on the Vault key-value secrets engine to store information and
Vault requires a permissions policy for the Crossplane service account.
<!-- vale Crossplane.Spelling = NO -->
<!-- allow "kv" -->
### Enable the Vault kv secrets engine
<!-- vale Crossplane.Spelling = YES -->
Enable the [Vault KV Secrets Engine].
{{< hint "important" >}}
Vault has two versions of the
[KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv).
This example uses version 2.
{{</hint >}}
```shell {copy-lines="1"}
kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
```
### Create a Vault policy for Crossplane
Create the Vault policy to allow Crossplane to read and write data from Vault.
```shell {copy-lines="1-8"}
kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
path "secret/data/*" {
capabilities = ["create", "read", "update", "delete"]
}
path "secret/metadata/*" {
capabilities = ["create", "read", "update", "delete"]
}
EOF
Success! Uploaded policy: crossplane
```
Apply the policy to Vault.
```shell {copy-lines="1-5"}
kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
bound_service_account_names="*" \
bound_service_account_namespaces=crossplane-system \
policies=crossplane \
ttl=24h
Success! Data written to: auth/kubernetes/role/crossplane
```
## Install Crossplane
{{<hint "important" >}}
Crossplane v1.12 introduced the plugin support. Make sure your version of Crossplane supports plugins.
{{< /hint >}}
Install the Crossplane with the External Secrets Stores feature enabled.
```shell
helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'
```
## Install the Crossplane Vault plugin
The Crossplane Vault plugin isn't part of the default Crossplane install.
The plugin installs as a unique Pod that uses the [Vault Agent Sidecar
Injection] to connect the Vault secret store to Crossplane.
First, configure annotations for the Vault plugin pod.
```yaml
cat > values.yaml <<EOF
podAnnotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-token: "true"
vault.hashicorp.com/role: crossplane
vault.hashicorp.com/agent-run-as-user: "65532"
EOF
```
Next, install the Crossplane ESS Plugin pod to the `crossplane-system` namespace
and apply the Vault annotations.
```shell
helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml
```
## Configure Crossplane
Using the Vault plugin requires configuration to connect to the Vault
service. The plugin also requires Providers to enable external secret stores.
With the plugin and providers configured, Crossplane requires two `StoreConfig`
objects to describe how Crossplane and the Providers communicate with vault.
### Enable external secret stores in the Provider
{{<hint "note">}}
This example uses Provider GCP, but the
{{<hover label="ControllerConfig" line="2">}}ControllerConfig{{</hover>}} is the
same for all Providers.
{{</hint >}}
Create a `ControllerConfig` object to enable external secret stores.
```yaml {label="ControllerConfig"}
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: vault-config
spec:
args:
- --enable-external-secret-stores" | kubectl apply -f -
```
Install the Provider and apply the ControllerConfig.
```yaml
echo "apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-gcp
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
controllerConfigRef:
name: vault-config" | kubectl apply -f -
```
### Connect the Crossplane plugin to Vault
Create a {{<hover label="VaultConfig" line="2">}}VaultConfig{{</hover>}}
resource for the plugin to connect to the Vault service:
```yaml {label="VaultConfig"}
echo "apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
metadata:
name: vault-internal
spec:
server: http://vault.vault-system:8200
mountPath: secret/
version: v2
auth:
method: Token
token:
source: Filesystem
fs:
path: /vault/secrets/token" | kubectl apply -f -
```
### Create a Crossplane StoreConfig
Create a {{<hover label="xp-storeconfig" line="2">}}StoreConfig{{</hover >}}
object from the
{{<hover label="xp-storeconfig" line="1">}}secrets.crossplane.io{{</hover >}}
group. Crossplane uses the StoreConfig to connect to the Vault plugin service.
The {{<hover label="xp-storeconfig" line="10">}}configRef{{</hover >}} connects
the StoreConfig to the specific Vault plugin configuration.
```yaml {label="xp-storeconfig"}
echo "apiVersion: secrets.crossplane.io/v1alpha1
kind: StoreConfig
metadata:
name: vault
spec:
type: Plugin
defaultScope: crossplane-system
plugin:
endpoint: ess-plugin-vault.crossplane-system:4040
configRef:
apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
name: vault-internal" | kubectl apply -f -
```
### Create a Provider StoreConfig
Create a {{<hover label="gcp-storeconfig" line="2">}}StoreConfig{{</hover >}}
object from the Provider's API group,
{{<hover label="gcp-storeconfig" line="1">}}gcp.crossplane.io{{</hover >}}.
The Provider uses this StoreConfig to communicate with Vault for
Managed Resources.
The {{<hover label="gcp-storeconfig" line="10">}}configRef{{</hover >}} connects
the StoreConfig to the specific Vault plugin configuration.
```yaml {label="gcp-storeconfig"}
echo "apiVersion: gcp.crossplane.io/v1alpha1
kind: StoreConfig
metadata:
name: vault
spec:
type: Plugin
defaultScope: crossplane-system
plugin:
endpoint: ess-plugin-vault.crossplane-system:4040
configRef:
apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
name: vault-internal" | kubectl apply -f -
```
## Create Provider resources
Check that Crossplane installed the Provider and the Provider is healthy.
```shell {copy-lines="1"}
kubectl get providers
NAME INSTALLED HEALTHY PACKAGE AGE
provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m
```
### Create a CompositeResourceDefinition
Create a `CompositeResourceDefinition` to define a custom API endpoint.
```yaml
echo "apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: compositeessinstances.ess.example.org
annotations:
feature: ess
spec:
group: ess.example.org
names:
kind: CompositeESSInstance
plural: compositeessinstances
claimNames:
kind: ESSInstance
plural: essinstances
connectionSecretKeys:
- publicKey
- publicKeyType
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
serviceAccount:
type: string
required:
- serviceAccount
required:
- parameters" | kubectl apply -f -
```
### Create a Composition
Create a `Composition` to create a Service Account and Service Account Key
inside GCP.
Creating a Service Account Key generates
{{<hover label="comp" line="39" >}}connectionDetails{{</hover>}} that the
Provider stores in Vault using the
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}} details.
```yaml {label="comp"}
echo "apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: essinstances.ess.example.org
labels:
feature: ess
spec:
publishConnectionDetailsWithStoreConfigRef:
name: vault
compositeTypeRef:
apiVersion: ess.example.org/v1alpha1
kind: CompositeESSInstance
resources:
- name: serviceaccount
base:
apiVersion: iam.gcp.crossplane.io/v1alpha1
kind: ServiceAccount
metadata:
name: ess-test-sa
spec:
forProvider:
displayName: a service account to test ess
- name: serviceaccountkey
base:
apiVersion: iam.gcp.crossplane.io/v1alpha1
kind: ServiceAccountKey
spec:
forProvider:
serviceAccountSelector:
matchControllerRef: true
publishConnectionDetailsTo:
name: ess-mr-conn
metadata:
labels:
environment: development
team: backend
configRef:
name: vault
connectionDetails:
- fromConnectionSecretKey: publicKey
- fromConnectionSecretKey: publicKeyType" | kubectl apply -f -
```
### Create a Claim
Now create a `Claim` to have Crossplane create the GCP resources and associated
secrets.
Like the Composition, the Claim uses
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}} to
connect to Vault and store the secrets.
```yaml {label="claim"}
echo "apiVersion: ess.example.org/v1alpha1
kind: ESSInstance
metadata:
name: my-ess
namespace: default
spec:
parameters:
serviceAccount: ess-test-sa
compositionSelector:
matchLabels:
feature: ess
publishConnectionDetailsTo:
name: ess-claim-conn
metadata:
labels:
environment: development
team: backend
configRef:
name: vault" | kubectl apply -f -
```
## Verify the resources
Verify all resources are `READY` and `SYNCED`:
```shell {copy-lines="1"}
kubectl get managed
NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess my-ess-zvmkz-vhklg@testingforbugbounty.iam.gserviceaccount.com
NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z
```
View the claims
```shell {copy-lines="1"}
kubectl -n default get claim
NAME READY CONNECTION-SECRET AGE
my-ess True 19s
```
View the composite resources.
```shell {copy-lines="1"}
kubectl get composite
NAME READY COMPOSITION AGE
my-ess-zvmkz True essinstances.ess.example.org 32s
```
## Verify Vault secrets
Look inside Vault to view the secrets from the managed resources.
```shell {copy-lines="1",label="vault-key"}
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
Keys
----
ess-claim-conn
```
The key {{<hover label="vault-key" line="4">}}ess-claim-conn{{</hover>}}
is the name of the Claim's
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}}
configuration.
Check connection secrets in the "crossplane-system" Vault scope.
```shell {copy-lines="1",label="scope-key"}
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
Keys
----
d2408335-eb88-4146-927b-8025f405da86
ess-mr-conn
```
The key
{{<hover label="scope-key"line="4">}}d2408335-eb88-4146-927b-8025f405da86{{</hover>}}
comes from
<!-- ## WHERE DOES IT COME FROM? -->
and the key
{{<hover label="scope-key"line="5">}}ess-mr-conn{{</hover>}}
comes from the Composition's
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}}
configuration.
Check contents of Claim's connection secret `ess-claim-conn` to see the key
created by the managed resource.
```shell {copy-lines="1"}
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
======= Metadata =======
Key Value
--- -----
created_time 2022-03-18T21:24:07.2085726Z
custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
deletion_time n/a
destroyed false
version 1
======== Data ========
Key Value
--- -----
publicKey -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
vwIDAQAB
-----END PUBLIC KEY-----
publicKeyType TYPE_RAW_PUBLIC_KEY
```
Check contents of managed resource connection secret `ess-mr-conn`. The public
key is identical to the public key in the Claim since the Claim is using this
managed resource.
```shell {copy-lines="1"}
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
======= Metadata =======
Key Value
--- -----
created_time 2022-03-18T21:21:07.9298076Z
custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
deletion_time n/a
destroyed false
version 2
========= Data =========
Key Value
--- -----
privateKey {
"type": "service_account",
"project_id": "REDACTED",
"private_key_id": "REDACTED",
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
"client_email": "ess-test-sa@REDACTED.iam.gserviceaccount.com",
"client_id": "REDACTED",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
}
privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
publicKey -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
vwIDAQAB
-----END PUBLIC KEY-----
publicKeyType TYPE_RAW_PUBLIC_KEY
```
### Remove the resources
Deleting the Claim removes the managed resources and associated keys from Vault.
```shell
kubectl delete claim my-ess
```
<!-- named links -->
[Vault]: https://www.vaultproject.io/
[External Secret Store]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-external-secret-stores.md
[this issue]: https://github.com/crossplane/crossplane/issues/2985
[Kubernetes Auth Method]: https://www.vaultproject.io/docs/auth/kubernetes
[Unseal]: https://www.vaultproject.io/docs/concepts/seal
[Vault KV Secrets Engine]: https://developer.hashicorp.com/vault/docs/secrets/kv
[Vault Agent Sidecar Injection]: https://www.vaultproject.io/docs/platform/k8s/injector
[ESS Plugin Vault]: https://github.com/crossplane-contrib/ess-plugin-vault

View File

@ -0,0 +1,506 @@
---
title: Vault Credential Injection
weight: 230
---
> This guide is adapted from the [Vault on Minikube] and [Vault Kubernetes
> Sidecar] guides.
Most Crossplane providers support supplying credentials from at least the
following sources:
- Kubernetes Secret
- Environment Variable
- Filesystem
A provider may optionally support additional credentials sources, but the common
sources cover a wide variety of use cases. One specific use case that is popular
among organizations that use [Vault] for secrets management is using a sidecar
to inject credentials into the filesystem. This guide will demonstrate how to
use the [Vault Kubernetes Sidecar] to provide credentials for [provider-gcp]
and [provider-aws].
> Note: in this guide we will copy GCP credentials and AWS access keys
> into Vault's KV secrets engine. This is a simple generic approach to
> managing secrets with Vault, but is not as robust as using Vault's
> dedicated cloud provider secrets engines for [AWS], [Azure], and [GCP].
## Setup
> Note: this guide walks through setting up Vault running in the same cluster as
> Crossplane. You may also choose to use an existing Vault instance that runs
> outside the cluster but has Kubernetes authentication enabled.
Before getting started, you must ensure that you have installed Crossplane and
Vault and that they are running in your cluster.
1. Install Crossplane
```console
kubectl create namespace crossplane-system
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
```
2. Install Vault Helm Chart
```console
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault
```
3. Unseal Vault Instance
In order for Vault to access encrypted data from physical storage, it must be
[unsealed].
```console
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
```
4. Enable Kubernetes Authentication Method
In order for Vault to be able to authenticate requests based on Kubernetes
service accounts, the [Kubernetes authentication backend] must be enabled. This
requires logging in to Vault and configuring it with a service account token,
API server address, and certificate. Because we are running Vault in Kubernetes,
these values are already available via the container filesystem and environment
variables.
```console
cat cluster-keys.json | jq -r ".root_token" # get root token
kubectl exec -it vault-0 -- /bin/sh
vault login # use root token from above
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
```
5. Exit Vault Container
The next steps will be executed in your local environment.
```console
exit
```
{{< tabs >}}
{{< tab "GCP" >}}
## Create GCP Service Account
In order to provision infrastructure on GCP, you will need to create a service
account with appropriate permissions. In this guide we will only provision a
CloudSQL instance, so the service account will be bound to the `cloudsql.admin`
role. The following steps will setup a GCP service account, give it the
necessary permissions for Crossplane to be able to manage CloudSQL instances,
and emit the service account credentials in a JSON file.
```console
# replace this with your own gcp project id and the name of the service account
# that will be created.
PROJECT_ID=my-project
NEW_SA_NAME=test-service-account-name
# create service account
SA="${NEW_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create $NEW_SA_NAME --project $PROJECT_ID
# enable cloud API
SERVICE="sqladmin.googleapis.com"
gcloud services enable $SERVICE --project $PROJECT_ID
# grant access to cloud API
ROLE="roles/cloudsql.admin"
gcloud projects add-iam-policy-binding --role="$ROLE" $PROJECT_ID --member "serviceAccount:$SA"
# create service account keyfile
gcloud iam service-accounts keys create creds.json --project $PROJECT_ID --iam-account $SA
```
You should now have valid service account credentials in `creds.json`.
## Store Credentials in Vault
After setting up Vault, you will need to store your credentials in the [kv
secrets engine].
> Note: the steps below involve copying credentials into the container
> filesystem before storing them in Vault. You may also choose to use Vault's
> HTTP API or UI by port-forwarding the container to your local environment
> (`kubectl port-forward vault-0 8200:8200`).
1. Copy Credentials File into Vault Container
Copy your credentials into the container filesystem so that your can store them
in Vault.
```console
kubectl cp creds.json vault-0:/tmp/creds.json
```
2. Enable KV Secrets Engine
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
secrets engine at the `secret` path.
```console
kubectl exec -it vault-0 -- /bin/sh
vault secrets enable -path=secret kv-v2
```
3. Store GCP Credentials in KV Engine
The path of your GCP credentials is how the secret will be referenced when
injecting it into the `provider-gcp` controller `Pod`.
```console
vault kv put secret/provider-creds/gcp-default @tmp/creds.json
```
4. Clean Up Credentials File
You no longer need our GCP credentials file in the container filesystem, so go
ahead and clean it up.
```console
rm tmp/creds.json
```
{{< /tab >}}
{{< tab "AWS" >}}
## Create AWS IAM User
In order to provision infrastructure on AWS, you will need to use an existing or create a new IAM
user with appropriate permissions. The following steps will create an AWS IAM user and give it the necessary
permissions.
> Note: if you have an existing IAM user with appropriate permissions, you can skip this step but you will
> still need to provide the values for the `ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
```console
# create a new IAM user
IAM_USER=test-user
aws iam create-user --user-name $IAM_USER
# grant the IAM user the necessary permissions
aws iam attach-user-policy --user-name $IAM_USER --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
# create a new IAM access key for the user
aws iam create-access-key --user-name $IAM_USER > creds.json
# assign the access key values to environment variables
ACCESS_KEY_ID=$(jq -r .AccessKey.AccessKeyId creds.json)
AWS_SECRET_ACCESS_KEY=$(jq -r .AccessKey.SecretAccessKey creds.json)
```
## Store Credentials in Vault
After setting up Vault, you will need to store your credentials in the [kv
secrets engine].
1. Enable KV Secrets Engine
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
secrets engine at the `secret` path.
```console
kubectl exec -it vault-0 -- env \
ACCESS_KEY_ID=${ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
/bin/sh
vault secrets enable -path=secret kv-v2
```
2. Store AWS Credentials in KV Engine
The path of your AWS credentials is how the secret will be referenced when
injecting it into the `provider-aws` controller `Pod`.
```
vault kv put secret/provider-creds/aws-default access_key="$ACCESS_KEY_ID" secret_key="$AWS_SECRET_ACCESS_KEY"
```
{{< /tab >}}
{{< /tabs >}}
## Create a Vault Policy for Reading Provider Credentials
In order for our controllers to have the Vault sidecar inject the credentials
into their filesystem, you must associate the `Pod` with a [policy]. This policy
will allow for reading and listing all secrets on the `provider-creds` path in
the `kv-v2` secrets engine.
```console
vault policy write provider-creds - <<EOF
path "secret/data/provider-creds/*" {
capabilities = ["read", "list"]
}
EOF
```
## Create a Role for Crossplane Provider Pods
1. Create Role
The last step is to create a role that is bound to the policy you created and
associate it with a group of Kubernetes service accounts. This role can be
assumed by any (`*`) service account in the `crossplane-system` namespace.
```console
vault write auth/kubernetes/role/crossplane-providers \
bound_service_account_names="*" \
bound_service_account_namespaces=crossplane-system \
policies=provider-creds \
ttl=24h
```
2. Exit Vault Container
The next steps will be executed in your local environment.
```console
exit
```
{{< tabs >}}
{{< tab "GCP" >}}
## Install provider-gcp
You are now ready to install `provider-gcp`. Crossplane provides a
`ControllerConfig` type that allows you to customize the deployment of a
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
by any number of `Provider` objects that wish to use its configuration. In the
example below, the `Pod` annotations indicate to the Vault mutating webhook that
we want for the secret stored at `secret/provider-creds/gcp-default` to be
injected into the container filesystem by assuming role `crossplane-providers`.
There is also so template formatting added to make sure the secret data is
presented in a form that `provider-gcp` is expecting.
{% raw %}
```console
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: vault-config
spec:
metadata:
annotations:
vault.hashicorp.com/agent-inject: \"true\"
vault.hashicorp.com/role: "crossplane-providers"
vault.hashicorp.com/agent-inject-secret-creds.txt: "secret/provider-creds/gcp-default"
vault.hashicorp.com/agent-inject-template-creds.txt: |
{{- with secret \"secret/provider-creds/gcp-default\" -}}
{{ .Data.data | toJSON }}
{{- end -}}
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-gcp
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.22.0
controllerConfigRef:
name: vault-config" | kubectl apply -f -
```
{% endraw %}
## Configure provider-gcp
One `provider-gcp` is installed and running, you will want to create a
`ProviderConfig` that specifies the credentials in the filesystem that should be
used to provision managed resources that reference this `ProviderConfig`.
Because the name of this `ProviderConfig` is `default` it will be used by any
managed resources that do not explicitly reference a `ProviderConfig`.
> Note: make sure that the `PROJECT_ID` environment variable that was defined
> earlier is still set correctly.
```console
echo "apiVersion: gcp.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
projectID: ${PROJECT_ID}
credentials:
source: Filesystem
fs:
path: /vault/secrets/creds.txt" | kubectl apply -f -
```
To verify that the GCP credentials are being injected into the container run the
following command:
```console
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-gcp -o name --no-headers=true)
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-gcp -- cat /vault/secrets/creds.txt
```
## Provision Infrastructure
The final step is to actually provision a `CloudSQLInstance`. Creating the
object below will result in the creation of a Cloud SQL Postgres database on
GCP.
```console
echo "apiVersion: database.gcp.crossplane.io/v1beta1
kind: CloudSQLInstance
metadata:
name: postgres-vault-demo
spec:
forProvider:
databaseVersion: POSTGRES_12
region: us-central1
settings:
tier: db-custom-1-3840
dataDiskType: PD_SSD
dataDiskSizeGb: 10
writeConnectionSecretToRef:
namespace: crossplane-system
name: cloudsqlpostgresql-conn" | kubectl apply -f -
```
You can monitor the progress of the database provisioning with the following
command:
```console
kubectl get cloudsqlinstance -w
```
{{< /tab >}}
{{< tab "AWS" >}}
## Install provider-aws
You are now ready to install `provider-aws`. Crossplane provides a
`ControllerConfig` type that allows you to customize the deployment of a
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
by any number of `Provider` objects that wish to use its configuration. In the
example below, the `Pod` annotations indicate to the Vault mutating webhook that
we want for the secret stored at `secret/provider-creds/aws-default` to be
injected into the container filesystem by assuming role `crossplane-providers`.
There is also some template formatting added to make sure the secret data is
presented in a form that `provider-aws` is expecting.
{% raw %}
```console
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: aws-vault-config
spec:
args:
- --debug
metadata:
annotations:
vault.hashicorp.com/agent-inject: \"true\"
vault.hashicorp.com/role: \"crossplane-providers\"
vault.hashicorp.com/agent-inject-secret-creds.txt: \"secret/provider-creds/aws-default\"
vault.hashicorp.com/agent-inject-template-creds.txt: |
{{- with secret \"secret/provider-creds/aws-default\" -}}
[default]
aws_access_key_id="{{ .Data.data.access_key }}"
aws_secret_access_key="{{ .Data.data.secret_key }}"
{{- end -}}
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: aws-vault-config" | kubectl apply -f -
```
{% endraw %}
## Configure provider-aws
Once `provider-aws` is installed and running, you will want to create a
`ProviderConfig` that specifies the credentials in the filesystem that should be
used to provision managed resources that reference this `ProviderConfig`.
Because the name of this `ProviderConfig` is `default` it will be used by any
managed resources that do not explicitly reference a `ProviderConfig`.
```console
echo "apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Filesystem
fs:
path: /vault/secrets/creds.txt" | kubectl apply -f -
```
To verify that the AWS credentials are being injected into the container run the
following command:
```console
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-aws -o name --no-headers=true)
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-aws -- cat /vault/secrets/creds.txt
```
## Provision Infrastructure
The final step is to actually provision a `Bucket`. Creating the
object below will result in the creation of a S3 bucket on AWS.
```console
echo "apiVersion: s3.aws.crossplane.io/v1beta1
kind: Bucket
metadata:
name: s3-vault-demo
spec:
forProvider:
acl: private
locationConstraint: us-east-1
publicAccessBlockConfiguration:
blockPublicPolicy: true
tagging:
tagSet:
- key: Name
value: s3-vault-demo
providerConfigRef:
name: default" | kubectl apply -f -
```
You can monitor the progress of the bucket provisioning with the following
command:
```console
kubectl get bucket -w
```
{{< /tab >}}
{{< /tabs >}}
<!-- named links -->
[Vault on Minikube]: https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube
[Vault Kubernetes Sidecar]: https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar
[Vault]: https://www.vaultproject.io/
[Vault Kubernetes Sidecar]: https://www.vaultproject.io/docs/platform/k8s/injector
[provider-gcp]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-gcp
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
[AWS]: https://www.vaultproject.io/docs/secrets/aws
[Azure]: https://www.vaultproject.io/docs/secrets/azure
[GCP]: https://www.vaultproject.io/docs/secrets/gcp
[unsealed]: https://www.vaultproject.io/docs/concepts/seal
[Kubernetes authentication backend]: https://www.vaultproject.io/docs/auth/kubernetes
[kv secrets engine]: https://www.vaultproject.io/docs/secrets/kv/kv-v2
[policy]: https://www.vaultproject.io/docs/concepts/policies

View File

@ -11,7 +11,7 @@ Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource determine what resources it should create when you create a composite resource
(XR). Read the (XR). Read the
[concepts](https://docs.crossplane.io/latest/concepts/composition-functions) [concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions. page to learn more about composition functions.
You can write a function to template resources using a general purpose You can write a function to template resources using a general purpose
@ -22,7 +22,7 @@ conditionals. This guide explains how to write a composition function in
{{< hint "important" >}} {{< hint "important" >}}
It helps to be familiar with It helps to be familiar with
[how composition functions work](https://docs.crossplane.io/latest/concepts/composition-functions#how-composition-functions-work) [how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide. before following this guide.
{{< /hint >}} {{< /hint >}}
@ -134,7 +134,7 @@ should delete the `input` and `package/input` directories.
The `input` directory defines a Go struct that a function can use to take input, The `input` directory defines a Go struct that a function can use to take input,
using the `input` field from a Composition. The using the `input` field from a Composition. The
[composition functions](https://docs.crossplane.io/latest/concepts/composition-functions) [composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains how to pass an input to a composition function. documentation explains how to pass an input to a composition function.
The `package/input` directory contains an OpenAPI schema generated from the The `package/input` directory contains an OpenAPI schema generated from the
@ -757,7 +757,7 @@ then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the Crossplane control plane. See the
[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions). [composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane. to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform. Use Docker to build a runtime for each platform.
@ -808,7 +808,7 @@ crossplane xpkg build \
{{<hint "tip">}} {{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the Crossplane packages are special OCI images. Read more about packages in the
[packages documentation](https://docs.crossplane.io/latest/concepts/packages). [packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}} {{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the Push both package files to a registry. Pushing both files to one tag in the

View File

@ -11,7 +11,7 @@ Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource determine what resources it should create when you create a composite resource
(XR). Read the (XR). Read the
[concepts](https://docs.crossplane.io/latest/concepts/composition-functions) [concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions. page to learn more about composition functions.
You can write a function to template resources using a general purpose You can write a function to template resources using a general purpose
@ -22,7 +22,7 @@ conditionals. This guide explains how to write a composition function in
{{< hint "important" >}} {{< hint "important" >}}
It helps to be familiar with It helps to be familiar with
[how composition functions work](https://docs.crossplane.io/latest/concepts/composition-functions#how-composition-functions-work) [how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide. before following this guide.
{{< /hint >}} {{< /hint >}}
@ -132,7 +132,7 @@ The `package/input` directory defines the OpenAPI schema for the a function's
input. The function in this guide doesn't accept an input. Delete the input. The function in this guide doesn't accept an input. Delete the
`package/input` directory. `package/input` directory.
The [composition functions](https://docs.crossplane.io/latest/concepts/composition-functions) The [composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains composition function inputs. documentation explains composition function inputs.
{{<hint "tip">}} {{<hint "tip">}}
@ -656,7 +656,7 @@ then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the Crossplane control plane. See the
[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions). [composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane. to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform. Use Docker to build a runtime for each platform.
@ -715,7 +715,7 @@ crossplane xpkg build \
{{<hint "tip">}} {{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the Crossplane packages are special OCI images. Read more about packages in the
[packages documentation](https://docs.crossplane.io/latest/concepts/packages). [packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}} {{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the Push both package files to a registry. Pushing both files to one tag in the

View File

@ -0,0 +1,34 @@
---
title: Learn
description: Learn more about Crossplane.
---
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!
***Learn more about using Crossplane***
- [Latest Design Docs](https://github.com/crossplane/crossplane/tree/master/design)
- [Roadmap](https://github.com/crossplane/crossplane/blob/master/ROADMAP.md)
- [Crossplane Architecture](https://docs.google.com/document/d/1whncqdUeU2cATGEJhHvzXWC9xdK29Er45NJeoemxebo/edit?usp=sharing)
- [GitLab deploys into multiple clouds from kubectl using Crossplane](https://about.gitlab.com/2019/05/20/gitlab-first-deployed-kubernetes-api-to-multiple-clouds/)
- [CNCF Talks & Community Presentations](https://www.youtube.com/playlist?list=PL510POnNVaaZJj9OG6PbgsZvgYbhwJRyE)
- [Software Engineering Daily - Intro Podcast](https://softwareengineeringdaily.com/2019/01/02/crossplane-multicloud-control-plane-with-bassam-tabbara/)
***Writing Kubernetes controllers to extend Crossplane***
- [Keep the Space Shuttle Flying: Writing Robust Operators](https://www.youtube.com/watch?v=uf97lOApOv8)
- [Best practices for building Kubernetes Operators](https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps)
- [Programming Kubernetes Book](https://www.oreilly.com/library/view/programming-kubernetes/9781492047094/)
- [Contributor Guide](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md)
***Join the growing Crossplane community and get involved!***
- Join our [Community Slack](https://slack.crossplane.io/)!
- Submit an issue on [GitHub](https://github.com/crossplane/crossplane)
- Attend our bi-weekly [Community Meeting](https://github.com/crossplane/crossplane#get-involved)
- Join our bi-weekly live stream: [The Binding Status](https://github.com/crossplane/tbs)
- Subscribe to our [YouTube Channel](https://www.youtube.com/channel/UC19FgzMBMqBro361HbE46Fw)
- Drop us a note on Twitter: [@crossplane_io](https://twitter.com/crossplane_io)
- Email us: [info@crossplane.io](mailto:info@crossplane.io)
<!-- Named links -->
[join-crossplane-slack]: https://slack.crossplane.io
[contact-us]: https://github.com/crossplane/crossplane#contact

View File

@ -0,0 +1,56 @@
---
title: Feature Lifecycle
toc: true
weight: 309
indent: true
---
# Feature Lifecycle
Crossplane follows a similar feature lifecycle to [upstream
Kubernetes][kube-features]. All major new features must be added in alpha. Alpha
features are expected to eventually graduate to beta, and then to general
availability (GA). Features that languish at alpha or beta may be subject to
deprecation.
## Alpha Features
Alpha are off by default, and must be enabled by a feature flag, for example
`--enable-composition-revisions`. API types pertaining to alpha features use a
`vNalphaN` style API version, like `v1alpha`. **Alpha features are subject to
removal or breaking changes without notice**, and generally not considered ready
for use in production.
In some cases alpha features require fields be added to existing beta or GA
API types. In these cases fields must clearly be marked (i.e in their OpenAPI
schema) as alpha and subject to alpha API constraints (or lack thereof).
All alpha features should have an issue tracking their graduation to beta.
## Beta Features
Beta features are on by default, but may be disabled by a feature flag. API
types pertaining to beta features use a `vNbetaN` style API version, like
`v1beta1`. Beta features are considered to be well tested, and will not be
removed completely without being marked deprecated for at least two releases.
The schema and/or semantics of objects may change in incompatible ways in a
subsequent beta or stable release. When this happens, we will provide
instructions for migrating to the next version. This may require deleting,
editing, and re-creating API objects. The editing process may require some
thought. This may require downtime for applications that rely on the feature.
In some cases beta features require fields be added to existing GA API types. In
these cases fields must clearly be marked (i.e in their OpenAPI schema) as beta
and subject to beta API constraints (or lack thereof).
All beta features should have an issue tracking their graduation to GA.
## GA Features
GA features are always enabled - they cannot be disabled. API types pertaining
to GA features use `vN` style API versions, like `v1`. GA features are widely
used and thoroughly tested. They guarantee API stability - only backward
compatible changes are allowed.
[kube-features]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages

View File

@ -0,0 +1,100 @@
---
title: Release Cycle
weight: 308
---
Starting with the v1.10.0 release, Crossplane is released on a quarterly (13
week) cadence. A cycle is comprised of three general stages:
- Weeks 1—11: [Active Development]
- Week 12: [Feature Freeze]
- Week 13: [Code Freeze]
This results in four releases per year, with the most recent three releases
being maintained at any given time. When a new release is cut, the fourth most
recent release reaches end of life (EOL). Users can expect any given release to
be maintained for nine months.
### Definition of maintenance
The Crossplane community defines maintenance in that relevant bug fixes that are
merged to the main development branch will be eligible to be back-ported to the
release branch of any currently maintained version, and patch releases will be
cut appropriately. It's also possible that a fix may be merged directly to the
release branch if no longer applicable on the main development branch.
Maintenance doesn't indicate any SLA on response time for user support in the
form of Slack messages or issues, but problems will be addressed on a best
effort basis by maintainers and contributors for currently maintained releases.
### Patch releases
_This policy is subject to change in the future._
Patch releases are cut for currently maintained minor versions on an as-needed
basis. Any critical back-ported fixes will be included in a patch release as
soon as possible after merge.
### Pre-releases
_This policy is subject to change in the future._
Alpha, Beta, and RC releases are cut for an upcoming release on an as-needed
basis. As a policy, at least one pre-release will be cut prior to any minor
release. Pre-releases won't be made on release branches.
### Provider releases
The Crossplane release cycle isn't required to be adhered to by any other
Crossplane projects, but a similar cadence is encouraged. Maintainers listed in
each repository's `OWNERS.md` file are responsible for determining and
publishing the release cycle for their project.
## Release stages
The following stages are the main milestones in a Crossplane release.
### Active development
During active development, any code that meets the requisite criteria (i.e.
passing appropriate tests, approved by a maintainer, etc.) will be merged into
the main development branch. At present, there is no requirement to formally
submit an enhancement proposal prior to the start of the release cycle, but
contributors are encouraged to open an issue and gather feedback before starting
work on a major implementation (see [CONTRIBUTING.md] for more information).
### Feature freeze
During feature freeze, no new functionality should be merged into the main
development branch. Bug fixes, documentation changes, and non-critical changes
may be made. In the case that a new feature is deemed absolutely necessary for a
release, the Crossplane maintainers will weigh the impact of the change and make
a decision on whether it should be included.
### Code freeze
During code freeze, there should be no changes merged to the main development
branch with the following exceptions:
- Fixes to a failing test that's deemed to be incorrectly testing
functionality.
- Documentation only changes. It's possible that a documentation freeze will be
implemented in the future, but it's not currently enforced.
- Fixes to a critical bug that wasn't previously identified. Merging a bug fix
during code freeze requires application for and approval of an exception by
Crossplane maintainers. This process is currently informal, but may be
formalized in the future.
## Release dates
Crossplane releases once a quarter (every 13 weeks). Typically, the release
happens on the Tuesday of the last week of the quarter, as shown on the
[community calendar][community calendar]. Keep in mind that the specific date is
**approximate**. A lot of factors can alter the date slightly, such as code
reviews, testing, and bug fixing to ensure a quality release.
<!-- Named links -->
[Active Development]: #active-development
[Feature Freeze]: #feature-freeze
[Code Freeze]: #code-freeze
[CONTRIBUTING.md]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
[community calendar]: https://calendar.google.com/calendar/embed?src=c_2cdn0hs9e2m05rrv1233cjoj1k%40group.calendar.google.com

View File

@ -204,4 +204,4 @@ spec:
name: my-claim-secret name: my-claim-secret
``` ```
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).

View File

@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
{{</hint >}} {{</hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Set composite resource defaults ### Set composite resource defaults
XRDs can set default parameters for composite resources and Claims. XRDs can set default parameters for composite resources and Claims.

View File

@ -189,7 +189,7 @@ spec:
### Composition revision policy ### Composition revision policy
Crossplane tracks changes to Compositions as Crossplane tracks changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) . [Composition revisions]({{<ref "composition-revisions">}}) .
A composite resource can use A composite resource can use
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
@ -217,7 +217,7 @@ spec:
### Composition revision selection ### Composition revision selection
Crossplane records changes to Compositions as Crossplane records changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}). [Composition revisions]({{<ref "composition-revisions">}}).
A composite resource can A composite resource can
select a specific Composition revision. select a specific Composition revision.
@ -309,7 +309,7 @@ spec:
``` ```
Composite resources can write connection secrets to an Composite resources can write connection secrets to an
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}), [external secret store]({{<ref "../guides/vault-as-secret-store">}}),
like HashiCorp Vault. like HashiCorp Vault.
{{<hint "important" >}} {{<hint "important" >}}
@ -332,10 +332,10 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
external secret stores. external secret stores.
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Pausing composite resources ### Pausing composite resources

View File

@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
{{<hint "tip">}} {{<hint "tip">}}
Crossplane has a Crossplane has a
[guide to writing a composition function in Go]({{<ref "../../knowledge-base/guides/write-a-composition-function-in-go">}}). [guide to writing a composition function in Go]({{<ref "../guides/write-a-composition-function-in-go">}}).
{{</hint>}} {{</hint>}}
When you're writing a composition function it's useful to know how composition When you're writing a composition function it's useful to know how composition

View File

@ -0,0 +1,444 @@
---
title: Composition Revisions
---
This guide discusses the use of "Composition Revisions" to safely make and roll
back changes to a Crossplane [`Composition`][composition-type]. It assumes
familiarity with Crossplane, and particularly with
[Compositions].
A `Composition` configures how Crossplane should reconcile a Composite Resource
(XR). Put otherwise, when you create an XR the selected `Composition` determines
what managed resources Crossplane will create in response. Let's say for example
that you define a `PlatformDB` XR, which represents your organisation's common
database configuration of an Azure MySQL Server and a few firewall rules. The
`Composition` contains the 'base' configuration for the MySQL server and the
firewall rules that is extended by the configuration for the `PlatformDB`.
There is a one-to-many relationship between a `Composition` and the XRs that use
it. You might define a `Composition` named `big-platform-db` that is used by ten
different `PlatformDB` XRs. Usually, in the interest of self-service, the
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
For example the `Composition` may be written and maintained by a platform team
member, while individual application teams create `PlatformDB` XRs that use said
`Composition`.
Each `Composition` is mutable - you can update it as your organisation's needs
change. However, without Composition Revisions updating a `Composition` can be a
risky process. Crossplane constantly uses the `Composition` to ensure that your
actual infrastructure - your MySQL Servers and firewall rules - match your
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
`Composition`, all 10 of those XRs will be instantly updated in accordance with
any updates you make to the `big-platform-db` `Composition`.
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
update your XRs to leverage the latest `Composition` settings at your own pace.
This enables you to [canary] changes to your infrastructure, or to roll back
some XRs to previous `Composition` settings without rolling back all XRs.
## Using Composition Revisions
When you enable Composition Revisions three things happen:
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
which `CompositionRevision` they use.
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
specifies how they should be updated to new Composition Revisions.
Each time you edit a `Composition` Crossplane will automatically create a
`CompositionRevision` that represents that 'revision' of the `Composition` -
that unique state. Each revision is allocated an increasing revision number.
This gives `CompositionRevision` consumers an idea about which revision is
'newest'.
Crossplane distinguishes between the 'newest' and the 'current' revision of a
`Composition`. That is, if you revert a `Composition` to a previous state that
corresponds to an existing `CompositionRevision` that revision will become
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
`Composition` configuration).
You can discover which revisions exist using `kubectl`:
```console
# Find all revisions of the Composition named 'example'
kubectl get compositionrevision -l crossplane.io/composition-name=example
```
This should produce output something like:
```console
NAME REVISION CURRENT AGE
example-18pdg 1 False 4m36s
example-2bgdr 2 True 73s
example-xjrdm 3 False 61s
```
> A `Composition` is a mutable resource that you can update as your needs
> change over time. Each `CompositionRevision` is an immutable snapshot of those
> needs at a particular point in time.
Crossplane behaves the same way by default whether Composition Revisions are
enabled or not. This is because when you enable Composition Revisions all XRs
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
policies:
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
* `Manual`: Require manual intervention to change `CompositionRevision`.
The below XR uses the `Manual` policy. When this policy is used the XR will
select the current `CompositionRevision` when it is first created, but must
manually be updated when you wish it to use another `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
# The Manual policy specifies that you do not want this XR to update to the
# current CompositionRevision automatically.
compositionUpdatePolicy: Manual
compositionRef:
name: example
writeConnectionSecretToRef:
name: db-conn
```
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
policy you must edit the `compositionRevisionRef` field when you want your XR to
use a different `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
compositionUpdatePolicy: Manual
compositionRef:
name: example
# Update the referenced CompositionRevision if and when you are ready.
compositionRevisionRef:
name: example-18pdg
writeConnectionSecretToRef:
name: db-conn
```
## Complete example
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
assign different CompositionRevisions to the created composite resources each time the composition is updated.
### Preparation
##### Install Crossplane
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
```shell
kubectl create namespace crossplane-system
helm repo add crossplane-master https://charts.crossplane.io/master/
helm repo update
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
kubectl get pods -n crossplane-system
```
Expected Output:
```shell
NAME READY STATUS RESTARTS AGE
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
```
#### Deploy Composition and XRD Examples
Apply the example Composition.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: true
enableDnsHostnames: true
name: my-vcp
```
Apply the example XRD.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: myvpcs.aws.example.upbound.io
spec:
group: aws.example.upbound.io
names:
kind: MyVPC
plural: myvpcs
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
id:
type: string
description: ID of this VPC that other objects will use to refer to it.
required:
- id
```
Verify that Crossplane created the Composition revision
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
{{< hint "note" >}}
The label `dev` is automatically created from the Composition.
{{< /hint >}}
### Create Composite Resources
This tutorial has four composite resources to cover different update policies and composition selection options.
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
#### Default update policy
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-auto
spec:
id: vpc-auto
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-auto created
```
#### Manual update policy
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-man
spec:
id: vpc-man
compositionUpdatePolicy: Manual
compositionRevisionRef:
name: myvpcs.aws.example.upbound.io-ad265bc
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-man created
```
#### Using a selector
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-dev
spec:
id: vpc-dev
compositionRevisionSelector:
matchLabels:
channel: dev
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-dev created
```
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-staging
spec:
id: vpc-staging
compositionRevisionSelector:
matchLabels:
channel: staging
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-staging created
```
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
All other XRs have a `REVISION` matching the created Composition Revision.
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging False <none> Automatic map[channel:staging]
```
{{< hint "note" >}}
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
{{< /hint >}}
### Create new Composition revisions
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
also trigger a new CompositionRevision.
#### Update the Composition label
Update the `Composition` label to `channel: staging`:
```shell
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-auto` always use the latest Revision.
`vpc-staging` now matches the label applied to Revision revision:2.
{{< /hint >}}
#### Update Composition Spec and Label
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
Apply the following changes to update the `Composition` spec and label:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: false
enableDnsHostnames: true
name: my-vcp
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
myvpcs.aws.example.upbound.io-f81c553 3 dev
```
{{< hint "note" >}}
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
{{< /hint >}}
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-dev` matches the updated label applied to Revision revision:3.
`vpc-staging` matches the label applied to Revision revision:2.
{{< /hint >}}
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
[install-guide]: {{<ref "../../master/software/install" >}}

View File

@ -748,7 +748,7 @@ details.
This section discusses creating Kubernetes secrets. This section discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store. with an external secret store.
{{</hint >}} {{</hint >}}
@ -958,7 +958,7 @@ for more information on restricting secret keys.
{{< /hint >}} {{< /hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
{{<hint "warning">}} {{<hint "warning">}}
You can't change the You can't change the
@ -973,7 +973,7 @@ recreate the Composition to change the
#### Save connection details to an external secret store #### Save connection details to an external secret store
Crossplane Crossplane
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
write secrets and connection details to external secret stores like HashiCorp write secrets and connection details to external secret stores like HashiCorp
Vault. Vault.
@ -1018,7 +1018,7 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
integration guide. integration guide.
### Resource readiness checks ### Resource readiness checks

View File

@ -0,0 +1,607 @@
---
title: Understanding Connection Details
weight: 11
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
---
Using connection details in Crossplane requires the following components:
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
* Optionally, define the `connectionSecretKeys` in a
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
{{<hint "note">}}
This guide discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store.
{{</hint >}}
## Background
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
resource, the resource may generate resource-specific details. These details can include
usernames, passwords or connection details like an IP address.
Crossplane refers to this information as the _connection details_ or
_connection secrets_.
The Provider
defines what information to present as a _connection
detail_ from a managed resource.
<!-- vale gitlab.SentenceLength = NO -->
<!-- wordy because of type names -->
When a managed resource is part of a
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
and optionally, the
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
and where they're stored.
<!-- vale gitlab.SentenceLength = YES -->
{{<hint "note">}}
All the following examples use the same set of Compositions,
CompositeResourceDefinitions and Claims.
All examples rely on
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
to create resources.
{{<expand "Reference Composition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xsecrettest.example.org
spec:
writeConnectionSecretsToNamespace: other-namespace
compositeTypeRef:
apiVersion: example.org/v1alpha1
kind: XSecretTest
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchControllerRef: true
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret1"
- name: user
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
spec:
forProvider: {}
- name: user2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
metadata:
labels:
docs.crossplane.io: user
spec:
forProvider: {}
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
- name: key2-password
fromConnectionSecretKey: password
- name: key2-secret
fromConnectionSecretKey: attribute.secret
- name: key2-smtp
fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret2"
```
{{</expand >}}
{{<expand "Reference CompositeResourceDefinition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xsecrettests.example.org
spec:
group: example.org
connectionSecretKeys:
- username
- password
- attribute.secret
- attribute.ses_smtp_password_v4
- key2-user
- key2-pass
- key2-secret
- key2-smtp
names:
kind: XSecretTest
plural: xsecrettests
claimNames:
kind: SecretTest
plural: secrettests
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
```
{{</ expand >}}
{{<expand "Reference Claim" >}}
```yaml
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
{{</expand >}}
{{</hint >}}
## Connection secrets in a managed resource
<!-- vale gitlab.Substitutions = NO -->
<!-- vale gitlab.SentenceLength = NO -->
<!-- under 25 words -->
When a managed resource creates connection secrets, Crossplane can write the
secrets to a
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
or an
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
<!-- vale gitlab.SentenceLength = YES -->
<!-- vale gitlab.Substitutions = YES -->
Creating an individual managed resource shows the connection secrets the
resource creates.
{{<hint "note" >}}
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
documentation for more information on configuring resources and storing
connection secrets for individual resources.
{{< /hint >}}
For example, create an
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
connection secrets in a Kubernetes secret named
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
in the
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
```yaml {label="mr"}
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
metadata:
name: test-accesskey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: default
name: my-accesskey-secret
```
View the Kubernetes secret to see the connection details from the managed
resource.
This includes an
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
```yaml {label="mrSecret",copy-lines="1"}
kubectl describe secret my-accesskey-secret
Name: my-accesskey-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: connection.crossplane.io/v1alpha1
Data
====
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
username: 20 bytes
```
Compositions and CompositeResourceDefinitions require the exact names of the
secrets generated by a resource.
## Connection secrets in Compositions
Resources in a Composition that create connection details still create a
secret object containing their connection details.
Crossplane also generates
another secret object for each composite resource,
containing the secrets from all the defined resources.
For example, a Composition defines two
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
objects.
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
the resource
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane also creates a secret object for the entire Composition
saved in the namespace defined by
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
with a Crossplane generated name.
```yaml {label="comp1",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key1
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1-secret
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2-secret
# Removed for brevity
```
After applying a Claim, view the Kubernetes secrets to see three secret objects
created.
The secret
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="6">}}key1{{</hover>}},
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
Crossplane creates another secret in the namespace
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
secrets from resource in the Composition.
```shell {label="compGetSec",copy-lines="1"}
kubectl get secrets -A
NAMESPACE NAME TYPE DATA AGE
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
```
Although Crossplane creates a secret object, by default, Crossplane doesn't add
any data to the object.
```yaml {copy-lines="none"}
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
```
The Composition must list the connection secrets to store for each resource.
Use the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
each resource and define the secret keys the resource creates.
{{<hint "warning">}}
You can't change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
of a Composition.
You must delete and
recreate the Composition to change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
{{</hint >}}
```yaml {label="comp2",copy-lines="16-20"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
# Removed for brevity
```
After applying a Claim the composite resource secret object contains the list of
keys listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
```shell {copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
```
{{<hint "important">}}
If a key isn't listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
it isn't stored in the secret object.
{{< /hint >}}
### Managing conflicting secret keys
If resources produce conflicting keys, create a unique name with a connection
details
{{<hover label="comp3" line="25">}}name{{</hover>}}.
```yaml {label="comp3",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
The secret object contains both keys,
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
and
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
```shell {label="comp3Sec",copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
key2-user: 20 bytes
# Removed for brevity.
```
## Connection secrets in Composite Resource Definitions
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
put in the combined secret and provided to a Claim.
By default an XRD writes all secret keys listed in the composed resource
`connectionDetails` to the combined secret object.
Limit the keys passed to the combined secret object and Claims with a
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
the secret key names to create. Crossplane only adds the keys listed to the
combined secret.
{{<hint "warning">}}
You can't change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
You must delete and
recreate the XRD to change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
{{</hint >}}
For example, an XRD may restrict the secrets to only the
{{<hover label="xrd" line="5">}}username{{</hover>}},
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
```yaml {label="xrd",copy-lines="4-12"}
kind: CompositeResourceDefinition
spec:
# Removed for brevity.
connectionSecretKeys:
- username
- password
- key2-user
```
The secret from an individual resource contains all the resources detailed in
the Composition's `connectionDetails`.
```shell {label="xrdSec",copy-lines="1"}
kubectl describe secret key1 -n docs
Name: key1
Namespace: docs
Data
====
password: 40 bytes
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
```
The Claim's secret only contains the
keys allowed by the XRD
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
fields.
```shell {label="xrdSec2",copy-lines="2"}
kubectl describe secret my-access-key-secret
Name: my-access-key-secret
Data
====
key2-user: 20 bytes
password: 40 bytes
username: 20 bytes
```
## Secret objects
Compositions create a secret object for each resource and an extra secret
containing all the secrets from all resources.
Crossplane saves the resource secret objects in the location defined by the
resource's
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane saves the combined secret with a Crossplane generated name in the
namespace defined in the Composition's
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
```yaml {label="comp4",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
If a Claim uses a secret, it's stored in the same namespace as the Claim with
the name defined in the Claim's
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
```yaml {label="claim3",copy-lines="none"}
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
After applying the Claim Crossplane creates the following secrets:
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
* The composite resource secret object in the
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
Composition's `writeConnectionSecretsToNamespace`.
```shell {label="allSec",copy-lines="none"}
kubectl get secret -A
NAMESPACE NAME TYPE DATA AGE
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
docs key1 connection.crossplane.io/v1alpha1 4 31m
docs key2 connection.crossplane.io/v1alpha1 4 31m
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
```

View File

@ -357,7 +357,7 @@ Crossplane supports the following policies:
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. | | `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. | | `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. | | `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| `Update` | Crossplane changes the external resource when changing the managed resource. | | `Update` | Crossplane changes the external resource when changing the managed resource. |
{{</table >}} {{</table >}}
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | | {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
{{< /table >}} {{< /table >}}
@ -568,7 +568,7 @@ metadata:
{{<hint "tip" >}} {{<hint "tip" >}}
Read the Read the
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}}) [Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
guide for details on using StoreConfig objects. guide for details on using StoreConfig objects.
{{< /hint >}} {{< /hint >}}

View File

@ -395,7 +395,7 @@ If you remove the Provider first, you must manually delete external resources
through your cloud provider. Managed resources must be manually deleted by through your cloud provider. Managed resources must be manually deleted by
removing their finalizers. removing their finalizers.
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}). For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
{{< /hint >}} {{< /hint >}}
## Verify a Provider ## Verify a Provider
@ -584,12 +584,12 @@ replacement for Controller configuration and is available in v1.14+.
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
the Provider's pod. The the Provider's pod. The
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1) [Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
defines the supported set of ControllerConfig settings. defines the supported set of ControllerConfig settings.
The most common use case for ControllerConfigs are providing `args` to a The most common use case for ControllerConfigs are providing `args` to a
Provider's pod enabling optional services. For example, enabling Provider's pod enabling optional services. For example, enabling
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider) [external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
for a Provider. for a Provider.
Each Provider determines their supported set of `args`. Each Provider determines their supported set of `args`.

View File

@ -0,0 +1,5 @@
---
title: Guides
weight: 400
description: Crossplane integrations and detailed examples.
---

View File

@ -0,0 +1,216 @@
---
title: Configuring Crossplane with Argo CD
weight: 270
---
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
cluster into a Universal Control Plane for all of your resources. There are
configuration details required in order for the two to work together properly.
This doc will help you understand these requirements. It is recommended to use
Argo CD version 2.4.8 or later with Crossplane.
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
### Configuring Argo CD with Crossplane
#### Set Resource Tracking Method
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
to be configured to use the annotation mechanism.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
```
#### Set Health Status
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
of Provider's CRDs.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
{{<hint "note">}}
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
{{</hint>}}
```yaml {label="argocfg"}
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
resource.customizations: |
"*.upbound.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Ready" then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
"*.crossplane.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"Composition",
"CompositionRevision",
"DeploymentRuntimeConfig",
"ControllerConfig",
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
```
#### Set Resource Exclusion
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
resource.exclusions: |
- apiGroups:
- "*"
kinds:
- ProviderConfigUsage
```
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
#### Increase K8s Client QPS
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.

View File

@ -0,0 +1,10 @@
---
title: Disaster Recovery with Crossplane
weight: 10
---
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
using Crossplane to provision resources and Velero for Kubernetes backup and
recovery.
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).

View File

@ -0,0 +1,285 @@
---
title: Import Existing Resources
weight: 200
---
If you have resources that are already provisioned in a Provider,
you can import them as managed resources and let Crossplane manage them.
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
field enables importing external resources into Crossplane.
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
or [automatically]({{<ref "#import-resources-automatically">}}).
## Import resources manually
Crossplane can discover and import existing Provider resources by matching the
`crossplane.io/external-name` annotation in a managed resource.
To import an existing external resource in a Provider, create a new managed
resource with the `crossplane.io/external-name` annotation. Set the annotation
value to the name of the resource in the Provider.
For example, to import an existing GCP Network named
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
create a new managed resource and use the
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
annotation.
```yaml {label="annotation",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
annotations:
crossplane.io/external-name: my-existing-network
```
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
field can be anything you want. For example,
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
{{< hint "note" >}}
This name is the
name of the Kubernetes object. It's not related to the resource name inside the
Provider.
{{< /hint >}}
```yaml {label="name",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
```
Leave the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
Crossplane imports the settings and automatically applies them to the managed
resource.
{{< hint "important" >}}
If the managed resource has _required_ fields in the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
the `forProvider` field.
The values of those fields must match what's inside the Provider or Crossplane
overwrites the existing values.
{{< /hint >}}
```yaml {label="fp",copy-lines="all"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
spec:
forProvider: {}
```
Crossplane now controls and manages this imported resource. Any changes to the
managed resource `spec` changes the external resource.
## Import resources automatically
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
Crossplane imports observe only resources but never changes or deletes the
resources.
{{<hint "important" >}}
The managed resource `managementPolicies` option is a beta feature.
The Provider determines support for management policies.
Refer to the Provider's documentation to see if the Provider supports
management policies.
{{< /hint >}}
<!-- vale off -->
### Apply the Observe management policy
<!-- vale on -->
Create a new managed resource matching the
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
to import and add
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
For example, to import a GCP SQL DatabaseInstance, create a new resource with
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
set.
```yaml {label="oo-policy",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
spec:
managementPolicies: ["Observe"]
```
### Add the external-name annotation
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation for the resource. This name must match the name inside the Provider.
For example, for a GCP database named
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
the
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation with the value
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
```yaml {label="oo-ex-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Create a Kubernetes object name
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
Kubernetes object.
For example, name the Kubernetes object
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
```yaml {label="oo-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Identify a specific external resource
If more than one resource inside the Provider shares the same name, identify the
specific resource with a unique
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
For example, only import the GCP SQL database in the
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
```yaml {label="oo-region"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: "us-central1"
```
### Apply the managed resource
Apply the new managed resource. Crossplane syncs the status of the external
resource in the cloud with the newly created managed resource.
### View the discovered resource
Crossplane discovers the managed resource and populates the
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
fields with the values from the external resource.
```yaml {label="ooPopulated",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: us-central1
status:
atProvider:
connectionName: crossplane-playground:us-central1:my-external-database
databaseVersion: POSTGRES_14
deletionProtection: true
firstIpAddress: 35.184.74.79
id: my-external-database
publicIpAddress: 35.184.74.79
region: us-central1
# Removed for brevity
settings:
- activationPolicy: ALWAYS
availabilityType: REGIONAL
diskSize: 100
# Removed for brevity
pricingPlan: PER_USE
tier: db-custom-4-26624
version: 4
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
<!-- vale off -->
## Control imported ObserveOnly resources
<!-- vale on -->
Crossplane can take active control of observe only imported resources by
changing the `managementPolicies` after import.
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
of the managed resource to
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
Copy any required parameter values from
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
{{< hint "tip" >}}
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
{{< /hint >}}
```yaml {label="fc"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["*"]
forProvider:
databaseVersion: POSTGRES_14
region: us-central1
settings:
- diskSize: 100
tier: db-custom-4-26624
status:
atProvider:
databaseVersion: POSTGRES_14
region: us-central1
# Removed for brevity
settings:
- diskSize: 100
tier: db-custom-4-26624
# Removed for brevity
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T11:16:45Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
Crossplane now fully manages the imported resource. Crossplane applies any
changes to the managed resource in the Provider's external resource.

View File

@ -0,0 +1,323 @@
---
title: Multi-Tenant Crossplane
weight: 240
---
This guide describes how to use Crossplane effectively in multi-tenant
environments by utilizing Kubernetes primitives and compatible policy
enforcement projects in the cloud-native ecosystem.
## TL;DR
Infrastructure operators in multi-tenant Crossplane environments typically
utilize composition and Kubernetes RBAC to define lightweight, standardized
policies that dictate what level of self-service developers are given when
requesting infrastructure. This is primarily achieved through exposing abstract
resource types at the namespace scope, defining `Roles` for teams and
individuals within that namespace, and patching the `spec.providerConfigRef` of
the underlying managed resources so that they use a specific `ProviderConfig`
and credentials when provisioned from each namespace. Larger organizations, or
those with more complex environments, may choose to incorporate third-party
policy engines, or scale to multiple Crossplane clusters. The following sections
describe each of these scenarios in greater detail.
- [TL;DR](#tldr)
- [Background](#background)
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
- [Namespace Scoped Claims](#namespace-scoped-claims)
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
- [Control Plane of Control Planes](#control-plane-of-control-planes)
## Background
Crossplane is designed to run in multi-tenant environments where many teams are
consuming the services and abstractions provided by infrastructure operators in
the cluster. This functionality is facilitated by two major design patterns in
the Crossplane ecosystem.
### Cluster-Scoped Managed Resources
Typically, Crossplane providers, which supply granular [managed resources] that
reflect an external API, authenticate by using a `ProviderConfig` object that
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
filesystem, or an environment variable). Then, every managed resource references
a `ProviderConfig` that points to credentials with sufficient permissions to
manage that resource type.
For example, the following `ProviderConfig` for `provider-aws` points to a
Kubernetes `Secret` with AWS credentials.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: cool-aws-creds
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: creds
```
If a user desired for these credentials to be used to provision an
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
```yaml
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
name: rdsmysql
spec:
forProvider:
region: us-east-1
dbInstanceClass: db.t3.medium
masterUsername: masteruser
allocatedStorage: 20
engine: mysql
engineVersion: "5.6.35"
skipFinalSnapshotBeforeDeletion: true
providerConfigRef:
name: cool-aws-creds # name of ProviderConfig above
writeConnectionSecretToRef:
namespace: crossplane-system
name: aws-rdsmysql-conn
```
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
the RDS controller in `provider-aws` will resolve this reference by fetching the
`ProviderConfig`, obtaining the credentials it points to, and using those
credentials to reconcile the `RDSInstance`. This means that anyone who has been
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
In practice, Crossplane assumes that only folks acting as infrastructure
administrators or platform builders will interact directly with cluster-scoped
resources.
### Namespace Scoped Claims
While managed resources exist at the cluster scope, composite resources, which
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
the cluster or namespace scope. Platform builders define XRDs and
**Compositions** that specify what granular managed resources should be created
in response to the creation of an instance of the XRD. More information about
this architecture can be found in the [Composition] documentation.
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
defined will have a namespace-scoped variant.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xmysqlinstances.example.org
spec:
group: example.org
names:
kind: XMySQLInstance
plural: xmysqlinstances
claimNames:
kind: MySQLInstance
plural: mysqlinstances
...
```
When the example above is created, Crossplane will produce two
[CustomResourceDefinitions]:
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
**Composite Resource (XR)**.
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
**Claim (XRC)**.
Platform builders may choose to define an arbitrary number of Compositions that
map to these types, meaning that creating a `MySQLInstance` in a given namespace
can result in the creations of any set of managed resources at the cluster
scope. For instance, creating a `MySQLInstance` could result in the creation of
the `RDSInstance` defined above.
## Single Cluster Multi-Tenancy
Depending on the size and scope of an organization, platform teams may choose to
run one central Crossplane control plane, or many different ones for each team
or business unit. This section will focus on servicing multiple teams within a
single cluster, which may or may not be one of many other Crossplane clusters in
the organization.
### Composition as an Isolation Mechanism
While managed resources always reflect every field that the underlying provider
API exposes, XRDs can have any schema that a platform builder chooses. The
fields in the XRD schema can then be patched onto fields in the underlying
managed resource defined in a Composition, essentially exposing those fields as
configurable to the consumer of the XR or XRC.
This feature serves as a lightweight policy mechanism by only giving the
consumer the ability to customize the underlying resources to the extent the
platform builder desires. For instance, in the examples above, a platform
builder may choose to define a `spec.location` field in the schema of the
`XMySQLInstance` that is an enum with options `east` and `west`. In the
Composition, those fields could map to the `RDSInstance` `spec.region` field,
making the value either `us-east-1` or `us-west-1`. If no other patches were
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
create a very specifically configured `RDSInstance`, where they can only decide
the region where it lives and they are restricted to two options.
This model is in contrast to many infrastructure as code tools where the end
user must have provider credentials to create the underlying resources that are
rendered from the abstraction. Crossplane takes a different approach, defining
various credentials in the cluster (using the `ProviderConfig`), then giving
only the provider controllers the ability to utilize those credentials and
provision infrastructure on the users behalf. This creates a consistent
permission model, even when using many providers with differing IAM models, by
standardizing on Kubernetes RBAC.
### Namespaces as an Isolation Mechanism
While the ability to define abstract schemas and patches to concrete resource
types using composition is powerful, the ability to define Claim types at the
namespace scope enhances the functionality further by enabling RBAC to be
applied with namespace restrictions. Most users in a cluster do not have access
to cluster-scoped resources as they are considered only relevant to
infrastructure admins by both Kubernetes and Crossplane.
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
builder may choose to define permissions on `MySQLInstance` at the namespace
scope using a `Role`. This allows for giving users the ability to create and
manage `MySQLInstances` in their given namespace, but not the ability to see
those defined in other namespaces.
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
be utilized to configure managed resources based on the namespace in which the
corresponding XRC was defined. This is especially useful if a platform builder
wants to designate specific credentials or a set of credentials that users in a
given namespace can utilize when provisioning infrastructure using an XRC. This
can be accomplished today by creating one or more `ProviderConfig` objects that
include the name of the namespace in the `ProviderConfig` name. For example, if
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
credentials when the provider controller creates the underlying `RDSInstance`,
the platform builder could:
1. Define a `ProviderConfig` with name `team-1`.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: team-1
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: team-1-creds
key: creds
```
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
to the `providerConfigRef` of the `RDSInstance`.
```yaml
...
resources:
- base:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
spec:
forProvider:
...
patches:
- fromFieldPath: spec.claimRef.namespace
toFieldPath: spec.providerConfigRef.name
policy:
fromFieldPath: Required
```
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
namespace the corresponding `MySQLInstance` was created in.
> Note that this model currently only allows for a single `ProviderConfig` per
> namespace. However, future Crossplane releases should allow for defining a set
> of `ProviderConfig` that can be selected from using [Multiple Source Field
> patching].
### Policy Enforcement with Open Policy Agent
In some Crossplane deployment models, only using composition and RBAC to define
policy will not be flexible enough. However, because Crossplane brings
management of external infrastructure to the Kubernetes API, it is well suited
to integrate with other projects in the cloud-native ecosystem. Organizations
and individuals that need a more robust policy engine, or just prefer a more
general language for defining policy, often turn to [Open Policy Agent] (OPA).
OPA allows platform builders to write custom logic in [Rego], a domain-specific
language. Writing policy in this manner allows for not only incorporating the
information available in the specific resource being evaluated, but also using
other state represented in the cluster. Crossplane users typically install OPA's
[Gatekeeper] to make policy management as streamlined as possible.
> A live demo of using OPA with Crossplane can be viewed [here].
## Multi-Cluster Multi-Tenancy
Organizations that deploy Crossplane across many clusters typically take
advantage of two major features that make managing multiple control planes much
simpler.
### Reproducible Platforms with Configuration Packages
[Configuration packages] allow platform builders to package their XRDs and
Compositions into [OCI images] that can be distributed via any OCI-compliant
image registry. These packages can also declare dependencies on providers,
meaning that a single package can declare all of the granular managed resources,
the controllers that must be deployed to reconcile them, and the abstract types
that expose the underlying resources using composition.
Organizations with many Crossplane deployments utilize Configuration packages to
reproduce their platform in each cluster. This can be as simple as installing
Crossplane with the flag to automatically install a Configuration package
alongside it.
```
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
```
### Control Plane of Control Planes
Taking the multi-cluster multi-tenancy model one step further, some
organizations opt to manage their many Crossplane clusters using a single
central Crossplane control plane. This requires setting up the central cluster,
then using a provider to spin up new clusters (such as an [EKS Cluster] using
[provider-aws]), then using [provider-helm] to install Crossplane into the new
remote cluster, potentially bundling a common Configuration package into each
install using the method described above.
This advanced pattern allows for full management of Crossplane clusters using
Crossplane itself, and when done properly, is a scalable solution to providing
dedicated control planes to many tenants within a single organization.
<!-- Named Links -->
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
[Composition]: {{<ref "../../master/concepts/compositions" >}}
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
[Open Policy Agent]: https://www.openpolicyagent.org/
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
[here]: https://youtu.be/TaF0_syejXc
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
[OCI images]: https://github.com/opencontainers/image-spec
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
[Cloudfoundry]: https://www.cloudfoundry.org/
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo

View File

@ -0,0 +1,49 @@
---
title: Self-Signed CA Certs
weight: 270
---
> Using self-signed certificates is not advised in production, it is
recommended to only use self-signed certificates for testing.
When Crossplane loads Configuration and Provider Packages from private
registries, it must be configured to trust the CA and Intermediate certs.
Crossplane needs to be installed via the Helm chart with the
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
## Configure
1. Create a CA Bundle (A file containing your Root and Intermediate
certificates in a specific order). This can be done with any text editor or
from the command line, so long as the resulting file contains all required crt
files in the proper order. In many cases, this will be either a single
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
order of the crt files should be from lowest to highest in signing order.
For example, if you have a chain of two certificates below your Root
certificate, you place the bottom level Intermediate cert at the beginning of
the file, then the Intermediate cert that singed that cert, then the Root cert
that signed that cert.
2. Save the files as `[yourdomain].ca-bundle`.
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
```
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
--from-file=ca-bundle=./[yourdomain].ca-bundle
```
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
`ca-bundle`.
> Providing Helm with parameter values is convered in the Helm docs,
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
in an `override.yaml` file would look like this:
```
registryCaBundleConfig:
name: ca-bundle-config
key: ca-bundle
```

View File

@ -0,0 +1,443 @@
---
title: Troubleshoot
weight: 306
---
## Requested Resource Not Found
If you use the Crossplane CLI to install a `Provider` or
`Configuration` (e.g. `crossplane install provider
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
could not find the requested resource` error, more often than not, that is an
indicator that the Crossplane CLI you're using is outdated. In other words
some Crossplane API has been graduated from alpha to beta or stable and the old
plugin is not aware of this change.
## Resource Status and Conditions
Most Crossplane resources have a `status` section that can represent the current
state of that particular resource. Running `kubectl describe` against a
Crossplane resource will frequently give insightful information about its
condition. For example, to determine the status of a GCP `CloudSQLInstance`
managed resource use `kubectl describe` for the resource.
```shell {copy-lines="1"}
kubectl describe cloudsqlinstance my-db
Status:
Conditions:
Last Transition Time: 2019-09-16T13:46:42Z
Reason: Creating
Status: False
Type: Ready
```
Most Crossplane resources set the `Ready` condition. `Ready` represents the
availability of the resource - whether it is creating, deleting, available,
unavailable, binding, etc.
## Resource Events
Most Crossplane resources emit _events_ when something interesting happens. You
can see the events associated with a resource by running `kubectl describe` -
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
particular namespace by running `kubectl get events`.
```console
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
```
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
> are cluster scoped. Crossplane emits events for cluster scoped resources to
> the 'default' namespace.
## Crossplane Logs
The next place to look to get more information or investigate a failure would be
in the Crossplane pod logs, which should be running in the `crossplane-system`
namespace. To get the current Crossplane logs, run the following:
```shell
kubectl -n crossplane-system logs -lapp=crossplane
```
> Note that Crossplane emits few logs by default - events are typically the best
> place to look for information about what Crossplane is doing. You may need to
> restart Crossplane with the `--debug` flag if you can't find what you're
> looking for.
## Provider Logs
Remember that much of Crossplane's functionality is provided by providers. You
can use `kubectl logs` to view provider logs too. By convention, they also emit
few logs by default.
```shell
kubectl -n crossplane-system logs <name-of-provider-pod>
```
All providers maintained by the Crossplane community mirror Crossplane's support
of the `--debug` flag. The easiest way to set flags on a provider is to create a
`ControllerConfig` and reference it from the `Provider`:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: debug-config
spec:
args:
- --debug
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: debug-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Compositions and composite resource definition
### General troubleshooting steps
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
2. If there are errors in the events, address them.
3. If there are no errors, follow its sub-resources.
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
4. Repeat this process for each resource returned.
{{< hint "note" >}}
The rest of this section show you how to debug issues related to compositions without using external tooling.
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
{{< /hint >}}
### Examples
#### Composition
<!-- vale Google.WordList = NO -->
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
The example application never reaches available state as shown below.
1. View the claim.
```bash
kubectl describe exampleapp example-application
Status:
Conditions:
Last Transition Time: 2022-03-01T22:57:38Z
Reason: Composite resource claim is waiting for composite resource to become Ready
Status: False
Type: Ready
Events: <none>
```
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
```bash
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XExampleApp",
"name": "example-application-xqlsz"
}
```
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
4. View the cluster scoped resource's events.
```bash
kubectl describe xexampleapp example-application-xqlsz
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
```
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
```bash
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
[
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XDynamoDBTable",
"name": "example-application-xqlsz-6j9nm"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-lp9wt"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-btwkn"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "IRSA"
}
]
```
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
#### Composite resource definition
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
1. Get the XRD
```bash
kubectl get xrd testing.awsblueprints.io
NAME ESTABLISHED OFFERED AGE
testing.awsblueprints.io 66s
```
2. Notice its status it not established. You describe this XRD to get its events.
```bash
kubectl describe xrd testing.awsblueprints.io
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
```
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
#### Providers
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
documentation]({{<ref "/master/concepts/packages">}})
for more information about Crossplane Packages.
If you are experiencing provider issues, steps below are a good starting point.
1. Check the status of provider object.
```bash
kubectl describe provider.pkg.crossplane.io provider-aws
Status:
Conditions:
Last Transition Time: 2022-08-04T16:19:44Z
Reason: HealthyPackageRevision
Status: True
Type: Healthy
Last Transition Time: 2022-08-04T16:14:29Z
Reason: ActivePackageRevision
Status: True
Type: Installed
Current Identifier: crossplane/provider-aws:v0.29.0
Current Revision: provider-aws-a2e16ca2fc1a
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
```
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
```bash
kubectl get providerrevision provider-aws-a2e16ca2fc1a
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
```
When you describe the object, you find all CRDs managed by this object.
```bash
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
Status:
Controller Ref:
Name: provider-aws-a2e16ca2fc1a
Object Refs:
API Version: apiextensions.k8s.io/v1
Kind: CustomResourceDefinition
Name: natgateways.ec2.aws.crossplane.io
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
```
The event field also indicates any issues that may have occurred during this process.
<!-- vale Google.WordList = YES -->
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
```bash
kubectl get deployment -n crossplane-system
NAME READY UP-TO-DATE AVAILABLE AGE
crossplane 1/1 1 1 105d
crossplane-rbac-manager 1/1 1 1 105d
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
kubectl get pods -n crossplane-system
NAME READY STATUS RESTARTS AGE
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
```
If there are any pods failing, check its logs and remedy the problem.
## Pausing Crossplane
Sometimes, for example when you encounter a bug, it can be useful to pause
Crossplane if you want to stop it from actively attempting to manage your
resources. To pause Crossplane without deleting all of its resources, run the
following command to simply scale down its deployment:
```bash
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
```
Once you have been able to rectify the problem or smooth things out, you can
unpause Crossplane simply by scaling its deployment back up:
```bash
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
```
## Pausing Providers
Providers can also be paused when troubleshooting an issue or orchestrating a
complex migration of resources. Creating and referencing a `ControllerConfig` is
the easiest way to scale down a provider, and the `ControllerConfig` can be
modified or the reference can be removed to scale it back up:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: scale-config
spec:
replicas: 0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: scale-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Deleting When a Resource Hangs
The resources that Crossplane manages will automatically be cleaned up so as not
to leave anything running behind. This is accomplished by using finalizers, but
in certain scenarios the finalizer can prevent the Kubernetes object from
getting deleted.
To deal with this, we essentially want to patch the object to remove its
finalizer, which will then allow it to be deleted completely. Note that this
won't necessarily delete the external resource that Crossplane was managing, so
you will want to go to your cloud provider's console and look there for any
lingering resources to clean up.
In general, a finalizer can be removed from an object with this command:
```shell
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
```
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
`my-db`, you can remove its finalizer with:
```shell
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
```
## Tips, Tricks, and Troubleshooting
In this section we'll cover some common tips, tricks, and troubleshooting steps
for working with Composite Resources. If you're trying to track down why your
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
has some useful information.
### Troubleshooting Claims and XRs
Crossplane relies heavily on status conditions and events for troubleshooting.
You can see both using `kubectl describe` - for example:
```console
# Describe the PostgreSQLInstance claim named my-db
kubectl describe postgresqlinstance.database.example.org my-db
```
Per Kubernetes convention, Crossplane keeps errors close to the place they
happen. This means that if your claim is not becoming ready due to an issue with
your `Composition` or with a composed resource you'll need to "follow the
references" to find out why. Your claim will only tell you that the XR is not
yet ready.
To follow the references:
1. Find your XR by running `kubectl describe` on your claim and looking for its
"Resource Ref" (aka `spec.resourceRef`).
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
with the `Composition` you're using, if any.
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
resources.
1. Run `kubectl describe` on each referenced composed resource to determine
whether it is ready and what issues, if any, it is encountering.
<!-- Named Links -->
[Requested Resource Not Found]: #requested-resource-not-found
[install Crossplane CLI]: "../getting-started/install-configure"
[Resource Status and Conditions]: #resource-status-and-conditions
[Resource Events]: #resource-events
[Crossplane Logs]: #crossplane-logs
[Provider Logs]: #provider-logs
[Pausing Crossplane]: #pausing-crossplane
[Pausing Providers]: #pausing-providers
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
[Installing Crossplane Package]: #installing-crossplane-package
[Crossplane package]: /master/concepts/packages/
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons

View File

@ -0,0 +1,627 @@
---
title: Vault as an External Secret Store
weight: 230
---
This guide walks through the steps required to configure Crossplane and
its Providers to use [Vault] as an [External Secret Store] (`ESS`) with [ESS Plugin Vault].
{{<hint "warning" >}}
External Secret Stores are an alpha feature.
They're not recommended for production use. Crossplane disables External Secret
Stores by default.
{{< /hint >}}
Crossplane uses sensitive information including Provider credentials, inputs to
managed resources and connection details.
The [Vault credential injection guide]({{<ref "vault-injection" >}}) details
using Vault and Crossplane for Provider credentials.
Crossplane doesn't support for using Vault for managed resources input.
[Crossplane issue #2985](https://github.com/crossplane/crossplane/issues/2985)
tracks support for this feature.
Supporting connection details with Vault requires a Crossplane external secret
store.
## Prerequisites
This guide requires [Helm](https://helm.sh) version 3.11 or later.
## Install Vault
{{<hint "note" >}}
Detailed instructions on [installing
Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/helm)
are available from the Vault documentation.
{{< /hint >}}
### Add the Vault Helm chart
Add the Helm repository for `hashicorp`.
```shell
helm repo add hashicorp https://helm.releases.hashicorp.com --force-update
```
Install Vault using Helm.
```shell
helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace
```
### Unseal Vault
If Vault is [sealed](https://developer.hashicorp.com/vault/docs/concepts/seal)
unseal Vault using the unseal keys.
Get the Vault keys.
```shell
kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
```
Unseal the vault using the keys.
```shell {copy-lines="1"}
kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.13.1
Build Date 2023-03-23T12:51:35Z
Storage Type file
Cluster Name vault-cluster-df884357
Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
HA Enabled false
```
## Configure Vault Kubernetes authentication
Enable the [Kubernetes auth method] for Vault to authenticate requests based on
Kubernetes service accounts.
### Get the Vault root token
The Vault root token is inside the JSON file created when
[unsealing Vault](#unseal-vault).
```shell
cat cluster-keys.json | jq -r ".root_token"
```
### Enable Kubernetes authentication
Connect to a shell in the Vault pod.
```shell {copy-lines="1"}
kubectl -n vault-system exec -it vault-0 -- /bin/sh
/ $
```
From the Vault shell, login to Vault using the _root token_.
```shell {copy-lines="1"}
vault login # use the root token from above
Token (will be hidden):
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token hvs.TSN4SssfMBM0HAtwGrxgARgn
token_accessor qodxHrINVlRXKyrGeeDkxnih
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
```
Enable the Kubernetes authentication method in Vault.
```shell {copy-lines="1"}
vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/
```
Configure Vault to communicate with Kubernetes and exit the Vault shell
```shell {copy-lines="1-4"}
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config
/ $ exit
```
## Configure Vault for Crossplane integration
Crossplane relies on the Vault key-value secrets engine to store information and
Vault requires a permissions policy for the Crossplane service account.
<!-- vale Crossplane.Spelling = NO -->
<!-- allow "kv" -->
### Enable the Vault kv secrets engine
<!-- vale Crossplane.Spelling = YES -->
Enable the [Vault KV Secrets Engine].
{{< hint "important" >}}
Vault has two versions of the
[KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv).
This example uses version 2.
{{</hint >}}
```shell {copy-lines="1"}
kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
Success! Enabled the kv-v2 secrets engine at: secret/
```
### Create a Vault policy for Crossplane
Create the Vault policy to allow Crossplane to read and write data from Vault.
```shell {copy-lines="1-8"}
kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
path "secret/data/*" {
capabilities = ["create", "read", "update", "delete"]
}
path "secret/metadata/*" {
capabilities = ["create", "read", "update", "delete"]
}
EOF
Success! Uploaded policy: crossplane
```
Apply the policy to Vault.
```shell {copy-lines="1-5"}
kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
bound_service_account_names="*" \
bound_service_account_namespaces=crossplane-system \
policies=crossplane \
ttl=24h
Success! Data written to: auth/kubernetes/role/crossplane
```
## Install Crossplane
{{<hint "important" >}}
Crossplane v1.12 introduced the plugin support. Make sure your version of Crossplane supports plugins.
{{< /hint >}}
Install the Crossplane with the External Secrets Stores feature enabled.
```shell
helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'
```
## Install the Crossplane Vault plugin
The Crossplane Vault plugin isn't part of the default Crossplane install.
The plugin installs as a unique Pod that uses the [Vault Agent Sidecar
Injection] to connect the Vault secret store to Crossplane.
First, configure annotations for the Vault plugin pod.
```yaml
cat > values.yaml <<EOF
podAnnotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-token: "true"
vault.hashicorp.com/role: crossplane
vault.hashicorp.com/agent-run-as-user: "65532"
EOF
```
Next, install the Crossplane ESS Plugin pod to the `crossplane-system` namespace
and apply the Vault annotations.
```shell
helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml
```
## Configure Crossplane
Using the Vault plugin requires configuration to connect to the Vault
service. The plugin also requires Providers to enable external secret stores.
With the plugin and providers configured, Crossplane requires two `StoreConfig`
objects to describe how Crossplane and the Providers communicate with vault.
### Enable external secret stores in the Provider
{{<hint "note">}}
This example uses Provider GCP, but the
{{<hover label="ControllerConfig" line="2">}}ControllerConfig{{</hover>}} is the
same for all Providers.
{{</hint >}}
Create a `ControllerConfig` object to enable external secret stores.
```yaml {label="ControllerConfig"}
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: vault-config
spec:
args:
- --enable-external-secret-stores" | kubectl apply -f -
```
Install the Provider and apply the ControllerConfig.
```yaml
echo "apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-gcp
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
controllerConfigRef:
name: vault-config" | kubectl apply -f -
```
### Connect the Crossplane plugin to Vault
Create a {{<hover label="VaultConfig" line="2">}}VaultConfig{{</hover>}}
resource for the plugin to connect to the Vault service:
```yaml {label="VaultConfig"}
echo "apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
metadata:
name: vault-internal
spec:
server: http://vault.vault-system:8200
mountPath: secret/
version: v2
auth:
method: Token
token:
source: Filesystem
fs:
path: /vault/secrets/token" | kubectl apply -f -
```
### Create a Crossplane StoreConfig
Create a {{<hover label="xp-storeconfig" line="2">}}StoreConfig{{</hover >}}
object from the
{{<hover label="xp-storeconfig" line="1">}}secrets.crossplane.io{{</hover >}}
group. Crossplane uses the StoreConfig to connect to the Vault plugin service.
The {{<hover label="xp-storeconfig" line="10">}}configRef{{</hover >}} connects
the StoreConfig to the specific Vault plugin configuration.
```yaml {label="xp-storeconfig"}
echo "apiVersion: secrets.crossplane.io/v1alpha1
kind: StoreConfig
metadata:
name: vault
spec:
type: Plugin
defaultScope: crossplane-system
plugin:
endpoint: ess-plugin-vault.crossplane-system:4040
configRef:
apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
name: vault-internal" | kubectl apply -f -
```
### Create a Provider StoreConfig
Create a {{<hover label="gcp-storeconfig" line="2">}}StoreConfig{{</hover >}}
object from the Provider's API group,
{{<hover label="gcp-storeconfig" line="1">}}gcp.crossplane.io{{</hover >}}.
The Provider uses this StoreConfig to communicate with Vault for
Managed Resources.
The {{<hover label="gcp-storeconfig" line="10">}}configRef{{</hover >}} connects
the StoreConfig to the specific Vault plugin configuration.
```yaml {label="gcp-storeconfig"}
echo "apiVersion: gcp.crossplane.io/v1alpha1
kind: StoreConfig
metadata:
name: vault
spec:
type: Plugin
defaultScope: crossplane-system
plugin:
endpoint: ess-plugin-vault.crossplane-system:4040
configRef:
apiVersion: secrets.crossplane.io/v1alpha1
kind: VaultConfig
name: vault-internal" | kubectl apply -f -
```
## Create Provider resources
Check that Crossplane installed the Provider and the Provider is healthy.
```shell {copy-lines="1"}
kubectl get providers
NAME INSTALLED HEALTHY PACKAGE AGE
provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m
```
### Create a CompositeResourceDefinition
Create a `CompositeResourceDefinition` to define a custom API endpoint.
```yaml
echo "apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: compositeessinstances.ess.example.org
annotations:
feature: ess
spec:
group: ess.example.org
names:
kind: CompositeESSInstance
plural: compositeessinstances
claimNames:
kind: ESSInstance
plural: essinstances
connectionSecretKeys:
- publicKey
- publicKeyType
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
parameters:
type: object
properties:
serviceAccount:
type: string
required:
- serviceAccount
required:
- parameters" | kubectl apply -f -
```
### Create a Composition
Create a `Composition` to create a Service Account and Service Account Key
inside GCP.
Creating a Service Account Key generates
{{<hover label="comp" line="39" >}}connectionDetails{{</hover>}} that the
Provider stores in Vault using the
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}} details.
```yaml {label="comp"}
echo "apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: essinstances.ess.example.org
labels:
feature: ess
spec:
publishConnectionDetailsWithStoreConfigRef:
name: vault
compositeTypeRef:
apiVersion: ess.example.org/v1alpha1
kind: CompositeESSInstance
resources:
- name: serviceaccount
base:
apiVersion: iam.gcp.crossplane.io/v1alpha1
kind: ServiceAccount
metadata:
name: ess-test-sa
spec:
forProvider:
displayName: a service account to test ess
- name: serviceaccountkey
base:
apiVersion: iam.gcp.crossplane.io/v1alpha1
kind: ServiceAccountKey
spec:
forProvider:
serviceAccountSelector:
matchControllerRef: true
publishConnectionDetailsTo:
name: ess-mr-conn
metadata:
labels:
environment: development
team: backend
configRef:
name: vault
connectionDetails:
- fromConnectionSecretKey: publicKey
- fromConnectionSecretKey: publicKeyType" | kubectl apply -f -
```
### Create a Claim
Now create a `Claim` to have Crossplane create the GCP resources and associated
secrets.
Like the Composition, the Claim uses
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}} to
connect to Vault and store the secrets.
```yaml {label="claim"}
echo "apiVersion: ess.example.org/v1alpha1
kind: ESSInstance
metadata:
name: my-ess
namespace: default
spec:
parameters:
serviceAccount: ess-test-sa
compositionSelector:
matchLabels:
feature: ess
publishConnectionDetailsTo:
name: ess-claim-conn
metadata:
labels:
environment: development
team: backend
configRef:
name: vault" | kubectl apply -f -
```
## Verify the resources
Verify all resources are `READY` and `SYNCED`:
```shell {copy-lines="1"}
kubectl get managed
NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess my-ess-zvmkz-vhklg@testingforbugbounty.iam.gserviceaccount.com
NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z
```
View the claims
```shell {copy-lines="1"}
kubectl -n default get claim
NAME READY CONNECTION-SECRET AGE
my-ess True 19s
```
View the composite resources.
```shell {copy-lines="1"}
kubectl get composite
NAME READY COMPOSITION AGE
my-ess-zvmkz True essinstances.ess.example.org 32s
```
## Verify Vault secrets
Look inside Vault to view the secrets from the managed resources.
```shell {copy-lines="1",label="vault-key"}
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
Keys
----
ess-claim-conn
```
The key {{<hover label="vault-key" line="4">}}ess-claim-conn{{</hover>}}
is the name of the Claim's
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}}
configuration.
Check connection secrets in the "crossplane-system" Vault scope.
```shell {copy-lines="1",label="scope-key"}
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
Keys
----
d2408335-eb88-4146-927b-8025f405da86
ess-mr-conn
```
The key
{{<hover label="scope-key"line="4">}}d2408335-eb88-4146-927b-8025f405da86{{</hover>}}
comes from
<!-- ## WHERE DOES IT COME FROM? -->
and the key
{{<hover label="scope-key"line="5">}}ess-mr-conn{{</hover>}}
comes from the Composition's
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}}
configuration.
Check contents of Claim's connection secret `ess-claim-conn` to see the key
created by the managed resource.
```shell {copy-lines="1"}
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
======= Metadata =======
Key Value
--- -----
created_time 2022-03-18T21:24:07.2085726Z
custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
deletion_time n/a
destroyed false
version 1
======== Data ========
Key Value
--- -----
publicKey -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
vwIDAQAB
-----END PUBLIC KEY-----
publicKeyType TYPE_RAW_PUBLIC_KEY
```
Check contents of managed resource connection secret `ess-mr-conn`. The public
key is identical to the public key in the Claim since the Claim is using this
managed resource.
```shell {copy-lines="1"}
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
======= Metadata =======
Key Value
--- -----
created_time 2022-03-18T21:21:07.9298076Z
custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
deletion_time n/a
destroyed false
version 2
========= Data =========
Key Value
--- -----
privateKey {
"type": "service_account",
"project_id": "REDACTED",
"private_key_id": "REDACTED",
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
"client_email": "ess-test-sa@REDACTED.iam.gserviceaccount.com",
"client_id": "REDACTED",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
}
privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
publicKey -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
vwIDAQAB
-----END PUBLIC KEY-----
publicKeyType TYPE_RAW_PUBLIC_KEY
```
### Remove the resources
Deleting the Claim removes the managed resources and associated keys from Vault.
```shell
kubectl delete claim my-ess
```
<!-- named links -->
[Vault]: https://www.vaultproject.io/
[External Secret Store]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-external-secret-stores.md
[this issue]: https://github.com/crossplane/crossplane/issues/2985
[Kubernetes Auth Method]: https://www.vaultproject.io/docs/auth/kubernetes
[Unseal]: https://www.vaultproject.io/docs/concepts/seal
[Vault KV Secrets Engine]: https://developer.hashicorp.com/vault/docs/secrets/kv
[Vault Agent Sidecar Injection]: https://www.vaultproject.io/docs/platform/k8s/injector
[ESS Plugin Vault]: https://github.com/crossplane-contrib/ess-plugin-vault

View File

@ -0,0 +1,506 @@
---
title: Vault Credential Injection
weight: 230
---
> This guide is adapted from the [Vault on Minikube] and [Vault Kubernetes
> Sidecar] guides.
Most Crossplane providers support supplying credentials from at least the
following sources:
- Kubernetes Secret
- Environment Variable
- Filesystem
A provider may optionally support additional credentials sources, but the common
sources cover a wide variety of use cases. One specific use case that is popular
among organizations that use [Vault] for secrets management is using a sidecar
to inject credentials into the filesystem. This guide will demonstrate how to
use the [Vault Kubernetes Sidecar] to provide credentials for [provider-gcp]
and [provider-aws].
> Note: in this guide we will copy GCP credentials and AWS access keys
> into Vault's KV secrets engine. This is a simple generic approach to
> managing secrets with Vault, but is not as robust as using Vault's
> dedicated cloud provider secrets engines for [AWS], [Azure], and [GCP].
## Setup
> Note: this guide walks through setting up Vault running in the same cluster as
> Crossplane. You may also choose to use an existing Vault instance that runs
> outside the cluster but has Kubernetes authentication enabled.
Before getting started, you must ensure that you have installed Crossplane and
Vault and that they are running in your cluster.
1. Install Crossplane
```console
kubectl create namespace crossplane-system
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
```
2. Install Vault Helm Chart
```console
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault
```
3. Unseal Vault Instance
In order for Vault to access encrypted data from physical storage, it must be
[unsealed].
```console
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
```
4. Enable Kubernetes Authentication Method
In order for Vault to be able to authenticate requests based on Kubernetes
service accounts, the [Kubernetes authentication backend] must be enabled. This
requires logging in to Vault and configuring it with a service account token,
API server address, and certificate. Because we are running Vault in Kubernetes,
these values are already available via the container filesystem and environment
variables.
```console
cat cluster-keys.json | jq -r ".root_token" # get root token
kubectl exec -it vault-0 -- /bin/sh
vault login # use root token from above
vault auth enable kubernetes
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
```
5. Exit Vault Container
The next steps will be executed in your local environment.
```console
exit
```
{{< tabs >}}
{{< tab "GCP" >}}
## Create GCP Service Account
In order to provision infrastructure on GCP, you will need to create a service
account with appropriate permissions. In this guide we will only provision a
CloudSQL instance, so the service account will be bound to the `cloudsql.admin`
role. The following steps will setup a GCP service account, give it the
necessary permissions for Crossplane to be able to manage CloudSQL instances,
and emit the service account credentials in a JSON file.
```console
# replace this with your own gcp project id and the name of the service account
# that will be created.
PROJECT_ID=my-project
NEW_SA_NAME=test-service-account-name
# create service account
SA="${NEW_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create $NEW_SA_NAME --project $PROJECT_ID
# enable cloud API
SERVICE="sqladmin.googleapis.com"
gcloud services enable $SERVICE --project $PROJECT_ID
# grant access to cloud API
ROLE="roles/cloudsql.admin"
gcloud projects add-iam-policy-binding --role="$ROLE" $PROJECT_ID --member "serviceAccount:$SA"
# create service account keyfile
gcloud iam service-accounts keys create creds.json --project $PROJECT_ID --iam-account $SA
```
You should now have valid service account credentials in `creds.json`.
## Store Credentials in Vault
After setting up Vault, you will need to store your credentials in the [kv
secrets engine].
> Note: the steps below involve copying credentials into the container
> filesystem before storing them in Vault. You may also choose to use Vault's
> HTTP API or UI by port-forwarding the container to your local environment
> (`kubectl port-forward vault-0 8200:8200`).
1. Copy Credentials File into Vault Container
Copy your credentials into the container filesystem so that your can store them
in Vault.
```console
kubectl cp creds.json vault-0:/tmp/creds.json
```
2. Enable KV Secrets Engine
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
secrets engine at the `secret` path.
```console
kubectl exec -it vault-0 -- /bin/sh
vault secrets enable -path=secret kv-v2
```
3. Store GCP Credentials in KV Engine
The path of your GCP credentials is how the secret will be referenced when
injecting it into the `provider-gcp` controller `Pod`.
```console
vault kv put secret/provider-creds/gcp-default @tmp/creds.json
```
4. Clean Up Credentials File
You no longer need our GCP credentials file in the container filesystem, so go
ahead and clean it up.
```console
rm tmp/creds.json
```
{{< /tab >}}
{{< tab "AWS" >}}
## Create AWS IAM User
In order to provision infrastructure on AWS, you will need to use an existing or create a new IAM
user with appropriate permissions. The following steps will create an AWS IAM user and give it the necessary
permissions.
> Note: if you have an existing IAM user with appropriate permissions, you can skip this step but you will
> still need to provide the values for the `ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
```console
# create a new IAM user
IAM_USER=test-user
aws iam create-user --user-name $IAM_USER
# grant the IAM user the necessary permissions
aws iam attach-user-policy --user-name $IAM_USER --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
# create a new IAM access key for the user
aws iam create-access-key --user-name $IAM_USER > creds.json
# assign the access key values to environment variables
ACCESS_KEY_ID=$(jq -r .AccessKey.AccessKeyId creds.json)
AWS_SECRET_ACCESS_KEY=$(jq -r .AccessKey.SecretAccessKey creds.json)
```
## Store Credentials in Vault
After setting up Vault, you will need to store your credentials in the [kv
secrets engine].
1. Enable KV Secrets Engine
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
secrets engine at the `secret` path.
```console
kubectl exec -it vault-0 -- env \
ACCESS_KEY_ID=${ACCESS_KEY_ID} \
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
/bin/sh
vault secrets enable -path=secret kv-v2
```
2. Store AWS Credentials in KV Engine
The path of your AWS credentials is how the secret will be referenced when
injecting it into the `provider-aws` controller `Pod`.
```
vault kv put secret/provider-creds/aws-default access_key="$ACCESS_KEY_ID" secret_key="$AWS_SECRET_ACCESS_KEY"
```
{{< /tab >}}
{{< /tabs >}}
## Create a Vault Policy for Reading Provider Credentials
In order for our controllers to have the Vault sidecar inject the credentials
into their filesystem, you must associate the `Pod` with a [policy]. This policy
will allow for reading and listing all secrets on the `provider-creds` path in
the `kv-v2` secrets engine.
```console
vault policy write provider-creds - <<EOF
path "secret/data/provider-creds/*" {
capabilities = ["read", "list"]
}
EOF
```
## Create a Role for Crossplane Provider Pods
1. Create Role
The last step is to create a role that is bound to the policy you created and
associate it with a group of Kubernetes service accounts. This role can be
assumed by any (`*`) service account in the `crossplane-system` namespace.
```console
vault write auth/kubernetes/role/crossplane-providers \
bound_service_account_names="*" \
bound_service_account_namespaces=crossplane-system \
policies=provider-creds \
ttl=24h
```
2. Exit Vault Container
The next steps will be executed in your local environment.
```console
exit
```
{{< tabs >}}
{{< tab "GCP" >}}
## Install provider-gcp
You are now ready to install `provider-gcp`. Crossplane provides a
`ControllerConfig` type that allows you to customize the deployment of a
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
by any number of `Provider` objects that wish to use its configuration. In the
example below, the `Pod` annotations indicate to the Vault mutating webhook that
we want for the secret stored at `secret/provider-creds/gcp-default` to be
injected into the container filesystem by assuming role `crossplane-providers`.
There is also so template formatting added to make sure the secret data is
presented in a form that `provider-gcp` is expecting.
{% raw %}
```console
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: vault-config
spec:
metadata:
annotations:
vault.hashicorp.com/agent-inject: \"true\"
vault.hashicorp.com/role: "crossplane-providers"
vault.hashicorp.com/agent-inject-secret-creds.txt: "secret/provider-creds/gcp-default"
vault.hashicorp.com/agent-inject-template-creds.txt: |
{{- with secret \"secret/provider-creds/gcp-default\" -}}
{{ .Data.data | toJSON }}
{{- end -}}
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-gcp
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.22.0
controllerConfigRef:
name: vault-config" | kubectl apply -f -
```
{% endraw %}
## Configure provider-gcp
One `provider-gcp` is installed and running, you will want to create a
`ProviderConfig` that specifies the credentials in the filesystem that should be
used to provision managed resources that reference this `ProviderConfig`.
Because the name of this `ProviderConfig` is `default` it will be used by any
managed resources that do not explicitly reference a `ProviderConfig`.
> Note: make sure that the `PROJECT_ID` environment variable that was defined
> earlier is still set correctly.
```console
echo "apiVersion: gcp.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
projectID: ${PROJECT_ID}
credentials:
source: Filesystem
fs:
path: /vault/secrets/creds.txt" | kubectl apply -f -
```
To verify that the GCP credentials are being injected into the container run the
following command:
```console
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-gcp -o name --no-headers=true)
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-gcp -- cat /vault/secrets/creds.txt
```
## Provision Infrastructure
The final step is to actually provision a `CloudSQLInstance`. Creating the
object below will result in the creation of a Cloud SQL Postgres database on
GCP.
```console
echo "apiVersion: database.gcp.crossplane.io/v1beta1
kind: CloudSQLInstance
metadata:
name: postgres-vault-demo
spec:
forProvider:
databaseVersion: POSTGRES_12
region: us-central1
settings:
tier: db-custom-1-3840
dataDiskType: PD_SSD
dataDiskSizeGb: 10
writeConnectionSecretToRef:
namespace: crossplane-system
name: cloudsqlpostgresql-conn" | kubectl apply -f -
```
You can monitor the progress of the database provisioning with the following
command:
```console
kubectl get cloudsqlinstance -w
```
{{< /tab >}}
{{< tab "AWS" >}}
## Install provider-aws
You are now ready to install `provider-aws`. Crossplane provides a
`ControllerConfig` type that allows you to customize the deployment of a
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
by any number of `Provider` objects that wish to use its configuration. In the
example below, the `Pod` annotations indicate to the Vault mutating webhook that
we want for the secret stored at `secret/provider-creds/aws-default` to be
injected into the container filesystem by assuming role `crossplane-providers`.
There is also some template formatting added to make sure the secret data is
presented in a form that `provider-aws` is expecting.
{% raw %}
```console
echo "apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: aws-vault-config
spec:
args:
- --debug
metadata:
annotations:
vault.hashicorp.com/agent-inject: \"true\"
vault.hashicorp.com/role: \"crossplane-providers\"
vault.hashicorp.com/agent-inject-secret-creds.txt: \"secret/provider-creds/aws-default\"
vault.hashicorp.com/agent-inject-template-creds.txt: |
{{- with secret \"secret/provider-creds/aws-default\" -}}
[default]
aws_access_key_id="{{ .Data.data.access_key }}"
aws_secret_access_key="{{ .Data.data.secret_key }}"
{{- end -}}
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: aws-vault-config" | kubectl apply -f -
```
{% endraw %}
## Configure provider-aws
Once `provider-aws` is installed and running, you will want to create a
`ProviderConfig` that specifies the credentials in the filesystem that should be
used to provision managed resources that reference this `ProviderConfig`.
Because the name of this `ProviderConfig` is `default` it will be used by any
managed resources that do not explicitly reference a `ProviderConfig`.
```console
echo "apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Filesystem
fs:
path: /vault/secrets/creds.txt" | kubectl apply -f -
```
To verify that the AWS credentials are being injected into the container run the
following command:
```console
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-aws -o name --no-headers=true)
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-aws -- cat /vault/secrets/creds.txt
```
## Provision Infrastructure
The final step is to actually provision a `Bucket`. Creating the
object below will result in the creation of a S3 bucket on AWS.
```console
echo "apiVersion: s3.aws.crossplane.io/v1beta1
kind: Bucket
metadata:
name: s3-vault-demo
spec:
forProvider:
acl: private
locationConstraint: us-east-1
publicAccessBlockConfiguration:
blockPublicPolicy: true
tagging:
tagSet:
- key: Name
value: s3-vault-demo
providerConfigRef:
name: default" | kubectl apply -f -
```
You can monitor the progress of the bucket provisioning with the following
command:
```console
kubectl get bucket -w
```
{{< /tab >}}
{{< /tabs >}}
<!-- named links -->
[Vault on Minikube]: https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube
[Vault Kubernetes Sidecar]: https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar
[Vault]: https://www.vaultproject.io/
[Vault Kubernetes Sidecar]: https://www.vaultproject.io/docs/platform/k8s/injector
[provider-gcp]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-gcp
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
[AWS]: https://www.vaultproject.io/docs/secrets/aws
[Azure]: https://www.vaultproject.io/docs/secrets/azure
[GCP]: https://www.vaultproject.io/docs/secrets/gcp
[unsealed]: https://www.vaultproject.io/docs/concepts/seal
[Kubernetes authentication backend]: https://www.vaultproject.io/docs/auth/kubernetes
[kv secrets engine]: https://www.vaultproject.io/docs/secrets/kv/kv-v2
[policy]: https://www.vaultproject.io/docs/concepts/policies

View File

@ -0,0 +1,838 @@
---
title: Write a Composition Function in Go
state: beta
alphaVersion: "1.11"
betaVersion: "1.14"
weight: 80
description: "Composition functions allow you to template resources using Go"
---
Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource
(XR). Read the
[concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions.
You can write a function to template resources using a general purpose
programming language. Using a general purpose programming language allows a
function to use advanced logic to template resources, like loops and
conditionals. This guide explains how to write a composition function in
[Go](https://go.dev).
{{< hint "important" >}}
It helps to be familiar with
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide.
{{< /hint >}}
## Understand the steps
This guide covers writing a composition function for an
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
```yaml {label="xr"}
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<!-- vale gitlab.FutureTense = NO -->
<!--
This section is setting the stage for future sections. It doesn't make sense to
refer to the function in the present tense, because it doesn't exist yet.
-->
An `XBuckets` XR has a region and an array of bucket names. The function will
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
<!-- vale gitlab.FutureTense = YES -->
To write a function in Go:
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
1. [Test the function end-to-end](#test-the-function-end-to-end)
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
This guide covers each of these steps in detail.
## Install the tools you need to write the function
To write a function in Go you need:
* [Go](https://go.dev/dl/) v1.21 or newer. The guide uses Go v1.21.
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
CLI v1.14.
{{<hint "note">}}
You don't need access to a Kubernetes cluster or a Crossplane control plane to
build or test a composition function.
{{</hint>}}
## Initialize the function from a template
Use the `crossplane beta xpkg init` command to initialize a new function. When
you run this command it initializes your function using
[a GitHub repository](https://github.com/crossplane/function-template-go)
as a template.
```shell {copy-lines=1}
crossplane beta xpkg init function-xbuckets function-template-go -d function-xbuckets
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
```
The `crossplane beta init xpkg` command creates a directory named
`function-xbuckets`. When you run the command the new directory should look like
this:
```shell {copy-lines=1}
ls function-xbuckets
Dockerfile fn.go fn_test.go go.mod go.sum input/ LICENSE main.go package/ README.md renovate.json
```
The `fn.go` file is where you add the function's code. It's useful to know about
some other files in the template:
* `main.go` runs the function. You don't need to edit `main.go`.
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
* The `input` directory defines the function's input type.
* The `package` directory contains metadata used to build the function package.
{{<hint "tip">}}
<!-- vale gitlab.FutureTense = NO -->
<!--
This tip talks about future plans for Crossplane.
-->
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
template GitHub repository. A future CLI release will automate tasks like
replacing the template name with the new function's name. See Crossplane issue
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
<!-- vale gitlab.FutureTense = YES -->
{{</hint>}}
You must make some changes before you start adding code:
* Edit `package/crossplane.yaml` to change the package's name.
* Edit `go.mod` to change the Go module's name.
Name your package `function-xbuckets`.
The name of your module depends on where you want to keep your function code. If
you push Go code to GitHub, you can use your GitHub username. For example
`module github.com/negz/function-xbuckets`.
The function in this guide doesn't use an input type. For this function you
should delete the `input` and `package/input` directories.
The `input` directory defines a Go struct that a function can use to take input,
using the `input` field from a Composition. The
[composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains how to pass an input to a composition function.
The `package/input` directory contains an OpenAPI schema generated from the
structs in the `input` directory.
{{<hint "tip">}}
If you're writing a function that uses an input, edit the input to meet your
function's requirements.
Change the input's kind and API group. Don't use `Input` and
`template.fn.crossplane.io`. Instead use something meaningful to your function.
When you edit files under the `input` directory you must update some generated
files by running `go generate`. See `input/generate.go` for details.
```shell
go generate ./...
```
{{</hint>}}
## Edit the template to add the function's logic
You add your function's logic to the
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
method in `fn.go`. When you first open the file it contains a "hello world"
function.
```go {label="hello-world"}
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
rsp := response.To(req, response.DefaultTTL)
in := &v1beta1.Input{}
if err := request.GetInput(req, in); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
return rsp, nil
}
response.Normalf(rsp, "I was run with input %q", in.Example)
return rsp, nil
}
```
All Go composition functions have a `RunFunction` method. Crossplane passes
everything the function needs to run in a
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} struct.
The function tells Crossplane what resources it should compose by returning a
{{<hover label="hello-world" line="13">}}RunFunctionResponse{{</hover>}} struct.
{{<hint "tip">}}
Crossplane generates the `RunFunctionRequest` and `RunFunctionResponse` structs
using [Protocol Buffers](http://protobuf.dev). You can find detailed schemas for
`RunFunctionRequest` and `RunFunctionResponse` in the
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
{{</hint>}}
Edit the `RunFunction` method to replace it with this code.
```go {hl_lines="4-56"}
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
rsp := response.To(req, response.DefaultTTL)
xr, err := request.GetObservedCompositeResource(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
return rsp, nil
}
region, err := xr.Resource.GetString("spec.region")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
return rsp, nil
}
names, err := xr.Resource.GetStringArray("spec.names")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
return rsp, nil
}
desired, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
return rsp, nil
}
_ = v1beta1.AddToScheme(composed.Scheme)
for _, name := range names {
b := &v1beta1.Bucket{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"crossplane.io/external-name": name,
},
},
Spec: v1beta1.BucketSpec{
ForProvider: v1beta1.BucketParameters{
Region: ptr.To[string](region),
},
},
}
cd, err := composed.From(b)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
return rsp, nil
}
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
}
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
return rsp, nil
}
return rsp, nil
}
```
Expand the below block to view the full `fn.go`, including imports and
commentary explaining the function's logic.
{{<expand "The full fn.go file" >}}
```go
package main
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"github.com/upbound/provider-aws/apis/s3/v1beta1"
"github.com/crossplane/function-sdk-go/errors"
"github.com/crossplane/function-sdk-go/logging"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/resource"
"github.com/crossplane/function-sdk-go/resource/composed"
"github.com/crossplane/function-sdk-go/response"
)
// Function returns whatever response you ask it to.
type Function struct {
fnv1beta1.UnimplementedFunctionRunnerServiceServer
log logging.Logger
}
// RunFunction observes an XBuckets composite resource (XR). It adds an S3
// bucket to the desired state for every entry in the XR's spec.names array.
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
// Create a response to the request. This copies the desired state and
// pipeline context from the request to the response.
rsp := response.To(req, response.DefaultTTL)
// Read the observed XR from the request. Most functions use the observed XR
// to add desired managed resources.
xr, err := request.GetObservedCompositeResource(req)
if err != nil {
// If the function can't read the XR, the request is malformed. This
// should never happen. The function returns a fatal result. This tells
// Crossplane to stop running functions and return an error.
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
return rsp, nil
}
// Create an updated logger with useful information about the XR.
log := f.log.WithValues(
"xr-version", xr.Resource.GetAPIVersion(),
"xr-kind", xr.Resource.GetKind(),
"xr-name", xr.Resource.GetName(),
)
// Get the region from the XR. The XR has getter methods like GetString,
// GetBool, etc. You can use them to get values by their field path.
region, err := xr.Resource.GetString("spec.region")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
return rsp, nil
}
// Get the array of bucket names from the XR.
names, err := xr.Resource.GetStringArray("spec.names")
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
return rsp, nil
}
// Get all desired composed resources from the request. The function will
// update this map of resources, then save it. This get, update, set pattern
// ensures the function keeps any resources added by other functions.
desired, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
return rsp, nil
}
// Add v1beta1 types (including Bucket) to the composed resource scheme.
// composed.From uses this to automatically set apiVersion and kind.
_ = v1beta1.AddToScheme(composed.Scheme)
// Add a desired S3 bucket for each name.
for _, name := range names {
// One advantage of writing a function in Go is strong typing. The
// function can import and use managed resource types from the provider.
b := &v1beta1.Bucket{
ObjectMeta: metav1.ObjectMeta{
// Set the external name annotation to the desired bucket name.
// This controls what the bucket will be named in AWS.
Annotations: map[string]string{
"crossplane.io/external-name": name,
},
},
Spec: v1beta1.BucketSpec{
ForProvider: v1beta1.BucketParameters{
// Set the bucket's region to the value read from the XR.
Region: ptr.To[string](region),
},
},
}
// Convert the bucket to the unstructured resource data format the SDK
// uses to store desired composed resources.
cd, err := composed.From(b)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
return rsp, nil
}
// Add the bucket to the map of desired composed resources. It's
// important that the function adds the same bucket every time it's
// called. It's also important that the bucket is added with the same
// resource.Name every time it's called. The function prefixes the name
// with "xbuckets-" to avoid collisions with any other composed
// resources that might be in the desired resources map.
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
}
// Finally, save the updated desired composed resources to the response.
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
return rsp, nil
}
// Log what the function did. This will only appear in the function's pod
// logs. A function can use response.Normal and response.Warning to emit
// Kubernetes events associated with the XR it's operating on.
log.Info("Added desired buckets", "region", region, "count", len(names))
return rsp, nil
}
```
{{</expand>}}
This code:
1. Gets the observed composite resource from the `RunFunctionRequest`.
1. Gets the region and bucket names from the observed composite resource.
1. Adds one desired S3 bucket for each bucket name.
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
The code uses the `v1beta1.Bucket` type from
[Upbound's AWS S3 provider](https://github.com/upbound/provider-aws). One
advantage of writing a function in Go is that you can compose resources using
the same strongly typed structs Crossplane uses in its providers.
You must get the AWS Provider Go module to use this type:
```shell
go get github.com/upbound/provider-aws@v0.43.0
```
Crossplane provides a
[software development kit](https://github.com/crossplane/function-sdk-go) (SDK)
for writing composition functions in [Go](https://go.dev). This function uses
utilities from the SDK. In particular the `request` and `response` packages make
working with the `RunFunctionRequest` and `RunFunctionResponse` types easier.
{{<hint "tip">}}
Read the
[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go)
for the SDK.
{{</hint>}}
## Test the function end-to-end
Test your function by adding unit tests, and by using the `crossplane beta
render` command.
Go has rich support for unit testing. When you initialize a function from the
template it adds some unit tests to `fn_test.go`. These tests follow Go's
[recommendations](https://github.com/golang/go/wiki/TestComments). They use only
[`pkg/testing`](https://pkg.go.dev/testing) from the Go standard library and
[`google/go-cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp).
To add test cases, update the `cases` map in `TestRunFunction`. Expand the below
block to view the full `fn_test.go` file for the function.
{{<expand "The full fn_test.go file" >}}
```go
package main
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/crossplane/crossplane-runtime/pkg/logging"
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
"github.com/crossplane/function-sdk-go/resource"
)
func TestRunFunction(t *testing.T) {
type args struct {
ctx context.Context
req *fnv1beta1.RunFunctionRequest
}
type want struct {
rsp *fnv1beta1.RunFunctionResponse
err error
}
cases := map[string]struct {
reason string
args args
want want
}{
"AddTwoBuckets": {
reason: "The Function should add two buckets to the desired composed resources",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
// MustStructJSON is a handy way to provide mock
// resources.
Resource: resource.MustStructJSON(`{
"apiVersion": "example.crossplane.io/v1alpha1",
"kind": "XBuckets",
"metadata": {
"name": "test"
},
"spec": {
"region": "us-east-2",
"names": [
"test-bucket-a",
"test-bucket-b"
]
}
}`),
},
},
},
},
want: want{
rsp: &fnv1beta1.RunFunctionResponse{
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(60 * time.Second)},
Desired: &fnv1beta1.State{
Resources: map[string]*fnv1beta1.Resource{
"xbuckets-test-bucket-a": {Resource: resource.MustStructJSON(`{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-a"
}
},
"spec": {
"forProvider": {
"region": "us-east-2"
}
}
}`)},
"xbuckets-test-bucket-b": {Resource: resource.MustStructJSON(`{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-b"
}
},
"spec": {
"forProvider": {
"region": "us-east-2"
}
}
}`)},
},
},
},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{log: logging.NewNopLogger()}
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)
if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff)
}
})
}
}
```
{{</expand>}}
Run the unit tests using the `go test` command:
```shell
go test -v -cover .
=== RUN TestRunFunction
=== RUN TestRunFunction/AddTwoBuckets
--- PASS: TestRunFunction (0.00s)
--- PASS: TestRunFunction/AddTwoBuckets (0.00s)
PASS
coverage: 52.6% of statements
ok github.com/negz/function-xbuckets 0.016s coverage: 52.6% of statements
```
You can preview the output of a Composition that uses this function using
the Crossplane CLI. You don't need a Crossplane control plane to do this.
Create a directory under `function-xbuckets` named `example` and create
Composite Resource, Composition and Function YAML files.
Expand the following block to see example files.
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
You can recreate the output below using by running `crossplane beta render` with
these files.
The `xr.yaml` file contains the composite resource to render:
```yaml
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<br />
The `composition.yaml` file contains the Composition to use to render the
composite resource:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: create-buckets
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: XBuckets
mode: Pipeline
pipeline:
- step: create-buckets
functionRef:
name: function-xbuckets
```
<br />
The `functions.yaml` file contains the Functions the Composition references in
its pipeline steps:
```yaml
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
spec:
# The CLI ignores this package when using the Development runtime.
# You can set it to any value.
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
```
{{</expand>}}
The Function in `functions.yaml` uses the
{{<hover label="development" line="6">}}Development{{</hover>}}
runtime. This tells `crossplane beta render` that your function is running
locally. It connects to your locally running function instead of using Docker to
pull and run the function.
```yaml {label="development"}
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
```
Use `go run` to run your function locally.
```shell {label="run"}
go run . --insecure --debug
```
{{<hint "warning">}}
The {{<hover label="run" line="1">}}insecure{{</hover>}} flag tells the function
to run without encryption or authentication. Only use it during testing and
development.
{{</hint>}}
In a separate terminal, run `crossplane beta render`.
```shell
crossplane beta render xr.yaml composition.yaml functions.yaml
```
This command calls your function. In the terminal where your function is running
you should now see log output:
```shell
go run . --insecure --debug
2023-10-31T16:17:32.158-0700 INFO function-xbuckets/fn.go:29 Running Function {"tag": ""}
2023-10-31T16:17:32.159-0700 INFO function-xbuckets/fn.go:125 Added desired buckets {"xr-version": "example.crossplane.io/v1", "xr-kind": "XBuckets", "xr-name": "example-buckets", "region": "us-east-2", "count": 3}
```
The `crossplane beta render` command prints the desired resources the function
returns.
```yaml
---
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
crossplane.io/external-name: crossplane-functions-example-b
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
crossplane.io/external-name: crossplane-functions-example-c
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
crossplane.io/external-name: crossplane-functions-example-a
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
```
{{<hint "tip">}}
Read the composition functions documentation to learn more about
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
{{</hint>}}
## Build and push the function to a package registry
You build a function in two stages. First you build the function's runtime. This
is the Open Container Initiative (OCI) image Crossplane uses to run your
function. You then embed that runtime in a package, and push it to a package
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
registry.
A function supports a single platform, like `linux/amd64`, by default. You can
support multiple platforms by building a runtime and package for each platform,
then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform.
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
```
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
```
{{<hint "tip">}}
You can use whatever tag you want. There's no need to push the runtime images to
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
embed.
{{</hint>}}
Use the Crossplane CLI to build a package for each platform. Each package embeds
a runtime image.
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
the `package` directory, which contains `crossplane.yaml`. This includes
metadata about the package.
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
specifies the runtime image tag built using Docker.
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
specifies where to write the package file to disk. Crossplane package files use
the extension `.xpkg`.
```shell {label="build"}
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-amd64 \
--package-file=function-amd64.xpkg
```
```shell
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-arm64 \
--package-file=function-arm64.xpkg
```
{{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the
[packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the
registry creates a
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
package that runs on both `linux/arm64` and `linux/amd64` hosts.
```shell
crossplane xpkg push \
--package-files=function-amd64.xpkg,function-arm64.xpkg \
negz/function-xbuckets:v0.1.0
```
{{<hint "tip">}}
If you push the function to a GitHub repository the template automatically sets
up continuous integration (CI) using
[GitHub Actions](https://github.com/features/actions). The CI workflow will
lint, test, and build your function. You can see how the template configures CI
by reading `.github/workflows/ci.yaml`.
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
to work you must create a repository at https://marketplace.upbound.io. Give the
CI workflow access to push to the Marketplace by creating an API token and
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
token as a secret named `XPKG_TOKEN`.
{{</hint>}}

View File

@ -0,0 +1,745 @@
---
title: Write a Composition Function in Python
state: beta
alphaVersion: "1.11"
betaVersion: "1.14"
weight: 81
description: "Composition functions allow you to template resources using Python"
---
Composition functions (or just functions, for short) are custom programs that
template Crossplane resources. Crossplane calls composition functions to
determine what resources it should create when you create a composite resource
(XR). Read the
[concepts]{{<ref "../concepts/composition-functions" >}}
page to learn more about composition functions.
You can write a function to template resources using a general purpose
programming language. Using a general purpose programming language allows a
function to use advanced logic to template resources, like loops and
conditionals. This guide explains how to write a composition function in
[Python](https://python.org).
{{< hint "important" >}}
It helps to be familiar with
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
before following this guide.
{{< /hint >}}
## Understand the steps
This guide covers writing a composition function for an
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
```yaml {label="xr"}
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<!-- vale gitlab.FutureTense = NO -->
<!--
This section is setting the stage for future sections. It doesn't make sense to
refer to the function in the present tense, because it doesn't exist yet.
-->
An `XBuckets` XR has a region and an array of bucket names. The function will
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
<!-- vale gitlab.FutureTense = YES -->
To write a function in Python:
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
1. [Test the function end-to-end](#test-the-function-end-to-end)
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
This guide covers each of these steps in detail.
## Install the tools you need to write the function
To write a function in Python you need:
* [Python](https://www.python.org/downloads/) v3.11.
* [Hatch](https://hatch.pypa.io/), a Python build tool. This guide uses v1.7.
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
CLI v1.14.
{{<hint "note">}}
You don't need access to a Kubernetes cluster or a Crossplane control plane to
build or test a composition function.
{{</hint>}}
## Initialize the function from a template
Use the `crossplane beta xpkg init` command to initialize a new function. When
you run this command it initializes your function using
[a GitHub repository](https://github.com/crossplane/function-template-python)
as a template.
```shell {copy-lines=1}
crossplane beta xpkg init function-xbuckets https://github.com/crossplane/function-template-python -d function-xbuckets
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-python/tree/bfed6923ab4c8e7adeed70f41138645fc7d38111 (main)
```
The `crossplane beta init xpkg` command creates a directory named
`function-xbuckets`. When you run the command the new directory should look like
this:
```shell {copy-lines=1}
ls function-xbuckets
Dockerfile example/ function/ LICENSE package/ pyproject.toml README.md renovate.json tests/
```
Your function's code lives in the `function` directory:
```shell {copy-lines=1}
ls function/
__version__.py fn.py main.py
```
The `function/fn.py` file is where you add the function's code. It's useful to
know about some other files in the template:
* `function/main.py` runs the function. You don't need to edit `main.py`.
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
* The `package` directory contains metadata used to build the function package.
{{<hint "tip">}}
<!-- vale gitlab.FutureTense = NO -->
<!--
This tip talks about future plans for Crossplane.
-->
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
template GitHub repository. A future CLI release will automate tasks like
replacing the template name with the new function's name. See Crossplane issue
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
<!-- vale gitlab.FutureTense = YES -->
{{</hint>}}
Edit `package/crossplane.yaml` to change the package's name before you start
adding code. Name your package `function-xbuckets`.
The `package/input` directory defines the OpenAPI schema for the a function's
input. The function in this guide doesn't accept an input. Delete the
`package/input` directory.
The [composition functions]{{<ref "../concepts/composition-functions" >}}
documentation explains composition function inputs.
{{<hint "tip">}}
If you're writing a function that uses an input, edit the input YAML file to
meet your function's requirements.
Change the input's kind and API group. Don't use `Input` and
`template.fn.crossplane.io`. Instead use something meaningful to your function.
{{</hint>}}
## Edit the template to add the function's logic
You add your function's logic to the
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
method in `function/fn.py`. When you first open the file it contains a "hello
world" function.
```python {label="hello-world"}
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
rsp = response.to(req)
example = ""
if "example" in req.input:
example = req.input["example"]
# TODO: Add your function logic here!
response.normal(rsp, f"I was run with input {example}!")
log.info("I was run!", input=example)
return rsp
```
All Python composition functions have a `RunFunction` method. Crossplane passes
everything the function needs to run in a
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} object.
The function tells Crossplane what resources it should compose by returning a
{{<hover label="hello-world" line="15">}}RunFunctionResponse{{</hover>}} object.
Edit the `RunFunction` method to replace it with this code.
```python {hl_lines="7-28"}
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
rsp = response.to(req)
region = req.observed.composite.resource["spec"]["region"]
names = req.observed.composite.resource["spec"]["names"]
for name in names:
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": name,
},
},
"spec": {
"forProvider": {
"region": region,
},
},
}
)
log.info("Added desired buckets", region=region, count=len(names))
return rsp
```
Expand the below block to view the full `fn.py`, including imports and
commentary explaining the function's logic.
{{<expand "The full fn.py file" >}}
```python
"""A Crossplane composition function."""
import grpc
from crossplane.function import logging, response
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
from crossplane.function.proto.v1beta1 import run_function_pb2_grpc as grpcv1beta1
class FunctionRunner(grpcv1beta1.FunctionRunnerService):
"""A FunctionRunner handles gRPC RunFunctionRequests."""
def __init__(self):
"""Create a new FunctionRunner."""
self.log = logging.get_logger()
async def RunFunction(
self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext
) -> fnv1beta1.RunFunctionResponse:
"""Run the function."""
# Create a logger for this request.
log = self.log.bind(tag=req.meta.tag)
log.info("Running function")
# Create a response to the request. This copies the desired state and
# pipeline context from the request to the response.
rsp = response.to(req)
# Get the region and a list of bucket names from the observed composite
# resource (XR). Crossplane represents resources using the Struct
# well-known protobuf type. The Struct Python object can be accessed
# like a dictionary.
region = req.observed.composite.resource["spec"]["region"]
names = req.observed.composite.resource["spec"]["names"]
# Add a desired S3 bucket for each name.
for name in names:
# Crossplane represents desired composed resources using a protobuf
# map of messages. This works a little like a Python defaultdict.
# Instead of assigning to a new key in the dict-like map, you access
# the key and mutate its value as if it did exist.
#
# The below code works because accessing the xbuckets-{name} key
# automatically creates a new, empty fnv1beta1.Resource message. The
# Resource message has a resource field containing an empty Struct
# object that can be populated from a dictionary by calling update.
#
# https://protobuf.dev/reference/python/python-generated/#map-fields
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": name,
},
},
"spec": {
"forProvider": {
"region": region,
},
},
}
)
# Log what the function did. This will only appear in the function's pod
# logs. A function can use response.normal() and response.warning() to
# emit Kubernetes events associated with the XR it's operating on.
log.info("Added desired buckets", region=region, count=len(names))
return rsp
```
{{</expand>}}
This code:
1. Gets the observed composite resource from the `RunFunctionRequest`.
1. Gets the region and bucket names from the observed composite resource.
1. Adds one desired S3 bucket for each bucket name.
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
Crossplane provides a
[software development kit](https://github.com/crossplane/function-sdk-python)
(SDK) for writing composition functions in Python. This function uses utilities
from the SDK.
{{<hint "tip">}}
Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python).
{{</hint>}}
{{<hint "important">}}
The Python SDK automatically generates the `RunFunctionRequest` and
`RunFunctionResponse` Python objects from a
[Protocol Buffers](https://protobuf.dev) schema. You can see the schema in the
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
The fields of the generated Python objects behave similarly to builtin Python
types like dictionaries and lists. Be aware that there are some differences.
Notably, you access the map of observed and desired resources like a dictionary
but you can't add a new desired resource by assigning to a map key. Instead,
access and mutate the map key as if it already exists.
Instead of adding a new resource like this:
```python
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
rsp.desired.resources["new-resource"] = fnv1beta1.Resource(resource=resource)
```
Pretend it already exists and mutate it, like this:
```python
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
rsp.desired.resources["new-resource"].resource.update(resource)
```
Refer to the Protocol Buffers
[Python Generated Code Guide](https://protobuf.dev/reference/python/python-generated/#fields)
for further details.
{{</hint>}}
## Test the function end-to-end
Test your function by adding unit tests, and by using the `crossplane beta
render` command.
When you initialize a function from the
template it adds some unit tests to `tests/test_fn.py`. These tests use the
[`unittest`](https://docs.python.org/3/library/unittest.html) module from the
Python standard library.
To add test cases, update the `cases` list in `test_run_function`. Expand the
below block to view the full `tests/test_fn.py` file for the function.
{{<expand "The full test_fn.py file" >}}
```python
import dataclasses
import unittest
from crossplane.function import logging, resource
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
from google.protobuf import duration_pb2 as durationpb
from google.protobuf import json_format
from google.protobuf import struct_pb2 as structpb
from function import fn
class TestFunctionRunner(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
logging.configure(level=logging.Level.DISABLED)
self.maxDiff = 2000
async def test_run_function(self) -> None:
@dataclasses.dataclass
class TestCase:
reason: str
req: fnv1beta1.RunFunctionRequest
want: fnv1beta1.RunFunctionResponse
cases = [
TestCase(
reason="The function should compose two S3 buckets.",
req=fnv1beta1.RunFunctionRequest(
observed=fnv1beta1.State(
composite=fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "example.crossplane.io/v1alpha1",
"kind": "XBuckets",
"metadata": {"name": "test"},
"spec": {
"region": "us-east-2",
"names": ["test-bucket-a", "test-bucket-b"],
},
}
)
)
)
),
want=fnv1beta1.RunFunctionResponse(
meta=fnv1beta1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
desired=fnv1beta1.State(
resources={
"xbuckets-test-bucket-a": fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-a"
},
},
"spec": {
"forProvider": {"region": "us-east-2"}
},
}
)
),
"xbuckets-test-bucket-b": fnv1beta1.Resource(
resource=resource.dict_to_struct(
{
"apiVersion": "s3.aws.upbound.io/v1beta1",
"kind": "Bucket",
"metadata": {
"annotations": {
"crossplane.io/external-name": "test-bucket-b"
},
},
"spec": {
"forProvider": {"region": "us-east-2"}
},
}
)
),
},
),
context=structpb.Struct(),
),
),
]
runner = fn.FunctionRunner()
for case in cases:
got = await runner.RunFunction(case.req, None)
self.assertEqual(
json_format.MessageToDict(got),
json_format.MessageToDict(case.want),
"-want, +got",
)
if __name__ == "__main__":
unittest.main()
```
{{</expand>}}
Run the unit tests using `hatch run`:
```shell {copy-lines="1"}
hatch run test:unit
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
```
{{<hint "tip">}}
[Hatch](https://hatch.pypa.io/) is a Python build tool. It builds Python
artifacts like wheels. It also manages virtual environments, similar
to `virtualenv` or `venv`. The `hatch run` command creates a virtual environment
and runs a command in that environment.
{{</hint>}}
You can preview the output of a Composition that uses this function using
the Crossplane CLI. You don't need a Crossplane control plane to do this.
Create a directory under `function-xbuckets` named `example` and create
Composite Resource, Composition and Function YAML files.
Expand the following block to see example files.
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
You can recreate the output below using by running `crossplane beta render` with
these files.
The `xr.yaml` file contains the composite resource to render:
```yaml
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
spec:
region: us-east-2
names:
- crossplane-functions-example-a
- crossplane-functions-example-b
- crossplane-functions-example-c
```
<br />
The `composition.yaml` file contains the Composition to use to render the
composite resource:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: create-buckets
spec:
compositeTypeRef:
apiVersion: example.crossplane.io/v1
kind: XBuckets
mode: Pipeline
pipeline:
- step: create-buckets
functionRef:
name: function-xbuckets
```
<br />
The `functions.yaml` file contains the Functions the Composition references in
its pipeline steps:
```yaml
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
spec:
# The CLI ignores this package when using the Development runtime.
# You can set it to any value.
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
```
{{</expand>}}
The Function in `functions.yaml` uses the
{{<hover label="development" line="6">}}Development{{</hover>}}
runtime. This tells `crossplane beta render` that your function is running
locally. It connects to your locally running function instead of using Docker to
pull and run the function.
```yaml {label="development"}
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-xbuckets
annotations:
render.crossplane.io/runtime: Development
```
Use `hatch run development` to run your function locally.
```shell {label="run"}
hatch run development
```
{{<hint "warning">}}
`hatch run development` runs the function without encryption or authentication.
Only use it during testing and development.
{{</hint>}}
In a separate terminal, run `crossplane beta render`.
```shell
crossplane beta render xr.yaml composition.yaml functions.yaml
```
This command calls your function. In the terminal where your function is running
you should now see log output:
```shell
hatch run development
2024-01-11T22:12:58.153572Z [info ] Running function filename=fn.py lineno=22 tag=
2024-01-11T22:12:58.153792Z [info ] Added desired buckets count=3 filename=fn.py lineno=68 region=us-east-2 tag=
```
The `crossplane beta render` command prints the desired resources the function
returns.
```yaml
---
apiVersion: example.crossplane.io/v1
kind: XBuckets
metadata:
name: example-buckets
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
crossplane.io/external-name: crossplane-functions-example-b
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
crossplane.io/external-name: crossplane-functions-example-c
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
---
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
crossplane.io/external-name: crossplane-functions-example-a
generateName: example-buckets-
labels:
crossplane.io/composite: example-buckets
ownerReferences:
# Omitted for brevity
spec:
forProvider:
region: us-east-2
```
{{<hint "tip">}}
Read the composition functions documentation to learn more about
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
{{</hint>}}
## Build and push the function to a package registry
You build a function in two stages. First you build the function's runtime. This
is the Open Container Initiative (OCI) image Crossplane uses to run your
function. You then embed that runtime in a package, and push it to a package
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
registry.
A function supports a single platform, like `linux/amd64`, by default. You can
support multiple platforms by building a runtime and package for each platform,
then pushing all the packages to a single tag in the registry.
Pushing your function to a registry allows you to use your function in a
Crossplane control plane. See the
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
to learn how to use a function in a control plane.
Use Docker to build a runtime for each platform.
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
```
```shell {copy-lines="1"}
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
```
{{<hint "tip">}}
You can use whatever tag you want. There's no need to push the runtime images to
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
embed.
{{</hint>}}
{{<hint "important">}}
Docker uses emulation to create images for different platforms. If building an
image for a different platform fails, make sure you have installed `binfmt`. See
the
[Docker documentation](https://docs.docker.com/build/building/multi-platform/#qemu)
for instructions.
{{</hint>}}
Use the Crossplane CLI to build a package for each platform. Each package embeds
a runtime image.
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
the `package` directory, which contains `crossplane.yaml`. This includes
metadata about the package.
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
specifies the runtime image tag built using Docker.
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
specifies where to write the package file to disk. Crossplane package files use
the extension `.xpkg`.
```shell {label="build"}
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-amd64 \
--package-file=function-amd64.xpkg
```
```shell
crossplane xpkg build \
--package-root=package \
--embed-runtime-image=runtime-arm64 \
--package-file=function-arm64.xpkg
```
{{<hint "tip">}}
Crossplane packages are special OCI images. Read more about packages in the
[packages documentation]({{< ref "../concepts/packages" >}}).
{{</hint>}}
Push both package files to a registry. Pushing both files to one tag in the
registry creates a
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
package that runs on both `linux/arm64` and `linux/amd64` hosts.
```shell
crossplane xpkg push \
--package-files=function-amd64.xpkg,function-arm64.xpkg \
negz/function-xbuckets:v0.1.0
```
{{<hint "tip">}}
If you push the function to a GitHub repository the template automatically sets
up continuous integration (CI) using
[GitHub Actions](https://github.com/features/actions). The CI workflow will
lint, test, and build your function. You can see how the template configures CI
by reading `.github/workflows/ci.yaml`.
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
to work you must create a repository at https://marketplace.upbound.io. Give the
CI workflow access to push to the Marketplace by creating an API token and
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
token as a secret named `XPKG_TOKEN`.
{{</hint>}}

View File

@ -0,0 +1,34 @@
---
title: Learn
description: Learn more about Crossplane.
---
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!
***Learn more about using Crossplane***
- [Latest Design Docs](https://github.com/crossplane/crossplane/tree/master/design)
- [Roadmap](https://github.com/crossplane/crossplane/blob/master/ROADMAP.md)
- [Crossplane Architecture](https://docs.google.com/document/d/1whncqdUeU2cATGEJhHvzXWC9xdK29Er45NJeoemxebo/edit?usp=sharing)
- [GitLab deploys into multiple clouds from kubectl using Crossplane](https://about.gitlab.com/2019/05/20/gitlab-first-deployed-kubernetes-api-to-multiple-clouds/)
- [CNCF Talks & Community Presentations](https://www.youtube.com/playlist?list=PL510POnNVaaZJj9OG6PbgsZvgYbhwJRyE)
- [Software Engineering Daily - Intro Podcast](https://softwareengineeringdaily.com/2019/01/02/crossplane-multicloud-control-plane-with-bassam-tabbara/)
***Writing Kubernetes controllers to extend Crossplane***
- [Keep the Space Shuttle Flying: Writing Robust Operators](https://www.youtube.com/watch?v=uf97lOApOv8)
- [Best practices for building Kubernetes Operators](https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps)
- [Programming Kubernetes Book](https://www.oreilly.com/library/view/programming-kubernetes/9781492047094/)
- [Contributor Guide](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md)
***Join the growing Crossplane community and get involved!***
- Join our [Community Slack](https://slack.crossplane.io/)!
- Submit an issue on [GitHub](https://github.com/crossplane/crossplane)
- Attend our bi-weekly [Community Meeting](https://github.com/crossplane/crossplane#get-involved)
- Join our bi-weekly live stream: [The Binding Status](https://github.com/crossplane/tbs)
- Subscribe to our [YouTube Channel](https://www.youtube.com/channel/UC19FgzMBMqBro361HbE46Fw)
- Drop us a note on Twitter: [@crossplane_io](https://twitter.com/crossplane_io)
- Email us: [info@crossplane.io](mailto:info@crossplane.io)
<!-- Named links -->
[join-crossplane-slack]: https://slack.crossplane.io
[contact-us]: https://github.com/crossplane/crossplane#contact

View File

@ -0,0 +1,56 @@
---
title: Feature Lifecycle
toc: true
weight: 309
indent: true
---
# Feature Lifecycle
Crossplane follows a similar feature lifecycle to [upstream
Kubernetes][kube-features]. All major new features must be added in alpha. Alpha
features are expected to eventually graduate to beta, and then to general
availability (GA). Features that languish at alpha or beta may be subject to
deprecation.
## Alpha Features
Alpha are off by default, and must be enabled by a feature flag, for example
`--enable-composition-revisions`. API types pertaining to alpha features use a
`vNalphaN` style API version, like `v1alpha`. **Alpha features are subject to
removal or breaking changes without notice**, and generally not considered ready
for use in production.
In some cases alpha features require fields be added to existing beta or GA
API types. In these cases fields must clearly be marked (i.e in their OpenAPI
schema) as alpha and subject to alpha API constraints (or lack thereof).
All alpha features should have an issue tracking their graduation to beta.
## Beta Features
Beta features are on by default, but may be disabled by a feature flag. API
types pertaining to beta features use a `vNbetaN` style API version, like
`v1beta1`. Beta features are considered to be well tested, and will not be
removed completely without being marked deprecated for at least two releases.
The schema and/or semantics of objects may change in incompatible ways in a
subsequent beta or stable release. When this happens, we will provide
instructions for migrating to the next version. This may require deleting,
editing, and re-creating API objects. The editing process may require some
thought. This may require downtime for applications that rely on the feature.
In some cases beta features require fields be added to existing GA API types. In
these cases fields must clearly be marked (i.e in their OpenAPI schema) as beta
and subject to beta API constraints (or lack thereof).
All beta features should have an issue tracking their graduation to GA.
## GA Features
GA features are always enabled - they cannot be disabled. API types pertaining
to GA features use `vN` style API versions, like `v1`. GA features are widely
used and thoroughly tested. They guarantee API stability - only backward
compatible changes are allowed.
[kube-features]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages

View File

@ -0,0 +1,100 @@
---
title: Release Cycle
weight: 308
---
Starting with the v1.10.0 release, Crossplane is released on a quarterly (13
week) cadence. A cycle is comprised of three general stages:
- Weeks 1—11: [Active Development]
- Week 12: [Feature Freeze]
- Week 13: [Code Freeze]
This results in four releases per year, with the most recent three releases
being maintained at any given time. When a new release is cut, the fourth most
recent release reaches end of life (EOL). Users can expect any given release to
be maintained for nine months.
### Definition of maintenance
The Crossplane community defines maintenance in that relevant bug fixes that are
merged to the main development branch will be eligible to be back-ported to the
release branch of any currently maintained version, and patch releases will be
cut appropriately. It's also possible that a fix may be merged directly to the
release branch if no longer applicable on the main development branch.
Maintenance doesn't indicate any SLA on response time for user support in the
form of Slack messages or issues, but problems will be addressed on a best
effort basis by maintainers and contributors for currently maintained releases.
### Patch releases
_This policy is subject to change in the future._
Patch releases are cut for currently maintained minor versions on an as-needed
basis. Any critical back-ported fixes will be included in a patch release as
soon as possible after merge.
### Pre-releases
_This policy is subject to change in the future._
Alpha, Beta, and RC releases are cut for an upcoming release on an as-needed
basis. As a policy, at least one pre-release will be cut prior to any minor
release. Pre-releases won't be made on release branches.
### Provider releases
The Crossplane release cycle isn't required to be adhered to by any other
Crossplane projects, but a similar cadence is encouraged. Maintainers listed in
each repository's `OWNERS.md` file are responsible for determining and
publishing the release cycle for their project.
## Release stages
The following stages are the main milestones in a Crossplane release.
### Active development
During active development, any code that meets the requisite criteria (i.e.
passing appropriate tests, approved by a maintainer, etc.) will be merged into
the main development branch. At present, there is no requirement to formally
submit an enhancement proposal prior to the start of the release cycle, but
contributors are encouraged to open an issue and gather feedback before starting
work on a major implementation (see [CONTRIBUTING.md] for more information).
### Feature freeze
During feature freeze, no new functionality should be merged into the main
development branch. Bug fixes, documentation changes, and non-critical changes
may be made. In the case that a new feature is deemed absolutely necessary for a
release, the Crossplane maintainers will weigh the impact of the change and make
a decision on whether it should be included.
### Code freeze
During code freeze, there should be no changes merged to the main development
branch with the following exceptions:
- Fixes to a failing test that's deemed to be incorrectly testing
functionality.
- Documentation only changes. It's possible that a documentation freeze will be
implemented in the future, but it's not currently enforced.
- Fixes to a critical bug that wasn't previously identified. Merging a bug fix
during code freeze requires application for and approval of an exception by
Crossplane maintainers. This process is currently informal, but may be
formalized in the future.
## Release dates
Crossplane releases once a quarter (every 13 weeks). Typically, the release
happens on the Tuesday of the last week of the quarter, as shown on the
[community calendar][community calendar]. Keep in mind that the specific date is
**approximate**. A lot of factors can alter the date slightly, such as code
reviews, testing, and bug fixing to ensure a quality release.
<!-- Named links -->
[Active Development]: #active-development
[Feature Freeze]: #feature-freeze
[Code Freeze]: #code-freeze
[CONTRIBUTING.md]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
[community calendar]: https://calendar.google.com/calendar/embed?src=c_2cdn0hs9e2m05rrv1233cjoj1k%40group.calendar.google.com

View File

@ -28,7 +28,7 @@ Read the
* Changes to TLS certificates. Existing users of external secret stores need to * Changes to TLS certificates. Existing users of external secret stores need to
manually update their TLS certificates. Read [Crossplane issue #4565](https://github.com/crossplane/crossplane/pull/4656) for more information. manually update their TLS certificates. Read [Crossplane issue #4565](https://github.com/crossplane/crossplane/pull/4656) for more information.
* Removed Vault support for External Secret Stores. Crossplane * Removed Vault support for External Secret Stores. Crossplane
suggests using the [ESS Plugins]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) as a replacement. suggests using the [ESS Plugins]({{<ref "../guides/vault-as-secret-store">}}) as a replacement.
* Removed the `controllerConfigRef` from the Configuration package * Removed the `controllerConfigRef` from the Configuration package
and package revision APIs. and package revision APIs.
* The introduction of the new [Crossplane CLI]({{<ref "../cli" >}}) deprecates * The introduction of the new [Crossplane CLI]({{<ref "../cli" >}}) deprecates

View File

@ -1,5 +1,5 @@
--- ---
title: Crossplane API title: API Reference
weight: 400 weight: 400
description: "API details for Crossplane's core types" description: "API details for Crossplane's core types"
cascade: cascade:

View File

@ -1,6 +1,6 @@
--- ---
weight: 400 weight: 200
title: Crossplane CLI title: CLI Reference
description: "Documentation for the Crossplane command-line interface" description: "Documentation for the Crossplane command-line interface"
--- ---

View File

@ -1,6 +1,6 @@
--- ---
title: Concepts title: Concepts
weight: 100 weight: 50
description: Understand Crossplane's core components description: Understand Crossplane's core components
--- ---

View File

@ -204,4 +204,4 @@ spec:
name: my-claim-secret name: my-claim-secret
``` ```
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).

View File

@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
{{</hint >}} {{</hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Set composite resource defaults ### Set composite resource defaults
XRDs can set default parameters for composite resources and Claims. XRDs can set default parameters for composite resources and Claims.

View File

@ -189,7 +189,7 @@ spec:
### Composition revision policy ### Composition revision policy
Crossplane tracks changes to Compositions as Crossplane tracks changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) . [Composition revisions]({{<ref "composition-revisions">}}) .
A composite resource can use A composite resource can use
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
@ -217,7 +217,7 @@ spec:
### Composition revision selection ### Composition revision selection
Crossplane records changes to Compositions as Crossplane records changes to Compositions as
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}). [Composition revisions]({{<ref "composition-revisions">}}).
A composite resource can A composite resource can
select a specific Composition revision. select a specific Composition revision.
@ -309,7 +309,7 @@ spec:
``` ```
Composite resources can write connection secrets to an Composite resources can write connection secrets to an
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}), [external secret store]({{<ref "../guides/vault-as-secret-store">}}),
like HashiCorp Vault. like HashiCorp Vault.
{{<hint "important" >}} {{<hint "important" >}}
@ -332,10 +332,10 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
external secret stores. external secret stores.
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
### Pausing composite resources ### Pausing composite resources

View File

@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
{{<hint "tip">}} {{<hint "tip">}}
Crossplane has Crossplane has
[language specific guides]({{<ref "../../knowledge-base/guides">}}) to writing [language specific guides]({{<ref "../guides">}}) to writing
a composition function. Refer to the guide for your preferred language for a a composition function. Refer to the guide for your preferred language for a
more detailed guide to writing a function. more detailed guide to writing a function.
{{</hint>}} {{</hint>}}

View File

@ -0,0 +1,445 @@
---
title: Composition Revisions
weight: 35
---
This guide discusses the use of "Composition Revisions" to safely make and roll
back changes to a Crossplane [`Composition`][composition-type]. It assumes
familiarity with Crossplane, and particularly with
[Compositions].
A `Composition` configures how Crossplane should reconcile a Composite Resource
(XR). Put otherwise, when you create an XR the selected `Composition` determines
what managed resources Crossplane will create in response. Let's say for example
that you define a `PlatformDB` XR, which represents your organisation's common
database configuration of an Azure MySQL Server and a few firewall rules. The
`Composition` contains the 'base' configuration for the MySQL server and the
firewall rules that is extended by the configuration for the `PlatformDB`.
There is a one-to-many relationship between a `Composition` and the XRs that use
it. You might define a `Composition` named `big-platform-db` that is used by ten
different `PlatformDB` XRs. Usually, in the interest of self-service, the
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
For example the `Composition` may be written and maintained by a platform team
member, while individual application teams create `PlatformDB` XRs that use said
`Composition`.
Each `Composition` is mutable - you can update it as your organisation's needs
change. However, without Composition Revisions updating a `Composition` can be a
risky process. Crossplane constantly uses the `Composition` to ensure that your
actual infrastructure - your MySQL Servers and firewall rules - match your
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
`Composition`, all 10 of those XRs will be instantly updated in accordance with
any updates you make to the `big-platform-db` `Composition`.
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
update your XRs to leverage the latest `Composition` settings at your own pace.
This enables you to [canary] changes to your infrastructure, or to roll back
some XRs to previous `Composition` settings without rolling back all XRs.
## Using Composition Revisions
When you enable Composition Revisions three things happen:
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
which `CompositionRevision` they use.
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
specifies how they should be updated to new Composition Revisions.
Each time you edit a `Composition` Crossplane will automatically create a
`CompositionRevision` that represents that 'revision' of the `Composition` -
that unique state. Each revision is allocated an increasing revision number.
This gives `CompositionRevision` consumers an idea about which revision is
'newest'.
Crossplane distinguishes between the 'newest' and the 'current' revision of a
`Composition`. That is, if you revert a `Composition` to a previous state that
corresponds to an existing `CompositionRevision` that revision will become
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
`Composition` configuration).
You can discover which revisions exist using `kubectl`:
```console
# Find all revisions of the Composition named 'example'
kubectl get compositionrevision -l crossplane.io/composition-name=example
```
This should produce output something like:
```console
NAME REVISION CURRENT AGE
example-18pdg 1 False 4m36s
example-2bgdr 2 True 73s
example-xjrdm 3 False 61s
```
> A `Composition` is a mutable resource that you can update as your needs
> change over time. Each `CompositionRevision` is an immutable snapshot of those
> needs at a particular point in time.
Crossplane behaves the same way by default whether Composition Revisions are
enabled or not. This is because when you enable Composition Revisions all XRs
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
policies:
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
* `Manual`: Require manual intervention to change `CompositionRevision`.
The below XR uses the `Manual` policy. When this policy is used the XR will
select the current `CompositionRevision` when it is first created, but must
manually be updated when you wish it to use another `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
# The Manual policy specifies that you do not want this XR to update to the
# current CompositionRevision automatically.
compositionUpdatePolicy: Manual
compositionRef:
name: example
writeConnectionSecretToRef:
name: db-conn
```
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
policy you must edit the `compositionRevisionRef` field when you want your XR to
use a different `CompositionRevision`.
```yaml
apiVersion: example.org/v1alpha1
kind: PlatformDB
metadata:
name: example
spec:
parameters:
storageGB: 20
compositionUpdatePolicy: Manual
compositionRef:
name: example
# Update the referenced CompositionRevision if and when you are ready.
compositionRevisionRef:
name: example-18pdg
writeConnectionSecretToRef:
name: db-conn
```
## Complete example
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
assign different CompositionRevisions to the created composite resources each time the composition is updated.
### Preparation
##### Install Crossplane
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
```shell
kubectl create namespace crossplane-system
helm repo add crossplane-master https://charts.crossplane.io/master/
helm repo update
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
kubectl get pods -n crossplane-system
```
Expected Output:
```shell
NAME READY STATUS RESTARTS AGE
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
```
#### Deploy Composition and XRD Examples
Apply the example Composition.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: true
enableDnsHostnames: true
name: my-vcp
```
Apply the example XRD.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: myvpcs.aws.example.upbound.io
spec:
group: aws.example.upbound.io
names:
kind: MyVPC
plural: myvpcs
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
id:
type: string
description: ID of this VPC that other objects will use to refer to it.
required:
- id
```
Verify that Crossplane created the Composition revision
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
{{< hint "note" >}}
The label `dev` is automatically created from the Composition.
{{< /hint >}}
### Create Composite Resources
This tutorial has four composite resources to cover different update policies and composition selection options.
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
#### Default update policy
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-auto
spec:
id: vpc-auto
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-auto created
```
#### Manual update policy
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-man
spec:
id: vpc-man
compositionUpdatePolicy: Manual
compositionRevisionRef:
name: myvpcs.aws.example.upbound.io-ad265bc
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-man created
```
#### Using a selector
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-dev
spec:
id: vpc-dev
compositionRevisionSelector:
matchLabels:
channel: dev
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-dev created
```
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
```yaml
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
metadata:
name: vpc-staging
spec:
id: vpc-staging
compositionRevisionSelector:
matchLabels:
channel: staging
```
Expected Output:
```shell
myvpc.aws.example.upbound.io/vpc-staging created
```
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
All other XRs have a `REVISION` matching the created Composition Revision.
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging False <none> Automatic map[channel:staging]
```
{{< hint "note" >}}
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
{{< /hint >}}
### Create new Composition revisions
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
also trigger a new CompositionRevision.
#### Update the Composition label
Update the `Composition` label to `channel: staging`:
```shell
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
```
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-auto` always use the latest Revision.
`vpc-staging` now matches the label applied to Revision revision:2.
{{< /hint >}}
#### Update Composition Spec and Label
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
Apply the following changes to update the `Composition` spec and label:
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
labels:
channel: dev
name: myvpcs.aws.example.upbound.io
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: aws.example.upbound.io/v1alpha1
kind: MyVPC
resources:
- base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:
region: us-west-1
cidrBlock: 192.168.0.0/16
enableDnsSupport: false
enableDnsHostnames: true
name: my-vcp
```
Expected Output:
```shell
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
```
Verify that Crossplane creates a new Composition revision:
```shell
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
```
Expected Output:
```shell
NAME REVISION CHANNEL
myvpcs.aws.example.upbound.io-727b3c8 2 staging
myvpcs.aws.example.upbound.io-ad265bc 1 dev
myvpcs.aws.example.upbound.io-f81c553 3 dev
```
{{< hint "note" >}}
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
{{< /hint >}}
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
```shell
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
```
Expected Output:
```shell
NAME SYNCED REVISION POLICY MATCHLABEL
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
```
{{< hint "note" >}}
`vpc-dev` matches the updated label applied to Revision revision:3.
`vpc-staging` matches the label applied to Revision revision:2.
{{< /hint >}}
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
[install-guide]: {{<ref "../../master/software/install" >}}

View File

@ -748,7 +748,7 @@ details.
This section discusses creating Kubernetes secrets. This section discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store. with an external secret store.
{{</hint >}} {{</hint >}}
@ -958,7 +958,7 @@ for more information on restricting secret keys.
{{< /hint >}} {{< /hint >}}
For more information on connection secrets read the For more information on connection secrets read the
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}). [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
{{<hint "warning">}} {{<hint "warning">}}
You can't change the You can't change the
@ -973,7 +973,7 @@ recreate the Composition to change the
#### Save connection details to an external secret store #### Save connection details to an external secret store
Crossplane Crossplane
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
write secrets and connection details to external secret stores like HashiCorp write secrets and connection details to external secret stores like HashiCorp
Vault. Vault.
@ -1018,7 +1018,7 @@ spec:
# Removed for brevity # Removed for brevity
``` ```
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}}) For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
integration guide. integration guide.
### Resource readiness checks ### Resource readiness checks

View File

@ -0,0 +1,607 @@
---
title: Connection Details
weight: 110
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
---
Using connection details in Crossplane requires the following components:
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
* Optionally, define the `connectionSecretKeys` in a
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
{{<hint "note">}}
This guide discusses creating Kubernetes secrets.
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
with an external secret store.
{{</hint >}}
## Background
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
resource, the resource may generate resource-specific details. These details can include
usernames, passwords or connection details like an IP address.
Crossplane refers to this information as the _connection details_ or
_connection secrets_.
The Provider
defines what information to present as a _connection
detail_ from a managed resource.
<!-- vale gitlab.SentenceLength = NO -->
<!-- wordy because of type names -->
When a managed resource is part of a
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
and optionally, the
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
and where they're stored.
<!-- vale gitlab.SentenceLength = YES -->
{{<hint "note">}}
All the following examples use the same set of Compositions,
CompositeResourceDefinitions and Claims.
All examples rely on
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
to create resources.
{{<expand "Reference Composition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: xsecrettest.example.org
spec:
writeConnectionSecretsToNamespace: other-namespace
compositeTypeRef:
apiVersion: example.org/v1alpha1
kind: XSecretTest
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchControllerRef: true
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret1"
- name: user
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
spec:
forProvider: {}
- name: user2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: User
metadata:
labels:
docs.crossplane.io: user
spec:
forProvider: {}
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
- name: key2-password
fromConnectionSecretKey: password
- name: key2-secret
fromConnectionSecretKey: attribute.secret
- name: key2-smtp
fromConnectionSecretKey: attribute.ses_smtp_password_v4
patches:
- fromFieldPath: "metadata.uid"
toFieldPath: "spec.writeConnectionSecretToRef.name"
transforms:
- type: string
string:
fmt: "%s-secret2"
```
{{</expand >}}
{{<expand "Reference CompositeResourceDefinition" >}}
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xsecrettests.example.org
spec:
group: example.org
connectionSecretKeys:
- username
- password
- attribute.secret
- attribute.ses_smtp_password_v4
- key2-user
- key2-pass
- key2-secret
- key2-smtp
names:
kind: XSecretTest
plural: xsecrettests
claimNames:
kind: SecretTest
plural: secrettests
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
```
{{</ expand >}}
{{<expand "Reference Claim" >}}
```yaml
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
{{</expand >}}
{{</hint >}}
## Connection secrets in a managed resource
<!-- vale gitlab.Substitutions = NO -->
<!-- vale gitlab.SentenceLength = NO -->
<!-- under 25 words -->
When a managed resource creates connection secrets, Crossplane can write the
secrets to a
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
or an
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
<!-- vale gitlab.SentenceLength = YES -->
<!-- vale gitlab.Substitutions = YES -->
Creating an individual managed resource shows the connection secrets the
resource creates.
{{<hint "note" >}}
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
documentation for more information on configuring resources and storing
connection secrets for individual resources.
{{< /hint >}}
For example, create an
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
connection secrets in a Kubernetes secret named
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
in the
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
```yaml {label="mr"}
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
metadata:
name: test-accesskey
spec:
forProvider:
userSelector:
matchLabels:
docs.crossplane.io: user
writeConnectionSecretToRef:
namespace: default
name: my-accesskey-secret
```
View the Kubernetes secret to see the connection details from the managed
resource.
This includes an
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
```yaml {label="mrSecret",copy-lines="1"}
kubectl describe secret my-accesskey-secret
Name: my-accesskey-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: connection.crossplane.io/v1alpha1
Data
====
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
username: 20 bytes
```
Compositions and CompositeResourceDefinitions require the exact names of the
secrets generated by a resource.
## Connection secrets in Compositions
Resources in a Composition that create connection details still create a
secret object containing their connection details.
Crossplane also generates
another secret object for each composite resource,
containing the secrets from all the defined resources.
For example, a Composition defines two
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
objects.
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
the resource
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane also creates a secret object for the entire Composition
saved in the namespace defined by
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
with a Crossplane generated name.
```yaml {label="comp1",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key1
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1-secret
- name: key2
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2-secret
# Removed for brevity
```
After applying a Claim, view the Kubernetes secrets to see three secret objects
created.
The secret
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="6">}}key1{{</hover>}},
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
Crossplane creates another secret in the namespace
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
secrets from resource in the Composition.
```shell {label="compGetSec",copy-lines="1"}
kubectl get secrets -A
NAMESPACE NAME TYPE DATA AGE
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
```
Although Crossplane creates a secret object, by default, Crossplane doesn't add
any data to the object.
```yaml {copy-lines="none"}
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
```
The Composition must list the connection secrets to store for each resource.
Use the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
each resource and define the secret keys the resource creates.
{{<hint "warning">}}
You can't change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
of a Composition.
You must delete and
recreate the Composition to change the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
{{</hint >}}
```yaml {label="comp2",copy-lines="16-20"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
apiVersion: iam.aws.upbound.io/v1beta1
kind: AccessKey
spec:
forProvider:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- fromConnectionSecretKey: password
- fromConnectionSecretKey: attribute.secret
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
# Removed for brevity
```
After applying a Claim the composite resource secret object contains the list of
keys listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
```shell {copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
password: 40 bytes
```
{{<hint "important">}}
If a key isn't listed in the
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
it isn't stored in the secret object.
{{< /hint >}}
### Managing conflicting secret keys
If resources produce conflicting keys, create a unique name with a connection
details
{{<hover label="comp3" line="25">}}name{{</hover>}}.
```yaml {label="comp3",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
The secret object contains both keys,
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
and
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
```shell {label="comp3Sec",copy-lines="1"}
kubectl describe secret -n other-namespace
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
Namespace: other-namespace
Type: connection.crossplane.io/v1alpha1
Data
====
username: 20 bytes
key2-user: 20 bytes
# Removed for brevity.
```
## Connection secrets in Composite Resource Definitions
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
put in the combined secret and provided to a Claim.
By default an XRD writes all secret keys listed in the composed resource
`connectionDetails` to the combined secret object.
Limit the keys passed to the combined secret object and Claims with a
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
the secret key names to create. Crossplane only adds the keys listed to the
combined secret.
{{<hint "warning">}}
You can't change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
You must delete and
recreate the XRD to change the
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
{{</hint >}}
For example, an XRD may restrict the secrets to only the
{{<hover label="xrd" line="5">}}username{{</hover>}},
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
```yaml {label="xrd",copy-lines="4-12"}
kind: CompositeResourceDefinition
spec:
# Removed for brevity.
connectionSecretKeys:
- username
- password
- key2-user
```
The secret from an individual resource contains all the resources detailed in
the Composition's `connectionDetails`.
```shell {label="xrdSec",copy-lines="1"}
kubectl describe secret key1 -n docs
Name: key1
Namespace: docs
Data
====
password: 40 bytes
username: 20 bytes
attribute.secret: 40 bytes
attribute.ses_smtp_password_v4: 44 bytes
```
The Claim's secret only contains the
keys allowed by the XRD
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
fields.
```shell {label="xrdSec2",copy-lines="2"}
kubectl describe secret my-access-key-secret
Name: my-access-key-secret
Data
====
key2-user: 20 bytes
password: 40 bytes
username: 20 bytes
```
## Secret objects
Compositions create a secret object for each resource and an extra secret
containing all the secrets from all resources.
Crossplane saves the resource secret objects in the location defined by the
resource's
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
Crossplane saves the combined secret with a Crossplane generated name in the
namespace defined in the Composition's
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
```yaml {label="comp4",copy-lines="none"}
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
writeConnectionSecretsToNamespace: other-namespace
resources:
- name: key
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key1
connectionDetails:
- fromConnectionSecretKey: username
- name: key2
base:
kind: AccessKey
spec:
# Removed for brevity
writeConnectionSecretToRef:
namespace: docs
name: key2
connectionDetails:
- name: key2-user
fromConnectionSecretKey: username
```
If a Claim uses a secret, it's stored in the same namespace as the Claim with
the name defined in the Claim's
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
```yaml {label="claim3",copy-lines="none"}
apiVersion: example.org/v1alpha1
kind: SecretTest
metadata:
name: test-secrets
namespace: default
spec:
writeConnectionSecretToRef:
name: my-access-key-secret
```
After applying the Claim Crossplane creates the following secrets:
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
* The composite resource secret object in the
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
Composition's `writeConnectionSecretsToNamespace`.
```shell {label="allSec",copy-lines="none"}
kubectl get secret -A
NAMESPACE NAME TYPE DATA AGE
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
docs key1 connection.crossplane.io/v1alpha1 4 31m
docs key2 connection.crossplane.io/v1alpha1 4 31m
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
```

View File

@ -357,7 +357,7 @@ Crossplane supports the following policies:
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. | | `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. | | `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. | | `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| `Update` | Crossplane changes the external resource when changing the managed resource. | | `Update` | Crossplane changes the external resource when changing the managed resource. |
{{</table >}} {{</table >}}
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | | {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). | | | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
{{< /table >}} {{< /table >}}
@ -567,7 +567,7 @@ metadata:
{{<hint "tip" >}} {{<hint "tip" >}}
Read the Read the
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}}) [Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
guide for details on using StoreConfig objects. guide for details on using StoreConfig objects.
{{< /hint >}} {{< /hint >}}

View File

@ -404,7 +404,7 @@ If you remove the Provider first, you must manually delete external resources
through your cloud provider. Managed resources must be manually deleted by through your cloud provider. Managed resources must be manually deleted by
removing their finalizers. removing their finalizers.
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}). For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
{{< /hint >}} {{< /hint >}}
## Verify a Provider ## Verify a Provider
@ -593,12 +593,12 @@ replacement for Controller configuration and is available in v1.14+.
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
the Provider's pod. The the Provider's pod. The
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1) [Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
defines the supported set of ControllerConfig settings. defines the supported set of ControllerConfig settings.
The most common use case for ControllerConfigs are providing `args` to a The most common use case for ControllerConfigs are providing `args` to a
Provider's pod enabling optional services. For example, enabling Provider's pod enabling optional services. For example, enabling
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider) [external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
for a Provider. for a Provider.
Each Provider determines their supported set of `args`. Each Provider determines their supported set of `args`.

View File

@ -0,0 +1,5 @@
---
title: Guides
weight: 100
description: Crossplane integrations and detailed examples.
---

View File

@ -0,0 +1,216 @@
---
title: Configuring Crossplane with Argo CD
weight: 270
---
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
cluster into a Universal Control Plane for all of your resources. There are
configuration details required in order for the two to work together properly.
This doc will help you understand these requirements. It is recommended to use
Argo CD version 2.4.8 or later with Crossplane.
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
### Configuring Argo CD with Crossplane
#### Set Resource Tracking Method
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
to be configured to use the annotation mechanism.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
```
#### Set Health Status
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
of Provider's CRDs.
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
{{<hint "note">}}
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
{{</hint>}}
```yaml {label="argocfg"}
apiVersion: v1
kind: ConfigMap
data:
application.resourceTrackingMethod: annotation
resource.customizations: |
"*.upbound.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Ready" then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
"*.crossplane.io/*":
health.lua: |
health_status = {
status = "Progressing",
message = "Provisioning ..."
}
local function contains (table, val)
for i, v in ipairs(table) do
if v == val then
return true
end
end
return false
end
local has_no_status = {
"Composition",
"CompositionRevision",
"DeploymentRuntimeConfig",
"ControllerConfig",
"ProviderConfig",
"ProviderConfigUsage"
}
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
health_status.status = "Healthy"
health_status.message = "Resource is in use."
return health_status
end
return health_status
end
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "LastAsyncOperation" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if condition.type == "Synced" then
if condition.status == "False" then
health_status.status = "Degraded"
health_status.message = condition.message
return health_status
end
end
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
if condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Resource is up-to-date."
return health_status
end
end
end
return health_status
```
#### Set Resource Exclusion
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
```yaml
apiVersion: v1
kind: ConfigMap
data:
resource.exclusions: |
- apiGroups:
- "*"
kinds:
- ProviderConfigUsage
```
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
#### Increase K8s Client QPS
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.

View File

@ -0,0 +1,10 @@
---
title: Disaster Recovery with Crossplane
weight: 10
---
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
using Crossplane to provision resources and Velero for Kubernetes backup and
recovery.
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).

View File

@ -0,0 +1,285 @@
---
title: Import Existing Resources
weight: 200
---
If you have resources that are already provisioned in a Provider,
you can import them as managed resources and let Crossplane manage them.
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
field enables importing external resources into Crossplane.
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
or [automatically]({{<ref "#import-resources-automatically">}}).
## Import resources manually
Crossplane can discover and import existing Provider resources by matching the
`crossplane.io/external-name` annotation in a managed resource.
To import an existing external resource in a Provider, create a new managed
resource with the `crossplane.io/external-name` annotation. Set the annotation
value to the name of the resource in the Provider.
For example, to import an existing GCP Network named
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
create a new managed resource and use the
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
annotation.
```yaml {label="annotation",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
annotations:
crossplane.io/external-name: my-existing-network
```
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
field can be anything you want. For example,
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
{{< hint "note" >}}
This name is the
name of the Kubernetes object. It's not related to the resource name inside the
Provider.
{{< /hint >}}
```yaml {label="name",copy-lines="none"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
```
Leave the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
Crossplane imports the settings and automatically applies them to the managed
resource.
{{< hint "important" >}}
If the managed resource has _required_ fields in the
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
the `forProvider` field.
The values of those fields must match what's inside the Provider or Crossplane
overwrites the existing values.
{{< /hint >}}
```yaml {label="fp",copy-lines="all"}
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
name: imported-network
annotations:
crossplane.io/external-name: my-existing-network
spec:
forProvider: {}
```
Crossplane now controls and manages this imported resource. Any changes to the
managed resource `spec` changes the external resource.
## Import resources automatically
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
Crossplane imports observe only resources but never changes or deletes the
resources.
{{<hint "important" >}}
The managed resource `managementPolicies` option is a beta feature.
The Provider determines support for management policies.
Refer to the Provider's documentation to see if the Provider supports
management policies.
{{< /hint >}}
<!-- vale off -->
### Apply the Observe management policy
<!-- vale on -->
Create a new managed resource matching the
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
to import and add
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
For example, to import a GCP SQL DatabaseInstance, create a new resource with
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
set.
```yaml {label="oo-policy",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
spec:
managementPolicies: ["Observe"]
```
### Add the external-name annotation
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation for the resource. This name must match the name inside the Provider.
For example, for a GCP database named
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
the
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
annotation with the value
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
```yaml {label="oo-ex-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Create a Kubernetes object name
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
Kubernetes object.
For example, name the Kubernetes object
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
```yaml {label="oo-name",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
```
### Identify a specific external resource
If more than one resource inside the Provider shares the same name, identify the
specific resource with a unique
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
For example, only import the GCP SQL database in the
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
```yaml {label="oo-region"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: "us-central1"
```
### Apply the managed resource
Apply the new managed resource. Crossplane syncs the status of the external
resource in the cloud with the newly created managed resource.
### View the discovered resource
Crossplane discovers the managed resource and populates the
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
fields with the values from the external resource.
```yaml {label="ooPopulated",copy-lines="none"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["Observe"]
forProvider:
region: us-central1
status:
atProvider:
connectionName: crossplane-playground:us-central1:my-external-database
databaseVersion: POSTGRES_14
deletionProtection: true
firstIpAddress: 35.184.74.79
id: my-external-database
publicIpAddress: 35.184.74.79
region: us-central1
# Removed for brevity
settings:
- activationPolicy: ALWAYS
availabilityType: REGIONAL
diskSize: 100
# Removed for brevity
pricingPlan: PER_USE
tier: db-custom-4-26624
version: 4
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
<!-- vale off -->
## Control imported ObserveOnly resources
<!-- vale on -->
Crossplane can take active control of observe only imported resources by
changing the `managementPolicies` after import.
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
of the managed resource to
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
Copy any required parameter values from
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
{{< hint "tip" >}}
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
{{< /hint >}}
```yaml {label="fc"}
apiVersion: sql.gcp.upbound.io/v1beta1
kind: DatabaseInstance
metadata:
name: my-imported-database
annotations:
crossplane.io/external-name: my-external-database
spec:
managementPolicies: ["*"]
forProvider:
databaseVersion: POSTGRES_14
region: us-central1
settings:
- diskSize: 100
tier: db-custom-4-26624
status:
atProvider:
databaseVersion: POSTGRES_14
region: us-central1
# Removed for brevity
settings:
- diskSize: 100
tier: db-custom-4-26624
# Removed for brevity
conditions:
- lastTransitionTime: "2023-02-22T07:16:51Z"
reason: Available
status: "True"
type: Ready
- lastTransitionTime: "2023-02-22T11:16:45Z"
reason: ReconcileSuccess
status: "True"
type: Synced
```
Crossplane now fully manages the imported resource. Crossplane applies any
changes to the managed resource in the Provider's external resource.

View File

@ -0,0 +1,323 @@
---
title: Multi-Tenant Crossplane
weight: 240
---
This guide describes how to use Crossplane effectively in multi-tenant
environments by utilizing Kubernetes primitives and compatible policy
enforcement projects in the cloud-native ecosystem.
## TL;DR
Infrastructure operators in multi-tenant Crossplane environments typically
utilize composition and Kubernetes RBAC to define lightweight, standardized
policies that dictate what level of self-service developers are given when
requesting infrastructure. This is primarily achieved through exposing abstract
resource types at the namespace scope, defining `Roles` for teams and
individuals within that namespace, and patching the `spec.providerConfigRef` of
the underlying managed resources so that they use a specific `ProviderConfig`
and credentials when provisioned from each namespace. Larger organizations, or
those with more complex environments, may choose to incorporate third-party
policy engines, or scale to multiple Crossplane clusters. The following sections
describe each of these scenarios in greater detail.
- [TL;DR](#tldr)
- [Background](#background)
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
- [Namespace Scoped Claims](#namespace-scoped-claims)
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
- [Control Plane of Control Planes](#control-plane-of-control-planes)
## Background
Crossplane is designed to run in multi-tenant environments where many teams are
consuming the services and abstractions provided by infrastructure operators in
the cluster. This functionality is facilitated by two major design patterns in
the Crossplane ecosystem.
### Cluster-Scoped Managed Resources
Typically, Crossplane providers, which supply granular [managed resources] that
reflect an external API, authenticate by using a `ProviderConfig` object that
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
filesystem, or an environment variable). Then, every managed resource references
a `ProviderConfig` that points to credentials with sufficient permissions to
manage that resource type.
For example, the following `ProviderConfig` for `provider-aws` points to a
Kubernetes `Secret` with AWS credentials.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: cool-aws-creds
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-creds
key: creds
```
If a user desired for these credentials to be used to provision an
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
```yaml
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
metadata:
name: rdsmysql
spec:
forProvider:
region: us-east-1
dbInstanceClass: db.t3.medium
masterUsername: masteruser
allocatedStorage: 20
engine: mysql
engineVersion: "5.6.35"
skipFinalSnapshotBeforeDeletion: true
providerConfigRef:
name: cool-aws-creds # name of ProviderConfig above
writeConnectionSecretToRef:
namespace: crossplane-system
name: aws-rdsmysql-conn
```
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
the RDS controller in `provider-aws` will resolve this reference by fetching the
`ProviderConfig`, obtaining the credentials it points to, and using those
credentials to reconcile the `RDSInstance`. This means that anyone who has been
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
In practice, Crossplane assumes that only folks acting as infrastructure
administrators or platform builders will interact directly with cluster-scoped
resources.
### Namespace Scoped Claims
While managed resources exist at the cluster scope, composite resources, which
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
the cluster or namespace scope. Platform builders define XRDs and
**Compositions** that specify what granular managed resources should be created
in response to the creation of an instance of the XRD. More information about
this architecture can be found in the [Composition] documentation.
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
defined will have a namespace-scoped variant.
```yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xmysqlinstances.example.org
spec:
group: example.org
names:
kind: XMySQLInstance
plural: xmysqlinstances
claimNames:
kind: MySQLInstance
plural: mysqlinstances
...
```
When the example above is created, Crossplane will produce two
[CustomResourceDefinitions]:
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
**Composite Resource (XR)**.
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
**Claim (XRC)**.
Platform builders may choose to define an arbitrary number of Compositions that
map to these types, meaning that creating a `MySQLInstance` in a given namespace
can result in the creations of any set of managed resources at the cluster
scope. For instance, creating a `MySQLInstance` could result in the creation of
the `RDSInstance` defined above.
## Single Cluster Multi-Tenancy
Depending on the size and scope of an organization, platform teams may choose to
run one central Crossplane control plane, or many different ones for each team
or business unit. This section will focus on servicing multiple teams within a
single cluster, which may or may not be one of many other Crossplane clusters in
the organization.
### Composition as an Isolation Mechanism
While managed resources always reflect every field that the underlying provider
API exposes, XRDs can have any schema that a platform builder chooses. The
fields in the XRD schema can then be patched onto fields in the underlying
managed resource defined in a Composition, essentially exposing those fields as
configurable to the consumer of the XR or XRC.
This feature serves as a lightweight policy mechanism by only giving the
consumer the ability to customize the underlying resources to the extent the
platform builder desires. For instance, in the examples above, a platform
builder may choose to define a `spec.location` field in the schema of the
`XMySQLInstance` that is an enum with options `east` and `west`. In the
Composition, those fields could map to the `RDSInstance` `spec.region` field,
making the value either `us-east-1` or `us-west-1`. If no other patches were
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
create a very specifically configured `RDSInstance`, where they can only decide
the region where it lives and they are restricted to two options.
This model is in contrast to many infrastructure as code tools where the end
user must have provider credentials to create the underlying resources that are
rendered from the abstraction. Crossplane takes a different approach, defining
various credentials in the cluster (using the `ProviderConfig`), then giving
only the provider controllers the ability to utilize those credentials and
provision infrastructure on the users behalf. This creates a consistent
permission model, even when using many providers with differing IAM models, by
standardizing on Kubernetes RBAC.
### Namespaces as an Isolation Mechanism
While the ability to define abstract schemas and patches to concrete resource
types using composition is powerful, the ability to define Claim types at the
namespace scope enhances the functionality further by enabling RBAC to be
applied with namespace restrictions. Most users in a cluster do not have access
to cluster-scoped resources as they are considered only relevant to
infrastructure admins by both Kubernetes and Crossplane.
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
builder may choose to define permissions on `MySQLInstance` at the namespace
scope using a `Role`. This allows for giving users the ability to create and
manage `MySQLInstances` in their given namespace, but not the ability to see
those defined in other namespaces.
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
be utilized to configure managed resources based on the namespace in which the
corresponding XRC was defined. This is especially useful if a platform builder
wants to designate specific credentials or a set of credentials that users in a
given namespace can utilize when provisioning infrastructure using an XRC. This
can be accomplished today by creating one or more `ProviderConfig` objects that
include the name of the namespace in the `ProviderConfig` name. For example, if
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
credentials when the provider controller creates the underlying `RDSInstance`,
the platform builder could:
1. Define a `ProviderConfig` with name `team-1`.
```yaml
apiVersion: aws.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: team-1
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: team-1-creds
key: creds
```
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
to the `providerConfigRef` of the `RDSInstance`.
```yaml
...
resources:
- base:
apiVersion: database.aws.crossplane.io/v1beta1
kind: RDSInstance
spec:
forProvider:
...
patches:
- fromFieldPath: spec.claimRef.namespace
toFieldPath: spec.providerConfigRef.name
policy:
fromFieldPath: Required
```
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
namespace the corresponding `MySQLInstance` was created in.
> Note that this model currently only allows for a single `ProviderConfig` per
> namespace. However, future Crossplane releases should allow for defining a set
> of `ProviderConfig` that can be selected from using [Multiple Source Field
> patching].
### Policy Enforcement with Open Policy Agent
In some Crossplane deployment models, only using composition and RBAC to define
policy will not be flexible enough. However, because Crossplane brings
management of external infrastructure to the Kubernetes API, it is well suited
to integrate with other projects in the cloud-native ecosystem. Organizations
and individuals that need a more robust policy engine, or just prefer a more
general language for defining policy, often turn to [Open Policy Agent] (OPA).
OPA allows platform builders to write custom logic in [Rego], a domain-specific
language. Writing policy in this manner allows for not only incorporating the
information available in the specific resource being evaluated, but also using
other state represented in the cluster. Crossplane users typically install OPA's
[Gatekeeper] to make policy management as streamlined as possible.
> A live demo of using OPA with Crossplane can be viewed [here].
## Multi-Cluster Multi-Tenancy
Organizations that deploy Crossplane across many clusters typically take
advantage of two major features that make managing multiple control planes much
simpler.
### Reproducible Platforms with Configuration Packages
[Configuration packages] allow platform builders to package their XRDs and
Compositions into [OCI images] that can be distributed via any OCI-compliant
image registry. These packages can also declare dependencies on providers,
meaning that a single package can declare all of the granular managed resources,
the controllers that must be deployed to reconcile them, and the abstract types
that expose the underlying resources using composition.
Organizations with many Crossplane deployments utilize Configuration packages to
reproduce their platform in each cluster. This can be as simple as installing
Crossplane with the flag to automatically install a Configuration package
alongside it.
```
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
```
### Control Plane of Control Planes
Taking the multi-cluster multi-tenancy model one step further, some
organizations opt to manage their many Crossplane clusters using a single
central Crossplane control plane. This requires setting up the central cluster,
then using a provider to spin up new clusters (such as an [EKS Cluster] using
[provider-aws]), then using [provider-helm] to install Crossplane into the new
remote cluster, potentially bundling a common Configuration package into each
install using the method described above.
This advanced pattern allows for full management of Crossplane clusters using
Crossplane itself, and when done properly, is a scalable solution to providing
dedicated control planes to many tenants within a single organization.
<!-- Named Links -->
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
[Composition]: {{<ref "../../master/concepts/compositions" >}}
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
[Open Policy Agent]: https://www.openpolicyagent.org/
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
[here]: https://youtu.be/TaF0_syejXc
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
[OCI images]: https://github.com/opencontainers/image-spec
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
[Cloudfoundry]: https://www.cloudfoundry.org/
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo

View File

@ -0,0 +1,49 @@
---
title: Self-Signed CA Certs
weight: 270
---
> Using self-signed certificates is not advised in production, it is
recommended to only use self-signed certificates for testing.
When Crossplane loads Configuration and Provider Packages from private
registries, it must be configured to trust the CA and Intermediate certs.
Crossplane needs to be installed via the Helm chart with the
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
## Configure
1. Create a CA Bundle (A file containing your Root and Intermediate
certificates in a specific order). This can be done with any text editor or
from the command line, so long as the resulting file contains all required crt
files in the proper order. In many cases, this will be either a single
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
order of the crt files should be from lowest to highest in signing order.
For example, if you have a chain of two certificates below your Root
certificate, you place the bottom level Intermediate cert at the beginning of
the file, then the Intermediate cert that singed that cert, then the Root cert
that signed that cert.
2. Save the files as `[yourdomain].ca-bundle`.
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
```
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
--from-file=ca-bundle=./[yourdomain].ca-bundle
```
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
`ca-bundle`.
> Providing Helm with parameter values is convered in the Helm docs,
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
in an `override.yaml` file would look like this:
```
registryCaBundleConfig:
name: ca-bundle-config
key: ca-bundle
```

View File

@ -0,0 +1,443 @@
---
title: Troubleshoot Crossplane
weight: 306
---
## Requested Resource Not Found
If you use the Crossplane CLI to install a `Provider` or
`Configuration` (e.g. `crossplane install provider
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
could not find the requested resource` error, more often than not, that is an
indicator that the Crossplane CLI you're using is outdated. In other words
some Crossplane API has been graduated from alpha to beta or stable and the old
plugin is not aware of this change.
## Resource Status and Conditions
Most Crossplane resources have a `status` section that can represent the current
state of that particular resource. Running `kubectl describe` against a
Crossplane resource will frequently give insightful information about its
condition. For example, to determine the status of a GCP `CloudSQLInstance`
managed resource use `kubectl describe` for the resource.
```shell {copy-lines="1"}
kubectl describe cloudsqlinstance my-db
Status:
Conditions:
Last Transition Time: 2019-09-16T13:46:42Z
Reason: Creating
Status: False
Type: Ready
```
Most Crossplane resources set the `Ready` condition. `Ready` represents the
availability of the resource - whether it is creating, deleting, available,
unavailable, binding, etc.
## Resource Events
Most Crossplane resources emit _events_ when something interesting happens. You
can see the events associated with a resource by running `kubectl describe` -
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
particular namespace by running `kubectl get events`.
```console
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
```
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
> are cluster scoped. Crossplane emits events for cluster scoped resources to
> the 'default' namespace.
## Crossplane Logs
The next place to look to get more information or investigate a failure would be
in the Crossplane pod logs, which should be running in the `crossplane-system`
namespace. To get the current Crossplane logs, run the following:
```shell
kubectl -n crossplane-system logs -lapp=crossplane
```
> Note that Crossplane emits few logs by default - events are typically the best
> place to look for information about what Crossplane is doing. You may need to
> restart Crossplane with the `--debug` flag if you can't find what you're
> looking for.
## Provider Logs
Remember that much of Crossplane's functionality is provided by providers. You
can use `kubectl logs` to view provider logs too. By convention, they also emit
few logs by default.
```shell
kubectl -n crossplane-system logs <name-of-provider-pod>
```
All providers maintained by the Crossplane community mirror Crossplane's support
of the `--debug` flag. The easiest way to set flags on a provider is to create a
`ControllerConfig` and reference it from the `Provider`:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: debug-config
spec:
args:
- --debug
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: debug-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Compositions and composite resource definition
### General troubleshooting steps
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
2. If there are errors in the events, address them.
3. If there are no errors, follow its sub-resources.
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
4. Repeat this process for each resource returned.
{{< hint "note" >}}
The rest of this section show you how to debug issues related to compositions without using external tooling.
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
{{< /hint >}}
### Examples
#### Composition
<!-- vale Google.WordList = NO -->
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
The example application never reaches available state as shown below.
1. View the claim.
```bash
kubectl describe exampleapp example-application
Status:
Conditions:
Last Transition Time: 2022-03-01T22:57:38Z
Reason: Composite resource claim is waiting for composite resource to become Ready
Status: False
Type: Ready
Events: <none>
```
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
```bash
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XExampleApp",
"name": "example-application-xqlsz"
}
```
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
4. View the cluster scoped resource's events.
```bash
kubectl describe xexampleapp example-application-xqlsz
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
```
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
```bash
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
[
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XDynamoDBTable",
"name": "example-application-xqlsz-6j9nm"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-lp9wt"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "XIAMPolicy",
"name": "example-application-xqlsz-btwkn"
},
{
"apiVersion": "awsblueprints.io/v1alpha1",
"kind": "IRSA"
}
]
```
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
#### Composite resource definition
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
1. Get the XRD
```bash
kubectl get xrd testing.awsblueprints.io
NAME ESTABLISHED OFFERED AGE
testing.awsblueprints.io 66s
```
2. Notice its status it not established. You describe this XRD to get its events.
```bash
kubectl describe xrd testing.awsblueprints.io
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
```
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
#### Providers
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
documentation]({{<ref "/master/concepts/packages">}})
for more information about Crossplane Packages.
If you are experiencing provider issues, steps below are a good starting point.
1. Check the status of provider object.
```bash
kubectl describe provider.pkg.crossplane.io provider-aws
Status:
Conditions:
Last Transition Time: 2022-08-04T16:19:44Z
Reason: HealthyPackageRevision
Status: True
Type: Healthy
Last Transition Time: 2022-08-04T16:14:29Z
Reason: ActivePackageRevision
Status: True
Type: Installed
Current Identifier: crossplane/provider-aws:v0.29.0
Current Revision: provider-aws-a2e16ca2fc1a
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
```
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
```bash
kubectl get providerrevision provider-aws-a2e16ca2fc1a
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
```
When you describe the object, you find all CRDs managed by this object.
```bash
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
Status:
Controller Ref:
Name: provider-aws-a2e16ca2fc1a
Object Refs:
API Version: apiextensions.k8s.io/v1
Kind: CustomResourceDefinition
Name: natgateways.ec2.aws.crossplane.io
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
....
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
```
The event field also indicates any issues that may have occurred during this process.
<!-- vale Google.WordList = YES -->
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
```bash
kubectl get deployment -n crossplane-system
NAME READY UP-TO-DATE AVAILABLE AGE
crossplane 1/1 1 1 105d
crossplane-rbac-manager 1/1 1 1 105d
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
kubectl get pods -n crossplane-system
NAME READY STATUS RESTARTS AGE
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
```
If there are any pods failing, check its logs and remedy the problem.
## Pausing Crossplane
Sometimes, for example when you encounter a bug, it can be useful to pause
Crossplane if you want to stop it from actively attempting to manage your
resources. To pause Crossplane without deleting all of its resources, run the
following command to simply scale down its deployment:
```bash
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
```
Once you have been able to rectify the problem or smooth things out, you can
unpause Crossplane simply by scaling its deployment back up:
```bash
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
```
## Pausing Providers
Providers can also be paused when troubleshooting an issue or orchestrating a
complex migration of resources. Creating and referencing a `ControllerConfig` is
the easiest way to scale down a provider, and the `ControllerConfig` can be
modified or the reference can be removed to scale it back up:
```yaml
apiVersion: pkg.crossplane.io/v1alpha1
kind: ControllerConfig
metadata:
name: scale-config
spec:
replicas: 0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
controllerConfigRef:
name: scale-config
```
> Note that a reference to a `ControllerConfig` can be added to an already
> installed `Provider` and it will update its `Deployment` accordingly.
## Deleting When a Resource Hangs
The resources that Crossplane manages will automatically be cleaned up so as not
to leave anything running behind. This is accomplished by using finalizers, but
in certain scenarios the finalizer can prevent the Kubernetes object from
getting deleted.
To deal with this, we essentially want to patch the object to remove its
finalizer, which will then allow it to be deleted completely. Note that this
won't necessarily delete the external resource that Crossplane was managing, so
you will want to go to your cloud provider's console and look there for any
lingering resources to clean up.
In general, a finalizer can be removed from an object with this command:
```shell
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
```
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
`my-db`, you can remove its finalizer with:
```shell
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
```
## Tips, Tricks, and Troubleshooting
In this section we'll cover some common tips, tricks, and troubleshooting steps
for working with Composite Resources. If you're trying to track down why your
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
has some useful information.
### Troubleshooting Claims and XRs
Crossplane relies heavily on status conditions and events for troubleshooting.
You can see both using `kubectl describe` - for example:
```console
# Describe the PostgreSQLInstance claim named my-db
kubectl describe postgresqlinstance.database.example.org my-db
```
Per Kubernetes convention, Crossplane keeps errors close to the place they
happen. This means that if your claim is not becoming ready due to an issue with
your `Composition` or with a composed resource you'll need to "follow the
references" to find out why. Your claim will only tell you that the XR is not
yet ready.
To follow the references:
1. Find your XR by running `kubectl describe` on your claim and looking for its
"Resource Ref" (aka `spec.resourceRef`).
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
with the `Composition` you're using, if any.
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
resources.
1. Run `kubectl describe` on each referenced composed resource to determine
whether it is ready and what issues, if any, it is encountering.
<!-- Named Links -->
[Requested Resource Not Found]: #requested-resource-not-found
[install Crossplane CLI]: "../getting-started/install-configure"
[Resource Status and Conditions]: #resource-status-and-conditions
[Resource Events]: #resource-events
[Crossplane Logs]: #crossplane-logs
[Provider Logs]: #provider-logs
[Pausing Crossplane]: #pausing-crossplane
[Pausing Providers]: #pausing-providers
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
[Installing Crossplane Package]: #installing-crossplane-package
[Crossplane package]: /master/concepts/packages/
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons

Some files were not shown because too many files have changed in this diff Show More