From 2b598cb79f494b6de0dd1c6821c6568f3693a4b2 Mon Sep 17 00:00:00 2001 From: Pete Lumbis Date: Mon, 22 Apr 2024 15:44:32 -0400 Subject: [PATCH] Remove knowledge base section (#755) Remove KB and add redirects --- content/contribute/contribute.md | 37 - content/knowledge-base/_index.md | 32 - content/knowledge-base/guides/_index.md | 12 - .../guides/composition-revisions.md | 136 --- content/knowledge-base/integrations/_index.md | 7 - content/master/api/_index.md | 2 +- content/master/cli/_index.md | 4 +- content/master/concepts/_index.md | 2 +- content/master/concepts/claims.md | 2 +- .../composite-resource-definitions.md | 2 +- .../master/concepts/composite-resources.md | 10 +- .../master/concepts/composition-functions.md | 2 +- .../master/concepts/composition-revisions.md | 445 ++++++++++ content/master/concepts/compositions.md | 8 +- content/master/concepts/connection-details.md | 607 +++++++++++++ content/master/concepts/managed-resources.md | 8 +- content/master/concepts/providers.md | 6 +- .../master/getting-started/provider-azure.md | 2 +- content/master/guides/_index.md | 5 + .../guides/crossplane-with-argo-cd.md} | 0 .../guides/disaster-recovery.md | 0 .../guides/import-existing-resources.md | 0 .../guides/multi-tenant.md | 0 .../guides/self-signed-ca-certs.md | 0 .../master/guides/troubleshoot-crossplane.md | 443 +++++++++ .../guides}/vault-as-secret-store.md | 0 .../guides}/vault-injection.md | 0 .../write-a-composition-function-in-go.md | 838 ++++++++++++++++++ .../write-a-composition-function-in-python.md | 745 ++++++++++++++++ .../learn-more.md => master/learn/_index.md} | 3 +- .../learn}/feature-lifecycle.md | 0 .../guides => master/learn}/release-cycle.md | 0 content/master/release-notes/_index.md | 2 +- content/master/software/_index.md | 4 +- content/master/software/upgrade.md | 2 +- content/v1.13/concepts/claims.md | 2 +- .../composite-resource-definitions.md | 2 +- content/v1.13/concepts/composite-resources.md | 10 +- .../concepts/composition-revisions.md} | 161 +++- content/v1.13/concepts/compositions.md | 8 +- .../concepts}/connection-details.md | 2 +- content/v1.13/concepts/managed-resources.md | 6 +- content/v1.13/concepts/providers.md | 4 +- content/v1.13/guides/_index.md | 5 + .../v1.13/guides/crossplane-with-argo-cd.md | 216 +++++ content/v1.13/guides/disaster-recovery.md | 10 + .../v1.13/guides/import-existing-resources.md | 285 ++++++ content/v1.13/guides/multi-tenant.md | 323 +++++++ content/v1.13/guides/self-signed-ca-certs.md | 49 + .../guides/troubleshoot-crossplane.md} | 0 content/v1.13/guides/vault-as-secret-store.md | 627 +++++++++++++ content/v1.13/guides/vault-injection.md | 506 +++++++++++ .../write-a-composition-function-in-go.md | 10 +- .../write-a-composition-function-in-python.md | 10 +- content/v1.13/learn/_index.md | 34 + content/v1.13/learn/feature-lifecycle.md | 56 ++ content/v1.13/learn/release-cycle.md | 100 +++ content/v1.14/concepts/claims.md | 2 +- .../composite-resource-definitions.md | 2 +- content/v1.14/concepts/composite-resources.md | 10 +- .../v1.14/concepts/composition-functions.md | 2 +- .../v1.14/concepts/composition-revisions.md | 444 ++++++++++ content/v1.14/concepts/compositions.md | 8 +- content/v1.14/concepts/connection-details.md | 607 +++++++++++++ content/v1.14/concepts/managed-resources.md | 6 +- content/v1.14/concepts/providers.md | 6 +- content/v1.14/guides/_index.md | 5 + .../v1.14/guides/crossplane-with-argo-cd.md | 216 +++++ content/v1.14/guides/disaster-recovery.md | 10 + .../v1.14/guides/import-existing-resources.md | 285 ++++++ content/v1.14/guides/multi-tenant.md | 323 +++++++ content/v1.14/guides/self-signed-ca-certs.md | 49 + .../v1.14/guides/troubleshoot-crossplane.md | 443 +++++++++ content/v1.14/guides/vault-as-secret-store.md | 627 +++++++++++++ content/v1.14/guides/vault-injection.md | 506 +++++++++++ .../write-a-composition-function-in-go.md | 838 ++++++++++++++++++ .../write-a-composition-function-in-python.md | 745 ++++++++++++++++ content/v1.14/learn/_index.md | 34 + content/v1.14/learn/feature-lifecycle.md | 56 ++ content/v1.14/learn/release-cycle.md | 100 +++ content/v1.14/release-notes/1.14.0.md | 2 +- content/v1.15/api/_index.md | 2 +- content/v1.15/cli/_index.md | 4 +- content/v1.15/concepts/_index.md | 2 +- content/v1.15/concepts/claims.md | 2 +- .../composite-resource-definitions.md | 2 +- content/v1.15/concepts/composite-resources.md | 10 +- .../v1.15/concepts/composition-functions.md | 2 +- .../v1.15/concepts/composition-revisions.md | 445 ++++++++++ content/v1.15/concepts/compositions.md | 8 +- content/v1.15/concepts/connection-details.md | 607 +++++++++++++ content/v1.15/concepts/managed-resources.md | 6 +- content/v1.15/concepts/providers.md | 6 +- content/v1.15/guides/_index.md | 5 + .../v1.15/guides/crossplane-with-argo-cd.md | 216 +++++ content/v1.15/guides/disaster-recovery.md | 10 + .../v1.15/guides/import-existing-resources.md | 285 ++++++ content/v1.15/guides/multi-tenant.md | 323 +++++++ content/v1.15/guides/self-signed-ca-certs.md | 49 + .../v1.15/guides/troubleshoot-crossplane.md | 443 +++++++++ content/v1.15/guides/vault-as-secret-store.md | 627 +++++++++++++ content/v1.15/guides/vault-injection.md | 506 +++++++++++ .../write-a-composition-function-in-go.md | 838 ++++++++++++++++++ .../write-a-composition-function-in-python.md | 745 ++++++++++++++++ content/v1.15/learn/_index.md | 35 + content/v1.15/learn/feature-lifecycle.md | 56 ++ content/v1.15/learn/release-cycle.md | 100 +++ content/v1.15/release-notes/_index.md | 2 +- content/v1.15/software/_index.md | 4 +- content/v1.15/software/upgrade.md | 2 +- netlify.toml | 114 +++ themes/geekboot/layouts/_default/home.html | 55 +- .../layouts/partials/docs-sidebar.html | 4 - .../layouts/partials/feature-state-alert.html | 6 +- .../partials/sidebar/knowledge-base.html | 7 - 115 files changed, 16243 insertions(+), 403 deletions(-) delete mode 100644 content/knowledge-base/_index.md delete mode 100644 content/knowledge-base/guides/_index.md delete mode 100644 content/knowledge-base/guides/composition-revisions.md delete mode 100644 content/knowledge-base/integrations/_index.md create mode 100644 content/master/concepts/composition-revisions.md create mode 100644 content/master/concepts/connection-details.md create mode 100644 content/master/guides/_index.md rename content/{knowledge-base/integrations/argo-cd-crossplane.md => master/guides/crossplane-with-argo-cd.md} (100%) rename content/{knowledge-base => master}/guides/disaster-recovery.md (100%) rename content/{knowledge-base => master}/guides/import-existing-resources.md (100%) rename content/{knowledge-base => master}/guides/multi-tenant.md (100%) rename content/{knowledge-base => master}/guides/self-signed-ca-certs.md (100%) create mode 100644 content/master/guides/troubleshoot-crossplane.md rename content/{knowledge-base/integrations => master/guides}/vault-as-secret-store.md (100%) rename content/{knowledge-base/integrations => master/guides}/vault-injection.md (100%) create mode 100644 content/master/guides/write-a-composition-function-in-go.md create mode 100644 content/master/guides/write-a-composition-function-in-python.md rename content/{knowledge-base/guides/learn-more.md => master/learn/_index.md} (97%) rename content/{knowledge-base/guides => master/learn}/feature-lifecycle.md (100%) rename content/{knowledge-base/guides => master/learn}/release-cycle.md (100%) rename content/{knowledge-base/guides/composition-revisions-example.md => v1.13/concepts/composition-revisions.md} (62%) rename content/{knowledge-base/guides => v1.13/concepts}/connection-details.md (99%) create mode 100644 content/v1.13/guides/_index.md create mode 100644 content/v1.13/guides/crossplane-with-argo-cd.md create mode 100644 content/v1.13/guides/disaster-recovery.md create mode 100644 content/v1.13/guides/import-existing-resources.md create mode 100644 content/v1.13/guides/multi-tenant.md create mode 100644 content/v1.13/guides/self-signed-ca-certs.md rename content/{knowledge-base/guides/troubleshoot.md => v1.13/guides/troubleshoot-crossplane.md} (100%) create mode 100644 content/v1.13/guides/vault-as-secret-store.md create mode 100644 content/v1.13/guides/vault-injection.md rename content/{knowledge-base => v1.13}/guides/write-a-composition-function-in-go.md (98%) rename content/{knowledge-base => v1.13}/guides/write-a-composition-function-in-python.md (98%) create mode 100644 content/v1.13/learn/_index.md create mode 100644 content/v1.13/learn/feature-lifecycle.md create mode 100644 content/v1.13/learn/release-cycle.md create mode 100644 content/v1.14/concepts/composition-revisions.md create mode 100644 content/v1.14/concepts/connection-details.md create mode 100644 content/v1.14/guides/_index.md create mode 100644 content/v1.14/guides/crossplane-with-argo-cd.md create mode 100644 content/v1.14/guides/disaster-recovery.md create mode 100644 content/v1.14/guides/import-existing-resources.md create mode 100644 content/v1.14/guides/multi-tenant.md create mode 100644 content/v1.14/guides/self-signed-ca-certs.md create mode 100644 content/v1.14/guides/troubleshoot-crossplane.md create mode 100644 content/v1.14/guides/vault-as-secret-store.md create mode 100644 content/v1.14/guides/vault-injection.md create mode 100644 content/v1.14/guides/write-a-composition-function-in-go.md create mode 100644 content/v1.14/guides/write-a-composition-function-in-python.md create mode 100644 content/v1.14/learn/_index.md create mode 100644 content/v1.14/learn/feature-lifecycle.md create mode 100644 content/v1.14/learn/release-cycle.md create mode 100644 content/v1.15/concepts/composition-revisions.md create mode 100644 content/v1.15/concepts/connection-details.md create mode 100644 content/v1.15/guides/_index.md create mode 100644 content/v1.15/guides/crossplane-with-argo-cd.md create mode 100644 content/v1.15/guides/disaster-recovery.md create mode 100644 content/v1.15/guides/import-existing-resources.md create mode 100644 content/v1.15/guides/multi-tenant.md create mode 100644 content/v1.15/guides/self-signed-ca-certs.md create mode 100644 content/v1.15/guides/troubleshoot-crossplane.md create mode 100644 content/v1.15/guides/vault-as-secret-store.md create mode 100644 content/v1.15/guides/vault-injection.md create mode 100644 content/v1.15/guides/write-a-composition-function-in-go.md create mode 100644 content/v1.15/guides/write-a-composition-function-in-python.md create mode 100644 content/v1.15/learn/_index.md create mode 100644 content/v1.15/learn/feature-lifecycle.md create mode 100644 content/v1.15/learn/release-cycle.md delete mode 100644 themes/geekboot/layouts/partials/sidebar/knowledge-base.html diff --git a/content/contribute/contribute.md b/content/contribute/contribute.md index 052e5e0d..5db86af1 100644 --- a/content/contribute/contribute.md +++ b/content/contribute/contribute.md @@ -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 root. -### Types of content -Crossplane documentation has three content sections: -* The [Contributing Guide]({{}}) with details on - how to contribute to the Crossplane documentation. -* The [Knowledge Base]({{}}) is for content related to - Crossplane integrations, in-depth articles or how-to guides. -* [User documentation]({{}}) 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 Each page contains metadata called [front matter](https://gohugo.io/content-management/front-matter/). Each page diff --git a/content/knowledge-base/_index.md b/content/knowledge-base/_index.md deleted file mode 100644 index 1aa36dc7..00000000 --- a/content/knowledge-base/_index.md +++ /dev/null @@ -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 >}} -
- -The Crossplane Knowledge Base is a collection of documents covering recommended -practices, third-party integrations and Crossplane-related how-tos. - -## Topics -* [Configuration Guides]({{}}) covers topics related to operating - and using Crossplane. -* [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" >}}). \ No newline at end of file diff --git a/content/knowledge-base/guides/_index.md b/content/knowledge-base/guides/_index.md deleted file mode 100644 index 663b543a..00000000 --- a/content/knowledge-base/guides/_index.md +++ /dev/null @@ -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. - diff --git a/content/knowledge-base/guides/composition-revisions.md b/content/knowledge-base/guides/composition-revisions.md deleted file mode 100644 index d1533299..00000000 --- a/content/knowledge-base/guides/composition-revisions.md +++ /dev/null @@ -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]: {{}} -[Compositions]: {{}} -[canary]: https://martinfowler.com/bliki/CanaryRelease.html -[install-guide]: {{}} diff --git a/content/knowledge-base/integrations/_index.md b/content/knowledge-base/integrations/_index.md deleted file mode 100644 index f7f529bf..00000000 --- a/content/knowledge-base/integrations/_index.md +++ /dev/null @@ -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. diff --git a/content/master/api/_index.md b/content/master/api/_index.md index 5fbcb8d8..6075e613 100644 --- a/content/master/api/_index.md +++ b/content/master/api/_index.md @@ -1,5 +1,5 @@ --- -title: Crossplane API +title: API Reference weight: 400 description: "API details for Crossplane's core types" cascade: diff --git a/content/master/cli/_index.md b/content/master/cli/_index.md index 10150e54..35d0e2b1 100644 --- a/content/master/cli/_index.md +++ b/content/master/cli/_index.md @@ -1,6 +1,6 @@ --- -weight: 400 -title: Crossplane CLI +weight: 200 +title: CLI Reference description: "Documentation for the Crossplane command-line interface" --- diff --git a/content/master/concepts/_index.md b/content/master/concepts/_index.md index 7d00df9f..3c821d9e 100644 --- a/content/master/concepts/_index.md +++ b/content/master/concepts/_index.md @@ -1,6 +1,6 @@ --- title: Concepts -weight: 100 +weight: 50 description: Understand Crossplane's core components --- diff --git a/content/master/concepts/claims.md b/content/master/concepts/claims.md index bfda614b..65e62f72 100644 --- a/content/master/concepts/claims.md +++ b/content/master/concepts/claims.md @@ -204,4 +204,4 @@ spec: name: my-claim-secret ``` -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file diff --git a/content/master/concepts/composite-resource-definitions.md b/content/master/concepts/composite-resource-definitions.md index a4dbc9d2..38ce1d28 100644 --- a/content/master/concepts/composite-resource-definitions.md +++ b/content/master/concepts/composite-resource-definitions.md @@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`. {{}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). ### Set composite resource defaults XRDs can set default parameters for composite resources and Claims. diff --git a/content/master/concepts/composite-resources.md b/content/master/concepts/composite-resources.md index db3b6a0b..35ccd792 100644 --- a/content/master/concepts/composite-resources.md +++ b/content/master/concepts/composite-resources.md @@ -189,7 +189,7 @@ spec: ### Composition revision policy Crossplane tracks changes to Compositions as -[Composition revisions]({{}}) . +[Composition revisions]({{}}) . A composite resource can use a {{}}compositionUpdatePolicy{{}} to @@ -217,7 +217,7 @@ spec: ### Composition revision selection Crossplane records changes to Compositions as -[Composition revisions]({{}}). +[Composition revisions]({{}}). A composite resource can select a specific Composition revision. @@ -309,7 +309,7 @@ spec: ``` Composite resources can write connection secrets to an -[external secret store]({{}}), +[external secret store]({{}}), like HashiCorp Vault. {{}} @@ -332,10 +332,10 @@ spec: # Removed for brevity ``` -Read the [External Secrets Store]({{}}) documentation for more information on using +Read the [External Secrets Store]({{}}) documentation for more information on using external secret stores. -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). ### Pausing composite resources diff --git a/content/master/concepts/composition-functions.md b/content/master/concepts/composition-functions.md index 3ba69f47..4fbdf632 100644 --- a/content/master/concepts/composition-functions.md +++ b/content/master/concepts/composition-functions.md @@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0. {{}} Crossplane has -[language specific guides]({{}}) to writing +[language specific guides]({{}}) to writing a composition function. Refer to the guide for your preferred language for a more detailed guide to writing a function. {{}} diff --git a/content/master/concepts/composition-revisions.md b/content/master/concepts/composition-revisions.md new file mode 100644 index 00000000..03433442 --- /dev/null +++ b/content/master/concepts/composition-revisions.md @@ -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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +vpc-staging False 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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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 +vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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]: {{}} +[Compositions]: {{}} +[canary]: https://martinfowler.com/bliki/CanaryRelease.html +[install-guide]: {{}} diff --git a/content/master/concepts/compositions.md b/content/master/concepts/compositions.md index 281e57d5..746eec63 100644 --- a/content/master/concepts/compositions.md +++ b/content/master/concepts/compositions.md @@ -748,7 +748,7 @@ details. This section discusses creating Kubernetes secrets. Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). -Read the [external secrets store guide]({{}}) for more information on using Crossplane +Read the [external secrets store guide]({{}}) for more information on using Crossplane with an external secret store. {{}} @@ -958,7 +958,7 @@ for more information on restricting secret keys. {{< /hint >}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). {{}} You can't change the @@ -973,7 +973,7 @@ recreate the Composition to change the #### Save connection details to an external secret store Crossplane -[External Secret Stores]({{}}) +[External Secret Stores]({{}}) write secrets and connection details to external secret stores like HashiCorp Vault. @@ -1018,7 +1018,7 @@ spec: # Removed for brevity ``` -For more details read the [External Secret Stores]({{}}) +For more details read the [External Secret Stores]({{}}) integration guide. ### Resource readiness checks diff --git a/content/master/concepts/connection-details.md b/content/master/concepts/connection-details.md new file mode 100644 index 00000000..3bf8b8d0 --- /dev/null +++ b/content/master/concepts/connection-details.md @@ -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]({{}}). +* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{}}). +* Define the `writeConnectionSecretToRef` name and namespace for each resource in the + [Composition]({{}}). +* Define the list of secret keys produced by each composed resource with `connectionDetails` in the + [Composition]({{}}). +* Optionally, define the `connectionSecretKeys` in a + [CompositeResourceDefinition]({{}}). + +{{}} +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]({{}}) for more information on using Crossplane +with an external secret store. +{{}} + +## Background +When a [Provider]({{}}) 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. + + + +When a managed resource is part of a +[Composition]({{}}), the Composition, +[Composite Resource Definition]({{}}) +and optionally, the +[Claim]({{}}) define what details are visible +and where they're stored. + + +{{}} +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. + +{{}} +```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" +``` +{{}} + +{{}} + +```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 +``` +{{}} + +{{}} +```yaml +apiVersion: example.org/v1alpha1 +kind: SecretTest +metadata: + name: test-secrets + namespace: default +spec: + writeConnectionSecretToRef: + name: my-access-key-secret +``` +{{}} +{{}} + +## Connection secrets in a managed resource + + + + +When a managed resource creates connection secrets, Crossplane can write the +secrets to a +[Kubernetes secret]({{}}) +or an +[external secret store]({{}}). + + + +Creating an individual managed resource shows the connection secrets the +resource creates. + +{{}} +Read the [managed resources]({{}}) +documentation for more information on configuring resources and storing +connection secrets for individual resources. +{{< /hint >}} + + +For example, create an +{{}}AccessKey{{}} resource and save the +connection secrets in a Kubernetes secret named +{{}}my-accesskey-secret{{}} +in the +{{}}default{{}} 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 +{{}}attribute.secret{{}}, +{{}}attribute.ses_smtp_password_v4{{}}, +{{}}password{{}} and +{{}}username{{}} + +```yaml {label="mrSecret",copy-lines="1"} +kubectl describe secret my-accesskey-secret +Name: my-accesskey-secret +Namespace: default +Labels: +Annotations: + +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 +{{}}AccessKey{{}} +objects. +Each {{}}AccessKey{{}} writes a +connection secrets to the {{}}name{{}} +inside the {{}}namespace{{}} defined by +the resource +{{}}writeConnectionSecretToRef{{}}. + +Crossplane also creates a secret object for the entire Composition +saved in the namespace defined by +{{}}writeConnectionSecretsToNamespace{{}} +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 +{{}}key1-secret{{}} is from the resource +{{}}key1{{}}, +{{}}key2-secret{{}} is from the resource +{{}}key2{{}}. + +Crossplane creates another secret in the namespace +{{}}other-namespace{{}} 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 +{{}}connectionDetails{{}} object under +each resource and define the secret keys the resource creates. + + +{{}} +You can't change the +{{}}connectionDetails{{}} +of a Composition. +You must delete and +recreate the Composition to change the +{{}}connectionDetails{{}}. +{{}} + +```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 +{{}}connectionDetails{{}}. + +```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 +``` + +{{}} +If a key isn't listed in the +{{}}connectionDetails{{}} +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 +{{}}name{{}}. + +```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, +{{}}username{{}} +and +{{}}key2-user{{}} + +```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 +{{}}connectionSecretKeys{{}} object. + +Inside the {{}}connectionSecretKeys{{}} list +the secret key names to create. Crossplane only adds the keys listed to the +combined secret. + +{{}} +You can't change the +{{}}connectionSecretKeys{{}} of an XRD. +You must delete and +recreate the XRD to change the +{{}}connectionSecretKeys{{}}. +{{}} + +For example, an XRD may restrict the secrets to only the +{{}}username{{}}, +{{}}password{{}} and custom named +{{}}key2-user{{}} 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 +{{}}connectionSecretKeys{{}} +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 +{{}}writeConnectionSecretToRef{{}}. + +Crossplane saves the combined secret with a Crossplane generated name in the +namespace defined in the Composition's +{{}}writeConnectionSecretsToNamespace{{}}. + +```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 +{{}}writeConnectionSecretToRef{{}}. + +```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, {{}}my-access-key-secret{{}} + in the Claim's {{}}namespace{{}}. +* The first resource's secret object, {{}}key1{{}}. +* The second resource's secret object, {{}}key2{{}}. +* The composite resource secret object in the + {{}}other-namespace{{}} 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 +``` \ No newline at end of file diff --git a/content/master/concepts/managed-resources.md b/content/master/concepts/managed-resources.md index 69321ad8..d06ad7fe 100644 --- a/content/master/concepts/managed-resources.md +++ b/content/master/concepts/managed-resources.md @@ -50,7 +50,7 @@ kind: Instance A managed resource's `deletionPolicy` tells the Provider what to do after 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. #### 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. | | `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]({{}}) section for more details. | -| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | +| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | | `Update` | Crossplane changes the external resource when changing the managed resource. | {{}} @@ -373,7 +373,7 @@ The following is a list of common policy combinations: | {{}} | | {{}} | {{}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{}} | | | {{}} | {{}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{}} | | | {{}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | -| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | +| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | {{< /table >}} @@ -567,7 +567,7 @@ metadata: {{}} Read the -[Vault as an External Secrets Store]({{}}) +[Vault as an External Secrets Store]({{}}) guide for details on using StoreConfig objects. {{< /hint >}} diff --git a/content/master/concepts/providers.md b/content/master/concepts/providers.md index 77b0b544..099f2835 100644 --- a/content/master/concepts/providers.md +++ b/content/master/concepts/providers.md @@ -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 removing their finalizers. -For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). +For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). {{< /hint >}} ## 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 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. The most common use case for ControllerConfigs are providing `args` to a 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. Each Provider determines their supported set of `args`. diff --git a/content/master/getting-started/provider-azure.md b/content/master/getting-started/provider-azure.md index 5b28be1e..5e8e1d91 100644 --- a/content/master/getting-started/provider-azure.md +++ b/content/master/getting-started/provider-azure.md @@ -236,4 +236,4 @@ virtualnetwork.network.azure.upbound.io "crossplane-quickstart-network" deleted * Explore Azure resources that Crossplane can configure in the [Provider CRD reference](https://marketplace.upbound.io/providers/upbound/provider-family-azure/). * Join the [Crossplane Slack](https://slack.crossplane.io/) and connect with - Crossplane users and contributors. \ No newline at end of file + Crossplane users and contributors. diff --git a/content/master/guides/_index.md b/content/master/guides/_index.md new file mode 100644 index 00000000..7b1ee4b5 --- /dev/null +++ b/content/master/guides/_index.md @@ -0,0 +1,5 @@ +--- +title: Guides +weight: 100 +description: Crossplane integrations and detailed examples. +--- \ No newline at end of file diff --git a/content/knowledge-base/integrations/argo-cd-crossplane.md b/content/master/guides/crossplane-with-argo-cd.md similarity index 100% rename from content/knowledge-base/integrations/argo-cd-crossplane.md rename to content/master/guides/crossplane-with-argo-cd.md diff --git a/content/knowledge-base/guides/disaster-recovery.md b/content/master/guides/disaster-recovery.md similarity index 100% rename from content/knowledge-base/guides/disaster-recovery.md rename to content/master/guides/disaster-recovery.md diff --git a/content/knowledge-base/guides/import-existing-resources.md b/content/master/guides/import-existing-resources.md similarity index 100% rename from content/knowledge-base/guides/import-existing-resources.md rename to content/master/guides/import-existing-resources.md diff --git a/content/knowledge-base/guides/multi-tenant.md b/content/master/guides/multi-tenant.md similarity index 100% rename from content/knowledge-base/guides/multi-tenant.md rename to content/master/guides/multi-tenant.md diff --git a/content/knowledge-base/guides/self-signed-ca-certs.md b/content/master/guides/self-signed-ca-certs.md similarity index 100% rename from content/knowledge-base/guides/self-signed-ca-certs.md rename to content/master/guides/self-signed-ca-certs.md diff --git a/content/master/guides/troubleshoot-crossplane.md b/content/master/guides/troubleshoot-crossplane.md new file mode 100644 index 00000000..a8d36ed3 --- /dev/null +++ b/content/master/guides/troubleshoot-crossplane.md @@ -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 +``` + +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 -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 + +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: + ``` + +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]({{}}) +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. + +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 -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. + + + + + +[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 + + diff --git a/content/knowledge-base/integrations/vault-as-secret-store.md b/content/master/guides/vault-as-secret-store.md similarity index 100% rename from content/knowledge-base/integrations/vault-as-secret-store.md rename to content/master/guides/vault-as-secret-store.md diff --git a/content/knowledge-base/integrations/vault-injection.md b/content/master/guides/vault-injection.md similarity index 100% rename from content/knowledge-base/integrations/vault-injection.md rename to content/master/guides/vault-injection.md diff --git a/content/master/guides/write-a-composition-function-in-go.md b/content/master/guides/write-a-composition-function-in-go.md new file mode 100644 index 00000000..0430957c --- /dev/null +++ b/content/master/guides/write-a-composition-function-in-go.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +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. + +{{}} +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 ./... +``` +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} struct. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} struct. + +{{}} +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). +{{}} + +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. + +{{}} +```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 +} +``` +{{}} + +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. + +{{}} +Read the +[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go) +for the SDK. +{{}} + +## 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. + +{{}} +```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) + } + }) + } +} +``` +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +The {{}}insecure{{}} flag tells the function +to run without encryption or authentication. Only use it during testing and +development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/master/guides/write-a-composition-function-in-python.md b/content/master/guides/write-a-composition-function-in-python.md new file mode 100644 index 00000000..4a9e63a1 --- /dev/null +++ b/content/master/guides/write-a-composition-function-in-python.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +documentation explains composition function inputs. + +{{}} +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. +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} object. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} 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. + +{{}} +```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 +``` +{{}} + +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. + +{{}} +Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python). +{{}} + +{{}} +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. +{{}} + +## 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. + +{{}} +```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() +``` +{{}} + +Run the unit tests using `hatch run`: + +```shell {copy-lines="1"} +hatch run test:unit +. +---------------------------------------------------------------------- +Ran 1 test in 0.003s + +OK +``` + +{{}} +[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. +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +`hatch run development` runs the function without encryption or authentication. +Only use it during testing and development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/knowledge-base/guides/learn-more.md b/content/master/learn/_index.md similarity index 97% rename from content/knowledge-base/guides/learn-more.md rename to content/master/learn/_index.md index c88dd5ff..e693ab1b 100644 --- a/content/knowledge-base/guides/learn-more.md +++ b/content/master/learn/_index.md @@ -1,6 +1,7 @@ --- 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]! diff --git a/content/knowledge-base/guides/feature-lifecycle.md b/content/master/learn/feature-lifecycle.md similarity index 100% rename from content/knowledge-base/guides/feature-lifecycle.md rename to content/master/learn/feature-lifecycle.md diff --git a/content/knowledge-base/guides/release-cycle.md b/content/master/learn/release-cycle.md similarity index 100% rename from content/knowledge-base/guides/release-cycle.md rename to content/master/learn/release-cycle.md diff --git a/content/master/release-notes/_index.md b/content/master/release-notes/_index.md index c1a4b201..526e5674 100644 --- a/content/master/release-notes/_index.md +++ b/content/master/release-notes/_index.md @@ -1,6 +1,6 @@ --- title: Release Notes -weight: 20 +weight: 600 description: "Crossplane release notes" product: "Release Notes" cascade: diff --git a/content/master/software/_index.md b/content/master/software/_index.md index 7cef2dd1..e1b4933a 100644 --- a/content/master/software/_index.md +++ b/content/master/software/_index.md @@ -1,6 +1,6 @@ --- -title: Install and Uninstall Crossplane -weight: 300 +title: Install, Upgrade and Uninstall +weight: 10 description: Manage Crossplane installations --- diff --git a/content/master/software/upgrade.md b/content/master/software/upgrade.md index b11f3644..c145cc17 100644 --- a/content/master/software/upgrade.md +++ b/content/master/software/upgrade.md @@ -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. 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]({{}}) with the upgrade command. For example, to maintain the original image registry use diff --git a/content/v1.13/concepts/claims.md b/content/v1.13/concepts/claims.md index 62619d93..cc0582e2 100644 --- a/content/v1.13/concepts/claims.md +++ b/content/v1.13/concepts/claims.md @@ -205,4 +205,4 @@ spec: ``` For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). \ No newline at end of file +[Connection Secrets knowledge base article]({{}}). \ No newline at end of file diff --git a/content/v1.13/concepts/composite-resource-definitions.md b/content/v1.13/concepts/composite-resource-definitions.md index 9653e876..9d4e37ae 100644 --- a/content/v1.13/concepts/composite-resource-definitions.md +++ b/content/v1.13/concepts/composite-resource-definitions.md @@ -623,7 +623,7 @@ recreate the XRD to change the `connectionSecretKeys`. {{
}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). ### Set composite resource defaults XRDs can set default parameters for composite resources and Claims. diff --git a/content/v1.13/concepts/composite-resources.md b/content/v1.13/concepts/composite-resources.md index fb307a2e..01bb41cd 100644 --- a/content/v1.13/concepts/composite-resources.md +++ b/content/v1.13/concepts/composite-resources.md @@ -190,7 +190,7 @@ spec: ### Composition revision policy Crossplane tracks changes to Compositions as -[Composition revisions]({{}}) . +[Composition revisions]({{}}) . A composite resource can use a {{}}compositionUpdatePolicy{{}} to @@ -218,7 +218,7 @@ spec: ### Composition revision selection Crossplane records changes to Compositions as -[Composition revisions]({{}}). +[Composition revisions]({{}}). A composite resource can select a specific Composition revision. @@ -311,7 +311,7 @@ spec: ``` Composite resources can write connection secrets to an -[external secret store]({{}}), +[external secret store]({{}}), like HashiCorp Vault. {{}} @@ -334,11 +334,11 @@ spec: # Removed for brevity ``` -Read the [External Secrets Store]({{}}) documentation for more information on using +Read the [External Secrets Store]({{}}) documentation for more information on using external secret stores. For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). ### Pausing composite resources diff --git a/content/knowledge-base/guides/composition-revisions-example.md b/content/v1.13/concepts/composition-revisions.md similarity index 62% rename from content/knowledge-base/guides/composition-revisions-example.md rename to content/v1.13/concepts/composition-revisions.md index 034a3b39..a91b677a 100644 --- a/content/knowledge-base/guides/composition-revisions-example.md +++ b/content/v1.13/concepts/composition-revisions.md @@ -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 (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 +### Preparation +##### Install Crossplane Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running. ```shell 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 ``` -### Deploy Composition and XRD Examples +#### Deploy Composition and XRD Examples Apply the example Composition. ```yaml @@ -95,13 +226,13 @@ The label `dev` is automatically created from the Composition. {{< /hint >}} -## Create Composite Resources +### 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 +#### Default update policy Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default: ```yaml apiVersion: aws.example.upbound.io/v1alpha1 @@ -116,7 +247,7 @@ Expected Output: myvpc.aws.example.upbound.io/vpc-auto created ``` -### Manual update policy +#### Manual update policy Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`. ```yaml apiVersion: aws.example.upbound.io/v1alpha1 @@ -135,7 +266,7 @@ Expected Output: myvpc.aws.example.upbound.io/vpc-man created ``` -### Using a selector +#### Using a selector Create an XR with a `compositionRevisionSelector` of `channel: dev`: ```yaml apiVersion: aws.example.upbound.io/v1alpha1 @@ -189,11 +320,11 @@ vpc-staging False Automatic map[c The `vpc-staging` XR label doesn't match any existing Composition Revisions. {{< /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 also trigger a new CompositionRevision. -### Update the Composition label +#### Update the Composition label Update the `Composition` label to `channel: staging`: ```shell 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. {{< /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`. 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" >}} `vpc-dev` matches the updated label applied to Revision revision:3. `vpc-staging` matches the label applied to Revision revision:2. -{{< /hint >}} \ No newline at end of file +{{< /hint >}} + + +[composition-type]: {{}} +[Compositions]: {{}} +[canary]: https://martinfowler.com/bliki/CanaryRelease.html +[install-guide]: {{}} diff --git a/content/v1.13/concepts/compositions.md b/content/v1.13/concepts/compositions.md index 4de43e16..24f9d24d 100644 --- a/content/v1.13/concepts/compositions.md +++ b/content/v1.13/concepts/compositions.md @@ -716,7 +716,7 @@ details. This section discusses creating Kubernetes secrets. Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). -Read the [external secrets store guide]({{}}) for more information on using Crossplane +Read the [external secrets store guide]({{}}) for more information on using Crossplane with an external secret store. {{}} @@ -926,7 +926,7 @@ for more information on restricting secret keys. {{< /hint >}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). {{}} You can't change the @@ -941,7 +941,7 @@ recreate the Composition to change the #### Save connection details to an external secret store Crossplane -[External Secret Stores]({{}}) +[External Secret Stores]({{}}) write secrets and connection details to external secret stores like HashiCorp Vault. @@ -986,7 +986,7 @@ spec: # Removed for brevity ``` -For more details read the [External Secret Stores]({{}}) +For more details read the [External Secret Stores]({{}}) integration guide. ### Resource readiness checks diff --git a/content/knowledge-base/guides/connection-details.md b/content/v1.13/concepts/connection-details.md similarity index 99% rename from content/knowledge-base/guides/connection-details.md rename to content/v1.13/concepts/connection-details.md index 19d13487..0e1675f6 100644 --- a/content/knowledge-base/guides/connection-details.md +++ b/content/v1.13/concepts/connection-details.md @@ -18,7 +18,7 @@ Using connection details in Crossplane requires the following components: 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]({{}}) for more information on using Crossplane +Read the [external secrets store guide]({{}}) for more information on using Crossplane with an external secret store. {{}} diff --git a/content/v1.13/concepts/managed-resources.md b/content/v1.13/concepts/managed-resources.md index 40f9b7f5..1e987066 100644 --- a/content/v1.13/concepts/managed-resources.md +++ b/content/v1.13/concepts/managed-resources.md @@ -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. | | `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]({{}}) section for more details. | -| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | +| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | | `Update` | Crossplane changes the external resource when changing the managed resource. | {{}} @@ -388,7 +388,7 @@ The following is a list of common policy combinations: | {{}} | | {{}} | {{}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{}} | | | {{}} | {{}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{}} | | | {{}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | -| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | +| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | {{< /table >}} @@ -583,7 +583,7 @@ metadata: {{}} Read the -[Vault as an External Secrets Store]({{}}) +[Vault as an External Secrets Store]({{}}) guide for details on using StoreConfig objects. {{< /hint >}} diff --git a/content/v1.13/concepts/providers.md b/content/v1.13/concepts/providers.md index e7a48a5f..c02b019e 100644 --- a/content/v1.13/concepts/providers.md +++ b/content/v1.13/concepts/providers.md @@ -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 removing their finalizers. -For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). +For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). {{< /hint >}} ## 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 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. Each Provider determines their supported set of `args`. diff --git a/content/v1.13/guides/_index.md b/content/v1.13/guides/_index.md new file mode 100644 index 00000000..b7e61dc1 --- /dev/null +++ b/content/v1.13/guides/_index.md @@ -0,0 +1,5 @@ +--- +title: Guides +weight: 400 +description: Crossplane integrations and detailed examples. +--- \ No newline at end of file diff --git a/content/v1.13/guides/crossplane-with-argo-cd.md b/content/v1.13/guides/crossplane-with-argo-cd.md new file mode 100644 index 00000000..a27fc7f6 --- /dev/null +++ b/content/v1.13/guides/crossplane-with-argo-cd.md @@ -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`. +{{}} +{{}} ProviderConfig{{}} may have no status or a `status.users` field. +{{}} +```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. + diff --git a/content/v1.13/guides/disaster-recovery.md b/content/v1.13/guides/disaster-recovery.md new file mode 100644 index 00000000..e0007372 --- /dev/null +++ b/content/v1.13/guides/disaster-recovery.md @@ -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/). \ No newline at end of file diff --git a/content/v1.13/guides/import-existing-resources.md b/content/v1.13/guides/import-existing-resources.md new file mode 100644 index 00000000..6df790c9 --- /dev/null +++ b/content/v1.13/guides/import-existing-resources.md @@ -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`]({{}}) +field enables importing external resources into Crossplane. + +Crossplane can import resources either [manually]({{}}) +or [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 +{{}}my-existing-network{{}}, +create a new managed resource and use the +{{}}my-existing-network{{}} 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 {{}}metadata.name{{}} +field can be anything you want. For example, +{{}}imported-network{{}}. + +{{< 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 +{{}}spec.forProvider{{}} 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 +{{}}spec.forProvider{{}} 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]({{}}). + +Crossplane imports observe only resources but never changes or deletes the +resources. + +{{}} +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 >}} + + +### Apply the Observe management policy + + +Create a new managed resource matching the +{{}}apiVersion{{}} and +{{}}kind{{}} of the resource +to import and add +{{}}managementPolicies: ["Observe"]{{}} to the +{{}}spec{{}} + +For example, to import a GCP SQL DatabaseInstance, create a new resource with +the {{}}managementPolicies: ["Observe"]{{}} +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 {{}}crossplane.io/external-name{{}} +annotation for the resource. This name must match the name inside the Provider. + +For example, for a GCP database named +{{}}my-external-database{{}}, apply +the +{{}}crossplane.io/external-name{{}} +annotation with the value +{{}}my-external-database{{}}. + +```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 {{}}name{{}} to use for the +Kubernetes object. + +For example, name the Kubernetes object +{{}}my-imported-database{{}}. + +```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 +{{}}spec.forProvider{{}} field. + +For example, only import the GCP SQL database in the +{{}}us-central1{{}} 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 +{{}}status.atProvider{{}} +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 +``` + +## Control imported ObserveOnly resources + + +Crossplane can take active control of observe only imported resources by +changing the `managementPolicies` after import. + +Change the {{}}managementPolicies{{}} field +of the managed resource to +{{}}["*"]{{}}. + +Copy any required parameter values from +{{}}status.atProvider{{}} and provide them +in {{}}spec.forProvider{{}}. + +{{< 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. \ No newline at end of file diff --git a/content/v1.13/guides/multi-tenant.md b/content/v1.13/guides/multi-tenant.md new file mode 100644 index 00000000..6a378509 --- /dev/null +++ b/content/v1.13/guides/multi-tenant.md @@ -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. + + + +[managed resources]: {{}} +[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +[Composition]: {{}} +[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]: {{}} +[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 diff --git a/content/v1.13/guides/self-signed-ca-certs.md b/content/v1.13/guides/self-signed-ca-certs.md new file mode 100644 index 00000000..c5734aeb --- /dev/null +++ b/content/v1.13/guides/self-signed-ca-certs.md @@ -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]({{}}). + +## 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 +``` diff --git a/content/knowledge-base/guides/troubleshoot.md b/content/v1.13/guides/troubleshoot-crossplane.md similarity index 100% rename from content/knowledge-base/guides/troubleshoot.md rename to content/v1.13/guides/troubleshoot-crossplane.md diff --git a/content/v1.13/guides/vault-as-secret-store.md b/content/v1.13/guides/vault-as-secret-store.md new file mode 100644 index 00000000..d76ceb97 --- /dev/null +++ b/content/v1.13/guides/vault-as-secret-store.md @@ -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]. + +{{}} +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]({{}}) 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 + +{{}} +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. + + + +### Enable the Vault kv secrets engine + + +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. +{{}} + +```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 - <}} +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 <}} +This example uses Provider GCP, but the +{{}}ControllerConfig{{}} is the +same for all Providers. +{{}} + +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 {{}}VaultConfig{{}} +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 {{}}StoreConfig{{}} +object from the +{{}}secrets.crossplane.io{{}} +group. Crossplane uses the StoreConfig to connect to the Vault plugin service. + +The {{}}configRef{{}} 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 {{}}StoreConfig{{}} +object from the Provider's API group, +{{}}gcp.crossplane.io{{}}. +The Provider uses this StoreConfig to communicate with Vault for +Managed Resources. + +The {{}}configRef{{}} 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 +{{}}connectionDetails{{}} that the +Provider stores in Vault using the +{{}}publishConnectionDetailsTo{{}} 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 +{{}}publishConnectionDetailsTo{{}} 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 {{}}ess-claim-conn{{}} +is the name of the Claim's +{{}}publishConnectionDetailsTo{{}} +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 +{{}}d2408335-eb88-4146-927b-8025f405da86{{}} +comes from + + + +and the key +{{}}ess-mr-conn{{}} +comes from the Composition's +{{}}publishConnectionDetailsTo{{}} +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 +``` + + + +[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 \ No newline at end of file diff --git a/content/v1.13/guides/vault-injection.md b/content/v1.13/guides/vault-injection.md new file mode 100644 index 00000000..189b07f6 --- /dev/null +++ b/content/v1.13/guides/vault-injection.md @@ -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 - <}} +{{< 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 >}} + + + +[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 diff --git a/content/knowledge-base/guides/write-a-composition-function-in-go.md b/content/v1.13/guides/write-a-composition-function-in-go.md similarity index 98% rename from content/knowledge-base/guides/write-a-composition-function-in-go.md rename to content/v1.13/guides/write-a-composition-function-in-go.md index cc0e6cec..4778b25f 100644 --- a/content/knowledge-base/guides/write-a-composition-function-in-go.md +++ b/content/v1.13/guides/write-a-composition-function-in-go.md @@ -11,7 +11,7 @@ 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](https://docs.crossplane.io/latest/concepts/composition-functions) +[concepts]{{}} page to learn more about composition functions. 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" >}} 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]{{}} before following this guide. {{< /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, using the `input` field from a Composition. The -[composition functions](https://docs.crossplane.io/latest/concepts/composition-functions) +[composition functions]{{}} documentation explains how to pass an input to a composition function. 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 Crossplane control plane. See the -[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions). +[composition functions documentation]{{}}. to learn how to use a function in a control plane. Use Docker to build a runtime for each platform. @@ -808,7 +808,7 @@ crossplane xpkg build \ {{}} 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" >}}). {{}} Push both package files to a registry. Pushing both files to one tag in the diff --git a/content/knowledge-base/guides/write-a-composition-function-in-python.md b/content/v1.13/guides/write-a-composition-function-in-python.md similarity index 98% rename from content/knowledge-base/guides/write-a-composition-function-in-python.md rename to content/v1.13/guides/write-a-composition-function-in-python.md index db10b7c6..de2f9ff1 100644 --- a/content/knowledge-base/guides/write-a-composition-function-in-python.md +++ b/content/v1.13/guides/write-a-composition-function-in-python.md @@ -11,7 +11,7 @@ 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](https://docs.crossplane.io/latest/concepts/composition-functions) +[concepts]{{}} page to learn more about composition functions. 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" >}} 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]{{}} before following this guide. {{< /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 `package/input` directory. -The [composition functions](https://docs.crossplane.io/latest/concepts/composition-functions) +The [composition functions]{{}} documentation explains composition function inputs. {{}} @@ -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 Crossplane control plane. See the -[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions). +[composition functions documentation]{{}}. to learn how to use a function in a control plane. Use Docker to build a runtime for each platform. @@ -715,7 +715,7 @@ crossplane xpkg build \ {{}} 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" >}}). {{}} Push both package files to a registry. Pushing both files to one tag in the diff --git a/content/v1.13/learn/_index.md b/content/v1.13/learn/_index.md new file mode 100644 index 00000000..a19b5d13 --- /dev/null +++ b/content/v1.13/learn/_index.md @@ -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) + + + +[join-crossplane-slack]: https://slack.crossplane.io +[contact-us]: https://github.com/crossplane/crossplane#contact diff --git a/content/v1.13/learn/feature-lifecycle.md b/content/v1.13/learn/feature-lifecycle.md new file mode 100644 index 00000000..16a59691 --- /dev/null +++ b/content/v1.13/learn/feature-lifecycle.md @@ -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 \ No newline at end of file diff --git a/content/v1.13/learn/release-cycle.md b/content/v1.13/learn/release-cycle.md new file mode 100644 index 00000000..76f68e32 --- /dev/null +++ b/content/v1.13/learn/release-cycle.md @@ -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. + + + +[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 diff --git a/content/v1.14/concepts/claims.md b/content/v1.14/concepts/claims.md index bfda614b..65e62f72 100644 --- a/content/v1.14/concepts/claims.md +++ b/content/v1.14/concepts/claims.md @@ -204,4 +204,4 @@ spec: name: my-claim-secret ``` -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file diff --git a/content/v1.14/concepts/composite-resource-definitions.md b/content/v1.14/concepts/composite-resource-definitions.md index 9baacfc0..a544423a 100644 --- a/content/v1.14/concepts/composite-resource-definitions.md +++ b/content/v1.14/concepts/composite-resource-definitions.md @@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`. {{}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). ### Set composite resource defaults XRDs can set default parameters for composite resources and Claims. diff --git a/content/v1.14/concepts/composite-resources.md b/content/v1.14/concepts/composite-resources.md index db3b6a0b..35ccd792 100644 --- a/content/v1.14/concepts/composite-resources.md +++ b/content/v1.14/concepts/composite-resources.md @@ -189,7 +189,7 @@ spec: ### Composition revision policy Crossplane tracks changes to Compositions as -[Composition revisions]({{}}) . +[Composition revisions]({{}}) . A composite resource can use a {{}}compositionUpdatePolicy{{}} to @@ -217,7 +217,7 @@ spec: ### Composition revision selection Crossplane records changes to Compositions as -[Composition revisions]({{}}). +[Composition revisions]({{}}). A composite resource can select a specific Composition revision. @@ -309,7 +309,7 @@ spec: ``` Composite resources can write connection secrets to an -[external secret store]({{}}), +[external secret store]({{}}), like HashiCorp Vault. {{}} @@ -332,10 +332,10 @@ spec: # Removed for brevity ``` -Read the [External Secrets Store]({{}}) documentation for more information on using +Read the [External Secrets Store]({{}}) documentation for more information on using external secret stores. -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). ### Pausing composite resources diff --git a/content/v1.14/concepts/composition-functions.md b/content/v1.14/concepts/composition-functions.md index 07a7af0d..8fca5ce4 100644 --- a/content/v1.14/concepts/composition-functions.md +++ b/content/v1.14/concepts/composition-functions.md @@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0. {{}} Crossplane has a -[guide to writing a composition function in Go]({{}}). +[guide to writing a composition function in Go]({{}}). {{}} When you're writing a composition function it's useful to know how composition diff --git a/content/v1.14/concepts/composition-revisions.md b/content/v1.14/concepts/composition-revisions.md new file mode 100644 index 00000000..a91b677a --- /dev/null +++ b/content/v1.14/concepts/composition-revisions.md @@ -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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +vpc-staging False 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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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 +vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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]: {{}} +[Compositions]: {{}} +[canary]: https://martinfowler.com/bliki/CanaryRelease.html +[install-guide]: {{}} diff --git a/content/v1.14/concepts/compositions.md b/content/v1.14/concepts/compositions.md index 281e57d5..746eec63 100644 --- a/content/v1.14/concepts/compositions.md +++ b/content/v1.14/concepts/compositions.md @@ -748,7 +748,7 @@ details. This section discusses creating Kubernetes secrets. Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). -Read the [external secrets store guide]({{}}) for more information on using Crossplane +Read the [external secrets store guide]({{}}) for more information on using Crossplane with an external secret store. {{}} @@ -958,7 +958,7 @@ for more information on restricting secret keys. {{< /hint >}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). {{}} You can't change the @@ -973,7 +973,7 @@ recreate the Composition to change the #### Save connection details to an external secret store Crossplane -[External Secret Stores]({{}}) +[External Secret Stores]({{}}) write secrets and connection details to external secret stores like HashiCorp Vault. @@ -1018,7 +1018,7 @@ spec: # Removed for brevity ``` -For more details read the [External Secret Stores]({{}}) +For more details read the [External Secret Stores]({{}}) integration guide. ### Resource readiness checks diff --git a/content/v1.14/concepts/connection-details.md b/content/v1.14/concepts/connection-details.md new file mode 100644 index 00000000..0e1675f6 --- /dev/null +++ b/content/v1.14/concepts/connection-details.md @@ -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]({{}}). +* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{}}). +* Define the `writeConnectionSecretToRef` name and namespace for each resource in the + [Composition]({{}}). +* Define the list of secret keys produced by each composed resource with `connectionDetails` in the + [Composition]({{}}). +* Optionally, define the `connectionSecretKeys` in a + [CompositeResourceDefinition]({{}}). + +{{}} +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]({{}}) for more information on using Crossplane +with an external secret store. +{{}} + +## Background +When a [Provider]({{}}) 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. + + + +When a managed resource is part of a +[Composition]({{}}), the Composition, +[Composite Resource Definition]({{}}) +and optionally, the +[Claim]({{}}) define what details are visible +and where they're stored. + + +{{}} +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. + +{{}} +```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" +``` +{{}} + +{{}} + +```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 +``` +{{}} + +{{}} +```yaml +apiVersion: example.org/v1alpha1 +kind: SecretTest +metadata: + name: test-secrets + namespace: default +spec: + writeConnectionSecretToRef: + name: my-access-key-secret +``` +{{}} +{{}} + +## Connection secrets in a managed resource + + + + +When a managed resource creates connection secrets, Crossplane can write the +secrets to a +[Kubernetes secret]({{}}) +or an +[external secret store]({{}}). + + + +Creating an individual managed resource shows the connection secrets the +resource creates. + +{{}} +Read the [managed resources]({{}}) +documentation for more information on configuring resources and storing +connection secrets for individual resources. +{{< /hint >}} + + +For example, create an +{{}}AccessKey{{}} resource and save the +connection secrets in a Kubernetes secret named +{{}}my-accesskey-secret{{}} +in the +{{}}default{{}} 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 +{{}}attribute.secret{{}}, +{{}}attribute.ses_smtp_password_v4{{}}, +{{}}password{{}} and +{{}}username{{}} + +```yaml {label="mrSecret",copy-lines="1"} +kubectl describe secret my-accesskey-secret +Name: my-accesskey-secret +Namespace: default +Labels: +Annotations: + +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 +{{}}AccessKey{{}} +objects. +Each {{}}AccessKey{{}} writes a +connection secrets to the {{}}name{{}} +inside the {{}}namespace{{}} defined by +the resource +{{}}writeConnectionSecretToRef{{}}. + +Crossplane also creates a secret object for the entire Composition +saved in the namespace defined by +{{}}writeConnectionSecretsToNamespace{{}} +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 +{{}}key1-secret{{}} is from the resource +{{}}key1{{}}, +{{}}key2-secret{{}} is from the resource +{{}}key2{{}}. + +Crossplane creates another secret in the namespace +{{}}other-namespace{{}} 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 +{{}}connectionDetails{{}} object under +each resource and define the secret keys the resource creates. + + +{{}} +You can't change the +{{}}connectionDetails{{}} +of a Composition. +You must delete and +recreate the Composition to change the +{{}}connectionDetails{{}}. +{{}} + +```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 +{{}}connectionDetails{{}}. + +```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 +``` + +{{}} +If a key isn't listed in the +{{}}connectionDetails{{}} +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 +{{}}name{{}}. + +```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, +{{}}username{{}} +and +{{}}key2-user{{}} + +```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 +{{}}connectionSecretKeys{{}} object. + +Inside the {{}}connectionSecretKeys{{}} list +the secret key names to create. Crossplane only adds the keys listed to the +combined secret. + +{{}} +You can't change the +{{}}connectionSecretKeys{{}} of an XRD. +You must delete and +recreate the XRD to change the +{{}}connectionSecretKeys{{}}. +{{}} + +For example, an XRD may restrict the secrets to only the +{{}}username{{}}, +{{}}password{{}} and custom named +{{}}key2-user{{}} 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 +{{}}connectionSecretKeys{{}} +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 +{{}}writeConnectionSecretToRef{{}}. + +Crossplane saves the combined secret with a Crossplane generated name in the +namespace defined in the Composition's +{{}}writeConnectionSecretsToNamespace{{}}. + +```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 +{{}}writeConnectionSecretToRef{{}}. + +```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, {{}}my-access-key-secret{{}} + in the Claim's {{}}namespace{{}}. +* The first resource's secret object, {{}}key1{{}}. +* The second resource's secret object, {{}}key2{{}}. +* The composite resource secret object in the + {{}}other-namespace{{}} 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 +``` \ No newline at end of file diff --git a/content/v1.14/concepts/managed-resources.md b/content/v1.14/concepts/managed-resources.md index f5d855ba..761a4434 100644 --- a/content/v1.14/concepts/managed-resources.md +++ b/content/v1.14/concepts/managed-resources.md @@ -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. | | `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]({{}}) section for more details. | -| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | +| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | | `Update` | Crossplane changes the external resource when changing the managed resource. | {{}} @@ -373,7 +373,7 @@ The following is a list of common policy combinations: | {{}} | | {{}} | {{}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{}} | | | {{}} | {{}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{}} | | | {{}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | -| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | +| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | {{< /table >}} @@ -568,7 +568,7 @@ metadata: {{}} Read the -[Vault as an External Secrets Store]({{}}) +[Vault as an External Secrets Store]({{}}) guide for details on using StoreConfig objects. {{< /hint >}} diff --git a/content/v1.14/concepts/providers.md b/content/v1.14/concepts/providers.md index 3403968d..756a2d62 100644 --- a/content/v1.14/concepts/providers.md +++ b/content/v1.14/concepts/providers.md @@ -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 removing their finalizers. -For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). +For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). {{< /hint >}} ## 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 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. The most common use case for ControllerConfigs are providing `args` to a 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. Each Provider determines their supported set of `args`. diff --git a/content/v1.14/guides/_index.md b/content/v1.14/guides/_index.md new file mode 100644 index 00000000..b7e61dc1 --- /dev/null +++ b/content/v1.14/guides/_index.md @@ -0,0 +1,5 @@ +--- +title: Guides +weight: 400 +description: Crossplane integrations and detailed examples. +--- \ No newline at end of file diff --git a/content/v1.14/guides/crossplane-with-argo-cd.md b/content/v1.14/guides/crossplane-with-argo-cd.md new file mode 100644 index 00000000..a27fc7f6 --- /dev/null +++ b/content/v1.14/guides/crossplane-with-argo-cd.md @@ -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`. +{{}} +{{}} ProviderConfig{{}} may have no status or a `status.users` field. +{{}} +```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. + diff --git a/content/v1.14/guides/disaster-recovery.md b/content/v1.14/guides/disaster-recovery.md new file mode 100644 index 00000000..e0007372 --- /dev/null +++ b/content/v1.14/guides/disaster-recovery.md @@ -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/). \ No newline at end of file diff --git a/content/v1.14/guides/import-existing-resources.md b/content/v1.14/guides/import-existing-resources.md new file mode 100644 index 00000000..6df790c9 --- /dev/null +++ b/content/v1.14/guides/import-existing-resources.md @@ -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`]({{}}) +field enables importing external resources into Crossplane. + +Crossplane can import resources either [manually]({{}}) +or [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 +{{}}my-existing-network{{}}, +create a new managed resource and use the +{{}}my-existing-network{{}} 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 {{}}metadata.name{{}} +field can be anything you want. For example, +{{}}imported-network{{}}. + +{{< 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 +{{}}spec.forProvider{{}} 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 +{{}}spec.forProvider{{}} 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]({{}}). + +Crossplane imports observe only resources but never changes or deletes the +resources. + +{{}} +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 >}} + + +### Apply the Observe management policy + + +Create a new managed resource matching the +{{}}apiVersion{{}} and +{{}}kind{{}} of the resource +to import and add +{{}}managementPolicies: ["Observe"]{{}} to the +{{}}spec{{}} + +For example, to import a GCP SQL DatabaseInstance, create a new resource with +the {{}}managementPolicies: ["Observe"]{{}} +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 {{}}crossplane.io/external-name{{}} +annotation for the resource. This name must match the name inside the Provider. + +For example, for a GCP database named +{{}}my-external-database{{}}, apply +the +{{}}crossplane.io/external-name{{}} +annotation with the value +{{}}my-external-database{{}}. + +```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 {{}}name{{}} to use for the +Kubernetes object. + +For example, name the Kubernetes object +{{}}my-imported-database{{}}. + +```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 +{{}}spec.forProvider{{}} field. + +For example, only import the GCP SQL database in the +{{}}us-central1{{}} 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 +{{}}status.atProvider{{}} +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 +``` + +## Control imported ObserveOnly resources + + +Crossplane can take active control of observe only imported resources by +changing the `managementPolicies` after import. + +Change the {{}}managementPolicies{{}} field +of the managed resource to +{{}}["*"]{{}}. + +Copy any required parameter values from +{{}}status.atProvider{{}} and provide them +in {{}}spec.forProvider{{}}. + +{{< 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. \ No newline at end of file diff --git a/content/v1.14/guides/multi-tenant.md b/content/v1.14/guides/multi-tenant.md new file mode 100644 index 00000000..6a378509 --- /dev/null +++ b/content/v1.14/guides/multi-tenant.md @@ -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. + + + +[managed resources]: {{}} +[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +[Composition]: {{}} +[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]: {{}} +[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 diff --git a/content/v1.14/guides/self-signed-ca-certs.md b/content/v1.14/guides/self-signed-ca-certs.md new file mode 100644 index 00000000..c5734aeb --- /dev/null +++ b/content/v1.14/guides/self-signed-ca-certs.md @@ -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]({{}}). + +## 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 +``` diff --git a/content/v1.14/guides/troubleshoot-crossplane.md b/content/v1.14/guides/troubleshoot-crossplane.md new file mode 100644 index 00000000..19dd2bca --- /dev/null +++ b/content/v1.14/guides/troubleshoot-crossplane.md @@ -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 +``` + +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 -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 + +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: + ``` + +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]({{}}) +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. + +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 -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. + + + + + +[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 + + diff --git a/content/v1.14/guides/vault-as-secret-store.md b/content/v1.14/guides/vault-as-secret-store.md new file mode 100644 index 00000000..d76ceb97 --- /dev/null +++ b/content/v1.14/guides/vault-as-secret-store.md @@ -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]. + +{{}} +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]({{}}) 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 + +{{}} +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. + + + +### Enable the Vault kv secrets engine + + +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. +{{}} + +```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 - <}} +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 <}} +This example uses Provider GCP, but the +{{}}ControllerConfig{{}} is the +same for all Providers. +{{}} + +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 {{}}VaultConfig{{}} +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 {{}}StoreConfig{{}} +object from the +{{}}secrets.crossplane.io{{}} +group. Crossplane uses the StoreConfig to connect to the Vault plugin service. + +The {{}}configRef{{}} 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 {{}}StoreConfig{{}} +object from the Provider's API group, +{{}}gcp.crossplane.io{{}}. +The Provider uses this StoreConfig to communicate with Vault for +Managed Resources. + +The {{}}configRef{{}} 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 +{{}}connectionDetails{{}} that the +Provider stores in Vault using the +{{}}publishConnectionDetailsTo{{}} 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 +{{}}publishConnectionDetailsTo{{}} 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 {{}}ess-claim-conn{{}} +is the name of the Claim's +{{}}publishConnectionDetailsTo{{}} +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 +{{}}d2408335-eb88-4146-927b-8025f405da86{{}} +comes from + + + +and the key +{{}}ess-mr-conn{{}} +comes from the Composition's +{{}}publishConnectionDetailsTo{{}} +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 +``` + + + +[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 \ No newline at end of file diff --git a/content/v1.14/guides/vault-injection.md b/content/v1.14/guides/vault-injection.md new file mode 100644 index 00000000..189b07f6 --- /dev/null +++ b/content/v1.14/guides/vault-injection.md @@ -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 - <}} +{{< 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 >}} + + + +[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 diff --git a/content/v1.14/guides/write-a-composition-function-in-go.md b/content/v1.14/guides/write-a-composition-function-in-go.md new file mode 100644 index 00000000..0430957c --- /dev/null +++ b/content/v1.14/guides/write-a-composition-function-in-go.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +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. + +{{}} +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 ./... +``` +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} struct. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} struct. + +{{}} +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). +{{}} + +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. + +{{}} +```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 +} +``` +{{}} + +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. + +{{}} +Read the +[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go) +for the SDK. +{{}} + +## 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. + +{{}} +```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) + } + }) + } +} +``` +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +The {{}}insecure{{}} flag tells the function +to run without encryption or authentication. Only use it during testing and +development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/v1.14/guides/write-a-composition-function-in-python.md b/content/v1.14/guides/write-a-composition-function-in-python.md new file mode 100644 index 00000000..4a9e63a1 --- /dev/null +++ b/content/v1.14/guides/write-a-composition-function-in-python.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +documentation explains composition function inputs. + +{{}} +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. +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} object. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} 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. + +{{}} +```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 +``` +{{}} + +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. + +{{}} +Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python). +{{}} + +{{}} +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. +{{}} + +## 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. + +{{}} +```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() +``` +{{}} + +Run the unit tests using `hatch run`: + +```shell {copy-lines="1"} +hatch run test:unit +. +---------------------------------------------------------------------- +Ran 1 test in 0.003s + +OK +``` + +{{}} +[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. +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +`hatch run development` runs the function without encryption or authentication. +Only use it during testing and development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/v1.14/learn/_index.md b/content/v1.14/learn/_index.md new file mode 100644 index 00000000..a19b5d13 --- /dev/null +++ b/content/v1.14/learn/_index.md @@ -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) + + + +[join-crossplane-slack]: https://slack.crossplane.io +[contact-us]: https://github.com/crossplane/crossplane#contact diff --git a/content/v1.14/learn/feature-lifecycle.md b/content/v1.14/learn/feature-lifecycle.md new file mode 100644 index 00000000..16a59691 --- /dev/null +++ b/content/v1.14/learn/feature-lifecycle.md @@ -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 \ No newline at end of file diff --git a/content/v1.14/learn/release-cycle.md b/content/v1.14/learn/release-cycle.md new file mode 100644 index 00000000..76f68e32 --- /dev/null +++ b/content/v1.14/learn/release-cycle.md @@ -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. + + + +[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 diff --git a/content/v1.14/release-notes/1.14.0.md b/content/v1.14/release-notes/1.14.0.md index 1110f91d..bb1cfbb3 100644 --- a/content/v1.14/release-notes/1.14.0.md +++ b/content/v1.14/release-notes/1.14.0.md @@ -28,7 +28,7 @@ Read the * 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. * Removed Vault support for External Secret Stores. Crossplane - suggests using the [ESS Plugins]({{}}) as a replacement. + suggests using the [ESS Plugins]({{}}) as a replacement. * Removed the `controllerConfigRef` from the Configuration package and package revision APIs. * The introduction of the new [Crossplane CLI]({{}}) deprecates diff --git a/content/v1.15/api/_index.md b/content/v1.15/api/_index.md index 5fbcb8d8..6075e613 100644 --- a/content/v1.15/api/_index.md +++ b/content/v1.15/api/_index.md @@ -1,5 +1,5 @@ --- -title: Crossplane API +title: API Reference weight: 400 description: "API details for Crossplane's core types" cascade: diff --git a/content/v1.15/cli/_index.md b/content/v1.15/cli/_index.md index 10150e54..35d0e2b1 100644 --- a/content/v1.15/cli/_index.md +++ b/content/v1.15/cli/_index.md @@ -1,6 +1,6 @@ --- -weight: 400 -title: Crossplane CLI +weight: 200 +title: CLI Reference description: "Documentation for the Crossplane command-line interface" --- diff --git a/content/v1.15/concepts/_index.md b/content/v1.15/concepts/_index.md index 7d00df9f..3c821d9e 100644 --- a/content/v1.15/concepts/_index.md +++ b/content/v1.15/concepts/_index.md @@ -1,6 +1,6 @@ --- title: Concepts -weight: 100 +weight: 50 description: Understand Crossplane's core components --- diff --git a/content/v1.15/concepts/claims.md b/content/v1.15/concepts/claims.md index bfda614b..65e62f72 100644 --- a/content/v1.15/concepts/claims.md +++ b/content/v1.15/concepts/claims.md @@ -204,4 +204,4 @@ spec: name: my-claim-secret ``` -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). \ No newline at end of file diff --git a/content/v1.15/concepts/composite-resource-definitions.md b/content/v1.15/concepts/composite-resource-definitions.md index a4dbc9d2..38ce1d28 100644 --- a/content/v1.15/concepts/composite-resource-definitions.md +++ b/content/v1.15/concepts/composite-resource-definitions.md @@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`. {{
}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). ### Set composite resource defaults XRDs can set default parameters for composite resources and Claims. diff --git a/content/v1.15/concepts/composite-resources.md b/content/v1.15/concepts/composite-resources.md index db3b6a0b..35ccd792 100644 --- a/content/v1.15/concepts/composite-resources.md +++ b/content/v1.15/concepts/composite-resources.md @@ -189,7 +189,7 @@ spec: ### Composition revision policy Crossplane tracks changes to Compositions as -[Composition revisions]({{}}) . +[Composition revisions]({{}}) . A composite resource can use a {{}}compositionUpdatePolicy{{}} to @@ -217,7 +217,7 @@ spec: ### Composition revision selection Crossplane records changes to Compositions as -[Composition revisions]({{}}). +[Composition revisions]({{}}). A composite resource can select a specific Composition revision. @@ -309,7 +309,7 @@ spec: ``` Composite resources can write connection secrets to an -[external secret store]({{}}), +[external secret store]({{}}), like HashiCorp Vault. {{}} @@ -332,10 +332,10 @@ spec: # Removed for brevity ``` -Read the [External Secrets Store]({{}}) documentation for more information on using +Read the [External Secrets Store]({{}}) documentation for more information on using external secret stores. -For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). +For more information on connection secrets read the [Connection Secrets knowledge base article]({{}}). ### Pausing composite resources diff --git a/content/v1.15/concepts/composition-functions.md b/content/v1.15/concepts/composition-functions.md index 3ba69f47..4fbdf632 100644 --- a/content/v1.15/concepts/composition-functions.md +++ b/content/v1.15/concepts/composition-functions.md @@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0. {{}} Crossplane has -[language specific guides]({{}}) to writing +[language specific guides]({{}}) to writing a composition function. Refer to the guide for your preferred language for a more detailed guide to writing a function. {{}} diff --git a/content/v1.15/concepts/composition-revisions.md b/content/v1.15/concepts/composition-revisions.md new file mode 100644 index 00000000..03433442 --- /dev/null +++ b/content/v1.15/concepts/composition-revisions.md @@ -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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +vpc-staging False 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 +vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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 +vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev] +vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual +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]: {{}} +[Compositions]: {{}} +[canary]: https://martinfowler.com/bliki/CanaryRelease.html +[install-guide]: {{}} diff --git a/content/v1.15/concepts/compositions.md b/content/v1.15/concepts/compositions.md index 281e57d5..746eec63 100644 --- a/content/v1.15/concepts/compositions.md +++ b/content/v1.15/concepts/compositions.md @@ -748,7 +748,7 @@ details. This section discusses creating Kubernetes secrets. Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/). -Read the [external secrets store guide]({{}}) for more information on using Crossplane +Read the [external secrets store guide]({{}}) for more information on using Crossplane with an external secret store. {{}} @@ -958,7 +958,7 @@ for more information on restricting secret keys. {{< /hint >}} For more information on connection secrets read the -[Connection Secrets knowledge base article]({{}}). +[Connection Secrets knowledge base article]({{}}). {{}} You can't change the @@ -973,7 +973,7 @@ recreate the Composition to change the #### Save connection details to an external secret store Crossplane -[External Secret Stores]({{}}) +[External Secret Stores]({{}}) write secrets and connection details to external secret stores like HashiCorp Vault. @@ -1018,7 +1018,7 @@ spec: # Removed for brevity ``` -For more details read the [External Secret Stores]({{}}) +For more details read the [External Secret Stores]({{}}) integration guide. ### Resource readiness checks diff --git a/content/v1.15/concepts/connection-details.md b/content/v1.15/concepts/connection-details.md new file mode 100644 index 00000000..3bf8b8d0 --- /dev/null +++ b/content/v1.15/concepts/connection-details.md @@ -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]({{}}). +* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{}}). +* Define the `writeConnectionSecretToRef` name and namespace for each resource in the + [Composition]({{}}). +* Define the list of secret keys produced by each composed resource with `connectionDetails` in the + [Composition]({{}}). +* Optionally, define the `connectionSecretKeys` in a + [CompositeResourceDefinition]({{}}). + +{{}} +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]({{}}) for more information on using Crossplane +with an external secret store. +{{}} + +## Background +When a [Provider]({{}}) 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. + + + +When a managed resource is part of a +[Composition]({{}}), the Composition, +[Composite Resource Definition]({{}}) +and optionally, the +[Claim]({{}}) define what details are visible +and where they're stored. + + +{{}} +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. + +{{}} +```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" +``` +{{}} + +{{}} + +```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 +``` +{{}} + +{{}} +```yaml +apiVersion: example.org/v1alpha1 +kind: SecretTest +metadata: + name: test-secrets + namespace: default +spec: + writeConnectionSecretToRef: + name: my-access-key-secret +``` +{{}} +{{}} + +## Connection secrets in a managed resource + + + + +When a managed resource creates connection secrets, Crossplane can write the +secrets to a +[Kubernetes secret]({{}}) +or an +[external secret store]({{}}). + + + +Creating an individual managed resource shows the connection secrets the +resource creates. + +{{}} +Read the [managed resources]({{}}) +documentation for more information on configuring resources and storing +connection secrets for individual resources. +{{< /hint >}} + + +For example, create an +{{}}AccessKey{{}} resource and save the +connection secrets in a Kubernetes secret named +{{}}my-accesskey-secret{{}} +in the +{{}}default{{}} 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 +{{}}attribute.secret{{}}, +{{}}attribute.ses_smtp_password_v4{{}}, +{{}}password{{}} and +{{}}username{{}} + +```yaml {label="mrSecret",copy-lines="1"} +kubectl describe secret my-accesskey-secret +Name: my-accesskey-secret +Namespace: default +Labels: +Annotations: + +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 +{{}}AccessKey{{}} +objects. +Each {{}}AccessKey{{}} writes a +connection secrets to the {{}}name{{}} +inside the {{}}namespace{{}} defined by +the resource +{{}}writeConnectionSecretToRef{{}}. + +Crossplane also creates a secret object for the entire Composition +saved in the namespace defined by +{{}}writeConnectionSecretsToNamespace{{}} +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 +{{}}key1-secret{{}} is from the resource +{{}}key1{{}}, +{{}}key2-secret{{}} is from the resource +{{}}key2{{}}. + +Crossplane creates another secret in the namespace +{{}}other-namespace{{}} 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 +{{}}connectionDetails{{}} object under +each resource and define the secret keys the resource creates. + + +{{}} +You can't change the +{{}}connectionDetails{{}} +of a Composition. +You must delete and +recreate the Composition to change the +{{}}connectionDetails{{}}. +{{}} + +```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 +{{}}connectionDetails{{}}. + +```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 +``` + +{{}} +If a key isn't listed in the +{{}}connectionDetails{{}} +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 +{{}}name{{}}. + +```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, +{{}}username{{}} +and +{{}}key2-user{{}} + +```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 +{{}}connectionSecretKeys{{}} object. + +Inside the {{}}connectionSecretKeys{{}} list +the secret key names to create. Crossplane only adds the keys listed to the +combined secret. + +{{}} +You can't change the +{{}}connectionSecretKeys{{}} of an XRD. +You must delete and +recreate the XRD to change the +{{}}connectionSecretKeys{{}}. +{{}} + +For example, an XRD may restrict the secrets to only the +{{}}username{{}}, +{{}}password{{}} and custom named +{{}}key2-user{{}} 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 +{{}}connectionSecretKeys{{}} +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 +{{}}writeConnectionSecretToRef{{}}. + +Crossplane saves the combined secret with a Crossplane generated name in the +namespace defined in the Composition's +{{}}writeConnectionSecretsToNamespace{{}}. + +```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 +{{}}writeConnectionSecretToRef{{}}. + +```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, {{}}my-access-key-secret{{}} + in the Claim's {{}}namespace{{}}. +* The first resource's secret object, {{}}key1{{}}. +* The second resource's secret object, {{}}key2{{}}. +* The composite resource secret object in the + {{}}other-namespace{{}} 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 +``` \ No newline at end of file diff --git a/content/v1.15/concepts/managed-resources.md b/content/v1.15/concepts/managed-resources.md index 6c466d56..d06ad7fe 100644 --- a/content/v1.15/concepts/managed-resources.md +++ b/content/v1.15/concepts/managed-resources.md @@ -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. | | `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]({{}}) section for more details. | -| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | +| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{}}). | | `Update` | Crossplane changes the external resource when changing the managed resource. | {{}} @@ -373,7 +373,7 @@ The following is a list of common policy combinations: | {{}} | | {{}} | {{}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. | | {{}} | | | {{}} | {{}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. | | {{}} | | | {{}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. | -| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | +| | | | {{}} | | Crossplane only observes a resource. Used for [observe only resources]({{}}). | | | | | | | No policy set. An alternative method for [pausing](#paused) a resource. | {{< /table >}} @@ -567,7 +567,7 @@ metadata: {{}} Read the -[Vault as an External Secrets Store]({{}}) +[Vault as an External Secrets Store]({{}}) guide for details on using StoreConfig objects. {{< /hint >}} diff --git a/content/v1.15/concepts/providers.md b/content/v1.15/concepts/providers.md index 77b0b544..099f2835 100644 --- a/content/v1.15/concepts/providers.md +++ b/content/v1.15/concepts/providers.md @@ -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 removing their finalizers. -For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). +For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{}}). {{< /hint >}} ## 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 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. The most common use case for ControllerConfigs are providing `args` to a 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. Each Provider determines their supported set of `args`. diff --git a/content/v1.15/guides/_index.md b/content/v1.15/guides/_index.md new file mode 100644 index 00000000..7b1ee4b5 --- /dev/null +++ b/content/v1.15/guides/_index.md @@ -0,0 +1,5 @@ +--- +title: Guides +weight: 100 +description: Crossplane integrations and detailed examples. +--- \ No newline at end of file diff --git a/content/v1.15/guides/crossplane-with-argo-cd.md b/content/v1.15/guides/crossplane-with-argo-cd.md new file mode 100644 index 00000000..a27fc7f6 --- /dev/null +++ b/content/v1.15/guides/crossplane-with-argo-cd.md @@ -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`. +{{}} +{{}} ProviderConfig{{}} may have no status or a `status.users` field. +{{}} +```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. + diff --git a/content/v1.15/guides/disaster-recovery.md b/content/v1.15/guides/disaster-recovery.md new file mode 100644 index 00000000..e0007372 --- /dev/null +++ b/content/v1.15/guides/disaster-recovery.md @@ -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/). \ No newline at end of file diff --git a/content/v1.15/guides/import-existing-resources.md b/content/v1.15/guides/import-existing-resources.md new file mode 100644 index 00000000..6df790c9 --- /dev/null +++ b/content/v1.15/guides/import-existing-resources.md @@ -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`]({{}}) +field enables importing external resources into Crossplane. + +Crossplane can import resources either [manually]({{}}) +or [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 +{{}}my-existing-network{{}}, +create a new managed resource and use the +{{}}my-existing-network{{}} 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 {{}}metadata.name{{}} +field can be anything you want. For example, +{{}}imported-network{{}}. + +{{< 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 +{{}}spec.forProvider{{}} 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 +{{}}spec.forProvider{{}} 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]({{}}). + +Crossplane imports observe only resources but never changes or deletes the +resources. + +{{}} +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 >}} + + +### Apply the Observe management policy + + +Create a new managed resource matching the +{{}}apiVersion{{}} and +{{}}kind{{}} of the resource +to import and add +{{}}managementPolicies: ["Observe"]{{}} to the +{{}}spec{{}} + +For example, to import a GCP SQL DatabaseInstance, create a new resource with +the {{}}managementPolicies: ["Observe"]{{}} +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 {{}}crossplane.io/external-name{{}} +annotation for the resource. This name must match the name inside the Provider. + +For example, for a GCP database named +{{}}my-external-database{{}}, apply +the +{{}}crossplane.io/external-name{{}} +annotation with the value +{{}}my-external-database{{}}. + +```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 {{}}name{{}} to use for the +Kubernetes object. + +For example, name the Kubernetes object +{{}}my-imported-database{{}}. + +```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 +{{}}spec.forProvider{{}} field. + +For example, only import the GCP SQL database in the +{{}}us-central1{{}} 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 +{{}}status.atProvider{{}} +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 +``` + +## Control imported ObserveOnly resources + + +Crossplane can take active control of observe only imported resources by +changing the `managementPolicies` after import. + +Change the {{}}managementPolicies{{}} field +of the managed resource to +{{}}["*"]{{}}. + +Copy any required parameter values from +{{}}status.atProvider{{}} and provide them +in {{}}spec.forProvider{{}}. + +{{< 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. \ No newline at end of file diff --git a/content/v1.15/guides/multi-tenant.md b/content/v1.15/guides/multi-tenant.md new file mode 100644 index 00000000..6a378509 --- /dev/null +++ b/content/v1.15/guides/multi-tenant.md @@ -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. + + + +[managed resources]: {{}} +[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ +[Composition]: {{}} +[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]: {{}} +[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 diff --git a/content/v1.15/guides/self-signed-ca-certs.md b/content/v1.15/guides/self-signed-ca-certs.md new file mode 100644 index 00000000..c5734aeb --- /dev/null +++ b/content/v1.15/guides/self-signed-ca-certs.md @@ -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]({{}}). + +## 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 +``` diff --git a/content/v1.15/guides/troubleshoot-crossplane.md b/content/v1.15/guides/troubleshoot-crossplane.md new file mode 100644 index 00000000..a8d36ed3 --- /dev/null +++ b/content/v1.15/guides/troubleshoot-crossplane.md @@ -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 +``` + +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 -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 + +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: + ``` + +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]({{}}) +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. + +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 -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. + + + + + +[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 + + diff --git a/content/v1.15/guides/vault-as-secret-store.md b/content/v1.15/guides/vault-as-secret-store.md new file mode 100644 index 00000000..d76ceb97 --- /dev/null +++ b/content/v1.15/guides/vault-as-secret-store.md @@ -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]. + +{{}} +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]({{}}) 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 + +{{}} +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. + + + +### Enable the Vault kv secrets engine + + +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. +{{}} + +```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 - <}} +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 <}} +This example uses Provider GCP, but the +{{}}ControllerConfig{{}} is the +same for all Providers. +{{}} + +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 {{}}VaultConfig{{}} +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 {{}}StoreConfig{{}} +object from the +{{}}secrets.crossplane.io{{}} +group. Crossplane uses the StoreConfig to connect to the Vault plugin service. + +The {{}}configRef{{}} 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 {{}}StoreConfig{{}} +object from the Provider's API group, +{{}}gcp.crossplane.io{{}}. +The Provider uses this StoreConfig to communicate with Vault for +Managed Resources. + +The {{}}configRef{{}} 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 +{{}}connectionDetails{{}} that the +Provider stores in Vault using the +{{}}publishConnectionDetailsTo{{}} 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 +{{}}publishConnectionDetailsTo{{}} 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 {{}}ess-claim-conn{{}} +is the name of the Claim's +{{}}publishConnectionDetailsTo{{}} +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 +{{}}d2408335-eb88-4146-927b-8025f405da86{{}} +comes from + + + +and the key +{{}}ess-mr-conn{{}} +comes from the Composition's +{{}}publishConnectionDetailsTo{{}} +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 +``` + + + +[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 \ No newline at end of file diff --git a/content/v1.15/guides/vault-injection.md b/content/v1.15/guides/vault-injection.md new file mode 100644 index 00000000..189b07f6 --- /dev/null +++ b/content/v1.15/guides/vault-injection.md @@ -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 - <}} +{{< 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 >}} + + + +[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 diff --git a/content/v1.15/guides/write-a-composition-function-in-go.md b/content/v1.15/guides/write-a-composition-function-in-go.md new file mode 100644 index 00000000..0430957c --- /dev/null +++ b/content/v1.15/guides/write-a-composition-function-in-go.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +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. + +{{}} +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 ./... +``` +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} struct. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} struct. + +{{}} +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). +{{}} + +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. + +{{}} +```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 +} +``` +{{}} + +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. + +{{}} +Read the +[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go) +for the SDK. +{{}} + +## 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. + +{{}} +```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) + } + }) + } +} +``` +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +The {{}}insecure{{}} flag tells the function +to run without encryption or authentication. Only use it during testing and +development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/v1.15/guides/write-a-composition-function-in-python.md b/content/v1.15/guides/write-a-composition-function-in-python.md new file mode 100644 index 00000000..4a9e63a1 --- /dev/null +++ b/content/v1.15/guides/write-a-composition-function-in-python.md @@ -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]{{}} +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]{{}} +before following this guide. +{{< /hint >}} + +## Understand the steps + +This guide covers writing a composition function for an +{{}}XBuckets{{}} 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 +``` + + + +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. + + +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]({{}}) v1.14 or newer. This guide uses Crossplane + CLI v1.14. + +{{}} +You don't need access to a Kubernetes cluster or a Crossplane control plane to +build or test a composition function. +{{}} + +## 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. + +{{}} + + +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. + +{{}} + +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]{{}} +documentation explains composition function inputs. + +{{}} +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. +{{}} + +## Edit the template to add the function's logic + +You add your function's logic to the +{{}}RunFunction{{}} +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 +{{}}RunFunctionRequest{{}} object. + +The function tells Crossplane what resources it should compose by returning a +{{}}RunFunctionResponse{{}} 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. + +{{}} +```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 +``` +{{}} + +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. + +{{}} +Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python). +{{}} + +{{}} +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. +{{}} + +## 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. + +{{}} +```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() +``` +{{}} + +Run the unit tests using `hatch run`: + +```shell {copy-lines="1"} +hatch run test:unit +. +---------------------------------------------------------------------- +Ran 1 test in 0.003s + +OK +``` + +{{}} +[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. +{{}} + +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. + +{{}} + +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 +``` + +
+ +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 +``` + +
+ +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 +``` +{{
}} + +The Function in `functions.yaml` uses the +{{}}Development{{}} +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 +``` + +{{}} +`hatch run development` runs the function without encryption or authentication. +Only use it during testing and development. +{{}} + +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 +``` + +{{}} +Read the composition functions documentation to learn more about +[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}). +{{}} + +## 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]{{}}. +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 +``` + +{{}} +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. +{{}} + +{{}} +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. +{{}} + +Use the Crossplane CLI to build a package for each platform. Each package embeds +a runtime image. + +The {{}}--package-root{{}} flag specifies +the `package` directory, which contains `crossplane.yaml`. This includes +metadata about the package. + +The {{}}--embed-runtime-image{{}} flag +specifies the runtime image tag built using Docker. + +The {{}}--package-file{{}} 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 +``` + +{{}} +Crossplane packages are special OCI images. Read more about packages in the +[packages documentation]({{< ref "../concepts/packages" >}}). +{{}} + +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 +``` + +{{}} +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`. +{{}} diff --git a/content/v1.15/learn/_index.md b/content/v1.15/learn/_index.md new file mode 100644 index 00000000..e693ab1b --- /dev/null +++ b/content/v1.15/learn/_index.md @@ -0,0 +1,35 @@ +--- +title: Learn More +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]! + +***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) + + + +[join-crossplane-slack]: https://slack.crossplane.io +[contact-us]: https://github.com/crossplane/crossplane#contact diff --git a/content/v1.15/learn/feature-lifecycle.md b/content/v1.15/learn/feature-lifecycle.md new file mode 100644 index 00000000..16a59691 --- /dev/null +++ b/content/v1.15/learn/feature-lifecycle.md @@ -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 \ No newline at end of file diff --git a/content/v1.15/learn/release-cycle.md b/content/v1.15/learn/release-cycle.md new file mode 100644 index 00000000..76f68e32 --- /dev/null +++ b/content/v1.15/learn/release-cycle.md @@ -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. + + + +[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 diff --git a/content/v1.15/release-notes/_index.md b/content/v1.15/release-notes/_index.md index c1a4b201..526e5674 100644 --- a/content/v1.15/release-notes/_index.md +++ b/content/v1.15/release-notes/_index.md @@ -1,6 +1,6 @@ --- title: Release Notes -weight: 20 +weight: 600 description: "Crossplane release notes" product: "Release Notes" cascade: diff --git a/content/v1.15/software/_index.md b/content/v1.15/software/_index.md index 7cef2dd1..e1b4933a 100644 --- a/content/v1.15/software/_index.md +++ b/content/v1.15/software/_index.md @@ -1,6 +1,6 @@ --- -title: Install and Uninstall Crossplane -weight: 300 +title: Install, Upgrade and Uninstall +weight: 10 description: Manage Crossplane installations --- diff --git a/content/v1.15/software/upgrade.md b/content/v1.15/software/upgrade.md index b11f3644..c145cc17 100644 --- a/content/v1.15/software/upgrade.md +++ b/content/v1.15/software/upgrade.md @@ -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. 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]({{}}) with the upgrade command. For example, to maintain the original image registry use diff --git a/netlify.toml b/netlify.toml index 1ad64417..cad9ddd0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -70,6 +70,120 @@ from = "/knowledge-base/install/**" to = "/latest/software/" status = 302 +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/composition-revisions" +to = "/latest/concepts/composition-revisions/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/composition-revisions-example" +to = "/latest/concepts/composition-revisions/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/connection-details" +to = "/latest/concepts/connection-details/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/integrations/argo-cd-crossplane" +to = "/latest/guides/crossplane-with-argo-cd/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/write-a-composition-function-in-go/" +to = "/latest/guides/write-a-composition-function-in-go/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/write-a-composition-function-in-python" +to = "/latest/guides/write-a-composition-function-in-python/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/multi-tenant/" +to = "/latest/guides/multi-tenant/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/import-existing-resources/" +to = "/latest/guides/import-existing-resources/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/integrations/vault-injection/" +to = "/latest/guides/vault-injection/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/integrations/vault-as-secret-store/" +to = "/latest/guides/vault-as-secret-store/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/troubleshoot/" +to = "/latest/guides/troubleshoot-crossplane/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/self-signed-ca-certs/" +to = "/latest/guides/self-signed-ca-certs/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/disaster-recovery/" +to = "/latest/guides/disaster-recovery/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/learn-more/" +to = "/latest/learn/" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/feature-lifecycle/" +to = "/latest/learn/feature-lifecycle" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/release-cycle/" +to = "/latest/learn/release-cycle" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/guides/" +to = "/latest/guides" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/integrations/" +to = "/latest/guides" +status = 301 + +[[redirects]] +# Moved KB article. See issue #749 +from = "/knowledge-base/" +to = "/latest/" +status = 301 + # Use [dev] to set configuration overrides for local # development environments run using Netlify Dev - except # for environment variables. Environment variables for Netlify diff --git a/themes/geekboot/layouts/_default/home.html b/themes/geekboot/layouts/_default/home.html index c451e185..f484046e 100644 --- a/themes/geekboot/layouts/_default/home.html +++ b/themes/geekboot/layouts/_default/home.html @@ -1,54 +1 @@ -{{ define "main" }} - -{{/* Define the section cards on the landing page. */}} -{{/* Cards are displayed in order */}} -{{ $sectionSlice := slice - (dict "User Documentation" "/img/automate.svg") - (dict "Knowledge Base" "/img/truck.svg") - (dict "Contributing Guide" "/img/multi-crane.svg") -}} - - - - - -
-
- - {{ $thisSite := .Site }} - {{ $version_section := slice }} - {{ range $sectionSlice }} - {{ range $docType, $headerImage := . }} - {{ if eq $docType "User Documentation" }} - {{ $version_section = where $thisSite.Sections ".Page.Params.version" $thisSite.Params.latest }} - {{ else }} - {{ $version_section = where $thisSite.Sections ".Page.Params.product" $docType }} - {{ end }} -
- ... -
-

{{range $version_section }}{{$docType}} {{ end }}

-
    - {{ range ((index $version_section 0).Pages).GroupBy "Weight" }} - {{ range .Pages.ByWeight }} - {{ if not .Params.tocHidden }} -
  • - {{ .Title }}
    - {{ .Description }} -
  • - {{ end }} - {{ end }} - {{ end }} -
-
-
- {{ end }} - {{ end }} -
-
-{{ end }} - +{{ partial "redirect" (dict "dest" (printf "/v%s/" (string .Site.Params.latest) ) ) }} \ No newline at end of file diff --git a/themes/geekboot/layouts/partials/docs-sidebar.html b/themes/geekboot/layouts/partials/docs-sidebar.html index 93b367da..148c2d75 100644 --- a/themes/geekboot/layouts/partials/docs-sidebar.html +++ b/themes/geekboot/layouts/partials/docs-sidebar.html @@ -43,10 +43,6 @@ {{ partialCached "sidebar/user-docs" . }} {{ end }} - {{ if ne .Page.Params.product "Knowledge Base" }} - {{ partialCached "sidebar/knowledge-base" . }} - {{ end }} - {{ if ne .Page.Params.product "Contributing Guide" }} {{ partialCached "sidebar/contributing-guide" . }} {{ end }} diff --git a/themes/geekboot/layouts/partials/feature-state-alert.html b/themes/geekboot/layouts/partials/feature-state-alert.html index 1bd9c4a2..ddfd1a6e 100644 --- a/themes/geekboot/layouts/partials/feature-state-alert.html +++ b/themes/geekboot/layouts/partials/feature-state-alert.html @@ -29,8 +29,12 @@
This feature graduated to beta status in v{{.Page.Params.betaVersion}}. {{ end }} + {{ $ver := (printf "v%s" .Page.Params.version) }} + {{ if eq .Page.Params.version "master" }} + {{ $ver = "master" }} + {{ end }}

- For more information read the Crossplane feature lifecycle. + For more information read the Crossplane feature lifecycle.

diff --git a/themes/geekboot/layouts/partials/sidebar/knowledge-base.html b/themes/geekboot/layouts/partials/sidebar/knowledge-base.html deleted file mode 100644 index 237b253a..00000000 --- a/themes/geekboot/layouts/partials/sidebar/knowledge-base.html +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file