From 9db24460d77c25c7e0d8136241de616c752a9b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonasz=20=C5=81asut-Balcerzak?= Date: Tue, 28 Apr 2026 12:25:10 +0200 Subject: [PATCH] Embed example manifests inline via shortcode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the inconsistent mix of save-and-apply YAML, kubectl heredocs, and duplicated manifest files with a single mechanism: example manifests live as real .yaml files Co-Authored-By: Claude Opus 4.7 Signed-off-by: Jonasz Łasut-Balcerzak --- config.yaml | 4 + .../get-started-with-composition.md | 584 +----------------- .../get-started-with-managed-resources.md | 52 +- .../get-started-with-operations.md | 157 +---- content/master/guides/change-logs.md | 104 +--- .../guides/connection-details-composition.md | 530 +--------------- .../disabling-unused-managed-resources.md | 31 +- content/master/guides/scalable-composition.md | 133 +--- .../get-started/composition/app.yaml | 7 + .../composition/composition-kcl.yaml | 74 +++ .../composition/composition-python.yaml | 69 +++ .../composition/composition-pythonic.yaml | 39 ++ .../composition-templated-yaml.yaml | 69 +++ .../composition/composition-yaml-cel.yaml | 60 ++ .../composition/composition-yaml.yaml | 72 +++ .../composition/fn-go-templating.yaml | 6 + .../get-started/composition/fn-kcl.yaml | 6 + .../get-started/composition/fn-kro.yaml | 6 + .../composition/fn-patch-and-transform.yaml | 6 + .../get-started/composition/fn-python.yaml | 6 + .../get-started/composition/fn-pythonic.yaml | 6 + .../get-started/composition/xrd.yaml | 35 ++ .../get-started/managed-resources/bucket.yaml | 8 + .../managed-resources/provider.yaml | 6 + .../managed-resources/providerconfig.yaml | 11 + .../get-started/operations/function.yaml | 6 + .../get-started/operations/ingress-rbac.yaml | 10 + .../get-started/operations/ingress.yaml | 17 + .../get-started/operations/operation.yaml | 75 +++ ...oymentruntimeconfig-enable-changelogs.yaml | 28 + .../object-configmap-for-changelogs.yaml | 14 + .../change-logs/provider-kubernetes.yaml | 10 + .../manifests/guides/change-logs/rbac.yaml | 32 + .../composition-go-templating.yaml | 70 +++ .../composition-kcl.yaml | 69 +++ .../composition-patch-and-transform.yaml | 83 +++ .../composition-python.yaml | 100 +++ .../composition-pythonic.yaml | 30 + .../fn-auto-ready.yaml | 6 + .../fn-go-templating.yaml | 6 + .../fn-kcl.yaml | 6 + .../fn-patch-and-transform.yaml | 6 + .../fn-python.yaml | 6 + .../fn-pythonic.yaml | 6 + .../my-keys.yaml | 8 + .../connection-details-composition/xrd.yaml | 26 + .../activation-policy.yaml | 9 + .../provider.yaml | 6 + .../composition-scale.yaml | 46 ++ .../scalable-composition/fn-auto-ready.yaml | 6 + .../guides/scalable-composition/fn-pat.yaml | 6 + .../scalable-composition/xrd-scale.yaml | 39 ++ .../getting-started/provider-aws-part-2.md | 170 +---- content/v1.20/getting-started/provider-aws.md | 42 +- .../getting-started/provider-azure-part-2.md | 236 +------ .../v1.20/getting-started/provider-azure.md | 45 +- .../getting-started/provider-gcp-part-2.md | 145 +---- content/v1.20/getting-started/provider-gcp.md | 27 +- content/v1.20/guides/change-logs.md | 181 ++---- .../aws-part-2/dynamo-with-bucket.yaml | 57 ++ .../function-patch-and-transform.yaml | 6 + .../aws-part-2/nosql-my-nosql-database.yaml | 6 + .../nosqlclaim-my-nosql-database.yaml | 7 + .../nosqls.database.example.com.yaml | 30 + .../aws-part-2/provider-aws-dynamodb.yaml | 6 + .../aws-part-2/provider-aws-s3.yaml | 6 + .../aws-part-2/providerconfig.yaml | 11 + .../manifests/getting-started/aws/bucket.yaml | 9 + .../getting-started/aws/provider.yaml | 6 + .../getting-started/aws/providerconfig.yaml | 11 + ...crossplane-quickstart-vm-with-network.yaml | 123 ++++ .../function-patch-and-transform.yaml | 6 + .../azure-part-2/provider-azure-compute.yaml | 6 + .../azure-part-2/provider-azure-network.yaml | 6 + .../azure-part-2/providerconfig.yaml | 11 + .../azure-part-2/virtualmachine-my-vm.yaml | 6 + .../virtualmachineclaim-my-namespaced-vm.yaml | 7 + .../virtualmachines.compute.example.com.yaml | 30 + .../getting-started/azure/provider.yaml | 6 + .../getting-started/azure/providerconfig.yaml | 11 + ...network-crossplane-quickstart-network.yaml | 10 + .../function-patch-and-transform.yaml | 6 + .../gcp-part-2/provider-gcp-pubsub.yaml | 6 + .../gcp-part-2/provider-gcp-storage.yaml | 6 + .../gcp-part-2/pubsub-my-pubsub-queue.yaml | 6 + .../pubsubclaim-my-pubsub-queue.yaml | 7 + .../gcp-part-2/pubsubs.queue.example.com.yaml | 30 + .../gcp-part-2/topic-with-bucket.yaml | 49 ++ .../manifests/getting-started/gcp/bucket.yaml | 11 + .../getting-started/gcp/provider.yaml | 6 + ...oymentruntimeconfig-enable-changelogs.yaml | 28 + .../object-configmap-for-changelogs.yaml | 14 + .../change-logs/provider-kubernetes.yaml | 10 + .../manifests/guides/change-logs/rbac.yaml | 32 + .../get-started-with-composition.md | 445 +------------ .../get-started-with-managed-resources.md | 52 +- .../get-started/composition/app.yaml | 7 + .../composition/composition-kcl.yaml | 74 +++ .../composition/composition-python.yaml | 69 +++ .../composition-templated-yaml.yaml | 69 +++ .../composition/composition-yaml.yaml | 72 +++ .../composition/fn-go-templating.yaml | 6 + .../get-started/composition/fn-kcl.yaml | 6 + .../composition/fn-patch-and-transform.yaml | 6 + .../get-started/composition/fn-python.yaml | 6 + .../get-started/composition/xrd.yaml | 35 ++ .../get-started/managed-resources/bucket.yaml | 8 + .../managed-resources/provider.yaml | 6 + .../managed-resources/providerconfig.yaml | 11 + .../get-started-with-composition.md | 504 +-------------- .../get-started-with-managed-resources.md | 52 +- .../get-started-with-operations.md | 157 +---- content/v2.0/guides/change-logs.md | 104 +--- .../guides/connection-details-composition.md | 530 +--------------- .../disabling-unused-managed-resources.md | 31 +- .../get-started/composition/app.yaml | 7 + .../composition/composition-kcl.yaml | 74 +++ .../composition/composition-python.yaml | 69 +++ .../composition/composition-pythonic.yaml | 39 ++ .../composition-templated-yaml.yaml | 69 +++ .../composition/composition-yaml.yaml | 72 +++ .../composition/fn-go-templating.yaml | 6 + .../get-started/composition/fn-kcl.yaml | 6 + .../composition/fn-patch-and-transform.yaml | 6 + .../get-started/composition/fn-python.yaml | 6 + .../get-started/composition/fn-pythonic.yaml | 6 + .../get-started/composition/xrd.yaml | 35 ++ .../get-started/managed-resources/bucket.yaml | 8 + .../managed-resources/provider.yaml | 6 + .../managed-resources/providerconfig.yaml | 11 + .../get-started/operations/function.yaml | 6 + .../get-started/operations/ingress-rbac.yaml | 10 + .../get-started/operations/ingress.yaml | 17 + .../get-started/operations/operation.yaml | 75 +++ ...oymentruntimeconfig-enable-changelogs.yaml | 28 + .../object-configmap-for-changelogs.yaml | 14 + .../change-logs/provider-kubernetes.yaml | 10 + .../manifests/guides/change-logs/rbac.yaml | 32 + .../composition-go-templating.yaml | 70 +++ .../composition-kcl.yaml | 69 +++ .../composition-patch-and-transform.yaml | 83 +++ .../composition-python.yaml | 100 +++ .../composition-pythonic.yaml | 33 + .../fn-auto-ready.yaml | 6 + .../fn-go-templating.yaml | 6 + .../fn-kcl.yaml | 6 + .../fn-patch-and-transform.yaml | 6 + .../fn-python.yaml | 6 + .../fn-pythonic.yaml | 6 + .../my-keys.yaml | 8 + .../connection-details-composition/xrd.yaml | 26 + .../activation-policy.yaml | 9 + .../provider.yaml | 6 + .../get-started-with-composition.md | 504 +-------------- .../get-started-with-managed-resources.md | 52 +- .../get-started-with-operations.md | 157 +---- content/v2.1/guides/change-logs.md | 104 +--- .../guides/connection-details-composition.md | 530 +--------------- .../disabling-unused-managed-resources.md | 31 +- .../get-started/composition/app.yaml | 7 + .../composition/composition-kcl.yaml | 74 +++ .../composition/composition-python.yaml | 69 +++ .../composition/composition-pythonic.yaml | 39 ++ .../composition-templated-yaml.yaml | 69 +++ .../composition/composition-yaml.yaml | 72 +++ .../composition/fn-go-templating.yaml | 6 + .../get-started/composition/fn-kcl.yaml | 6 + .../composition/fn-patch-and-transform.yaml | 6 + .../get-started/composition/fn-python.yaml | 6 + .../get-started/composition/fn-pythonic.yaml | 6 + .../get-started/composition/xrd.yaml | 35 ++ .../get-started/managed-resources/bucket.yaml | 8 + .../managed-resources/provider.yaml | 6 + .../managed-resources/providerconfig.yaml | 11 + .../get-started/operations/function.yaml | 6 + .../get-started/operations/ingress-rbac.yaml | 10 + .../get-started/operations/ingress.yaml | 17 + .../get-started/operations/operation.yaml | 75 +++ ...oymentruntimeconfig-enable-changelogs.yaml | 28 + .../object-configmap-for-changelogs.yaml | 14 + .../change-logs/provider-kubernetes.yaml | 10 + .../manifests/guides/change-logs/rbac.yaml | 32 + .../composition-go-templating.yaml | 70 +++ .../composition-kcl.yaml | 69 +++ .../composition-patch-and-transform.yaml | 83 +++ .../composition-python.yaml | 101 +++ .../composition-pythonic.yaml | 33 + .../fn-auto-ready.yaml | 6 + .../fn-go-templating.yaml | 6 + .../fn-kcl.yaml | 6 + .../fn-patch-and-transform.yaml | 6 + .../fn-python.yaml | 6 + .../fn-pythonic.yaml | 6 + .../my-keys.yaml | 8 + .../connection-details-composition/xrd.yaml | 26 + .../activation-policy.yaml | 9 + .../provider.yaml | 6 + .../get-started-with-composition.md | 584 +----------------- .../get-started-with-managed-resources.md | 52 +- .../get-started-with-operations.md | 157 +---- content/v2.2/guides/change-logs.md | 104 +--- .../guides/connection-details-composition.md | 530 +--------------- .../disabling-unused-managed-resources.md | 31 +- .../get-started/composition/app.yaml | 7 + .../composition/composition-kcl.yaml | 74 +++ .../composition/composition-python.yaml | 69 +++ .../composition/composition-pythonic.yaml | 39 ++ .../composition-templated-yaml.yaml | 69 +++ .../composition/composition-yaml-cel.yaml | 60 ++ .../composition/composition-yaml.yaml | 72 +++ .../composition/fn-go-templating.yaml | 6 + .../get-started/composition/fn-kcl.yaml | 6 + .../get-started/composition/fn-kro.yaml | 6 + .../composition/fn-patch-and-transform.yaml | 6 + .../get-started/composition/fn-python.yaml | 6 + .../get-started/composition/fn-pythonic.yaml | 6 + .../get-started/composition/xrd.yaml | 35 ++ .../get-started/managed-resources/bucket.yaml | 8 + .../managed-resources/provider.yaml | 6 + .../managed-resources/providerconfig.yaml | 11 + .../get-started/operations/function.yaml | 6 + .../get-started/operations/ingress-rbac.yaml | 10 + .../get-started/operations/ingress.yaml | 17 + .../get-started/operations/operation.yaml | 75 +++ ...oymentruntimeconfig-enable-changelogs.yaml | 28 + .../object-configmap-for-changelogs.yaml | 14 + .../change-logs/provider-kubernetes.yaml | 10 + .../manifests/guides/change-logs/rbac.yaml | 32 + .../composition-go-templating.yaml | 70 +++ .../composition-kcl.yaml | 69 +++ .../composition-patch-and-transform.yaml | 83 +++ .../composition-python.yaml | 100 +++ .../composition-pythonic.yaml | 30 + .../fn-auto-ready.yaml | 6 + .../fn-go-templating.yaml | 6 + .../fn-kcl.yaml | 6 + .../fn-patch-and-transform.yaml | 6 + .../fn-python.yaml | 6 + .../fn-pythonic.yaml | 6 + .../my-keys.yaml | 8 + .../connection-details-composition/xrd.yaml | 26 + .../activation-policy.yaml | 9 + .../provider.yaml | 6 + .../layouts/shortcodes/manifest-url.html | 33 + .../geekboot/layouts/shortcodes/manifest.html | 58 ++ utils/vale/.vale.ini | 2 +- 246 files changed, 5867 insertions(+), 6773 deletions(-) create mode 100644 content/master/manifests/get-started/composition/app.yaml create mode 100644 content/master/manifests/get-started/composition/composition-kcl.yaml create mode 100644 content/master/manifests/get-started/composition/composition-python.yaml create mode 100644 content/master/manifests/get-started/composition/composition-pythonic.yaml create mode 100644 content/master/manifests/get-started/composition/composition-templated-yaml.yaml create mode 100644 content/master/manifests/get-started/composition/composition-yaml-cel.yaml create mode 100644 content/master/manifests/get-started/composition/composition-yaml.yaml create mode 100644 content/master/manifests/get-started/composition/fn-go-templating.yaml create mode 100644 content/master/manifests/get-started/composition/fn-kcl.yaml create mode 100644 content/master/manifests/get-started/composition/fn-kro.yaml create mode 100644 content/master/manifests/get-started/composition/fn-patch-and-transform.yaml create mode 100644 content/master/manifests/get-started/composition/fn-python.yaml create mode 100644 content/master/manifests/get-started/composition/fn-pythonic.yaml create mode 100644 content/master/manifests/get-started/composition/xrd.yaml create mode 100644 content/master/manifests/get-started/managed-resources/bucket.yaml create mode 100644 content/master/manifests/get-started/managed-resources/provider.yaml create mode 100644 content/master/manifests/get-started/managed-resources/providerconfig.yaml create mode 100644 content/master/manifests/get-started/operations/function.yaml create mode 100644 content/master/manifests/get-started/operations/ingress-rbac.yaml create mode 100644 content/master/manifests/get-started/operations/ingress.yaml create mode 100644 content/master/manifests/get-started/operations/operation.yaml create mode 100644 content/master/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml create mode 100644 content/master/manifests/guides/change-logs/object-configmap-for-changelogs.yaml create mode 100644 content/master/manifests/guides/change-logs/provider-kubernetes.yaml create mode 100644 content/master/manifests/guides/change-logs/rbac.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/composition-go-templating.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/composition-kcl.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/composition-python.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/composition-pythonic.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-auto-ready.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-go-templating.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-kcl.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-python.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/fn-pythonic.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/my-keys.yaml create mode 100644 content/master/manifests/guides/connection-details-composition/xrd.yaml create mode 100644 content/master/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml create mode 100644 content/master/manifests/guides/disabling-unused-managed-resources/provider.yaml create mode 100644 content/master/manifests/guides/scalable-composition/composition-scale.yaml create mode 100644 content/master/manifests/guides/scalable-composition/fn-auto-ready.yaml create mode 100644 content/master/manifests/guides/scalable-composition/fn-pat.yaml create mode 100644 content/master/manifests/guides/scalable-composition/xrd-scale.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/dynamo-with-bucket.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/function-patch-and-transform.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/nosql-my-nosql-database.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/nosqlclaim-my-nosql-database.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/nosqls.database.example.com.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/provider-aws-dynamodb.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/provider-aws-s3.yaml create mode 100644 content/v1.20/manifests/getting-started/aws-part-2/providerconfig.yaml create mode 100644 content/v1.20/manifests/getting-started/aws/bucket.yaml create mode 100644 content/v1.20/manifests/getting-started/aws/provider.yaml create mode 100644 content/v1.20/manifests/getting-started/aws/providerconfig.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/crossplane-quickstart-vm-with-network.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/function-patch-and-transform.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/provider-azure-compute.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/provider-azure-network.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/providerconfig.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/virtualmachine-my-vm.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/virtualmachineclaim-my-namespaced-vm.yaml create mode 100644 content/v1.20/manifests/getting-started/azure-part-2/virtualmachines.compute.example.com.yaml create mode 100644 content/v1.20/manifests/getting-started/azure/provider.yaml create mode 100644 content/v1.20/manifests/getting-started/azure/providerconfig.yaml create mode 100644 content/v1.20/manifests/getting-started/azure/virtualnetwork-crossplane-quickstart-network.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/function-patch-and-transform.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-pubsub.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-storage.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/pubsub-my-pubsub-queue.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/pubsubclaim-my-pubsub-queue.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/pubsubs.queue.example.com.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp-part-2/topic-with-bucket.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp/bucket.yaml create mode 100644 content/v1.20/manifests/getting-started/gcp/provider.yaml create mode 100644 content/v1.20/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml create mode 100644 content/v1.20/manifests/guides/change-logs/object-configmap-for-changelogs.yaml create mode 100644 content/v1.20/manifests/guides/change-logs/provider-kubernetes.yaml create mode 100644 content/v1.20/manifests/guides/change-logs/rbac.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/app.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/composition-kcl.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/composition-python.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/composition-templated-yaml.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/composition-yaml.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/fn-go-templating.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/fn-kcl.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/fn-patch-and-transform.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/fn-python.yaml create mode 100644 content/v2.0-preview/manifests/get-started/composition/xrd.yaml create mode 100644 content/v2.0-preview/manifests/get-started/managed-resources/bucket.yaml create mode 100644 content/v2.0-preview/manifests/get-started/managed-resources/provider.yaml create mode 100644 content/v2.0-preview/manifests/get-started/managed-resources/providerconfig.yaml create mode 100644 content/v2.0/manifests/get-started/composition/app.yaml create mode 100644 content/v2.0/manifests/get-started/composition/composition-kcl.yaml create mode 100644 content/v2.0/manifests/get-started/composition/composition-python.yaml create mode 100644 content/v2.0/manifests/get-started/composition/composition-pythonic.yaml create mode 100644 content/v2.0/manifests/get-started/composition/composition-templated-yaml.yaml create mode 100644 content/v2.0/manifests/get-started/composition/composition-yaml.yaml create mode 100644 content/v2.0/manifests/get-started/composition/fn-go-templating.yaml create mode 100644 content/v2.0/manifests/get-started/composition/fn-kcl.yaml create mode 100644 content/v2.0/manifests/get-started/composition/fn-patch-and-transform.yaml create mode 100644 content/v2.0/manifests/get-started/composition/fn-python.yaml create mode 100644 content/v2.0/manifests/get-started/composition/fn-pythonic.yaml create mode 100644 content/v2.0/manifests/get-started/composition/xrd.yaml create mode 100644 content/v2.0/manifests/get-started/managed-resources/bucket.yaml create mode 100644 content/v2.0/manifests/get-started/managed-resources/provider.yaml create mode 100644 content/v2.0/manifests/get-started/managed-resources/providerconfig.yaml create mode 100644 content/v2.0/manifests/get-started/operations/function.yaml create mode 100644 content/v2.0/manifests/get-started/operations/ingress-rbac.yaml create mode 100644 content/v2.0/manifests/get-started/operations/ingress.yaml create mode 100644 content/v2.0/manifests/get-started/operations/operation.yaml create mode 100644 content/v2.0/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml create mode 100644 content/v2.0/manifests/guides/change-logs/object-configmap-for-changelogs.yaml create mode 100644 content/v2.0/manifests/guides/change-logs/provider-kubernetes.yaml create mode 100644 content/v2.0/manifests/guides/change-logs/rbac.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/composition-go-templating.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/composition-kcl.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/composition-python.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/composition-pythonic.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-auto-ready.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-go-templating.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-kcl.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-python.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/fn-pythonic.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/my-keys.yaml create mode 100644 content/v2.0/manifests/guides/connection-details-composition/xrd.yaml create mode 100644 content/v2.0/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml create mode 100644 content/v2.0/manifests/guides/disabling-unused-managed-resources/provider.yaml create mode 100644 content/v2.1/manifests/get-started/composition/app.yaml create mode 100644 content/v2.1/manifests/get-started/composition/composition-kcl.yaml create mode 100644 content/v2.1/manifests/get-started/composition/composition-python.yaml create mode 100644 content/v2.1/manifests/get-started/composition/composition-pythonic.yaml create mode 100644 content/v2.1/manifests/get-started/composition/composition-templated-yaml.yaml create mode 100644 content/v2.1/manifests/get-started/composition/composition-yaml.yaml create mode 100644 content/v2.1/manifests/get-started/composition/fn-go-templating.yaml create mode 100644 content/v2.1/manifests/get-started/composition/fn-kcl.yaml create mode 100644 content/v2.1/manifests/get-started/composition/fn-patch-and-transform.yaml create mode 100644 content/v2.1/manifests/get-started/composition/fn-python.yaml create mode 100644 content/v2.1/manifests/get-started/composition/fn-pythonic.yaml create mode 100644 content/v2.1/manifests/get-started/composition/xrd.yaml create mode 100644 content/v2.1/manifests/get-started/managed-resources/bucket.yaml create mode 100644 content/v2.1/manifests/get-started/managed-resources/provider.yaml create mode 100644 content/v2.1/manifests/get-started/managed-resources/providerconfig.yaml create mode 100644 content/v2.1/manifests/get-started/operations/function.yaml create mode 100644 content/v2.1/manifests/get-started/operations/ingress-rbac.yaml create mode 100644 content/v2.1/manifests/get-started/operations/ingress.yaml create mode 100644 content/v2.1/manifests/get-started/operations/operation.yaml create mode 100644 content/v2.1/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml create mode 100644 content/v2.1/manifests/guides/change-logs/object-configmap-for-changelogs.yaml create mode 100644 content/v2.1/manifests/guides/change-logs/provider-kubernetes.yaml create mode 100644 content/v2.1/manifests/guides/change-logs/rbac.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/composition-go-templating.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/composition-kcl.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/composition-python.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/composition-pythonic.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-auto-ready.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-go-templating.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-kcl.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-python.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/fn-pythonic.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/my-keys.yaml create mode 100644 content/v2.1/manifests/guides/connection-details-composition/xrd.yaml create mode 100644 content/v2.1/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml create mode 100644 content/v2.1/manifests/guides/disabling-unused-managed-resources/provider.yaml create mode 100644 content/v2.2/manifests/get-started/composition/app.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-kcl.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-python.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-pythonic.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-templated-yaml.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-yaml-cel.yaml create mode 100644 content/v2.2/manifests/get-started/composition/composition-yaml.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-go-templating.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-kcl.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-kro.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-python.yaml create mode 100644 content/v2.2/manifests/get-started/composition/fn-pythonic.yaml create mode 100644 content/v2.2/manifests/get-started/composition/xrd.yaml create mode 100644 content/v2.2/manifests/get-started/managed-resources/bucket.yaml create mode 100644 content/v2.2/manifests/get-started/managed-resources/provider.yaml create mode 100644 content/v2.2/manifests/get-started/managed-resources/providerconfig.yaml create mode 100644 content/v2.2/manifests/get-started/operations/function.yaml create mode 100644 content/v2.2/manifests/get-started/operations/ingress-rbac.yaml create mode 100644 content/v2.2/manifests/get-started/operations/ingress.yaml create mode 100644 content/v2.2/manifests/get-started/operations/operation.yaml create mode 100644 content/v2.2/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml create mode 100644 content/v2.2/manifests/guides/change-logs/object-configmap-for-changelogs.yaml create mode 100644 content/v2.2/manifests/guides/change-logs/provider-kubernetes.yaml create mode 100644 content/v2.2/manifests/guides/change-logs/rbac.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/composition-go-templating.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/composition-kcl.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/composition-python.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/composition-pythonic.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-auto-ready.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-go-templating.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-kcl.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-python.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/fn-pythonic.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/my-keys.yaml create mode 100644 content/v2.2/manifests/guides/connection-details-composition/xrd.yaml create mode 100644 content/v2.2/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml create mode 100644 content/v2.2/manifests/guides/disabling-unused-managed-resources/provider.yaml create mode 100644 themes/geekboot/layouts/shortcodes/manifest-url.html create mode 100644 themes/geekboot/layouts/shortcodes/manifest.html diff --git a/config.yaml b/config.yaml index b1e7ad976..2d00ee057 100644 --- a/config.yaml +++ b/config.yaml @@ -78,6 +78,10 @@ module: target: data/crds/ includeFiles: - "**/api/**.yaml" + - source: content + target: static + includeFiles: + - "**/manifests/**.yaml" # Give Hugo access to environmental variables matching a given regex. # These give Hugo access to Netlify data to generate proper URLs diff --git a/content/master/get-started/get-started-with-composition.md b/content/master/get-started/get-started-with-composition.md index 51a73ee58..7e616407d 100644 --- a/content/master/get-started/get-started-with-composition.md +++ b/content/master/get-started/get-started-with-composition.md @@ -121,54 +121,12 @@ A composite resource is a kind of custom resource. Create this _composite resource definition_ (XRD) to define the schema of the new `App` composite resource (XR). -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: apps.example.crossplane.io -spec: - scope: Namespaced - group: example.crossplane.io - names: - kind: App - plural: apps - versions: - - name: v1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - image: - description: The app's OCI container image. - type: string - required: - - image - status: - type: object - properties: - replicas: - description: The number of available app replicas. - type: integer - address: - description: The app's IP address. - type: string -``` - -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` +{{< manifest path="get-started/composition/xrd.yaml" >}} Check that Crossplane has established the XRD: ``` shell {copy-lines="1"} -kubectl get -f xrd.yaml +kubectl get -f {{< manifest-url path="get-started/composition/xrd.yaml" >}} NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 21s ``` @@ -198,25 +156,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s ``` @@ -228,25 +173,12 @@ or conditionals. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s ``` @@ -259,25 +191,12 @@ resources automatically. Create this composition function to install YAML+CEL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kro -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kro.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kro.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kro True True xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 6s ``` @@ -289,25 +208,12 @@ full [Python standard library](https://docs.python.org/3/library/index.html). Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s ``` @@ -319,25 +225,12 @@ It's fast and sandboxed. Create this composition function to install KCL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s ``` @@ -351,25 +244,12 @@ Crossplane function APIs. Create this composition function to install Pythonic support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -390,156 +270,13 @@ Create a composition to tell Crossplane what to do when you create or update an {{< tab "Templated YAML" >}} Create this composition to use templated YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-templated-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - template: - metadata: - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - containers: - - name: app - image: {{ .observed.composite.resource.spec.image }} - ports: - - containerPort: 80 - --- - apiVersion: v1 - kind: Service - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - selector: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - --- - apiVersion: example.crossplane.io/v1 - kind: App - status: - replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} - address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} -``` +{{< manifest path="get-started/composition/composition-templated-yaml.yaml" >}} {{< /tab >}} {{< tab "YAML" >}} Create this composition to use YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 2 - template: - spec: - containers: - - name: app - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: spec.image - toFieldPath: spec.template.spec.containers[0].image - - type: ToCompositeFieldPath - fromFieldPath: status.availableReplicas - toFieldPath: status.replicas - readinessChecks: - - type: MatchCondition - matchCondition: - type: Available - status: "True" - - name: service - base: - apiVersion: v1 - kind: Service - spec: - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector[example.crossplane.io/app] - - type: ToCompositeFieldPath - fromFieldPath: spec.clusterIP - toFieldPath: status.address - readinessChecks: - - type: NonEmpty - fieldPath: spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-yaml.yaml" >}} {{< /tab >}} {{< tab "YAML+CEL" >}} @@ -547,68 +284,7 @@ Create this composition to use YAML and CEL to configure Crossplane. Define resources in YAML, wire them with CEL expressions, and let Crossplane handle the rest. -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml-cel -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kro - input: - apiVersion: kro.fn.crossplane.io/v1beta1 - kind: ResourceGraph - status: - replicas: ${deployment.status.?availableReplicas.orValue(0)} - address: ${service.spec.?clusterIP.orValue("")} - resources: - - id: deployment - template: - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: ${schema.metadata.name} - template: - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - containers: - - name: app - image: ${schema.spec.image} - ports: - - containerPort: 80 - readyWhen: - - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")} - - id: service - template: - apiVersion: v1 - kind: Service - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - selector: - example.crossplane.io/app: ${schema.metadata.name} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - readyWhen: - - ${service.spec.?clusterIP.hasValue()} -``` +{{< manifest path="get-started/composition/composition-yaml-cel.yaml" >}} {{}} This function uses the same resource graph syntax as @@ -620,77 +296,7 @@ resource definitions work without changes. {{< tab "Python" >}} Create this composition to use Python to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-python -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - observed_xr = req.observed.composite.resource - - rsp.desired.resources["deployment"].resource.update({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "replicas": 2, - "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, - "template": { - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "containers": [{ - "name": "app", - "image": observed_xr["spec"]["image"], - "ports": [{"containerPort": 80}] - }], - }, - }, - }, - }) - - observed_deployment = req.observed.resources["deployment"].resource - if "status" in observed_deployment: - if "availableReplicas" in observed_deployment["status"]: - rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] - if "conditions" in observed_deployment["status"]: - for condition in observed_deployment["status"]["conditions"]: - if condition["type"] == "Available" and condition["status"] == "True": - rsp.desired.resources["deployment"].ready = True - - rsp.desired.resources["service"].resource.update({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], - }, - }) - - observed_service = req.observed.resources["service"].resource - if "spec" in observed_service and "clusterIP" in observed_service["spec"]: - rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] - rsp.desired.resources["service"].ready = True -``` +{{< manifest path="get-started/composition/composition-python.yaml" >}} {{}} You can write your own function in Python. @@ -706,138 +312,17 @@ Read the [guide to writing a composition function in Python]({{}} Create this composition to use KCL to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-kcl -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - observed_xr = option("params").oxr - - _desired_deployment = { - apiVersion = "apps/v1" - kind = "Deployment" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "deployment" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - replicas = 2 - selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} - template = { - metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - spec.containers = [{ - name = "app" - image = observed_xr.spec.image - ports = [{containerPort = 80}] - }] - } - } - } - - observed_deployment = option("params").ocds["deployment"]?.Resource - if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_service = { - apiVersion = "v1" - kind = "Service" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "service" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - selector = {"example.crossplane.io/app" = observed_xr.metadata.name} - ports = [{protocol = "TCP", port = 8080, targetPort = 80}] - } - } - - observed_service = option("params").ocds["service"]?.Resource - if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_xr = { - **option("params").dxr - - status.address = observed_service?.spec?.clusterIP or "" - status.replicas = observed_deployment?.status?.availableReplicas or 0 - } - - items = [_desired_deployment, _desired_service, _desired_xr] -``` +{{< manifest path="get-started/composition/composition-kcl.yaml" >}} {{< /tab >}} {{< tab "Pythonic" >}} Create this composition to use Pythonic to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-pythonic -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - labels = {'example.crossplane.io/app': self.metadata.name} - - d = self.resources.deployment('apps/v1', 'Deployment') - d.metadata.labels = labels - d.spec.replicas = 2 - d.spec.selector.matchLabels = labels - d.spec.template.metadata.labels = labels - d.spec.template.spec.containers[0].name = 'app' - d.spec.template.spec.containers[0].image = self.spec.image - d.spec.template.spec.containers[0].ports[0].containerPort = 80 - - s = self.resources.service('v1', 'Service') - s.metadata.labels = labels - s.spec.selector = labels - s.spec.ports[0].protocol = 'TCP' - s.spec.ports[0].port = 8080 - s.spec.ports[0].targetPort = 80 - - self.status.replicas = d.status.availableReplicas - self.status.address = s.observed.spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-pythonic.yaml" >}} {{< /tab >}} {{}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - {{}} A composition can include multiple functions. @@ -858,26 +343,12 @@ Crossplane now understands `App` custom resources. Create an `App`: -```yaml -apiVersion: example.crossplane.io/v1 -kind: App -metadata: - namespace: default - name: my-app -spec: - image: nginx -``` - -Save the `App` as `app.yaml` and apply it: - -```shell -kubectl apply -f app.yaml -``` +{{< manifest path="get-started/composition/app.yaml" >}} Check that the `App` is ready: ```shell {copy-lines="1"} -kubectl get -f app.yaml +kubectl get -f {{< manifest-url path="get-started/composition/app.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 56s ``` @@ -902,14 +373,19 @@ service/my-app-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` {{}} -Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates -the `Deployment`'s image to match. +Edit the `App`'s image: + +```shell +kubectl edit -f {{< manifest-url path="get-started/composition/app.yaml" >}} +``` + +Crossplane updates the `Deployment`'s image to match. {{}} Delete the `App`. ```shell {copy-lines="1"} -kubectl delete -f app.yaml +kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} ``` When you delete the `App`, Crossplane deletes the `Deployment` and `Service`. diff --git a/content/master/get-started/get-started-with-managed-resources.md b/content/master/get-started/get-started-with-managed-resources.md index fde7941ee..c378cc538 100644 --- a/content/master/get-started/get-started-with-managed-resources.md +++ b/content/master/get-started/get-started-with-managed-resources.md @@ -56,20 +56,7 @@ The AWS S3 provider installs support for all the AWS S3 managed resources. Create this provider to install the AWS S3 provider: -```yaml {label="provider",copy-lines="all"} -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: crossplane-contrib-provider-aws-s3 -spec: - package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: - -```shell {label="kube-apply-provider",copy-lines="all"} -kubectl apply -f provider.yaml -``` +{{< manifest path="get-started/managed-resources/provider.yaml" label="provider" >}} Check that Crossplane installed the provider: @@ -146,25 +133,7 @@ All providers need a configuration to tell them where to load credentials. Create this cluster-wide provider configuration: -```yaml {label="providerconfig",copy-lines="all"} -apiVersion: aws.m.upbound.io/v1beta1 -kind: ClusterProviderConfig -metadata: - name: default -spec: - credentials: - source: Secret - secretRef: - namespace: crossplane-system - name: aws-secret - key: creds -``` - -Save the provider configuration as `providerconfig.yaml` and apply it: - -```shell {label="kube-apply-providerconfig",copy-lines="all"} -kubectl apply -f providerconfig.yaml -``` +{{< manifest path="get-started/managed-resources/providerconfig.yaml" label="providerconfig" >}} This tells the provider to load credentials from [the secret](#save-the-providers-credentials). @@ -185,22 +154,7 @@ AWS S3 bucket names must be globally unique. This example uses `generateName` to generate a random name. Any unique name is acceptable. {{}} -```yaml {label="bucket"} -apiVersion: s3.aws.m.upbound.io/v1beta1 -kind: Bucket -metadata: - namespace: default - generateName: crossplane-bucket- -spec: - forProvider: - region: us-east-2 -``` - -Save the bucket to `bucket.yaml` and apply it: - -```shell {label="kube-create-bucket",copy-lines="all"} -kubectl create -f bucket.yaml -``` +{{< manifest path="get-started/managed-resources/bucket.yaml" label="bucket" command="kubectl create -f" >}} Check that Crossplane created the bucket: diff --git a/content/master/get-started/get-started-with-operations.md b/content/master/get-started/get-started-with-operations.md index 86a679c51..6abeb4ccc 100644 --- a/content/master/get-started/get-started-with-operations.md +++ b/content/master/get-started/get-started-with-operations.md @@ -105,55 +105,14 @@ Follow these steps to create your first `Operation`: Create an `Ingress` that references a real hostname but doesn't route actual traffic: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-app - namespace: default -spec: - rules: - - host: google.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nonexistent-service - port: - number: 80 -``` - -Save as `ingress.yaml` and apply it: - -```shell -kubectl apply -f ingress.yaml -``` +{{< manifest path="get-started/operations/ingress.yaml" >}} ### Grant Ingress permissions `Operations` need permission to access and change `Ingresses`. Create a `ClusterRole` that grants Crossplane access to `Ingresses`: -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: operations-ingress-access - labels: - rbac.crossplane.io/aggregate-to-crossplane: "true" -rules: -- apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch", "patch", "update"] -``` - -Save as `ingress-rbac.yaml` and apply it: - -```shell -kubectl apply -f ingress-rbac.yaml -``` +{{< manifest path="get-started/operations/ingress-rbac.yaml" >}} ### Install the function @@ -162,25 +121,12 @@ function, which supports both composition and operations. Create this function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `function.yaml` and apply it: - -```shell -kubectl apply -f function.yaml -``` +{{< manifest path="get-started/operations/function.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f function.yaml +kubectl get -f {{< manifest-url path="get-started/operations/function.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -189,97 +135,14 @@ crossplane-contrib-function-python True True xpkg.crossplane.io/cr Create this `Operation` that monitors the `Ingress` certificate: -```yaml -apiVersion: ops.crossplane.io/v1alpha1 -kind: Operation -metadata: - name: ingress-cert-monitor -spec: - mode: Pipeline - pipeline: - - step: check-ingress-certificate - functionRef: - name: crossplane-contrib-function-python - requirements: - requiredResources: - - requirementName: ingress - apiVersion: networking.k8s.io/v1 - kind: Ingress - name: example-app - namespace: default - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - import ssl - import socket - from datetime import datetime - - from crossplane.function import request, response - - def operate(req, rsp): - # Get the Ingress resource - ingress = request.get_required_resource(req, "ingress") - if not ingress: - response.set_output(rsp, {"error": "No ingress resource found"}) - return - - # Extract hostname from Ingress rules - hostname = ingress["spec"]["rules"][0]["host"] - port = 443 - - # Get SSL certificate info - context = ssl.create_default_context() - with socket.create_connection((hostname, port)) as sock: - with context.wrap_socket(sock, server_hostname=hostname) as ssock: - cert = ssock.getpeercert() - - # Parse expiration date - expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') - days_until_expiry = (expiry_date - datetime.now()).days - - # Add warning if certificate expires soon - if days_until_expiry < 30: - response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") - - # Annotate the Ingress with certificate expiry info - rsp.desired.resources["ingress"].resource.update({ - "apiVersion": "networking.k8s.io/v1", - "kind": "Ingress", - "metadata": { - "name": ingress["metadata"]["name"], - "namespace": ingress["metadata"]["namespace"], - "annotations": { - "cert-monitor.crossplane.io/expires": cert['notAfter'], - "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), - "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" - } - } - }) - - # Return results in operation output for monitoring - response.set_output(rsp, { - "ingressName": ingress["metadata"]["name"], - "hostname": hostname, - "certificateExpires": cert['notAfter'], - "daysUntilExpiry": days_until_expiry, - "status": "warning" if days_until_expiry < 30 else "ok" - }) -``` - - -Save the operation as `operation.yaml` and apply it: - -```shell -kubectl apply -f operation.yaml -``` +{{< manifest path="get-started/operations/operation.yaml" >}} ### Check the operation Check that the `Operation` runs successfully: ```shell {copy-lines="1"} -kubectl get -f operation.yaml +kubectl get -f {{< manifest-url path="get-started/operations/operation.yaml" >}} NAME SYNCED SUCCEEDED AGE ingress-cert-monitor True True 15s ``` @@ -346,10 +209,10 @@ information that other tools can use for monitoring and alerting. Delete the resources you created: ```shell -kubectl delete -f operation.yaml -kubectl delete -f ingress.yaml -kubectl delete -f ingress-rbac.yaml -kubectl delete -f function.yaml +kubectl delete -f {{< manifest-url path="get-started/operations/operation.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress-rbac.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/function.yaml" >}} ``` ## Next steps diff --git a/content/master/guides/change-logs.md b/content/master/guides/change-logs.md index c19bcd722..bbc07c0b3 100644 --- a/content/master/guides/change-logs.md +++ b/content/master/guides/change-logs.md @@ -80,38 +80,7 @@ steps: container{{}} and the {{}}sidecar container{{}}. -```yaml {label="drc",copy-lines="all"} -cat <}} ### Install the provider @@ -119,20 +88,7 @@ Install the {{}}provider{{}} and instruct it to use the {{}}DeploymentRuntimeConfig{{}} that was just created. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Configure permissions @@ -146,42 +102,7 @@ production environment. See more examples for configuring `provider-kubernetes` [examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/namespaced/provider). {{}} -```yaml {label="rbac",copy-lines="all"} -cat <}} ### Create a resource @@ -189,24 +110,7 @@ After installing and configuring the provider with change logs enabled, create a resource that generates change log entries that reflect the actions the control plane takes. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Examine the change logs diff --git a/content/master/guides/connection-details-composition.md b/content/master/guides/connection-details-composition.md index d4eaca673..7a7827d70 100644 --- a/content/master/guides/connection-details-composition.md +++ b/content/master/guides/connection-details-composition.md @@ -113,34 +113,7 @@ A CompositeResourceDefinition (XRD) defines composite resources. For this example, create an XRD for the `UserAccessKey` composite resource: -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: useraccesskeys.example.org -spec: - group: example.org - names: - kind: UserAccessKey - plural: useraccesskeys - scope: Namespaced - versions: - - name: v1alpha1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - writeConnectionSecretToRef: - type: object - properties: - name: - type: string -``` +{{< manifest path="guides/connection-details-composition/xrd.yaml" >}} {{}} This XRD schema defines a `.spec.writeConnectionSecretToRef.name` field that @@ -153,12 +126,6 @@ too. {{}} -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` - The Kubernetes API is now serving requests for the `UserAccessKey` composite resource. @@ -175,25 +142,12 @@ from the tabs below. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 8s ``` @@ -205,25 +159,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 15s ``` @@ -233,25 +174,12 @@ function-go-templating True True xpkg.crossplane.io/crossplane-con Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -261,25 +189,12 @@ function-python True True xpkg.crossplane.io/cr Create this composition function to install [KCL](https://kcl-lang.io) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 6s ``` @@ -289,25 +204,12 @@ function-kcl True True xpkg.crossplane.io/cross Create this composition function to install [Pythonic](https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -318,20 +220,7 @@ function-pythonic True True xpkg.crossplane.io/crossplane-contrib/fun This guide also uses `function-auto-ready`. This function automatically marks composed resources as ready when they're healthy: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-auto-ready -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 -``` - -Save this as `fn-auto-ready.yaml` and apply it: - -```shell -kubectl apply -f fn-auto-ready.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-auto-ready.yaml" >}} ### Configure the composition @@ -364,91 +253,7 @@ exposes their credentials as the composite resource's connection details `Secret {{< tab "YAML" >}} -```yaml {label="comp-pt"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-patch-and-transform -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: patch-and-transform - functionRef: - name: function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - writeConnectionSecretToRef: - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.writeConnectionSecretToRef.name - toFieldPath: name - resources: - - name: user - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - spec: - forProvider: {} - - name: accesskey-0 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-0 - connectionDetails: - - name: user-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-0" - - name: accesskey-1 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-1 - connectionDetails: - - name: user-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-1" - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-patch-and-transform.yaml" label="comp-pt" >}} @@ -482,78 +287,7 @@ spec: {{< tab "Templated YAML" >}} -```yaml {label="comp-gotmpl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-go-templating -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-templates - functionRef: - name: function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - metadata: - annotations: - {{ setResourceNameAnnotation "user" }} - spec: - forProvider: {} - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-0" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-1" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 - --- - apiVersion: v1 - kind: Secret - metadata: - name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} - annotations: - {{ setResourceNameAnnotation "connection-secret" }} - {{ if eq $.observed.resources nil }} - data: {} - {{ else }} - data: - user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} - user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} - password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} - password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} - {{ end }} - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-go-templating.yaml" label="comp-gotmpl" >}} @@ -584,109 +318,7 @@ spec: {{< tab "Python" >}} -```yaml {label="comp-python"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-python -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-python - functionRef: - name: function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - # Get observed composite resource - oxr = req.observed.composite.resource - oxr_name = oxr["metadata"]["name"] - - # IAM User - rsp.desired.resources["user"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "User", - "spec": { - "forProvider": {} - } - }) - - # Access Key 0 - rsp.desired.resources["accesskey-0"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-0" - } - } - }) - - # Access Key 1 - rsp.desired.resources["accesskey-1"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-1" - } - } - }) - - # Secret representing the composite resource's connection details - secret_resource = { - "apiVersion": "v1", - "kind": "Secret", - "metadata": {} - } - - # If a secret name was provided then use it - secret_name = "" - if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: - secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] - - secret_resource["metadata"]["name"] = secret_name - - # Only add data if we have connection details to populate - data = {} - if "accesskey-0" in req.observed.resources: - accesskey0_conn = req.observed.resources["accesskey-0"].connection_details - if "username" in accesskey0_conn: - data["user-0"] = accesskey0_conn["username"].decode("utf-8") - if "password" in accesskey0_conn: - data["password-0"] = accesskey0_conn["password"].decode("utf-8") - - if "accesskey-1" in req.observed.resources: - accesskey1_conn = req.observed.resources["accesskey-1"].connection_details - if "username" in accesskey1_conn: - data["user-1"] = accesskey1_conn["username"].decode("utf-8") - if "password" in accesskey1_conn: - data["password-1"] = accesskey1_conn["password"].decode("utf-8") - - if data: - secret_resource["stringData"] = data - - rsp.desired.resources["connection-secret"].resource.update(secret_resource) - - step: ready - functionRef: - name: function-auto-ready - -``` +{{< manifest path="guides/connection-details-composition/composition-python.yaml" label="comp-python" >}} @@ -717,77 +349,7 @@ spec: {{< tab "KCL" >}} -```yaml {label="comp-kcl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-kcl -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-kcl - functionRef: - name: function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - oxr = option("params").oxr - ocds = option("params").ocds - - user = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "User" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "user" - } - spec.forProvider = {} - } - - accesskey0 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-0" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" - } - - accesskey1 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-1" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" - } - - secret = { - apiVersion = "v1" - kind = "Secret" - metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "connection-secret" - } - data = { - "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" - "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" - "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" - "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" - } if ocds else {} - } - - items = [user, accesskey0, accesskey1, secret] - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-kcl.yaml" label="comp-kcl" >}} **How this Composition exposes connection details:** @@ -816,38 +378,7 @@ spec: {{< tab "Pythonic" >}} -```yaml {label="comp-pythonic"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-pythonic -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-pythonic - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - self.connectionSecret = self.spec.writeConnectionSecretToRef - - user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') - user.spec.forProvider = {} - - for ix in range(2): - key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') - key.spec.forProvider.user = user.status.atProvider.id - key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" - self.connection[f"user-{ix}"] = key.connection.username - self.connection[f"password-{ix}"] = key.connection.password -``` +{{< manifest path="guides/connection-details-composition/composition-pythonic.yaml" label="comp-pythonic" >}} @@ -877,12 +408,6 @@ spec: {{< /tabs >}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - ## Use the composite resource The Composition now specifies how to compose connection details for the @@ -890,27 +415,12 @@ The Composition now specifies how to compose connection details for the Create a `UserAccessKey` to see it in action: -```yaml -apiVersion: example.org/v1alpha1 -kind: UserAccessKey -metadata: - namespace: default - name: my-keys -spec: - writeConnectionSecretToRef: - name: my-keys-connection-details -``` - -Save the composite resource as `my-keys.yaml` and apply it: - -```shell -kubectl apply -f my-keys.yaml -``` +{{< manifest path="guides/connection-details-composition/my-keys.yaml" >}} Check that the composite resource is ready: ```shell {copy-lines="1"} -kubectl get -f my-keys.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-keys True True useraccesskeys-go-templating 45s ``` @@ -1128,7 +638,7 @@ namespace to the XR's namespace if left empty. Delete the composite resource to clean up: ```shell -kubectl delete -f my-keys.yaml +kubectl delete -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} ``` When you delete the composite resource, Crossplane deletes: diff --git a/content/master/guides/disabling-unused-managed-resources.md b/content/master/guides/disabling-unused-managed-resources.md index 3beb04a28..220c41544 100644 --- a/content/master/guides/disabling-unused-managed-resources.md +++ b/content/master/guides/disabling-unused-managed-resources.md @@ -86,20 +86,9 @@ kubectl delete managedresourceactivationpolicy default Install your provider as normal. Crossplane automatically converts the provider's CRDs to ManagedResourceDefinitions: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: provider-aws-ec2 -spec: - package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: +{{< manifest path="guides/disabling-unused-managed-resources/provider.yaml" >}} ```shell -kubectl apply -f provider.yaml - # Wait for provider to be ready kubectl wait --for=condition=Healthy provider/provider-aws-ec2 --timeout=5m ``` @@ -133,23 +122,7 @@ kubectl get crds | grep ec2.aws.m.crossplane.io Create a ManagedResourceActivationPolicy to selectively activate only the resources you need: -```yaml -apiVersion: apiextensions.crossplane.io/v1alpha1 -kind: ManagedResourceActivationPolicy -metadata: - name: my-app-resources -spec: - activate: - - instances.ec2.aws.m.crossplane.io # EC2 instances for compute - - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking - - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation -``` - -Save this as `activation-policy.yaml` and apply it: - -```shell -kubectl apply -f activation-policy.yaml -``` +{{< manifest path="guides/disabling-unused-managed-resources/activation-policy.yaml" >}} ## Step 5: Verify selective activation diff --git a/content/master/guides/scalable-composition.md b/content/master/guides/scalable-composition.md index 9203f888d..f5b62c7ef 100644 --- a/content/master/guides/scalable-composition.md +++ b/content/master/guides/scalable-composition.md @@ -64,38 +64,12 @@ This guide requires: Install `function-patch-and-transform` to compose resources and patch fields between the composite resource and its composed resources: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.3 -``` - -Save the function as `fn-pat.yaml` and apply it: - -```shell -kubectl apply -f fn-pat.yaml -``` +{{< manifest path="guides/scalable-composition/fn-pat.yaml" >}} This guide also uses `function-auto-ready`. This function automatically marks composed resources as ready when they're healthy: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-auto-ready -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3 -``` - -Save this as `fn-auto-ready.yaml` and apply it: - -```shell -kubectl apply -f fn-auto-ready.yaml -``` +{{< manifest path="guides/scalable-composition/fn-auto-ready.yaml" >}} Check that Crossplane installed the functions: @@ -111,53 +85,7 @@ function.pkg.crossplane.io/function-patch-and-transform True True Configure the `scale` subresource per version in the XRD's {{}}subresources{{}} field. -```yaml {label="xrdscale"} -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: myapps.example.org -spec: - group: example.org - names: - kind: MyApp - plural: myapps - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.replicas - name: DESIRED - type: string - - jsonPath: .status.replicas - name: CURRENT - type: string - name: v1alpha1 - served: true - referenceable: true - subresources: - scale: - specReplicasPath: .spec.replicas - statusReplicasPath: .status.replicas - labelSelectorPath: .status.labelSelector - schema: - openAPIV3Schema: - properties: - spec: - properties: - replicas: - type: integer - status: - properties: - replicas: - type: integer - labelSelector: - type: string -``` - -Save this as `xrd-scale.yaml` and apply it: - -```shell -kubectl apply -f xrd-scale.yaml -``` +{{< manifest path="guides/scalable-composition/xrd-scale.yaml" label="xrdscale" >}} Verify the XRD exists: @@ -193,60 +121,7 @@ the composed resources in the Composition. The following example uses `function-patch-and-transform` to forward `spec.replicas` from the composite resource to a `Deployment`: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: myapp -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: MyApp - mode: Pipeline - pipeline: - - step: patch-and-transform - functionRef: - name: function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 1 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.29.7-alpine - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.replicas - toFieldPath: spec.replicas - - type: ToCompositeFieldPath - fromFieldPath: status.readyReplicas - toFieldPath: status.replicas - - step: automatically-detect-readiness - functionRef: - name: function-auto-ready -``` - -Save this as `composition-scale.yaml` and apply it: - -```shell -kubectl apply -f composition-scale.yaml -``` +{{< manifest path="guides/scalable-composition/composition-scale.yaml" >}} Verify the composition exists: diff --git a/content/master/manifests/get-started/composition/app.yaml b/content/master/manifests/get-started/composition/app.yaml new file mode 100644 index 000000000..a24b2ecd9 --- /dev/null +++ b/content/master/manifests/get-started/composition/app.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: App +metadata: + namespace: default + name: my-app +spec: + image: nginx diff --git a/content/master/manifests/get-started/composition/composition-kcl.yaml b/content/master/manifests/get-started/composition/composition-kcl.yaml new file mode 100644 index 000000000..f75c71504 --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-kcl.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-kcl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + observed_xr = option("params").oxr + + _desired_deployment = { + apiVersion = "apps/v1" + kind = "Deployment" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "deployment" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + replicas = 2 + selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} + template = { + metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + spec.containers = [{ + name = "app" + image = observed_xr.spec.image + ports = [{containerPort = 80}] + }] + } + } + } + + observed_deployment = option("params").ocds["deployment"]?.Resource + if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): + _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_service = { + apiVersion = "v1" + kind = "Service" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "service" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + selector = {"example.crossplane.io/app" = observed_xr.metadata.name} + ports = [{protocol = "TCP", port = 8080, targetPort = 80}] + } + } + + observed_service = option("params").ocds["service"]?.Resource + if observed_service?.spec?.clusterIP: + _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_xr = { + **option("params").dxr + + status.address = observed_service?.spec?.clusterIP or "" + status.replicas = observed_deployment?.status?.availableReplicas or 0 + } + + items = [_desired_deployment, _desired_service, _desired_xr] diff --git a/content/master/manifests/get-started/composition/composition-python.yaml b/content/master/manifests/get-started/composition/composition-python.yaml new file mode 100644 index 000000000..85a404f2d --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-python.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-python +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + observed_xr = req.observed.composite.resource + + rsp.desired.resources["deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "replicas": 2, + "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, + "template": { + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "containers": [{ + "name": "app", + "image": observed_xr["spec"]["image"], + "ports": [{"containerPort": 80}] + }], + }, + }, + }, + }) + + observed_deployment = req.observed.resources["deployment"].resource + if "status" in observed_deployment: + if "availableReplicas" in observed_deployment["status"]: + rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] + if "conditions" in observed_deployment["status"]: + for condition in observed_deployment["status"]["conditions"]: + if condition["type"] == "Available" and condition["status"] == "True": + rsp.desired.resources["deployment"].ready = True + + rsp.desired.resources["service"].resource.update({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], + }, + }) + + observed_service = req.observed.resources["service"].resource + if "spec" in observed_service and "clusterIP" in observed_service["spec"]: + rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] + rsp.desired.resources["service"].ready = True diff --git a/content/master/manifests/get-started/composition/composition-pythonic.yaml b/content/master/manifests/get-started/composition/composition-pythonic.yaml new file mode 100644 index 000000000..9853a103e --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-pythonic.yaml @@ -0,0 +1,39 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-pythonic +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/app': self.metadata.name} + + d = self.resources.deployment('apps/v1', 'Deployment') + d.metadata.labels = labels + d.spec.replicas = 2 + d.spec.selector.matchLabels = labels + d.spec.template.metadata.labels = labels + d.spec.template.spec.containers[0].name = 'app' + d.spec.template.spec.containers[0].image = self.spec.image + d.spec.template.spec.containers[0].ports[0].containerPort = 80 + + s = self.resources.service('v1', 'Service') + s.metadata.labels = labels + s.spec.selector = labels + s.spec.ports[0].protocol = 'TCP' + s.spec.ports[0].port = 8080 + s.spec.ports[0].targetPort = 80 + + self.status.replicas = d.status.availableReplicas + self.status.address = s.observed.spec.clusterIP diff --git a/content/master/manifests/get-started/composition/composition-templated-yaml.yaml b/content/master/manifests/get-started/composition/composition-templated-yaml.yaml new file mode 100644 index 000000000..4c1a30cd2 --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-templated-yaml.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-templated-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: deployment + {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + template: + metadata: + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + containers: + - name: app + image: {{ .observed.composite.resource.spec.image }} + ports: + - containerPort: 80 + --- + apiVersion: v1 + kind: Service + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: service + {{ if (get (getComposedResource . "service").spec "clusterIP") }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + selector: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + --- + apiVersion: example.crossplane.io/v1 + kind: App + status: + replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} + address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} diff --git a/content/master/manifests/get-started/composition/composition-yaml-cel.yaml b/content/master/manifests/get-started/composition/composition-yaml-cel.yaml new file mode 100644 index 000000000..2dc93fb0c --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-yaml-cel.yaml @@ -0,0 +1,60 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml-cel +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kro + input: + apiVersion: kro.fn.crossplane.io/v1beta1 + kind: ResourceGraph + status: + replicas: ${deployment.status.?availableReplicas.orValue(0)} + address: ${service.spec.?clusterIP.orValue("")} + resources: + - id: deployment + template: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: ${schema.metadata.name} + template: + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + containers: + - name: app + image: ${schema.spec.image} + ports: + - containerPort: 80 + readyWhen: + - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")} + - id: service + template: + apiVersion: v1 + kind: Service + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + selector: + example.crossplane.io/app: ${schema.metadata.name} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + readyWhen: + - ${service.spec.?clusterIP.hasValue()} diff --git a/content/master/manifests/get-started/composition/composition-yaml.yaml b/content/master/manifests/get-started/composition/composition-yaml.yaml new file mode 100644 index 000000000..9907d5a34 --- /dev/null +++ b/content/master/manifests/get-started/composition/composition-yaml.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 2 + template: + spec: + containers: + - name: app + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: spec.image + toFieldPath: spec.template.spec.containers[0].image + - type: ToCompositeFieldPath + fromFieldPath: status.availableReplicas + toFieldPath: status.replicas + readinessChecks: + - type: MatchCondition + matchCondition: + type: Available + status: "True" + - name: service + base: + apiVersion: v1 + kind: Service + spec: + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector[example.crossplane.io/app] + - type: ToCompositeFieldPath + fromFieldPath: spec.clusterIP + toFieldPath: status.address + readinessChecks: + - type: NonEmpty + fieldPath: spec.clusterIP diff --git a/content/master/manifests/get-started/composition/fn-go-templating.yaml b/content/master/manifests/get-started/composition/fn-go-templating.yaml new file mode 100644 index 000000000..d11ea8e70 --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 diff --git a/content/master/manifests/get-started/composition/fn-kcl.yaml b/content/master/manifests/get-started/composition/fn-kcl.yaml new file mode 100644 index 000000000..befb6f66d --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 diff --git a/content/master/manifests/get-started/composition/fn-kro.yaml b/content/master/manifests/get-started/composition/fn-kro.yaml new file mode 100644 index 000000000..501c5d5f0 --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-kro.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kro +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 diff --git a/content/master/manifests/get-started/composition/fn-patch-and-transform.yaml b/content/master/manifests/get-started/composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..aecb6cd0f --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/master/manifests/get-started/composition/fn-python.yaml b/content/master/manifests/get-started/composition/fn-python.yaml new file mode 100644 index 000000000..b76bb3ee2 --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 diff --git a/content/master/manifests/get-started/composition/fn-pythonic.yaml b/content/master/manifests/get-started/composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/master/manifests/get-started/composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/master/manifests/get-started/composition/xrd.yaml b/content/master/manifests/get-started/composition/xrd.yaml new file mode 100644 index 000000000..e2d3856de --- /dev/null +++ b/content/master/manifests/get-started/composition/xrd.yaml @@ -0,0 +1,35 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: apps.example.crossplane.io +spec: + scope: Namespaced + group: example.crossplane.io + names: + kind: App + plural: apps + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + image: + description: The app's OCI container image. + type: string + required: + - image + status: + type: object + properties: + replicas: + description: The number of available app replicas. + type: integer + address: + description: The app's IP address. + type: string diff --git a/content/master/manifests/get-started/managed-resources/bucket.yaml b/content/master/manifests/get-started/managed-resources/bucket.yaml new file mode 100644 index 000000000..8c0ef2ecd --- /dev/null +++ b/content/master/manifests/get-started/managed-resources/bucket.yaml @@ -0,0 +1,8 @@ +apiVersion: s3.aws.m.upbound.io/v1beta1 +kind: Bucket +metadata: + namespace: default + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 diff --git a/content/master/manifests/get-started/managed-resources/provider.yaml b/content/master/manifests/get-started/managed-resources/provider.yaml new file mode 100644 index 000000000..3bc07d336 --- /dev/null +++ b/content/master/manifests/get-started/managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: crossplane-contrib-provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 diff --git a/content/master/manifests/get-started/managed-resources/providerconfig.yaml b/content/master/manifests/get-started/managed-resources/providerconfig.yaml new file mode 100644 index 000000000..2ad6259d4 --- /dev/null +++ b/content/master/manifests/get-started/managed-resources/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/master/manifests/get-started/operations/function.yaml b/content/master/manifests/get-started/operations/function.yaml new file mode 100644 index 000000000..a3ba5fbbc --- /dev/null +++ b/content/master/manifests/get-started/operations/function.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/master/manifests/get-started/operations/ingress-rbac.yaml b/content/master/manifests/get-started/operations/ingress-rbac.yaml new file mode 100644 index 000000000..63f54a463 --- /dev/null +++ b/content/master/manifests/get-started/operations/ingress-rbac.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operations-ingress-access + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "patch", "update"] diff --git a/content/master/manifests/get-started/operations/ingress.yaml b/content/master/manifests/get-started/operations/ingress.yaml new file mode 100644 index 000000000..ba30f83b0 --- /dev/null +++ b/content/master/manifests/get-started/operations/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example-app + namespace: default +spec: + rules: + - host: google.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nonexistent-service + port: + number: 80 diff --git a/content/master/manifests/get-started/operations/operation.yaml b/content/master/manifests/get-started/operations/operation.yaml new file mode 100644 index 000000000..aeb669f77 --- /dev/null +++ b/content/master/manifests/get-started/operations/operation.yaml @@ -0,0 +1,75 @@ +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +spec: + mode: Pipeline + pipeline: + - step: check-ingress-certificate + functionRef: + name: crossplane-contrib-function-python + requirements: + requiredResources: + - requirementName: ingress + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: example-app + namespace: default + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + # Get the Ingress resource + ingress = request.get_required_resource(req, "ingress") + if not ingress: + response.set_output(rsp, {"error": "No ingress resource found"}) + return + + # Extract hostname from Ingress rules + hostname = ingress["spec"]["rules"][0]["host"] + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Add warning if certificate expires soon + if days_until_expiry < 30: + response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") + + # Annotate the Ingress with certificate expiry info + rsp.desired.resources["ingress"].resource.update({ + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", + "metadata": { + "name": ingress["metadata"]["name"], + "namespace": ingress["metadata"]["namespace"], + "annotations": { + "cert-monitor.crossplane.io/expires": cert['notAfter'], + "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), + "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" + } + } + }) + + # Return results in operation output for monitoring + response.set_output(rsp, { + "ingressName": ingress["metadata"]["name"], + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) diff --git a/content/master/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml b/content/master/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml new file mode 100644 index 000000000..9076ea60a --- /dev/null +++ b/content/master/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml @@ -0,0 +1,28 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: enable-changelogs +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --enable-changelogs + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + - name: changelogs-sidecar + image: xpkg.crossplane.io/crossplane/changelogs-sidecar:v0.0.1 + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + volumes: + - name: changelogs-vol + emptyDir: {} + serviceAccountTemplate: + metadata: + name: provider-kubernetes diff --git a/content/master/manifests/guides/change-logs/object-configmap-for-changelogs.yaml b/content/master/manifests/guides/change-logs/object-configmap-for-changelogs.yaml new file mode 100644 index 000000000..a55e5b39e --- /dev/null +++ b/content/master/manifests/guides/change-logs/object-configmap-for-changelogs.yaml @@ -0,0 +1,14 @@ +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: configmap-for-changelogs +spec: + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + name: configmap-for-changelogs + data: + key-1: cool-value-1 diff --git a/content/master/manifests/guides/change-logs/provider-kubernetes.yaml b/content/master/manifests/guides/change-logs/provider-kubernetes.yaml new file mode 100644 index 000000000..75b22830a --- /dev/null +++ b/content/master/manifests/guides/change-logs/provider-kubernetes.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-kubernetes +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v0.18.0 + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: enable-changelogs diff --git a/content/master/manifests/guides/change-logs/rbac.yaml b/content/master/manifests/guides/change-logs/rbac.yaml new file mode 100644 index 000000000..2de5d1f8c --- /dev/null +++ b/content/master/manifests/guides/change-logs/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: configmap-edit +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: provider-kubernetes-configmap-edit +subjects: + - kind: ServiceAccount + name: provider-kubernetes + namespace: crossplane-system +roleRef: + kind: ClusterRole + name: configmap-edit + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: kubernetes.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: InjectedIdentity diff --git a/content/master/manifests/guides/connection-details-composition/composition-go-templating.yaml b/content/master/manifests/guides/connection-details-composition/composition-go-templating.yaml new file mode 100644 index 000000000..7570332de --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/composition-go-templating.yaml @@ -0,0 +1,70 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-go-templating +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-templates + functionRef: + name: function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + metadata: + annotations: + {{ setResourceNameAnnotation "user" }} + spec: + forProvider: {} + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-0" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-1" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 + --- + apiVersion: v1 + kind: Secret + metadata: + name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} + annotations: + {{ setResourceNameAnnotation "connection-secret" }} + {{ if eq $.observed.resources nil }} + data: {} + {{ else }} + data: + user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} + user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} + password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} + password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} + {{ end }} + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/master/manifests/guides/connection-details-composition/composition-kcl.yaml b/content/master/manifests/guides/connection-details-composition/composition-kcl.yaml new file mode 100644 index 000000000..fe80e07f5 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/composition-kcl.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-kcl +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-kcl + functionRef: + name: function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + oxr = option("params").oxr + ocds = option("params").ocds + + user = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "User" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "user" + } + spec.forProvider = {} + } + + accesskey0 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-0" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" + } + + accesskey1 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-1" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" + } + + secret = { + apiVersion = "v1" + kind = "Secret" + metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "connection-secret" + } + data = { + "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" + "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" + "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" + "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" + } if ocds else {} + } + + items = [user, accesskey0, accesskey1, secret] + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/master/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml b/content/master/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml new file mode 100644 index 000000000..1d6c7e397 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml @@ -0,0 +1,83 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-patch-and-transform +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + writeConnectionSecretToRef: + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.writeConnectionSecretToRef.name + toFieldPath: name + resources: + - name: user + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + spec: + forProvider: {} + - name: accesskey-0 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-0 + connectionDetails: + - name: user-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-0" + - name: accesskey-1 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-1 + connectionDetails: + - name: user-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-1" + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/master/manifests/guides/connection-details-composition/composition-python.yaml b/content/master/manifests/guides/connection-details-composition/composition-python.yaml new file mode 100644 index 000000000..a1f5c0011 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/composition-python.yaml @@ -0,0 +1,100 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-python +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-python + functionRef: + name: function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + # Get observed composite resource + oxr = req.observed.composite.resource + oxr_name = oxr["metadata"]["name"] + + # IAM User + rsp.desired.resources["user"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "User", + "spec": { + "forProvider": {} + } + }) + + # Access Key 0 + rsp.desired.resources["accesskey-0"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-0" + } + } + }) + + # Access Key 1 + rsp.desired.resources["accesskey-1"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-1" + } + } + }) + + # Secret representing the composite resource's connection details + secret_resource = { + "apiVersion": "v1", + "kind": "Secret", + "metadata": {} + } + + # If a secret name was provided then use it + secret_name = "" + if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: + secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] + + secret_resource["metadata"]["name"] = secret_name + + # Only add data if we have connection details to populate + data = {} + if "accesskey-0" in req.observed.resources: + accesskey0_conn = req.observed.resources["accesskey-0"].connection_details + if "username" in accesskey0_conn: + data["user-0"] = accesskey0_conn["username"].decode("utf-8") + if "password" in accesskey0_conn: + data["password-0"] = accesskey0_conn["password"].decode("utf-8") + + if "accesskey-1" in req.observed.resources: + accesskey1_conn = req.observed.resources["accesskey-1"].connection_details + if "username" in accesskey1_conn: + data["user-1"] = accesskey1_conn["username"].decode("utf-8") + if "password" in accesskey1_conn: + data["password-1"] = accesskey1_conn["password"].decode("utf-8") + + if data: + secret_resource["stringData"] = data + + rsp.desired.resources["connection-secret"].resource.update(secret_resource) + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/master/manifests/guides/connection-details-composition/composition-pythonic.yaml b/content/master/manifests/guides/connection-details-composition/composition-pythonic.yaml new file mode 100644 index 000000000..5a59f8d3f --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/composition-pythonic.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-pythonic +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-pythonic + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + self.connectionSecret = self.spec.writeConnectionSecretToRef + + user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') + user.spec.forProvider = {} + + for ix in range(2): + key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') + key.spec.forProvider.user = user.status.atProvider.id + key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" + self.connection[f"user-{ix}"] = key.connection.username + self.connection[f"password-{ix}"] = key.connection.password diff --git a/content/master/manifests/guides/connection-details-composition/fn-auto-ready.yaml b/content/master/manifests/guides/connection-details-composition/fn-auto-ready.yaml new file mode 100644 index 000000000..9f15ef44c --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-auto-ready.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-auto-ready +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 diff --git a/content/master/manifests/guides/connection-details-composition/fn-go-templating.yaml b/content/master/manifests/guides/connection-details-composition/fn-go-templating.yaml new file mode 100644 index 000000000..e6073c0e1 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 diff --git a/content/master/manifests/guides/connection-details-composition/fn-kcl.yaml b/content/master/manifests/guides/connection-details-composition/fn-kcl.yaml new file mode 100644 index 000000000..3e81ce32b --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 diff --git a/content/master/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml b/content/master/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..c03e0f832 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 diff --git a/content/master/manifests/guides/connection-details-composition/fn-python.yaml b/content/master/manifests/guides/connection-details-composition/fn-python.yaml new file mode 100644 index 000000000..f532a7b9a --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/master/manifests/guides/connection-details-composition/fn-pythonic.yaml b/content/master/manifests/guides/connection-details-composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/master/manifests/guides/connection-details-composition/my-keys.yaml b/content/master/manifests/guides/connection-details-composition/my-keys.yaml new file mode 100644 index 000000000..13191fc14 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/my-keys.yaml @@ -0,0 +1,8 @@ +apiVersion: example.org/v1alpha1 +kind: UserAccessKey +metadata: + namespace: default + name: my-keys +spec: + writeConnectionSecretToRef: + name: my-keys-connection-details diff --git a/content/master/manifests/guides/connection-details-composition/xrd.yaml b/content/master/manifests/guides/connection-details-composition/xrd.yaml new file mode 100644 index 000000000..a9ae5e2d6 --- /dev/null +++ b/content/master/manifests/guides/connection-details-composition/xrd.yaml @@ -0,0 +1,26 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: useraccesskeys.example.org +spec: + group: example.org + names: + kind: UserAccessKey + plural: useraccesskeys + scope: Namespaced + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + writeConnectionSecretToRef: + type: object + properties: + name: + type: string diff --git a/content/master/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml b/content/master/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml new file mode 100644 index 000000000..62aa3ae51 --- /dev/null +++ b/content/master/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml @@ -0,0 +1,9 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: ManagedResourceActivationPolicy +metadata: + name: my-app-resources +spec: + activate: + - instances.ec2.aws.m.crossplane.io # EC2 instances for compute + - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking + - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation diff --git a/content/master/manifests/guides/disabling-unused-managed-resources/provider.yaml b/content/master/manifests/guides/disabling-unused-managed-resources/provider.yaml new file mode 100644 index 000000000..a8c909540 --- /dev/null +++ b/content/master/manifests/guides/disabling-unused-managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-ec2 +spec: + package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 diff --git a/content/master/manifests/guides/scalable-composition/composition-scale.yaml b/content/master/manifests/guides/scalable-composition/composition-scale.yaml new file mode 100644 index 000000000..43b229c70 --- /dev/null +++ b/content/master/manifests/guides/scalable-composition/composition-scale.yaml @@ -0,0 +1,46 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: myapp +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: MyApp + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 1 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.29.7-alpine + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.replicas + toFieldPath: spec.replicas + - type: ToCompositeFieldPath + fromFieldPath: status.readyReplicas + toFieldPath: status.replicas + - step: automatically-detect-readiness + functionRef: + name: function-auto-ready diff --git a/content/master/manifests/guides/scalable-composition/fn-auto-ready.yaml b/content/master/manifests/guides/scalable-composition/fn-auto-ready.yaml new file mode 100644 index 000000000..ee3b5f62f --- /dev/null +++ b/content/master/manifests/guides/scalable-composition/fn-auto-ready.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-auto-ready +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.3 diff --git a/content/master/manifests/guides/scalable-composition/fn-pat.yaml b/content/master/manifests/guides/scalable-composition/fn-pat.yaml new file mode 100644 index 000000000..007ec5f26 --- /dev/null +++ b/content/master/manifests/guides/scalable-composition/fn-pat.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.3 diff --git a/content/master/manifests/guides/scalable-composition/xrd-scale.yaml b/content/master/manifests/guides/scalable-composition/xrd-scale.yaml new file mode 100644 index 000000000..739d833c3 --- /dev/null +++ b/content/master/manifests/guides/scalable-composition/xrd-scale.yaml @@ -0,0 +1,39 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: myapps.example.org +spec: + group: example.org + names: + kind: MyApp + plural: myapps + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.replicas + name: DESIRED + type: string + - jsonPath: .status.replicas + name: CURRENT + type: string + name: v1alpha1 + served: true + referenceable: true + subresources: + scale: + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + labelSelectorPath: .status.labelSelector + schema: + openAPIV3Schema: + properties: + spec: + properties: + replicas: + type: integer + status: + properties: + replicas: + type: integer + labelSelector: + type: string diff --git a/content/v1.20/getting-started/provider-aws-part-2.md b/content/v1.20/getting-started/provider-aws-part-2.md index 7023e3ae0..4bb3852a0 100644 --- a/content/v1.20/getting-started/provider-aws-part-2.md +++ b/content/v1.20/getting-started/provider-aws-part-2.md @@ -37,16 +37,7 @@ crossplane-stable/crossplane \ 2. When the Crossplane pods finish installing and are ready, apply the AWS Provider -```yaml {label="provider",copy-lines="all"} -cat <}} 3. Create a file with your AWS keys ```ini @@ -64,21 +55,8 @@ generic aws-secret \ ``` 5. Create a _ProviderConfig_ -```yaml {label="providerconfig",copy-lines="all"} -cat <}} {{}} ## Install the DynamoDB Provider @@ -89,16 +67,7 @@ Deploying a DynamoDB Table requires the DynamoDB Provider as well. Add the new Provider to the cluster. -```yaml -cat <}} View the new DynamoDB provider with `kubectl get providers`. @@ -236,40 +205,7 @@ must be {{}}oneOf{{}} either Apply this XRD to create the custom API in your Kubernetes cluster. -```yaml {label="xrd",copy-lines="all"} -cat <}} Adding the {{}}claimNames{{}} allows users to access this API either at the cluster level with the @@ -338,67 +274,7 @@ more information on configuring Compositions and all the available options. Apply this Composition to your cluster. -```yaml {label="comp",copy-lines="all"} -cat <}} The {{}}compositeTypeRef{{}} defines which custom APIs can use this template to create resources. @@ -410,16 +286,7 @@ You must install the function before you can use it in a Composition. Apply this Function to install `function-patch-and-transform`: -```yaml {label="install"} -cat <}} {{}} Read the [Composition documentation]({{}}) for @@ -449,16 +316,7 @@ With the custom API (XRD) installed and associated to a resource template Create a {{}}NoSQL{{}} object to create the cloud resources. -```yaml {copy-lines="all",label="xr"} -cat <}} View the resource with `kubectl get nosql`. @@ -522,17 +380,7 @@ kubectl create namespace crossplane-test Then create a Claim in the `crossplane-test` namespace. -```yaml {label="claim",copy-lines="all"} -cat <}} View the Claim with `kubectl get claim -n crossplane-test`. ```shell {copy-lines="1"} diff --git a/content/v1.20/getting-started/provider-aws.md b/content/v1.20/getting-started/provider-aws.md index 21cdb8299..19e12b352 100644 --- a/content/v1.20/getting-started/provider-aws.md +++ b/content/v1.20/getting-started/provider-aws.md @@ -30,16 +30,7 @@ This quickstart requires: Install the AWS S3 provider into the Kubernetes cluster with a Kubernetes configuration file. -```yaml {label="provider",copy-lines="all"} -cat <}} The Crossplane {{< hover label="provider" line="3" >}}Provider{{}} installs the Kubernetes _Custom Resource Definitions_ (CRDs) representing AWS S3 @@ -145,21 +136,8 @@ customizes the settings of the AWS Provider. Apply the {{< hover label="providerconfig" line="3">}}ProviderConfig{{}} with the this Kubernetes configuration file: -```yaml {label="providerconfig",copy-lines="all"} -cat <}} This attaches the AWS credentials, saved as a Kubernetes secret, as a {{< hover label="providerconfig" line="9">}}secretRef{{}}. @@ -183,19 +161,7 @@ AWS S3 bucket names must be globally unique. To generate a unique name the examp Any unique name is acceptable. {{< /hint >}} -```yaml {label="xr"} -cat <}} The {{< hover label="xr" line="2">}}apiVersion{{< /hover >}} and {{< hover label="xr" line="3">}}kind{{}} are from the provider's CRDs. diff --git a/content/v1.20/getting-started/provider-azure-part-2.md b/content/v1.20/getting-started/provider-azure-part-2.md index 3b4be91cf..493d0ebf1 100644 --- a/content/v1.20/getting-started/provider-azure-part-2.md +++ b/content/v1.20/getting-started/provider-azure-part-2.md @@ -38,16 +38,7 @@ crossplane-stable/crossplane \ 2. When the Crossplane pods finish installing and are ready, apply the Azure Provider -```yaml {label="provider",copy-lines="all"} -cat <}} 3. Use the Azure CLI to create a service principal and save the JSON output as `azure-crednetials.json` @@ -69,21 +60,8 @@ generic azure-secret \ ``` 5. Create a _ProviderConfig_ -```yaml {label="providerconfig",copy-lines="all"} -cat <}} {{}} ## Create a custom API @@ -211,40 +189,7 @@ must be {{}}oneOf{{}} either Apply this XRD to create the custom API in your Kubernetes cluster. -```yaml {label="xrd",copy-lines="all"} -cat <}} Adding the {{}}claimNames{{}} allows users to access this API either at the cluster level with the @@ -315,133 +260,7 @@ more information on configuring Compositions and all the available options. Apply this Composition to your cluster. -```yaml {label="comp",copy-lines="all"} -cat <}} The {{}}compositeTypeRef{{}} defines which custom APIs can use this template to create resources. @@ -453,16 +272,7 @@ You must install the function before you can use it in a Composition. Apply this Function to install `function-patch-and-transform`: -```yaml {label="install"} -cat <}} {{}} Read the [Composition documentation]({{}}) for @@ -489,16 +299,7 @@ machines requires the Azure Compute provider as well. Add the new Provider to the cluster. -```yaml -cat <}} View the new Compute provider with `kubectl get providers`. @@ -519,16 +320,7 @@ With the custom API (XRD) installed and associated to a resource template Create a {{}}VirtualMachine{{}} object to create the cloud resources. -```yaml {copy-lines="all",label="xr"} -cat <}} View the resource with `kubectl get VirtualMachine`. @@ -616,17 +408,7 @@ kubectl create namespace crossplane-test Then create a Claim in the `crossplane-test` namespace. -```yaml {label="claim",copy-lines="all"} -cat <}} View the Claim with `kubectl get claim -n crossplane-test`. ```shell {copy-lines="1"} diff --git a/content/v1.20/getting-started/provider-azure.md b/content/v1.20/getting-started/provider-azure.md index 5672cefc5..34420516a 100644 --- a/content/v1.20/getting-started/provider-azure.md +++ b/content/v1.20/getting-started/provider-azure.md @@ -32,16 +32,7 @@ This quickstart requires: Install the Azure Network resource provider into the Kubernetes cluster with a Kubernetes configuration file. -```yaml {label="provider",copy-lines="all"} -cat <}} The Crossplane {{< hover label="provider" line="3" >}}Provider{{}} installs the Kubernetes _Custom Resource Definitions_ (CRDs) representing Azure Networking @@ -149,21 +140,8 @@ creds: 629 bytes A `ProviderConfig` customizes the settings of the Azure Provider. Apply the {{< hover label="providerconfig" line="5">}}ProviderConfig{{}} with the command: -```yaml {label="providerconfig",copy-lines="all"} -cat <}} This attaches the Azure credentials, saved as a Kubernetes secret, as a {{< hover label="providerconfig" line="9">}}secretRef{{}}. @@ -181,22 +159,7 @@ Add your Azure Resource Group name. Follow the Azure documentation to if you don't have one. {{< /hint >}} -{{< editCode >}} -```yaml {label="xr"} -cat <}} +{{< manifest path="getting-started/azure/virtualnetwork-crossplane-quickstart-network.yaml" label="xr" command="kubectl create -f" >}} The {{< hover label="xr" line="2">}}apiVersion{{< /hover >}} and {{< hover label="xr" line="3">}}kind{{}} are from the provider's CRDs. diff --git a/content/v1.20/getting-started/provider-gcp-part-2.md b/content/v1.20/getting-started/provider-gcp-part-2.md index f2d3e6645..cb741751c 100644 --- a/content/v1.20/getting-started/provider-gcp-part-2.md +++ b/content/v1.20/getting-started/provider-gcp-part-2.md @@ -40,16 +40,7 @@ crossplane-stable/crossplane \ 2. When the Crossplane pods finish installing and are ready, apply the GCP Provider. -```yaml {label="provider",copy-lines="all"} -cat <}} 3. Create a file called `gcp-credentials.json` with your GCP service account JSON file. @@ -107,16 +98,7 @@ First install the GCP PubSub Provider. Add the new Provider to the cluster. -```yaml -cat <}} View the new PubSub provider with `kubectl get providers`. @@ -254,40 +236,7 @@ must be {{}}oneOf{{}} either Apply this XRD to create the custom API in your Kubernetes cluster. -```yaml {label="xrd",copy-lines="all"} -cat <}} Adding the {{}}claimNames{{}} allows users to access this API either at the cluster level with the @@ -357,59 +306,7 @@ more information on configuring Compositions and all the available options. Apply this Composition to your cluster. -```yaml {label="comp",copy-lines="all"} -cat <}} The {{}}compositeTypeRef{{}} defines which custom APIs can use this template to create resources. @@ -421,16 +318,7 @@ You must install the function before you can use it in a Composition. Apply this Function to install `function-patch-and-transform`: -```yaml {label="install"} -cat <}} {{}} Read the [Composition documentation]({{}}) for @@ -458,16 +346,7 @@ With the custom API (XRD) installed and associated to a resource template Create a {{}}PubSub{{}} object to create the cloud resources. -```yaml {copy-lines="all",label="xr"} -cat <}} View the resource with `kubectl get pubsub`. @@ -531,17 +410,7 @@ kubectl create namespace crossplane-test Then create a Claim in the `crossplane-test` namespace. -```yaml {label="claim",copy-lines="all"} -cat <}} View the Claim with `kubectl get claim -n crossplane-test`. ```shell {copy-lines="1"} diff --git a/content/v1.20/getting-started/provider-gcp.md b/content/v1.20/getting-started/provider-gcp.md index df0d2be29..da57ae8f6 100644 --- a/content/v1.20/getting-started/provider-gcp.md +++ b/content/v1.20/getting-started/provider-gcp.md @@ -29,16 +29,7 @@ This quickstart requires: Install the provider into the Kubernetes cluster with a Kubernetes configuration file. -```shell {label="provider",copy-lines="all"} -cat <}} The Crossplane {{< hover label="provider" line="3" >}}Provider{{}} installs the Kubernetes _Custom Resource Definitions_ (CRDs) representing GCP storage @@ -188,21 +179,7 @@ To generate a unique name use Create the Bucket with the following command: -```yaml {label="xr",copy-lines="all"} -cat <}} The {{< hover label="xr" line="2">}}apiVersion{{< /hover >}} and {{< hover label="xr" line="3">}}kind{{}} are from the provider's CRDs. diff --git a/content/v1.20/guides/change-logs.md b/content/v1.20/guides/change-logs.md index 139a4367d..2adb94bf9 100644 --- a/content/v1.20/guides/change-logs.md +++ b/content/v1.20/guides/change-logs.md @@ -6,33 +6,33 @@ state: alpha alphaVersion: "1.17" --- -The "change logs" feature is designed to help users of Crossplane Providers to -understand what changes a provider is making to the resources it's managing. -Whenever a provider creates, updates, or deletes a managed resource, an entry -explaining the details of the change is recorded in the provider's change log. +The change logs feature helps users of Crossplane Providers understand what +changes a provider makes to the resources it manages. Whenever a provider +creates, updates, or deletes a managed resource, the provider records an entry +explaining the details of the change in its change log. Change logs are important for awareness of the changes that a provider is making to its managed resources. Due to the nature of Crossplane's active reconciliation, it's possible for a provider to make changes to managed resources without any user interaction. Consider the scenario when someone updates a resource outside of Crossplane, for example via the AWS console or -`gcloud` CLI. When Crossplane detects this configuration drift it will -enforce its source of truth to eventually correct this unexpected change +`gcloud` CLI. When Crossplane detects this configuration drift, it +enforces the declared state and corrects the unexpected change without any user interaction. With Crossplane acting continuously and autonomously to update critical -infrastructure, it's vital for users to have insight into the operations being -performed, so they can build and maintain a strong sense of confidence and trust +infrastructure, it's vital for users to have insight into the operations the provider performs, +so they can build and maintain a strong sense of confidence and trust in their control planes. Change logs provide details about all changes the provider makes, so users can remain aware of any changes, even when they aren't explicitly expecting any. {{}} Change logs help you understand all the changes a provider is making to your resources, even when changes weren't explicitly requested, for -example as a result of Crossplane's automatic correction of configuration drift. +example because of Crossplane's automatic correction of configuration drift. {{}} -## Enabling Change Logs +## Enabling change logs {{}} Change logs are an alpha feature and must be explicitly enabled for each provider through the use of a `DeploymentRuntimeConfig`. @@ -40,7 +40,7 @@ enabled for each provider through the use of a `DeploymentRuntimeConfig`. To enable change logs for a provider, use a `DeploymentRuntimeConfig` to configure each provider pod that should start producing change logs. The -`DeploymentRuntimeConfig` has a few important configuration details: +`DeploymentRuntimeConfig` has several important configuration details: 1. A command line argument to the provider container that enables the change logs feature, for example `--enable-changelogs`. @@ -57,9 +57,9 @@ This guide assumes you have a control plane with [Crossplane installed]({{}} Not all providers support the change logs feature. Check with your provider of choice to confirm it has added support for change logs. @@ -70,51 +70,17 @@ This guide walks through a full example of generating change logs with ### Create a `DeploymentRuntimeConfig` -Create a `DeploymentRuntimeConfig` that will enable change logs for -the provider when it's installed by performing the necessary configuration +Create a `DeploymentRuntimeConfig` that enables change logs for +the provider when it's installed by performing the following configuration steps: -1. The {{}}--enable-changelogs{{}} flag is - set on the provider. -1. The {{}}sidecar container{{}} is added - to the provider pod. -1. A {{}}shared volume{{}} is declared and - then mounted in the {{}}provider +1. Set the {{}}--enable-changelogs{{}} flag on the provider. +1. Add the {{}}sidecar container{{}} to the provider pod. +1. Declare a {{}}shared volume{{}} and mount it in the {{}}provider container{{}} and the {{}}sidecar container{{}}. -```yaml {label="drc",copy-lines="all"} -cat <}} ### Install the provider @@ -122,99 +88,34 @@ Install the {{}}provider{{}} and instruct it to use the {{}}DeploymentRuntimeConfig{{}} that was just created. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Configure permissions -In order for the provider to create Kubernetes resources within the control -plane, it must be granted the appropriate permissions. This guide only creates a -`ConfigMap`, so only permissions for that resource type are needed. +To allow the provider to create Kubernetes resources in the control +plane, grant the appropriate permissions. This guide only creates a +`ConfigMap`, so it only requires permissions for that resource type. {{}} This guide grants specific permissions to the provider for example purposes. This approach isn't intended to be representative of a -production environment. More examples on configuring `provider-kubernetes` can -be found in its [examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/provider). +production environment. See more examples for configuring `provider-kubernetes` in its +[examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/provider). {{}} -```yaml {label="rbac",copy-lines="all"} -cat <}} ### Create a resource -Now that the provider is installed and configured with change logs enabled, -create a resource that will generate change logs entries reflecting the actions -the control plane is taking. - -```yaml {label="provider",copy-lines="all"} -cat <}} ### Examine the change logs -Check to see that the resource creation operation was recorded in the change -logs. Examine the pod logs for `provider-kubernetes`, specifically at the +Confirm that the change logs include the resource creation operation. +Examine the pod logs for `provider-kubernetes`, specifically the `changelogs-sidecar` container: ```shell {label="changelogs-output-full",copy-lines="1"} kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kubernetes -c changelogs-sidecar | jq @@ -231,9 +132,9 @@ kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kuberne ``` Each change log entry contains rich information about the state of the resource -when the change operation occurred. Since each entry is a structured `JSON` -object, they can be filtered and queried to find any subset of information you -are interested in: +when the change operation occurred. Because each entry is a structured `JSON` +object, you can filter and query them to find any subset of information that +interests you: ```shell {label="changelogs-output-scoped",copy-lines="1-2"} kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kubernetes -c changelogs-sidecar \ | jq '.timestamp + " " + .provider + " " + .kind + " " + .name + " " + .operation' @@ -242,8 +143,7 @@ kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kuberne ### Full lifecycle operations -In addition to change log entries that record the creation of resources, update -and delete operations will also generate corresponding change log entries. +Update and delete operations also generate corresponding change log entries. Update the resource by patching its data field `key-1` with a new value `cooler-value-2`: @@ -259,9 +159,8 @@ kubectl delete object configmap-for-changelogs object.kubernetes.crossplane.io "configmap-for-changelogs" deleted ``` -Check the change logs again to verify that both the update and delete operations -were recorded, and the full lifecycle of the object has been captured in the -change logs: +Check the change logs again to verify that they include both the update and delete operations and capture the +object's full lifecycle: ```shell {label="changelogs-output-final",copy-lines="1-2"} kubectl -n crossplane-system logs -l pkg.crossplane.io/provider=provider-kubernetes -c changelogs-sidecar \ | jq '.timestamp + " " + .provider + " " + .kind + " " + .name + " " + .operation' diff --git a/content/v1.20/manifests/getting-started/aws-part-2/dynamo-with-bucket.yaml b/content/v1.20/manifests/getting-started/aws-part-2/dynamo-with-bucket.yaml new file mode 100644 index 000000000..4b86d547e --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/dynamo-with-bucket.yaml @@ -0,0 +1,57 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: dynamo-with-bucket +spec: + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: s3Bucket + base: + apiVersion: s3.aws.upbound.io/v1beta1 + kind: Bucket + spec: + forProvider: + region: us-east-2 + providerConfigRef: + name: default + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.region" + transforms: + - type: map + map: + EU: "eu-north-1" + US: "us-east-2" + - name: dynamoDB + base: + apiVersion: dynamodb.aws.upbound.io/v1beta1 + kind: Table + spec: + forProvider: + region: "us-east-2" + writeCapacity: 1 + readCapacity: 1 + attribute: + - name: S3ID + type: S + hashKey: S3ID + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.region" + transforms: + - type: map + map: + EU: "eu-north-1" + US: "us-east-2" + compositeTypeRef: + apiVersion: database.example.com/v1alpha1 + kind: NoSQL diff --git a/content/v1.20/manifests/getting-started/aws-part-2/function-patch-and-transform.yaml b/content/v1.20/manifests/getting-started/aws-part-2/function-patch-and-transform.yaml new file mode 100644 index 000000000..fc2521c2d --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/function-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v1.20/manifests/getting-started/aws-part-2/nosql-my-nosql-database.yaml b/content/v1.20/manifests/getting-started/aws-part-2/nosql-my-nosql-database.yaml new file mode 100644 index 000000000..6a27b00a3 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/nosql-my-nosql-database.yaml @@ -0,0 +1,6 @@ +apiVersion: database.example.com/v1alpha1 +kind: NoSQL +metadata: + name: my-nosql-database +spec: + location: "US" diff --git a/content/v1.20/manifests/getting-started/aws-part-2/nosqlclaim-my-nosql-database.yaml b/content/v1.20/manifests/getting-started/aws-part-2/nosqlclaim-my-nosql-database.yaml new file mode 100644 index 000000000..643d58be3 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/nosqlclaim-my-nosql-database.yaml @@ -0,0 +1,7 @@ +apiVersion: database.example.com/v1alpha1 +kind: NoSQLClaim +metadata: + name: my-nosql-database + namespace: crossplane-test +spec: + location: "US" diff --git a/content/v1.20/manifests/getting-started/aws-part-2/nosqls.database.example.com.yaml b/content/v1.20/manifests/getting-started/aws-part-2/nosqls.database.example.com.yaml new file mode 100644 index 000000000..db7f5ad59 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/nosqls.database.example.com.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: nosqls.database.example.com +spec: + group: database.example.com + names: + kind: NoSQL + plural: nosqls + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + location: + type: string + oneOf: + - pattern: '^EU$' + - pattern: '^US$' + required: + - location + served: true + referenceable: true + claimNames: + kind: NoSQLClaim + plural: nosqlclaim diff --git a/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-dynamodb.yaml b/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-dynamodb.yaml new file mode 100644 index 000000000..0062b31c7 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-dynamodb.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-dynamodb +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-dynamodb:v1.21.1 diff --git a/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-s3.yaml b/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-s3.yaml new file mode 100644 index 000000000..d6f5b17e6 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/provider-aws-s3.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.21.1 diff --git a/content/v1.20/manifests/getting-started/aws-part-2/providerconfig.yaml b/content/v1.20/manifests/getting-started/aws-part-2/providerconfig.yaml new file mode 100644 index 000000000..f3c69b1c4 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws-part-2/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.upbound.io/v1beta1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v1.20/manifests/getting-started/aws/bucket.yaml b/content/v1.20/manifests/getting-started/aws/bucket.yaml new file mode 100644 index 000000000..0b78cf871 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws/bucket.yaml @@ -0,0 +1,9 @@ +apiVersion: s3.aws.upbound.io/v1beta1 +kind: Bucket +metadata: + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 + providerConfigRef: + name: default diff --git a/content/v1.20/manifests/getting-started/aws/provider.yaml b/content/v1.20/manifests/getting-started/aws/provider.yaml new file mode 100644 index 000000000..d6f5b17e6 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.21.1 diff --git a/content/v1.20/manifests/getting-started/aws/providerconfig.yaml b/content/v1.20/manifests/getting-started/aws/providerconfig.yaml new file mode 100644 index 000000000..f3c69b1c4 --- /dev/null +++ b/content/v1.20/manifests/getting-started/aws/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.upbound.io/v1beta1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v1.20/manifests/getting-started/azure-part-2/crossplane-quickstart-vm-with-network.yaml b/content/v1.20/manifests/getting-started/azure-part-2/crossplane-quickstart-vm-with-network.yaml new file mode 100644 index 000000000..164c78662 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/crossplane-quickstart-vm-with-network.yaml @@ -0,0 +1,123 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: crossplane-quickstart-vm-with-network +spec: + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: quickstart-vm + base: + apiVersion: compute.azure.upbound.io/v1beta1 + kind: LinuxVirtualMachine + spec: + forProvider: + adminUsername: adminuser + adminSshKey: + - publicKey: ssh-rsa + AAAAB3NzaC1yc2EAAAADAQABAAABAQC+wWK73dCr+jgQOAxNsHAnNNNMEMWOHYEccp6wJm2gotpr9katuF/ZAdou5AaW1C61slRkHRkpRRX9FA9CYBiitZgvCCz+3nWNN7l/Up54Zps/pHWGZLHNJZRYyAB6j5yVLMVHIHriY49d/GZTZVNB8GoJv9Gakwc/fuEZYYl4YDFiGMBP///TzlI4jhiJzjKnEvqPFki5p2ZRJqcbCiF4pJrxUQR/RXqVFQdbRLZgYfJ8xGB878RENq3yQ39d8dVOkq4edbkzwcUmwwwkYVPIoDGsYLaRHnG+To7FvMeyO7xDVQkMKzopTQV8AuKpyvpqu0a9pWOMaiCyDytO7GGN + example@docs.crossplane.io + username: adminuser + location: "Central US" + osDisk: + - caching: ReadWrite + storageAccountType: Standard_LRS + resourceGroupNameSelector: + matchControllerRef: true + size: Standard_B1ms + sourceImageReference: + - offer: debian-11 + publisher: Debian + sku: 11-backports-gen2 + version: latest + networkInterfaceIdsSelector: + matchControllerRef: true + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.location" + transforms: + - type: map + map: + EU: "Sweden Central" + US: "Central US" + - name: quickstart-nic + base: + apiVersion: network.azure.upbound.io/v1beta1 + kind: NetworkInterface + spec: + forProvider: + ipConfiguration: + - name: crossplane-quickstart-configuration + privateIpAddressAllocation: Dynamic + subnetIdSelector: + matchControllerRef: true + location: "Central US" + resourceGroupNameSelector: + matchControllerRef: true + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.location" + transforms: + - type: map + map: + EU: "Sweden Central" + US: "Central US" + - name: quickstart-subnet + base: + apiVersion: network.azure.upbound.io/v1beta1 + kind: Subnet + spec: + forProvider: + addressPrefixes: + - 10.0.1.0/24 + virtualNetworkNameSelector: + matchControllerRef: true + resourceGroupNameSelector: + matchControllerRef: true + - name: quickstart-network + base: + apiVersion: network.azure.upbound.io/v1beta1 + kind: VirtualNetwork + spec: + forProvider: + addressSpace: + - 10.0.0.0/16 + location: "Central US" + resourceGroupNameSelector: + matchControllerRef: true + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.location" + transforms: + - type: map + map: + EU: "Sweden Central" + US: "Central US" + - name: crossplane-resourcegroup + base: + apiVersion: azure.upbound.io/v1beta1 + kind: ResourceGroup + spec: + forProvider: + location: Central US + patches: + - type: FromCompositeFieldPath + fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.location" + transforms: + - type: map + map: + EU: "Sweden Central" + US: "Central US" + compositeTypeRef: + apiVersion: compute.example.com/v1alpha1 + kind: VirtualMachine diff --git a/content/v1.20/manifests/getting-started/azure-part-2/function-patch-and-transform.yaml b/content/v1.20/manifests/getting-started/azure-part-2/function-patch-and-transform.yaml new file mode 100644 index 000000000..fc2521c2d --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/function-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-compute.yaml b/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-compute.yaml new file mode 100644 index 000000000..3e8f3bbef --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-compute.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-azure-compute +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-azure-compute:v1.11.2 diff --git a/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-network.yaml b/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-network.yaml new file mode 100644 index 000000000..338d23a84 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/provider-azure-network.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-azure-network +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-azure-network:v1.11.2 diff --git a/content/v1.20/manifests/getting-started/azure-part-2/providerconfig.yaml b/content/v1.20/manifests/getting-started/azure-part-2/providerconfig.yaml new file mode 100644 index 000000000..c00c2b656 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: azure.upbound.io/v1beta1 +metadata: + name: default +kind: ProviderConfig +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: azure-secret + key: creds diff --git a/content/v1.20/manifests/getting-started/azure-part-2/virtualmachine-my-vm.yaml b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachine-my-vm.yaml new file mode 100644 index 000000000..893d88a93 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachine-my-vm.yaml @@ -0,0 +1,6 @@ +apiVersion: compute.example.com/v1alpha1 +kind: VirtualMachine +metadata: + name: my-vm +spec: + location: "EU" diff --git a/content/v1.20/manifests/getting-started/azure-part-2/virtualmachineclaim-my-namespaced-vm.yaml b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachineclaim-my-namespaced-vm.yaml new file mode 100644 index 000000000..78ad17058 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachineclaim-my-namespaced-vm.yaml @@ -0,0 +1,7 @@ +apiVersion: compute.example.com/v1alpha1 +kind: VirtualMachineClaim +metadata: + name: my-namespaced-vm + namespace: crossplane-test +spec: + location: "EU" diff --git a/content/v1.20/manifests/getting-started/azure-part-2/virtualmachines.compute.example.com.yaml b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachines.compute.example.com.yaml new file mode 100644 index 000000000..689e17ee5 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure-part-2/virtualmachines.compute.example.com.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: virtualmachines.compute.example.com +spec: + group: compute.example.com + names: + kind: VirtualMachine + plural: virtualmachines + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + location: + type: string + oneOf: + - pattern: '^EU$' + - pattern: '^US$' + required: + - location + served: true + referenceable: true + claimNames: + kind: VirtualMachineClaim + plural: virtualmachineclaims diff --git a/content/v1.20/manifests/getting-started/azure/provider.yaml b/content/v1.20/manifests/getting-started/azure/provider.yaml new file mode 100644 index 000000000..338d23a84 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-azure-network +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-azure-network:v1.11.2 diff --git a/content/v1.20/manifests/getting-started/azure/providerconfig.yaml b/content/v1.20/manifests/getting-started/azure/providerconfig.yaml new file mode 100644 index 000000000..c00c2b656 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: azure.upbound.io/v1beta1 +metadata: + name: default +kind: ProviderConfig +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: azure-secret + key: creds diff --git a/content/v1.20/manifests/getting-started/azure/virtualnetwork-crossplane-quickstart-network.yaml b/content/v1.20/manifests/getting-started/azure/virtualnetwork-crossplane-quickstart-network.yaml new file mode 100644 index 000000000..f17d0cb87 --- /dev/null +++ b/content/v1.20/manifests/getting-started/azure/virtualnetwork-crossplane-quickstart-network.yaml @@ -0,0 +1,10 @@ +apiVersion: network.azure.upbound.io/v1beta1 +kind: VirtualNetwork +metadata: + name: crossplane-quickstart-network +spec: + forProvider: + addressSpace: + - 10.0.0.0/16 + location: "Sweden Central" + resourceGroupName: docs diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/function-patch-and-transform.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/function-patch-and-transform.yaml new file mode 100644 index 000000000..fc2521c2d --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/function-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-pubsub.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-pubsub.yaml new file mode 100644 index 000000000..714d7969b --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-pubsub.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-gcp-pubsub +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-gcp-pubsub:v1.12.1 diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-storage.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-storage.yaml new file mode 100644 index 000000000..590d5d182 --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/provider-gcp-storage.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-gcp-storage +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-gcp-storage:v1.12.1 diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/pubsub-my-pubsub-queue.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/pubsub-my-pubsub-queue.yaml new file mode 100644 index 000000000..93c10d268 --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/pubsub-my-pubsub-queue.yaml @@ -0,0 +1,6 @@ +apiVersion: queue.example.com/v1alpha1 +kind: PubSub +metadata: + name: my-pubsub-queue +spec: + location: "US" diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/pubsubclaim-my-pubsub-queue.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/pubsubclaim-my-pubsub-queue.yaml new file mode 100644 index 000000000..9d36b2e3a --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/pubsubclaim-my-pubsub-queue.yaml @@ -0,0 +1,7 @@ +apiVersion: queue.example.com/v1alpha1 +kind: PubSubClaim +metadata: + name: my-pubsub-queue + namespace: crossplane-test +spec: + location: "US" diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/pubsubs.queue.example.com.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/pubsubs.queue.example.com.yaml new file mode 100644 index 000000000..9a2fd4e73 --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/pubsubs.queue.example.com.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: pubsubs.queue.example.com +spec: + group: queue.example.com + names: + kind: PubSub + plural: pubsubs + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + location: + type: string + oneOf: + - pattern: '^EU$' + - pattern: '^US$' + required: + - location + served: true + referenceable: true + claimNames: + kind: PubSubClaim + plural: pubsubclaims diff --git a/content/v1.20/manifests/getting-started/gcp-part-2/topic-with-bucket.yaml b/content/v1.20/manifests/getting-started/gcp-part-2/topic-with-bucket.yaml new file mode 100644 index 000000000..c6bba050c --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp-part-2/topic-with-bucket.yaml @@ -0,0 +1,49 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: topic-with-bucket +spec: + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: crossplane-quickstart-bucket + base: + apiVersion: storage.gcp.upbound.io/v1beta1 + kind: Bucket + spec: + forProvider: + location: "US" + patches: + - fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.location" + transforms: + - type: map + map: + EU: "EU" + US: "US" + - name: crossplane-quickstart-topic + base: + apiVersion: pubsub.gcp.upbound.io/v1beta1 + kind: Topic + spec: + forProvider: + messageStoragePolicy: + - allowedPersistenceRegions: + - "us-central1" + patches: + - fromFieldPath: "spec.location" + toFieldPath: "spec.forProvider.messageStoragePolicy[0].allowedPersistenceRegions[0]" + transforms: + - type: map + map: + EU: "europe-central2" + US: "us-central1" + compositeTypeRef: + apiVersion: queue.example.com/v1alpha1 + kind: PubSub diff --git a/content/v1.20/manifests/getting-started/gcp/bucket.yaml b/content/v1.20/manifests/getting-started/gcp/bucket.yaml new file mode 100644 index 000000000..78a92dcbc --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp/bucket.yaml @@ -0,0 +1,11 @@ +apiVersion: storage.gcp.upbound.io/v1beta1 +kind: Bucket +metadata: + generateName: crossplane-bucket- + labels: + docs.crossplane.io/example: provider-gcp +spec: + forProvider: + location: US + providerConfigRef: + name: default diff --git a/content/v1.20/manifests/getting-started/gcp/provider.yaml b/content/v1.20/manifests/getting-started/gcp/provider.yaml new file mode 100644 index 000000000..590d5d182 --- /dev/null +++ b/content/v1.20/manifests/getting-started/gcp/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-gcp-storage +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-gcp-storage:v1.12.1 diff --git a/content/v1.20/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml b/content/v1.20/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml new file mode 100644 index 000000000..9076ea60a --- /dev/null +++ b/content/v1.20/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml @@ -0,0 +1,28 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: enable-changelogs +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --enable-changelogs + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + - name: changelogs-sidecar + image: xpkg.crossplane.io/crossplane/changelogs-sidecar:v0.0.1 + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + volumes: + - name: changelogs-vol + emptyDir: {} + serviceAccountTemplate: + metadata: + name: provider-kubernetes diff --git a/content/v1.20/manifests/guides/change-logs/object-configmap-for-changelogs.yaml b/content/v1.20/manifests/guides/change-logs/object-configmap-for-changelogs.yaml new file mode 100644 index 000000000..a55e5b39e --- /dev/null +++ b/content/v1.20/manifests/guides/change-logs/object-configmap-for-changelogs.yaml @@ -0,0 +1,14 @@ +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: configmap-for-changelogs +spec: + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + name: configmap-for-changelogs + data: + key-1: cool-value-1 diff --git a/content/v1.20/manifests/guides/change-logs/provider-kubernetes.yaml b/content/v1.20/manifests/guides/change-logs/provider-kubernetes.yaml new file mode 100644 index 000000000..75b22830a --- /dev/null +++ b/content/v1.20/manifests/guides/change-logs/provider-kubernetes.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-kubernetes +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v0.18.0 + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: enable-changelogs diff --git a/content/v1.20/manifests/guides/change-logs/rbac.yaml b/content/v1.20/manifests/guides/change-logs/rbac.yaml new file mode 100644 index 000000000..2de5d1f8c --- /dev/null +++ b/content/v1.20/manifests/guides/change-logs/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: configmap-edit +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: provider-kubernetes-configmap-edit +subjects: + - kind: ServiceAccount + name: provider-kubernetes + namespace: crossplane-system +roleRef: + kind: ClusterRole + name: configmap-edit + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: kubernetes.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: InjectedIdentity diff --git a/content/v2.0-preview/get-started/get-started-with-composition.md b/content/v2.0-preview/get-started/get-started-with-composition.md index d36a775fb..eb55a3b0e 100644 --- a/content/v2.0-preview/get-started/get-started-with-composition.md +++ b/content/v2.0-preview/get-started/get-started-with-composition.md @@ -120,54 +120,12 @@ A composite resource is a kind of custom resource. Create this _composite resource definition_ (XRD) to define the schema of the new `App` composite resource (XR). -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: apps.example.crossplane.io -spec: - scope: Namespaced - group: example.crossplane.io - names: - kind: App - plural: apps - versions: - - name: v1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - image: - description: The app's OCI container image. - type: string - required: - - image - status: - type: object - properties: - replicas: - description: The number of available app replicas. - type: integer - address: - description: The app's IP address. - type: string -``` - -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` +{{< manifest path="get-started/composition/xrd.yaml" >}} Check that Crossplane has established the XRD: ``` shell {copy-lines="1"} -kubectl get -f xrd.yaml +kubectl get -f {{< manifest-url path="get-started/composition/xrd.yaml" >}} NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 21s ``` @@ -197,25 +155,12 @@ or conditionals. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s ``` @@ -227,25 +172,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s ``` @@ -257,25 +189,12 @@ full [Python standard library](https://docs.python.org/3/library/index.html). Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s ``` @@ -287,25 +206,12 @@ It's fast and sandboxed. Create this composition function to install KCL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s ``` @@ -326,232 +232,19 @@ Create a composition to tell Crossplane what to do when you create or update an {{< tab "YAML" >}} Create this composition to use YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 2 - template: - spec: - containers: - - name: app - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: spec.image - toFieldPath: spec.template.spec.containers[0].image - - type: ToCompositeFieldPath - fromFieldPath: status.availableReplicas - toFieldPath: status.replicas - readinessChecks: - - type: MatchCondition - matchCondition: - type: Available - status: "True" - - name: service - base: - apiVersion: v1 - kind: Service - spec: - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector[example.crossplane.io/app] - - type: ToCompositeFieldPath - fromFieldPath: spec.clusterIP - toFieldPath: status.address - readinessChecks: - - type: NonEmpty - fieldPath: spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-yaml.yaml" >}} {{< /tab >}} {{< tab "Templated YAML" >}} Create this composition to use templated YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-templated-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - template: - metadata: - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - containers: - - name: app - image: {{ .observed.composite.resource.spec.image }} - ports: - - containerPort: 80 - --- - apiVersion: v1 - kind: Service - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - selector: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - --- - apiVersion: example.crossplane.io/v1 - kind: App - status: - replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} - address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} -``` +{{< manifest path="get-started/composition/composition-templated-yaml.yaml" >}} {{< /tab >}} {{< tab "Python" >}} Create this composition to use Python to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-python -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - observed_xr = req.observed.composite.resource - - rsp.desired.resources["deployment"].resource.update({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "replicas": 2, - "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, - "template": { - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "containers": [{ - "name": "app", - "image": observed_xr["spec"]["image"], - "ports": [{"containerPort": 80}] - }], - }, - }, - }, - }) - - observed_deployment = req.observed.resources["deployment"].resource - if "status" in observed_deployment: - if "availableReplicas" in observed_deployment["status"]: - rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] - if "conditions" in observed_deployment["status"]: - for condition in observed_deployment["status"]["conditions"]: - if condition["type"] == "Available" and condition["status"] == "True": - rsp.desired.resources["deployment"].ready = True - - rsp.desired.resources["service"].resource.update({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], - }, - }) - - observed_service = req.observed.resources["service"].resource - if "spec" in observed_service and "clusterIP" in observed_service["spec"]: - rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] - rsp.desired.resources["service"].ready = True -``` +{{< manifest path="get-started/composition/composition-python.yaml" >}} {{}} You can write your own function in Python. @@ -567,92 +260,11 @@ Read the [guide to writing a composition function in Python]({{}} Create this composition to use KCL to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-kcl -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - observed_xr = option("params").oxr - - _desired_deployment = { - apiVersion = "apps/v1" - kind = "Deployment" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "deployment" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - replicas = 2 - selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} - template = { - metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - spec.containers = [{ - name = "app" - image = observed_xr.spec.image - ports = [{containerPort = 80}] - }] - } - } - } - - observed_deployment = option("params").ocds["deployment"]?.Resource - if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_service = { - apiVersion = "v1" - kind = "Service" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "service" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - selector = {"example.crossplane.io/app" = observed_xr.metadata.name} - ports = [{protocol = "TCP", port = 8080, targetPort = 80}] - } - } - - observed_service = option("params").ocds["service"]?.Resource - if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_xr = { - **option("params").dxr - - status.address = observed_service?.spec?.clusterIP or "" - status.replicas = observed_deployment?.status?.availableReplicas or 0 - } - - items = [_desired_deployment, _desired_service, _desired_xr] -``` +{{< manifest path="get-started/composition/composition-kcl.yaml" >}} {{< /tab >}} {{}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - {{}} A composition can include multiple functions. @@ -673,26 +285,12 @@ Crossplane now understands `App` custom resources. Create an `App`: -```yaml -apiVersion: example.crossplane.io/v1 -kind: App -metadata: - namespace: default - name: my-app -spec: - image: nginx -``` - -Save the `App` as `app.yaml` and apply it: - -```shell -kubectl apply -f app.yaml -``` +{{< manifest path="get-started/composition/app.yaml" >}} Check that the `App` is ready: ```shell {copy-lines="1"} -kubectl get -f app.yaml +kubectl get -f {{< manifest-url path="get-started/composition/app.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 56s ``` @@ -717,14 +315,19 @@ service/my-app-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` {{}} -Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates -the `Deployment`'s image to match. +Edit the `App`'s image: + +```shell +kubectl edit -f {{< manifest-url path="get-started/composition/app.yaml" >}} +``` + +Crossplane updates the `Deployment`'s image to match. {{}} Delete the `App`. ```shell {copy-lines="1"} -kubectl delete -f app.yaml +kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} ``` When you delete the `App`, Crossplane deletes the `Deployment` and `Service`. diff --git a/content/v2.0-preview/get-started/get-started-with-managed-resources.md b/content/v2.0-preview/get-started/get-started-with-managed-resources.md index ec3acf0cc..a81ae00e6 100644 --- a/content/v2.0-preview/get-started/get-started-with-managed-resources.md +++ b/content/v2.0-preview/get-started/get-started-with-managed-resources.md @@ -63,20 +63,7 @@ The AWS S3 provider installs support for all the AWS S3 managed resources. Create this provider to install the AWS S3 provider: -```yaml {label="provider",copy-lines="all"} -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: crossplane-contrib-provider-aws-s3 -spec: - package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.24.0-crossplane-v2-preview.0 -``` - -Save this as `provider.yaml` and apply it: - -```shell {label="kube-apply-provider",copy-lines="all"} -kubectl apply -f provider.yaml -``` +{{< manifest path="get-started/managed-resources/provider.yaml" label="provider" >}} Check that Crossplane installed the provider: @@ -153,25 +140,7 @@ All providers need a configuration to tell them where to load credentials. Create this provider configuration: -```yaml {label="providerconfig",copy-lines="all"} -apiVersion: aws.upbound.io/v1beta1 -kind: ProviderConfig -metadata: - name: default -spec: - credentials: - source: Secret - secretRef: - namespace: crossplane-system - name: aws-secret - key: creds -``` - -Save the provider configuration as `providerconfig.yaml` and apply it: - -```shell {label="kube-apply-providerconfig",copy-lines="all"} -kubectl apply -f providerconfig.yaml -``` +{{< manifest path="get-started/managed-resources/providerconfig.yaml" label="providerconfig" >}} This tells the provider to load credentials from [the secret](#save-the-providers-credentials). @@ -183,22 +152,7 @@ AWS S3 bucket names must be globally unique. This example uses `generateName` to generate a random name. Any unique name is acceptable. {{}} -```yaml {label="bucket"} -apiVersion: s3.aws.m.upbound.io/v1beta1 -kind: Bucket -metadata: - namespace: default - generateName: crossplane-bucket- -spec: - forProvider: - region: us-east-2 -``` - -Save the bucket to `bucket.yaml` and apply it: - -```shell {label="kube-create-bucket",copy-lines="all"} -kubectl create -f bucket.yaml -``` +{{< manifest path="get-started/managed-resources/bucket.yaml" label="bucket" command="kubectl create -f" >}} Check that Crossplane created the bucket: diff --git a/content/v2.0-preview/manifests/get-started/composition/app.yaml b/content/v2.0-preview/manifests/get-started/composition/app.yaml new file mode 100644 index 000000000..a24b2ecd9 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/app.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: App +metadata: + namespace: default + name: my-app +spec: + image: nginx diff --git a/content/v2.0-preview/manifests/get-started/composition/composition-kcl.yaml b/content/v2.0-preview/manifests/get-started/composition/composition-kcl.yaml new file mode 100644 index 000000000..f75c71504 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/composition-kcl.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-kcl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + observed_xr = option("params").oxr + + _desired_deployment = { + apiVersion = "apps/v1" + kind = "Deployment" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "deployment" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + replicas = 2 + selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} + template = { + metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + spec.containers = [{ + name = "app" + image = observed_xr.spec.image + ports = [{containerPort = 80}] + }] + } + } + } + + observed_deployment = option("params").ocds["deployment"]?.Resource + if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): + _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_service = { + apiVersion = "v1" + kind = "Service" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "service" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + selector = {"example.crossplane.io/app" = observed_xr.metadata.name} + ports = [{protocol = "TCP", port = 8080, targetPort = 80}] + } + } + + observed_service = option("params").ocds["service"]?.Resource + if observed_service?.spec?.clusterIP: + _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_xr = { + **option("params").dxr + + status.address = observed_service?.spec?.clusterIP or "" + status.replicas = observed_deployment?.status?.availableReplicas or 0 + } + + items = [_desired_deployment, _desired_service, _desired_xr] diff --git a/content/v2.0-preview/manifests/get-started/composition/composition-python.yaml b/content/v2.0-preview/manifests/get-started/composition/composition-python.yaml new file mode 100644 index 000000000..85a404f2d --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/composition-python.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-python +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + observed_xr = req.observed.composite.resource + + rsp.desired.resources["deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "replicas": 2, + "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, + "template": { + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "containers": [{ + "name": "app", + "image": observed_xr["spec"]["image"], + "ports": [{"containerPort": 80}] + }], + }, + }, + }, + }) + + observed_deployment = req.observed.resources["deployment"].resource + if "status" in observed_deployment: + if "availableReplicas" in observed_deployment["status"]: + rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] + if "conditions" in observed_deployment["status"]: + for condition in observed_deployment["status"]["conditions"]: + if condition["type"] == "Available" and condition["status"] == "True": + rsp.desired.resources["deployment"].ready = True + + rsp.desired.resources["service"].resource.update({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], + }, + }) + + observed_service = req.observed.resources["service"].resource + if "spec" in observed_service and "clusterIP" in observed_service["spec"]: + rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] + rsp.desired.resources["service"].ready = True diff --git a/content/v2.0-preview/manifests/get-started/composition/composition-templated-yaml.yaml b/content/v2.0-preview/manifests/get-started/composition/composition-templated-yaml.yaml new file mode 100644 index 000000000..4c1a30cd2 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/composition-templated-yaml.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-templated-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: deployment + {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + template: + metadata: + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + containers: + - name: app + image: {{ .observed.composite.resource.spec.image }} + ports: + - containerPort: 80 + --- + apiVersion: v1 + kind: Service + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: service + {{ if (get (getComposedResource . "service").spec "clusterIP") }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + selector: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + --- + apiVersion: example.crossplane.io/v1 + kind: App + status: + replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} + address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} diff --git a/content/v2.0-preview/manifests/get-started/composition/composition-yaml.yaml b/content/v2.0-preview/manifests/get-started/composition/composition-yaml.yaml new file mode 100644 index 000000000..9907d5a34 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/composition-yaml.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 2 + template: + spec: + containers: + - name: app + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: spec.image + toFieldPath: spec.template.spec.containers[0].image + - type: ToCompositeFieldPath + fromFieldPath: status.availableReplicas + toFieldPath: status.replicas + readinessChecks: + - type: MatchCondition + matchCondition: + type: Available + status: "True" + - name: service + base: + apiVersion: v1 + kind: Service + spec: + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector[example.crossplane.io/app] + - type: ToCompositeFieldPath + fromFieldPath: spec.clusterIP + toFieldPath: status.address + readinessChecks: + - type: NonEmpty + fieldPath: spec.clusterIP diff --git a/content/v2.0-preview/manifests/get-started/composition/fn-go-templating.yaml b/content/v2.0-preview/manifests/get-started/composition/fn-go-templating.yaml new file mode 100644 index 000000000..d11ea8e70 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 diff --git a/content/v2.0-preview/manifests/get-started/composition/fn-kcl.yaml b/content/v2.0-preview/manifests/get-started/composition/fn-kcl.yaml new file mode 100644 index 000000000..befb6f66d --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 diff --git a/content/v2.0-preview/manifests/get-started/composition/fn-patch-and-transform.yaml b/content/v2.0-preview/manifests/get-started/composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..aecb6cd0f --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v2.0-preview/manifests/get-started/composition/fn-python.yaml b/content/v2.0-preview/manifests/get-started/composition/fn-python.yaml new file mode 100644 index 000000000..b76bb3ee2 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 diff --git a/content/v2.0-preview/manifests/get-started/composition/xrd.yaml b/content/v2.0-preview/manifests/get-started/composition/xrd.yaml new file mode 100644 index 000000000..e2d3856de --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/composition/xrd.yaml @@ -0,0 +1,35 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: apps.example.crossplane.io +spec: + scope: Namespaced + group: example.crossplane.io + names: + kind: App + plural: apps + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + image: + description: The app's OCI container image. + type: string + required: + - image + status: + type: object + properties: + replicas: + description: The number of available app replicas. + type: integer + address: + description: The app's IP address. + type: string diff --git a/content/v2.0-preview/manifests/get-started/managed-resources/bucket.yaml b/content/v2.0-preview/manifests/get-started/managed-resources/bucket.yaml new file mode 100644 index 000000000..8c0ef2ecd --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/managed-resources/bucket.yaml @@ -0,0 +1,8 @@ +apiVersion: s3.aws.m.upbound.io/v1beta1 +kind: Bucket +metadata: + namespace: default + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 diff --git a/content/v2.0-preview/manifests/get-started/managed-resources/provider.yaml b/content/v2.0-preview/manifests/get-started/managed-resources/provider.yaml new file mode 100644 index 000000000..2d710d170 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: crossplane-contrib-provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.24.0-crossplane-v2-preview.0 diff --git a/content/v2.0-preview/manifests/get-started/managed-resources/providerconfig.yaml b/content/v2.0-preview/manifests/get-started/managed-resources/providerconfig.yaml new file mode 100644 index 000000000..f3c69b1c4 --- /dev/null +++ b/content/v2.0-preview/manifests/get-started/managed-resources/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.upbound.io/v1beta1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v2.0/get-started/get-started-with-composition.md b/content/v2.0/get-started/get-started-with-composition.md index ad93e45f8..8a7c0e54f 100644 --- a/content/v2.0/get-started/get-started-with-composition.md +++ b/content/v2.0/get-started/get-started-with-composition.md @@ -121,54 +121,12 @@ A composite resource is a kind of custom resource. Create this _composite resource definition_ (XRD) to define the schema of the new `App` composite resource (XR). -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: apps.example.crossplane.io -spec: - scope: Namespaced - group: example.crossplane.io - names: - kind: App - plural: apps - versions: - - name: v1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - image: - description: The app's OCI container image. - type: string - required: - - image - status: - type: object - properties: - replicas: - description: The number of available app replicas. - type: integer - address: - description: The app's IP address. - type: string -``` - -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` +{{< manifest path="get-started/composition/xrd.yaml" >}} Check that Crossplane has established the XRD: ``` shell {copy-lines="1"} -kubectl get -f xrd.yaml +kubectl get -f {{< manifest-url path="get-started/composition/xrd.yaml" >}} NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 21s ``` @@ -198,25 +156,12 @@ or conditionals. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s ``` @@ -228,25 +173,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s ``` @@ -258,25 +190,12 @@ full [Python standard library](https://docs.python.org/3/library/index.html). Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s ``` @@ -288,25 +207,12 @@ It's fast and sandboxed. Create this composition function to install KCL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s ``` @@ -320,25 +226,12 @@ Crossplane function APIs. Create this composition function to install Pythonic support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -359,232 +252,19 @@ Create a composition to tell Crossplane what to do when you create or update an {{< tab "YAML" >}} Create this composition to use YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 2 - template: - spec: - containers: - - name: app - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: spec.image - toFieldPath: spec.template.spec.containers[0].image - - type: ToCompositeFieldPath - fromFieldPath: status.availableReplicas - toFieldPath: status.replicas - readinessChecks: - - type: MatchCondition - matchCondition: - type: Available - status: "True" - - name: service - base: - apiVersion: v1 - kind: Service - spec: - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector[example.crossplane.io/app] - - type: ToCompositeFieldPath - fromFieldPath: spec.clusterIP - toFieldPath: status.address - readinessChecks: - - type: NonEmpty - fieldPath: spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-yaml.yaml" >}} {{< /tab >}} {{< tab "Templated YAML" >}} Create this composition to use templated YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-templated-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - template: - metadata: - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - containers: - - name: app - image: {{ .observed.composite.resource.spec.image }} - ports: - - containerPort: 80 - --- - apiVersion: v1 - kind: Service - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - selector: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - --- - apiVersion: example.crossplane.io/v1 - kind: App - status: - replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} - address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} -``` +{{< manifest path="get-started/composition/composition-templated-yaml.yaml" >}} {{< /tab >}} {{< tab "Python" >}} Create this composition to use Python to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-python -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - observed_xr = req.observed.composite.resource - - rsp.desired.resources["deployment"].resource.update({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "replicas": 2, - "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, - "template": { - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "containers": [{ - "name": "app", - "image": observed_xr["spec"]["image"], - "ports": [{"containerPort": 80}] - }], - }, - }, - }, - }) - - observed_deployment = req.observed.resources["deployment"].resource - if "status" in observed_deployment: - if "availableReplicas" in observed_deployment["status"]: - rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] - if "conditions" in observed_deployment["status"]: - for condition in observed_deployment["status"]["conditions"]: - if condition["type"] == "Available" and condition["status"] == "True": - rsp.desired.resources["deployment"].ready = True - - rsp.desired.resources["service"].resource.update({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], - }, - }) - - observed_service = req.observed.resources["service"].resource - if "spec" in observed_service and "clusterIP" in observed_service["spec"]: - rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] - rsp.desired.resources["service"].ready = True -``` +{{< manifest path="get-started/composition/composition-python.yaml" >}} {{}} You can write your own function in Python. @@ -600,138 +280,17 @@ Read the [guide to writing a composition function in Python]({{}} Create this composition to use KCL to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-kcl -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - observed_xr = option("params").oxr - - _desired_deployment = { - apiVersion = "apps/v1" - kind = "Deployment" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "deployment" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - replicas = 2 - selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} - template = { - metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - spec.containers = [{ - name = "app" - image = observed_xr.spec.image - ports = [{containerPort = 80}] - }] - } - } - } - - observed_deployment = option("params").ocds["deployment"]?.Resource - if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_service = { - apiVersion = "v1" - kind = "Service" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "service" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - selector = {"example.crossplane.io/app" = observed_xr.metadata.name} - ports = [{protocol = "TCP", port = 8080, targetPort = 80}] - } - } - - observed_service = option("params").ocds["service"]?.Resource - if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_xr = { - **option("params").dxr - - status.address = observed_service?.spec?.clusterIP or "" - status.replicas = observed_deployment?.status?.availableReplicas or 0 - } - - items = [_desired_deployment, _desired_service, _desired_xr] -``` +{{< manifest path="get-started/composition/composition-kcl.yaml" >}} {{< /tab >}} {{< tab "Pythonic" >}} Create this composition to use Pythonic to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-pythonic -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - labels = {'example.crossplane.io/app': self.metadata.name} - - d = self.resources.deployment('apps/v1', 'Deployment') - d.metadata.labels = labels - d.spec.replicas = 2 - d.spec.selector.matchLabels = labels - d.spec.template.metadata.labels = labels - d.spec.template.spec.containers[0].name = 'app' - d.spec.template.spec.containers[0].image = self.spec.image - d.spec.template.spec.containers[0].ports[0].containerPort = 80 - - s = self.resources.service('v1', 'Service') - s.metadata.labels = labels - s.spec.selector = labels - s.spec.ports[0].protocol = 'TCP' - s.spec.ports[0].port = 8080 - s.spec.ports[0].targetPort = 80 - - self.status.replicas = d.status.availableReplicas - self.status.address = s.observed.spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-pythonic.yaml" >}} {{< /tab >}} {{}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - {{}} A composition can include multiple functions. @@ -752,26 +311,12 @@ Crossplane now understands `App` custom resources. Create an `App`: -```yaml -apiVersion: example.crossplane.io/v1 -kind: App -metadata: - namespace: default - name: my-app -spec: - image: nginx -``` - -Save the `App` as `app.yaml` and apply it: - -```shell -kubectl apply -f app.yaml -``` +{{< manifest path="get-started/composition/app.yaml" >}} Check that the `App` is ready: ```shell {copy-lines="1"} -kubectl get -f app.yaml +kubectl get -f {{< manifest-url path="get-started/composition/app.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 56s ``` @@ -796,14 +341,19 @@ service/my-app-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` {{}} -Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates -the `Deployment`'s image to match. +Edit the `App`'s image: + +```shell +kubectl edit -f {{< manifest-url path="get-started/composition/app.yaml" >}} +``` + +Crossplane updates the `Deployment`'s image to match. {{}} Delete the `App`. ```shell {copy-lines="1"} -kubectl delete -f app.yaml +kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} ``` When you delete the `App`, Crossplane deletes the `Deployment` and `Service`. diff --git a/content/v2.0/get-started/get-started-with-managed-resources.md b/content/v2.0/get-started/get-started-with-managed-resources.md index fde7941ee..c378cc538 100644 --- a/content/v2.0/get-started/get-started-with-managed-resources.md +++ b/content/v2.0/get-started/get-started-with-managed-resources.md @@ -56,20 +56,7 @@ The AWS S3 provider installs support for all the AWS S3 managed resources. Create this provider to install the AWS S3 provider: -```yaml {label="provider",copy-lines="all"} -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: crossplane-contrib-provider-aws-s3 -spec: - package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: - -```shell {label="kube-apply-provider",copy-lines="all"} -kubectl apply -f provider.yaml -``` +{{< manifest path="get-started/managed-resources/provider.yaml" label="provider" >}} Check that Crossplane installed the provider: @@ -146,25 +133,7 @@ All providers need a configuration to tell them where to load credentials. Create this cluster-wide provider configuration: -```yaml {label="providerconfig",copy-lines="all"} -apiVersion: aws.m.upbound.io/v1beta1 -kind: ClusterProviderConfig -metadata: - name: default -spec: - credentials: - source: Secret - secretRef: - namespace: crossplane-system - name: aws-secret - key: creds -``` - -Save the provider configuration as `providerconfig.yaml` and apply it: - -```shell {label="kube-apply-providerconfig",copy-lines="all"} -kubectl apply -f providerconfig.yaml -``` +{{< manifest path="get-started/managed-resources/providerconfig.yaml" label="providerconfig" >}} This tells the provider to load credentials from [the secret](#save-the-providers-credentials). @@ -185,22 +154,7 @@ AWS S3 bucket names must be globally unique. This example uses `generateName` to generate a random name. Any unique name is acceptable. {{}} -```yaml {label="bucket"} -apiVersion: s3.aws.m.upbound.io/v1beta1 -kind: Bucket -metadata: - namespace: default - generateName: crossplane-bucket- -spec: - forProvider: - region: us-east-2 -``` - -Save the bucket to `bucket.yaml` and apply it: - -```shell {label="kube-create-bucket",copy-lines="all"} -kubectl create -f bucket.yaml -``` +{{< manifest path="get-started/managed-resources/bucket.yaml" label="bucket" command="kubectl create -f" >}} Check that Crossplane created the bucket: diff --git a/content/v2.0/get-started/get-started-with-operations.md b/content/v2.0/get-started/get-started-with-operations.md index 86a679c51..6abeb4ccc 100644 --- a/content/v2.0/get-started/get-started-with-operations.md +++ b/content/v2.0/get-started/get-started-with-operations.md @@ -105,55 +105,14 @@ Follow these steps to create your first `Operation`: Create an `Ingress` that references a real hostname but doesn't route actual traffic: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-app - namespace: default -spec: - rules: - - host: google.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nonexistent-service - port: - number: 80 -``` - -Save as `ingress.yaml` and apply it: - -```shell -kubectl apply -f ingress.yaml -``` +{{< manifest path="get-started/operations/ingress.yaml" >}} ### Grant Ingress permissions `Operations` need permission to access and change `Ingresses`. Create a `ClusterRole` that grants Crossplane access to `Ingresses`: -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: operations-ingress-access - labels: - rbac.crossplane.io/aggregate-to-crossplane: "true" -rules: -- apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch", "patch", "update"] -``` - -Save as `ingress-rbac.yaml` and apply it: - -```shell -kubectl apply -f ingress-rbac.yaml -``` +{{< manifest path="get-started/operations/ingress-rbac.yaml" >}} ### Install the function @@ -162,25 +121,12 @@ function, which supports both composition and operations. Create this function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `function.yaml` and apply it: - -```shell -kubectl apply -f function.yaml -``` +{{< manifest path="get-started/operations/function.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f function.yaml +kubectl get -f {{< manifest-url path="get-started/operations/function.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -189,97 +135,14 @@ crossplane-contrib-function-python True True xpkg.crossplane.io/cr Create this `Operation` that monitors the `Ingress` certificate: -```yaml -apiVersion: ops.crossplane.io/v1alpha1 -kind: Operation -metadata: - name: ingress-cert-monitor -spec: - mode: Pipeline - pipeline: - - step: check-ingress-certificate - functionRef: - name: crossplane-contrib-function-python - requirements: - requiredResources: - - requirementName: ingress - apiVersion: networking.k8s.io/v1 - kind: Ingress - name: example-app - namespace: default - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - import ssl - import socket - from datetime import datetime - - from crossplane.function import request, response - - def operate(req, rsp): - # Get the Ingress resource - ingress = request.get_required_resource(req, "ingress") - if not ingress: - response.set_output(rsp, {"error": "No ingress resource found"}) - return - - # Extract hostname from Ingress rules - hostname = ingress["spec"]["rules"][0]["host"] - port = 443 - - # Get SSL certificate info - context = ssl.create_default_context() - with socket.create_connection((hostname, port)) as sock: - with context.wrap_socket(sock, server_hostname=hostname) as ssock: - cert = ssock.getpeercert() - - # Parse expiration date - expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') - days_until_expiry = (expiry_date - datetime.now()).days - - # Add warning if certificate expires soon - if days_until_expiry < 30: - response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") - - # Annotate the Ingress with certificate expiry info - rsp.desired.resources["ingress"].resource.update({ - "apiVersion": "networking.k8s.io/v1", - "kind": "Ingress", - "metadata": { - "name": ingress["metadata"]["name"], - "namespace": ingress["metadata"]["namespace"], - "annotations": { - "cert-monitor.crossplane.io/expires": cert['notAfter'], - "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), - "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" - } - } - }) - - # Return results in operation output for monitoring - response.set_output(rsp, { - "ingressName": ingress["metadata"]["name"], - "hostname": hostname, - "certificateExpires": cert['notAfter'], - "daysUntilExpiry": days_until_expiry, - "status": "warning" if days_until_expiry < 30 else "ok" - }) -``` - - -Save the operation as `operation.yaml` and apply it: - -```shell -kubectl apply -f operation.yaml -``` +{{< manifest path="get-started/operations/operation.yaml" >}} ### Check the operation Check that the `Operation` runs successfully: ```shell {copy-lines="1"} -kubectl get -f operation.yaml +kubectl get -f {{< manifest-url path="get-started/operations/operation.yaml" >}} NAME SYNCED SUCCEEDED AGE ingress-cert-monitor True True 15s ``` @@ -346,10 +209,10 @@ information that other tools can use for monitoring and alerting. Delete the resources you created: ```shell -kubectl delete -f operation.yaml -kubectl delete -f ingress.yaml -kubectl delete -f ingress-rbac.yaml -kubectl delete -f function.yaml +kubectl delete -f {{< manifest-url path="get-started/operations/operation.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress-rbac.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/function.yaml" >}} ``` ## Next steps diff --git a/content/v2.0/guides/change-logs.md b/content/v2.0/guides/change-logs.md index c19bcd722..eb17ab63b 100644 --- a/content/v2.0/guides/change-logs.md +++ b/content/v2.0/guides/change-logs.md @@ -80,38 +80,7 @@ steps: container{{}} and the {{}}sidecar container{{}}. -```yaml {label="drc",copy-lines="all"} -cat <}} ### Install the provider @@ -119,20 +88,7 @@ Install the {{}}provider{{}} and instruct it to use the {{}}DeploymentRuntimeConfig{{}} that was just created. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Configure permissions @@ -146,42 +102,7 @@ production environment. See more examples for configuring `provider-kubernetes` [examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/namespaced/provider). {{}} -```yaml {label="rbac",copy-lines="all"} -cat <}} ### Create a resource @@ -189,24 +110,7 @@ After installing and configuring the provider with change logs enabled, create a resource that generates change log entries that reflect the actions the control plane takes. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Examine the change logs diff --git a/content/v2.0/guides/connection-details-composition.md b/content/v2.0/guides/connection-details-composition.md index d4eaca673..7a7827d70 100644 --- a/content/v2.0/guides/connection-details-composition.md +++ b/content/v2.0/guides/connection-details-composition.md @@ -113,34 +113,7 @@ A CompositeResourceDefinition (XRD) defines composite resources. For this example, create an XRD for the `UserAccessKey` composite resource: -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: useraccesskeys.example.org -spec: - group: example.org - names: - kind: UserAccessKey - plural: useraccesskeys - scope: Namespaced - versions: - - name: v1alpha1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - writeConnectionSecretToRef: - type: object - properties: - name: - type: string -``` +{{< manifest path="guides/connection-details-composition/xrd.yaml" >}} {{}} This XRD schema defines a `.spec.writeConnectionSecretToRef.name` field that @@ -153,12 +126,6 @@ too. {{}} -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` - The Kubernetes API is now serving requests for the `UserAccessKey` composite resource. @@ -175,25 +142,12 @@ from the tabs below. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 8s ``` @@ -205,25 +159,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 15s ``` @@ -233,25 +174,12 @@ function-go-templating True True xpkg.crossplane.io/crossplane-con Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -261,25 +189,12 @@ function-python True True xpkg.crossplane.io/cr Create this composition function to install [KCL](https://kcl-lang.io) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 6s ``` @@ -289,25 +204,12 @@ function-kcl True True xpkg.crossplane.io/cross Create this composition function to install [Pythonic](https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -318,20 +220,7 @@ function-pythonic True True xpkg.crossplane.io/crossplane-contrib/fun This guide also uses `function-auto-ready`. This function automatically marks composed resources as ready when they're healthy: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-auto-ready -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 -``` - -Save this as `fn-auto-ready.yaml` and apply it: - -```shell -kubectl apply -f fn-auto-ready.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-auto-ready.yaml" >}} ### Configure the composition @@ -364,91 +253,7 @@ exposes their credentials as the composite resource's connection details `Secret {{< tab "YAML" >}} -```yaml {label="comp-pt"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-patch-and-transform -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: patch-and-transform - functionRef: - name: function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - writeConnectionSecretToRef: - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.writeConnectionSecretToRef.name - toFieldPath: name - resources: - - name: user - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - spec: - forProvider: {} - - name: accesskey-0 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-0 - connectionDetails: - - name: user-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-0" - - name: accesskey-1 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-1 - connectionDetails: - - name: user-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-1" - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-patch-and-transform.yaml" label="comp-pt" >}} @@ -482,78 +287,7 @@ spec: {{< tab "Templated YAML" >}} -```yaml {label="comp-gotmpl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-go-templating -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-templates - functionRef: - name: function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - metadata: - annotations: - {{ setResourceNameAnnotation "user" }} - spec: - forProvider: {} - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-0" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-1" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 - --- - apiVersion: v1 - kind: Secret - metadata: - name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} - annotations: - {{ setResourceNameAnnotation "connection-secret" }} - {{ if eq $.observed.resources nil }} - data: {} - {{ else }} - data: - user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} - user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} - password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} - password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} - {{ end }} - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-go-templating.yaml" label="comp-gotmpl" >}} @@ -584,109 +318,7 @@ spec: {{< tab "Python" >}} -```yaml {label="comp-python"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-python -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-python - functionRef: - name: function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - # Get observed composite resource - oxr = req.observed.composite.resource - oxr_name = oxr["metadata"]["name"] - - # IAM User - rsp.desired.resources["user"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "User", - "spec": { - "forProvider": {} - } - }) - - # Access Key 0 - rsp.desired.resources["accesskey-0"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-0" - } - } - }) - - # Access Key 1 - rsp.desired.resources["accesskey-1"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-1" - } - } - }) - - # Secret representing the composite resource's connection details - secret_resource = { - "apiVersion": "v1", - "kind": "Secret", - "metadata": {} - } - - # If a secret name was provided then use it - secret_name = "" - if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: - secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] - - secret_resource["metadata"]["name"] = secret_name - - # Only add data if we have connection details to populate - data = {} - if "accesskey-0" in req.observed.resources: - accesskey0_conn = req.observed.resources["accesskey-0"].connection_details - if "username" in accesskey0_conn: - data["user-0"] = accesskey0_conn["username"].decode("utf-8") - if "password" in accesskey0_conn: - data["password-0"] = accesskey0_conn["password"].decode("utf-8") - - if "accesskey-1" in req.observed.resources: - accesskey1_conn = req.observed.resources["accesskey-1"].connection_details - if "username" in accesskey1_conn: - data["user-1"] = accesskey1_conn["username"].decode("utf-8") - if "password" in accesskey1_conn: - data["password-1"] = accesskey1_conn["password"].decode("utf-8") - - if data: - secret_resource["stringData"] = data - - rsp.desired.resources["connection-secret"].resource.update(secret_resource) - - step: ready - functionRef: - name: function-auto-ready - -``` +{{< manifest path="guides/connection-details-composition/composition-python.yaml" label="comp-python" >}} @@ -717,77 +349,7 @@ spec: {{< tab "KCL" >}} -```yaml {label="comp-kcl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-kcl -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-kcl - functionRef: - name: function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - oxr = option("params").oxr - ocds = option("params").ocds - - user = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "User" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "user" - } - spec.forProvider = {} - } - - accesskey0 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-0" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" - } - - accesskey1 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-1" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" - } - - secret = { - apiVersion = "v1" - kind = "Secret" - metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "connection-secret" - } - data = { - "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" - "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" - "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" - "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" - } if ocds else {} - } - - items = [user, accesskey0, accesskey1, secret] - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-kcl.yaml" label="comp-kcl" >}} **How this Composition exposes connection details:** @@ -816,38 +378,7 @@ spec: {{< tab "Pythonic" >}} -```yaml {label="comp-pythonic"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-pythonic -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-pythonic - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - self.connectionSecret = self.spec.writeConnectionSecretToRef - - user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') - user.spec.forProvider = {} - - for ix in range(2): - key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') - key.spec.forProvider.user = user.status.atProvider.id - key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" - self.connection[f"user-{ix}"] = key.connection.username - self.connection[f"password-{ix}"] = key.connection.password -``` +{{< manifest path="guides/connection-details-composition/composition-pythonic.yaml" label="comp-pythonic" >}} @@ -877,12 +408,6 @@ spec: {{< /tabs >}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - ## Use the composite resource The Composition now specifies how to compose connection details for the @@ -890,27 +415,12 @@ The Composition now specifies how to compose connection details for the Create a `UserAccessKey` to see it in action: -```yaml -apiVersion: example.org/v1alpha1 -kind: UserAccessKey -metadata: - namespace: default - name: my-keys -spec: - writeConnectionSecretToRef: - name: my-keys-connection-details -``` - -Save the composite resource as `my-keys.yaml` and apply it: - -```shell -kubectl apply -f my-keys.yaml -``` +{{< manifest path="guides/connection-details-composition/my-keys.yaml" >}} Check that the composite resource is ready: ```shell {copy-lines="1"} -kubectl get -f my-keys.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-keys True True useraccesskeys-go-templating 45s ``` @@ -1128,7 +638,7 @@ namespace to the XR's namespace if left empty. Delete the composite resource to clean up: ```shell -kubectl delete -f my-keys.yaml +kubectl delete -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} ``` When you delete the composite resource, Crossplane deletes: diff --git a/content/v2.0/guides/disabling-unused-managed-resources.md b/content/v2.0/guides/disabling-unused-managed-resources.md index 3beb04a28..220c41544 100644 --- a/content/v2.0/guides/disabling-unused-managed-resources.md +++ b/content/v2.0/guides/disabling-unused-managed-resources.md @@ -86,20 +86,9 @@ kubectl delete managedresourceactivationpolicy default Install your provider as normal. Crossplane automatically converts the provider's CRDs to ManagedResourceDefinitions: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: provider-aws-ec2 -spec: - package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: +{{< manifest path="guides/disabling-unused-managed-resources/provider.yaml" >}} ```shell -kubectl apply -f provider.yaml - # Wait for provider to be ready kubectl wait --for=condition=Healthy provider/provider-aws-ec2 --timeout=5m ``` @@ -133,23 +122,7 @@ kubectl get crds | grep ec2.aws.m.crossplane.io Create a ManagedResourceActivationPolicy to selectively activate only the resources you need: -```yaml -apiVersion: apiextensions.crossplane.io/v1alpha1 -kind: ManagedResourceActivationPolicy -metadata: - name: my-app-resources -spec: - activate: - - instances.ec2.aws.m.crossplane.io # EC2 instances for compute - - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking - - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation -``` - -Save this as `activation-policy.yaml` and apply it: - -```shell -kubectl apply -f activation-policy.yaml -``` +{{< manifest path="guides/disabling-unused-managed-resources/activation-policy.yaml" >}} ## Step 5: Verify selective activation diff --git a/content/v2.0/manifests/get-started/composition/app.yaml b/content/v2.0/manifests/get-started/composition/app.yaml new file mode 100644 index 000000000..a24b2ecd9 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/app.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: App +metadata: + namespace: default + name: my-app +spec: + image: nginx diff --git a/content/v2.0/manifests/get-started/composition/composition-kcl.yaml b/content/v2.0/manifests/get-started/composition/composition-kcl.yaml new file mode 100644 index 000000000..f75c71504 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/composition-kcl.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-kcl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + observed_xr = option("params").oxr + + _desired_deployment = { + apiVersion = "apps/v1" + kind = "Deployment" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "deployment" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + replicas = 2 + selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} + template = { + metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + spec.containers = [{ + name = "app" + image = observed_xr.spec.image + ports = [{containerPort = 80}] + }] + } + } + } + + observed_deployment = option("params").ocds["deployment"]?.Resource + if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): + _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_service = { + apiVersion = "v1" + kind = "Service" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "service" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + selector = {"example.crossplane.io/app" = observed_xr.metadata.name} + ports = [{protocol = "TCP", port = 8080, targetPort = 80}] + } + } + + observed_service = option("params").ocds["service"]?.Resource + if observed_service?.spec?.clusterIP: + _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_xr = { + **option("params").dxr + + status.address = observed_service?.spec?.clusterIP or "" + status.replicas = observed_deployment?.status?.availableReplicas or 0 + } + + items = [_desired_deployment, _desired_service, _desired_xr] diff --git a/content/v2.0/manifests/get-started/composition/composition-python.yaml b/content/v2.0/manifests/get-started/composition/composition-python.yaml new file mode 100644 index 000000000..85a404f2d --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/composition-python.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-python +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + observed_xr = req.observed.composite.resource + + rsp.desired.resources["deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "replicas": 2, + "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, + "template": { + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "containers": [{ + "name": "app", + "image": observed_xr["spec"]["image"], + "ports": [{"containerPort": 80}] + }], + }, + }, + }, + }) + + observed_deployment = req.observed.resources["deployment"].resource + if "status" in observed_deployment: + if "availableReplicas" in observed_deployment["status"]: + rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] + if "conditions" in observed_deployment["status"]: + for condition in observed_deployment["status"]["conditions"]: + if condition["type"] == "Available" and condition["status"] == "True": + rsp.desired.resources["deployment"].ready = True + + rsp.desired.resources["service"].resource.update({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], + }, + }) + + observed_service = req.observed.resources["service"].resource + if "spec" in observed_service and "clusterIP" in observed_service["spec"]: + rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] + rsp.desired.resources["service"].ready = True diff --git a/content/v2.0/manifests/get-started/composition/composition-pythonic.yaml b/content/v2.0/manifests/get-started/composition/composition-pythonic.yaml new file mode 100644 index 000000000..9853a103e --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/composition-pythonic.yaml @@ -0,0 +1,39 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-pythonic +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/app': self.metadata.name} + + d = self.resources.deployment('apps/v1', 'Deployment') + d.metadata.labels = labels + d.spec.replicas = 2 + d.spec.selector.matchLabels = labels + d.spec.template.metadata.labels = labels + d.spec.template.spec.containers[0].name = 'app' + d.spec.template.spec.containers[0].image = self.spec.image + d.spec.template.spec.containers[0].ports[0].containerPort = 80 + + s = self.resources.service('v1', 'Service') + s.metadata.labels = labels + s.spec.selector = labels + s.spec.ports[0].protocol = 'TCP' + s.spec.ports[0].port = 8080 + s.spec.ports[0].targetPort = 80 + + self.status.replicas = d.status.availableReplicas + self.status.address = s.observed.spec.clusterIP diff --git a/content/v2.0/manifests/get-started/composition/composition-templated-yaml.yaml b/content/v2.0/manifests/get-started/composition/composition-templated-yaml.yaml new file mode 100644 index 000000000..4c1a30cd2 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/composition-templated-yaml.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-templated-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: deployment + {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + template: + metadata: + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + containers: + - name: app + image: {{ .observed.composite.resource.spec.image }} + ports: + - containerPort: 80 + --- + apiVersion: v1 + kind: Service + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: service + {{ if (get (getComposedResource . "service").spec "clusterIP") }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + selector: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + --- + apiVersion: example.crossplane.io/v1 + kind: App + status: + replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} + address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} diff --git a/content/v2.0/manifests/get-started/composition/composition-yaml.yaml b/content/v2.0/manifests/get-started/composition/composition-yaml.yaml new file mode 100644 index 000000000..9907d5a34 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/composition-yaml.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 2 + template: + spec: + containers: + - name: app + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: spec.image + toFieldPath: spec.template.spec.containers[0].image + - type: ToCompositeFieldPath + fromFieldPath: status.availableReplicas + toFieldPath: status.replicas + readinessChecks: + - type: MatchCondition + matchCondition: + type: Available + status: "True" + - name: service + base: + apiVersion: v1 + kind: Service + spec: + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector[example.crossplane.io/app] + - type: ToCompositeFieldPath + fromFieldPath: spec.clusterIP + toFieldPath: status.address + readinessChecks: + - type: NonEmpty + fieldPath: spec.clusterIP diff --git a/content/v2.0/manifests/get-started/composition/fn-go-templating.yaml b/content/v2.0/manifests/get-started/composition/fn-go-templating.yaml new file mode 100644 index 000000000..d11ea8e70 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 diff --git a/content/v2.0/manifests/get-started/composition/fn-kcl.yaml b/content/v2.0/manifests/get-started/composition/fn-kcl.yaml new file mode 100644 index 000000000..befb6f66d --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 diff --git a/content/v2.0/manifests/get-started/composition/fn-patch-and-transform.yaml b/content/v2.0/manifests/get-started/composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..aecb6cd0f --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v2.0/manifests/get-started/composition/fn-python.yaml b/content/v2.0/manifests/get-started/composition/fn-python.yaml new file mode 100644 index 000000000..b76bb3ee2 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 diff --git a/content/v2.0/manifests/get-started/composition/fn-pythonic.yaml b/content/v2.0/manifests/get-started/composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.0/manifests/get-started/composition/xrd.yaml b/content/v2.0/manifests/get-started/composition/xrd.yaml new file mode 100644 index 000000000..e2d3856de --- /dev/null +++ b/content/v2.0/manifests/get-started/composition/xrd.yaml @@ -0,0 +1,35 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: apps.example.crossplane.io +spec: + scope: Namespaced + group: example.crossplane.io + names: + kind: App + plural: apps + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + image: + description: The app's OCI container image. + type: string + required: + - image + status: + type: object + properties: + replicas: + description: The number of available app replicas. + type: integer + address: + description: The app's IP address. + type: string diff --git a/content/v2.0/manifests/get-started/managed-resources/bucket.yaml b/content/v2.0/manifests/get-started/managed-resources/bucket.yaml new file mode 100644 index 000000000..8c0ef2ecd --- /dev/null +++ b/content/v2.0/manifests/get-started/managed-resources/bucket.yaml @@ -0,0 +1,8 @@ +apiVersion: s3.aws.m.upbound.io/v1beta1 +kind: Bucket +metadata: + namespace: default + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 diff --git a/content/v2.0/manifests/get-started/managed-resources/provider.yaml b/content/v2.0/manifests/get-started/managed-resources/provider.yaml new file mode 100644 index 000000000..3bc07d336 --- /dev/null +++ b/content/v2.0/manifests/get-started/managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: crossplane-contrib-provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 diff --git a/content/v2.0/manifests/get-started/managed-resources/providerconfig.yaml b/content/v2.0/manifests/get-started/managed-resources/providerconfig.yaml new file mode 100644 index 000000000..2ad6259d4 --- /dev/null +++ b/content/v2.0/manifests/get-started/managed-resources/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v2.0/manifests/get-started/operations/function.yaml b/content/v2.0/manifests/get-started/operations/function.yaml new file mode 100644 index 000000000..a3ba5fbbc --- /dev/null +++ b/content/v2.0/manifests/get-started/operations/function.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.0/manifests/get-started/operations/ingress-rbac.yaml b/content/v2.0/manifests/get-started/operations/ingress-rbac.yaml new file mode 100644 index 000000000..63f54a463 --- /dev/null +++ b/content/v2.0/manifests/get-started/operations/ingress-rbac.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operations-ingress-access + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "patch", "update"] diff --git a/content/v2.0/manifests/get-started/operations/ingress.yaml b/content/v2.0/manifests/get-started/operations/ingress.yaml new file mode 100644 index 000000000..ba30f83b0 --- /dev/null +++ b/content/v2.0/manifests/get-started/operations/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example-app + namespace: default +spec: + rules: + - host: google.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nonexistent-service + port: + number: 80 diff --git a/content/v2.0/manifests/get-started/operations/operation.yaml b/content/v2.0/manifests/get-started/operations/operation.yaml new file mode 100644 index 000000000..aeb669f77 --- /dev/null +++ b/content/v2.0/manifests/get-started/operations/operation.yaml @@ -0,0 +1,75 @@ +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +spec: + mode: Pipeline + pipeline: + - step: check-ingress-certificate + functionRef: + name: crossplane-contrib-function-python + requirements: + requiredResources: + - requirementName: ingress + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: example-app + namespace: default + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + # Get the Ingress resource + ingress = request.get_required_resource(req, "ingress") + if not ingress: + response.set_output(rsp, {"error": "No ingress resource found"}) + return + + # Extract hostname from Ingress rules + hostname = ingress["spec"]["rules"][0]["host"] + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Add warning if certificate expires soon + if days_until_expiry < 30: + response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") + + # Annotate the Ingress with certificate expiry info + rsp.desired.resources["ingress"].resource.update({ + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", + "metadata": { + "name": ingress["metadata"]["name"], + "namespace": ingress["metadata"]["namespace"], + "annotations": { + "cert-monitor.crossplane.io/expires": cert['notAfter'], + "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), + "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" + } + } + }) + + # Return results in operation output for monitoring + response.set_output(rsp, { + "ingressName": ingress["metadata"]["name"], + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) diff --git a/content/v2.0/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml b/content/v2.0/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml new file mode 100644 index 000000000..9076ea60a --- /dev/null +++ b/content/v2.0/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml @@ -0,0 +1,28 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: enable-changelogs +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --enable-changelogs + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + - name: changelogs-sidecar + image: xpkg.crossplane.io/crossplane/changelogs-sidecar:v0.0.1 + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + volumes: + - name: changelogs-vol + emptyDir: {} + serviceAccountTemplate: + metadata: + name: provider-kubernetes diff --git a/content/v2.0/manifests/guides/change-logs/object-configmap-for-changelogs.yaml b/content/v2.0/manifests/guides/change-logs/object-configmap-for-changelogs.yaml new file mode 100644 index 000000000..a55e5b39e --- /dev/null +++ b/content/v2.0/manifests/guides/change-logs/object-configmap-for-changelogs.yaml @@ -0,0 +1,14 @@ +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: configmap-for-changelogs +spec: + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + name: configmap-for-changelogs + data: + key-1: cool-value-1 diff --git a/content/v2.0/manifests/guides/change-logs/provider-kubernetes.yaml b/content/v2.0/manifests/guides/change-logs/provider-kubernetes.yaml new file mode 100644 index 000000000..75b22830a --- /dev/null +++ b/content/v2.0/manifests/guides/change-logs/provider-kubernetes.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-kubernetes +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v0.18.0 + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: enable-changelogs diff --git a/content/v2.0/manifests/guides/change-logs/rbac.yaml b/content/v2.0/manifests/guides/change-logs/rbac.yaml new file mode 100644 index 000000000..2de5d1f8c --- /dev/null +++ b/content/v2.0/manifests/guides/change-logs/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: configmap-edit +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: provider-kubernetes-configmap-edit +subjects: + - kind: ServiceAccount + name: provider-kubernetes + namespace: crossplane-system +roleRef: + kind: ClusterRole + name: configmap-edit + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: kubernetes.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: InjectedIdentity diff --git a/content/v2.0/manifests/guides/connection-details-composition/composition-go-templating.yaml b/content/v2.0/manifests/guides/connection-details-composition/composition-go-templating.yaml new file mode 100644 index 000000000..7570332de --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/composition-go-templating.yaml @@ -0,0 +1,70 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-go-templating +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-templates + functionRef: + name: function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + metadata: + annotations: + {{ setResourceNameAnnotation "user" }} + spec: + forProvider: {} + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-0" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-1" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 + --- + apiVersion: v1 + kind: Secret + metadata: + name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} + annotations: + {{ setResourceNameAnnotation "connection-secret" }} + {{ if eq $.observed.resources nil }} + data: {} + {{ else }} + data: + user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} + user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} + password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} + password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} + {{ end }} + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.0/manifests/guides/connection-details-composition/composition-kcl.yaml b/content/v2.0/manifests/guides/connection-details-composition/composition-kcl.yaml new file mode 100644 index 000000000..fe80e07f5 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/composition-kcl.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-kcl +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-kcl + functionRef: + name: function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + oxr = option("params").oxr + ocds = option("params").ocds + + user = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "User" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "user" + } + spec.forProvider = {} + } + + accesskey0 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-0" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" + } + + accesskey1 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-1" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" + } + + secret = { + apiVersion = "v1" + kind = "Secret" + metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "connection-secret" + } + data = { + "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" + "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" + "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" + "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" + } if ocds else {} + } + + items = [user, accesskey0, accesskey1, secret] + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.0/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml b/content/v2.0/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml new file mode 100644 index 000000000..1d6c7e397 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml @@ -0,0 +1,83 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-patch-and-transform +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + writeConnectionSecretToRef: + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.writeConnectionSecretToRef.name + toFieldPath: name + resources: + - name: user + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + spec: + forProvider: {} + - name: accesskey-0 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-0 + connectionDetails: + - name: user-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-0" + - name: accesskey-1 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-1 + connectionDetails: + - name: user-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-1" + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.0/manifests/guides/connection-details-composition/composition-python.yaml b/content/v2.0/manifests/guides/connection-details-composition/composition-python.yaml new file mode 100644 index 000000000..a1f5c0011 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/composition-python.yaml @@ -0,0 +1,100 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-python +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-python + functionRef: + name: function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + # Get observed composite resource + oxr = req.observed.composite.resource + oxr_name = oxr["metadata"]["name"] + + # IAM User + rsp.desired.resources["user"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "User", + "spec": { + "forProvider": {} + } + }) + + # Access Key 0 + rsp.desired.resources["accesskey-0"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-0" + } + } + }) + + # Access Key 1 + rsp.desired.resources["accesskey-1"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-1" + } + } + }) + + # Secret representing the composite resource's connection details + secret_resource = { + "apiVersion": "v1", + "kind": "Secret", + "metadata": {} + } + + # If a secret name was provided then use it + secret_name = "" + if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: + secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] + + secret_resource["metadata"]["name"] = secret_name + + # Only add data if we have connection details to populate + data = {} + if "accesskey-0" in req.observed.resources: + accesskey0_conn = req.observed.resources["accesskey-0"].connection_details + if "username" in accesskey0_conn: + data["user-0"] = accesskey0_conn["username"].decode("utf-8") + if "password" in accesskey0_conn: + data["password-0"] = accesskey0_conn["password"].decode("utf-8") + + if "accesskey-1" in req.observed.resources: + accesskey1_conn = req.observed.resources["accesskey-1"].connection_details + if "username" in accesskey1_conn: + data["user-1"] = accesskey1_conn["username"].decode("utf-8") + if "password" in accesskey1_conn: + data["password-1"] = accesskey1_conn["password"].decode("utf-8") + + if data: + secret_resource["stringData"] = data + + rsp.desired.resources["connection-secret"].resource.update(secret_resource) + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.0/manifests/guides/connection-details-composition/composition-pythonic.yaml b/content/v2.0/manifests/guides/connection-details-composition/composition-pythonic.yaml new file mode 100644 index 000000000..15036a2e0 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/composition-pythonic.yaml @@ -0,0 +1,33 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-pythonic +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-pythonic + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + self.connectionSecret = self.spec.writeConnectionSecretToRef + + user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') + user.spec.forProvider = {} + + for ix in range(2): + key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') + key.spec.forProvider.user = user.status.atProvider.id + key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" + self.connection[f"user-{ix}"] = key.connection.username + self.connection[f"password-{ix}"] = key.connection.password + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-auto-ready.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-auto-ready.yaml new file mode 100644 index 000000000..9f15ef44c --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-auto-ready.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-auto-ready +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-go-templating.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-go-templating.yaml new file mode 100644 index 000000000..e6073c0e1 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-kcl.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-kcl.yaml new file mode 100644 index 000000000..3e81ce32b --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..c03e0f832 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-python.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-python.yaml new file mode 100644 index 000000000..f532a7b9a --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.0/manifests/guides/connection-details-composition/fn-pythonic.yaml b/content/v2.0/manifests/guides/connection-details-composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.0/manifests/guides/connection-details-composition/my-keys.yaml b/content/v2.0/manifests/guides/connection-details-composition/my-keys.yaml new file mode 100644 index 000000000..13191fc14 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/my-keys.yaml @@ -0,0 +1,8 @@ +apiVersion: example.org/v1alpha1 +kind: UserAccessKey +metadata: + namespace: default + name: my-keys +spec: + writeConnectionSecretToRef: + name: my-keys-connection-details diff --git a/content/v2.0/manifests/guides/connection-details-composition/xrd.yaml b/content/v2.0/manifests/guides/connection-details-composition/xrd.yaml new file mode 100644 index 000000000..a9ae5e2d6 --- /dev/null +++ b/content/v2.0/manifests/guides/connection-details-composition/xrd.yaml @@ -0,0 +1,26 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: useraccesskeys.example.org +spec: + group: example.org + names: + kind: UserAccessKey + plural: useraccesskeys + scope: Namespaced + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + writeConnectionSecretToRef: + type: object + properties: + name: + type: string diff --git a/content/v2.0/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml b/content/v2.0/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml new file mode 100644 index 000000000..62aa3ae51 --- /dev/null +++ b/content/v2.0/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml @@ -0,0 +1,9 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: ManagedResourceActivationPolicy +metadata: + name: my-app-resources +spec: + activate: + - instances.ec2.aws.m.crossplane.io # EC2 instances for compute + - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking + - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation diff --git a/content/v2.0/manifests/guides/disabling-unused-managed-resources/provider.yaml b/content/v2.0/manifests/guides/disabling-unused-managed-resources/provider.yaml new file mode 100644 index 000000000..a8c909540 --- /dev/null +++ b/content/v2.0/manifests/guides/disabling-unused-managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-ec2 +spec: + package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 diff --git a/content/v2.1/get-started/get-started-with-composition.md b/content/v2.1/get-started/get-started-with-composition.md index ad93e45f8..8a7c0e54f 100644 --- a/content/v2.1/get-started/get-started-with-composition.md +++ b/content/v2.1/get-started/get-started-with-composition.md @@ -121,54 +121,12 @@ A composite resource is a kind of custom resource. Create this _composite resource definition_ (XRD) to define the schema of the new `App` composite resource (XR). -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: apps.example.crossplane.io -spec: - scope: Namespaced - group: example.crossplane.io - names: - kind: App - plural: apps - versions: - - name: v1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - image: - description: The app's OCI container image. - type: string - required: - - image - status: - type: object - properties: - replicas: - description: The number of available app replicas. - type: integer - address: - description: The app's IP address. - type: string -``` - -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` +{{< manifest path="get-started/composition/xrd.yaml" >}} Check that Crossplane has established the XRD: ``` shell {copy-lines="1"} -kubectl get -f xrd.yaml +kubectl get -f {{< manifest-url path="get-started/composition/xrd.yaml" >}} NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 21s ``` @@ -198,25 +156,12 @@ or conditionals. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s ``` @@ -228,25 +173,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s ``` @@ -258,25 +190,12 @@ full [Python standard library](https://docs.python.org/3/library/index.html). Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s ``` @@ -288,25 +207,12 @@ It's fast and sandboxed. Create this composition function to install KCL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s ``` @@ -320,25 +226,12 @@ Crossplane function APIs. Create this composition function to install Pythonic support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -359,232 +252,19 @@ Create a composition to tell Crossplane what to do when you create or update an {{< tab "YAML" >}} Create this composition to use YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 2 - template: - spec: - containers: - - name: app - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: spec.image - toFieldPath: spec.template.spec.containers[0].image - - type: ToCompositeFieldPath - fromFieldPath: status.availableReplicas - toFieldPath: status.replicas - readinessChecks: - - type: MatchCondition - matchCondition: - type: Available - status: "True" - - name: service - base: - apiVersion: v1 - kind: Service - spec: - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector[example.crossplane.io/app] - - type: ToCompositeFieldPath - fromFieldPath: spec.clusterIP - toFieldPath: status.address - readinessChecks: - - type: NonEmpty - fieldPath: spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-yaml.yaml" >}} {{< /tab >}} {{< tab "Templated YAML" >}} Create this composition to use templated YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-templated-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - template: - metadata: - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - containers: - - name: app - image: {{ .observed.composite.resource.spec.image }} - ports: - - containerPort: 80 - --- - apiVersion: v1 - kind: Service - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - selector: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - --- - apiVersion: example.crossplane.io/v1 - kind: App - status: - replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} - address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} -``` +{{< manifest path="get-started/composition/composition-templated-yaml.yaml" >}} {{< /tab >}} {{< tab "Python" >}} Create this composition to use Python to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-python -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - observed_xr = req.observed.composite.resource - - rsp.desired.resources["deployment"].resource.update({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "replicas": 2, - "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, - "template": { - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "containers": [{ - "name": "app", - "image": observed_xr["spec"]["image"], - "ports": [{"containerPort": 80}] - }], - }, - }, - }, - }) - - observed_deployment = req.observed.resources["deployment"].resource - if "status" in observed_deployment: - if "availableReplicas" in observed_deployment["status"]: - rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] - if "conditions" in observed_deployment["status"]: - for condition in observed_deployment["status"]["conditions"]: - if condition["type"] == "Available" and condition["status"] == "True": - rsp.desired.resources["deployment"].ready = True - - rsp.desired.resources["service"].resource.update({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], - }, - }) - - observed_service = req.observed.resources["service"].resource - if "spec" in observed_service and "clusterIP" in observed_service["spec"]: - rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] - rsp.desired.resources["service"].ready = True -``` +{{< manifest path="get-started/composition/composition-python.yaml" >}} {{}} You can write your own function in Python. @@ -600,138 +280,17 @@ Read the [guide to writing a composition function in Python]({{}} Create this composition to use KCL to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-kcl -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - observed_xr = option("params").oxr - - _desired_deployment = { - apiVersion = "apps/v1" - kind = "Deployment" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "deployment" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - replicas = 2 - selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} - template = { - metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - spec.containers = [{ - name = "app" - image = observed_xr.spec.image - ports = [{containerPort = 80}] - }] - } - } - } - - observed_deployment = option("params").ocds["deployment"]?.Resource - if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_service = { - apiVersion = "v1" - kind = "Service" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "service" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - selector = {"example.crossplane.io/app" = observed_xr.metadata.name} - ports = [{protocol = "TCP", port = 8080, targetPort = 80}] - } - } - - observed_service = option("params").ocds["service"]?.Resource - if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_xr = { - **option("params").dxr - - status.address = observed_service?.spec?.clusterIP or "" - status.replicas = observed_deployment?.status?.availableReplicas or 0 - } - - items = [_desired_deployment, _desired_service, _desired_xr] -``` +{{< manifest path="get-started/composition/composition-kcl.yaml" >}} {{< /tab >}} {{< tab "Pythonic" >}} Create this composition to use Pythonic to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-pythonic -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - labels = {'example.crossplane.io/app': self.metadata.name} - - d = self.resources.deployment('apps/v1', 'Deployment') - d.metadata.labels = labels - d.spec.replicas = 2 - d.spec.selector.matchLabels = labels - d.spec.template.metadata.labels = labels - d.spec.template.spec.containers[0].name = 'app' - d.spec.template.spec.containers[0].image = self.spec.image - d.spec.template.spec.containers[0].ports[0].containerPort = 80 - - s = self.resources.service('v1', 'Service') - s.metadata.labels = labels - s.spec.selector = labels - s.spec.ports[0].protocol = 'TCP' - s.spec.ports[0].port = 8080 - s.spec.ports[0].targetPort = 80 - - self.status.replicas = d.status.availableReplicas - self.status.address = s.observed.spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-pythonic.yaml" >}} {{< /tab >}} {{}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - {{}} A composition can include multiple functions. @@ -752,26 +311,12 @@ Crossplane now understands `App` custom resources. Create an `App`: -```yaml -apiVersion: example.crossplane.io/v1 -kind: App -metadata: - namespace: default - name: my-app -spec: - image: nginx -``` - -Save the `App` as `app.yaml` and apply it: - -```shell -kubectl apply -f app.yaml -``` +{{< manifest path="get-started/composition/app.yaml" >}} Check that the `App` is ready: ```shell {copy-lines="1"} -kubectl get -f app.yaml +kubectl get -f {{< manifest-url path="get-started/composition/app.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 56s ``` @@ -796,14 +341,19 @@ service/my-app-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` {{}} -Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates -the `Deployment`'s image to match. +Edit the `App`'s image: + +```shell +kubectl edit -f {{< manifest-url path="get-started/composition/app.yaml" >}} +``` + +Crossplane updates the `Deployment`'s image to match. {{}} Delete the `App`. ```shell {copy-lines="1"} -kubectl delete -f app.yaml +kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} ``` When you delete the `App`, Crossplane deletes the `Deployment` and `Service`. diff --git a/content/v2.1/get-started/get-started-with-managed-resources.md b/content/v2.1/get-started/get-started-with-managed-resources.md index fde7941ee..c378cc538 100644 --- a/content/v2.1/get-started/get-started-with-managed-resources.md +++ b/content/v2.1/get-started/get-started-with-managed-resources.md @@ -56,20 +56,7 @@ The AWS S3 provider installs support for all the AWS S3 managed resources. Create this provider to install the AWS S3 provider: -```yaml {label="provider",copy-lines="all"} -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: crossplane-contrib-provider-aws-s3 -spec: - package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: - -```shell {label="kube-apply-provider",copy-lines="all"} -kubectl apply -f provider.yaml -``` +{{< manifest path="get-started/managed-resources/provider.yaml" label="provider" >}} Check that Crossplane installed the provider: @@ -146,25 +133,7 @@ All providers need a configuration to tell them where to load credentials. Create this cluster-wide provider configuration: -```yaml {label="providerconfig",copy-lines="all"} -apiVersion: aws.m.upbound.io/v1beta1 -kind: ClusterProviderConfig -metadata: - name: default -spec: - credentials: - source: Secret - secretRef: - namespace: crossplane-system - name: aws-secret - key: creds -``` - -Save the provider configuration as `providerconfig.yaml` and apply it: - -```shell {label="kube-apply-providerconfig",copy-lines="all"} -kubectl apply -f providerconfig.yaml -``` +{{< manifest path="get-started/managed-resources/providerconfig.yaml" label="providerconfig" >}} This tells the provider to load credentials from [the secret](#save-the-providers-credentials). @@ -185,22 +154,7 @@ AWS S3 bucket names must be globally unique. This example uses `generateName` to generate a random name. Any unique name is acceptable. {{}} -```yaml {label="bucket"} -apiVersion: s3.aws.m.upbound.io/v1beta1 -kind: Bucket -metadata: - namespace: default - generateName: crossplane-bucket- -spec: - forProvider: - region: us-east-2 -``` - -Save the bucket to `bucket.yaml` and apply it: - -```shell {label="kube-create-bucket",copy-lines="all"} -kubectl create -f bucket.yaml -``` +{{< manifest path="get-started/managed-resources/bucket.yaml" label="bucket" command="kubectl create -f" >}} Check that Crossplane created the bucket: diff --git a/content/v2.1/get-started/get-started-with-operations.md b/content/v2.1/get-started/get-started-with-operations.md index 86a679c51..6abeb4ccc 100644 --- a/content/v2.1/get-started/get-started-with-operations.md +++ b/content/v2.1/get-started/get-started-with-operations.md @@ -105,55 +105,14 @@ Follow these steps to create your first `Operation`: Create an `Ingress` that references a real hostname but doesn't route actual traffic: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-app - namespace: default -spec: - rules: - - host: google.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nonexistent-service - port: - number: 80 -``` - -Save as `ingress.yaml` and apply it: - -```shell -kubectl apply -f ingress.yaml -``` +{{< manifest path="get-started/operations/ingress.yaml" >}} ### Grant Ingress permissions `Operations` need permission to access and change `Ingresses`. Create a `ClusterRole` that grants Crossplane access to `Ingresses`: -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: operations-ingress-access - labels: - rbac.crossplane.io/aggregate-to-crossplane: "true" -rules: -- apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch", "patch", "update"] -``` - -Save as `ingress-rbac.yaml` and apply it: - -```shell -kubectl apply -f ingress-rbac.yaml -``` +{{< manifest path="get-started/operations/ingress-rbac.yaml" >}} ### Install the function @@ -162,25 +121,12 @@ function, which supports both composition and operations. Create this function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `function.yaml` and apply it: - -```shell -kubectl apply -f function.yaml -``` +{{< manifest path="get-started/operations/function.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f function.yaml +kubectl get -f {{< manifest-url path="get-started/operations/function.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -189,97 +135,14 @@ crossplane-contrib-function-python True True xpkg.crossplane.io/cr Create this `Operation` that monitors the `Ingress` certificate: -```yaml -apiVersion: ops.crossplane.io/v1alpha1 -kind: Operation -metadata: - name: ingress-cert-monitor -spec: - mode: Pipeline - pipeline: - - step: check-ingress-certificate - functionRef: - name: crossplane-contrib-function-python - requirements: - requiredResources: - - requirementName: ingress - apiVersion: networking.k8s.io/v1 - kind: Ingress - name: example-app - namespace: default - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - import ssl - import socket - from datetime import datetime - - from crossplane.function import request, response - - def operate(req, rsp): - # Get the Ingress resource - ingress = request.get_required_resource(req, "ingress") - if not ingress: - response.set_output(rsp, {"error": "No ingress resource found"}) - return - - # Extract hostname from Ingress rules - hostname = ingress["spec"]["rules"][0]["host"] - port = 443 - - # Get SSL certificate info - context = ssl.create_default_context() - with socket.create_connection((hostname, port)) as sock: - with context.wrap_socket(sock, server_hostname=hostname) as ssock: - cert = ssock.getpeercert() - - # Parse expiration date - expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') - days_until_expiry = (expiry_date - datetime.now()).days - - # Add warning if certificate expires soon - if days_until_expiry < 30: - response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") - - # Annotate the Ingress with certificate expiry info - rsp.desired.resources["ingress"].resource.update({ - "apiVersion": "networking.k8s.io/v1", - "kind": "Ingress", - "metadata": { - "name": ingress["metadata"]["name"], - "namespace": ingress["metadata"]["namespace"], - "annotations": { - "cert-monitor.crossplane.io/expires": cert['notAfter'], - "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), - "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" - } - } - }) - - # Return results in operation output for monitoring - response.set_output(rsp, { - "ingressName": ingress["metadata"]["name"], - "hostname": hostname, - "certificateExpires": cert['notAfter'], - "daysUntilExpiry": days_until_expiry, - "status": "warning" if days_until_expiry < 30 else "ok" - }) -``` - - -Save the operation as `operation.yaml` and apply it: - -```shell -kubectl apply -f operation.yaml -``` +{{< manifest path="get-started/operations/operation.yaml" >}} ### Check the operation Check that the `Operation` runs successfully: ```shell {copy-lines="1"} -kubectl get -f operation.yaml +kubectl get -f {{< manifest-url path="get-started/operations/operation.yaml" >}} NAME SYNCED SUCCEEDED AGE ingress-cert-monitor True True 15s ``` @@ -346,10 +209,10 @@ information that other tools can use for monitoring and alerting. Delete the resources you created: ```shell -kubectl delete -f operation.yaml -kubectl delete -f ingress.yaml -kubectl delete -f ingress-rbac.yaml -kubectl delete -f function.yaml +kubectl delete -f {{< manifest-url path="get-started/operations/operation.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress-rbac.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/function.yaml" >}} ``` ## Next steps diff --git a/content/v2.1/guides/change-logs.md b/content/v2.1/guides/change-logs.md index c19bcd722..bbc07c0b3 100644 --- a/content/v2.1/guides/change-logs.md +++ b/content/v2.1/guides/change-logs.md @@ -80,38 +80,7 @@ steps: container{{}} and the {{}}sidecar container{{}}. -```yaml {label="drc",copy-lines="all"} -cat <}} ### Install the provider @@ -119,20 +88,7 @@ Install the {{}}provider{{}} and instruct it to use the {{}}DeploymentRuntimeConfig{{}} that was just created. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Configure permissions @@ -146,42 +102,7 @@ production environment. See more examples for configuring `provider-kubernetes` [examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/namespaced/provider). {{}} -```yaml {label="rbac",copy-lines="all"} -cat <}} ### Create a resource @@ -189,24 +110,7 @@ After installing and configuring the provider with change logs enabled, create a resource that generates change log entries that reflect the actions the control plane takes. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Examine the change logs diff --git a/content/v2.1/guides/connection-details-composition.md b/content/v2.1/guides/connection-details-composition.md index d4eaca673..7a7827d70 100644 --- a/content/v2.1/guides/connection-details-composition.md +++ b/content/v2.1/guides/connection-details-composition.md @@ -113,34 +113,7 @@ A CompositeResourceDefinition (XRD) defines composite resources. For this example, create an XRD for the `UserAccessKey` composite resource: -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: useraccesskeys.example.org -spec: - group: example.org - names: - kind: UserAccessKey - plural: useraccesskeys - scope: Namespaced - versions: - - name: v1alpha1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - writeConnectionSecretToRef: - type: object - properties: - name: - type: string -``` +{{< manifest path="guides/connection-details-composition/xrd.yaml" >}} {{}} This XRD schema defines a `.spec.writeConnectionSecretToRef.name` field that @@ -153,12 +126,6 @@ too. {{}} -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` - The Kubernetes API is now serving requests for the `UserAccessKey` composite resource. @@ -175,25 +142,12 @@ from the tabs below. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 8s ``` @@ -205,25 +159,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 15s ``` @@ -233,25 +174,12 @@ function-go-templating True True xpkg.crossplane.io/crossplane-con Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -261,25 +189,12 @@ function-python True True xpkg.crossplane.io/cr Create this composition function to install [KCL](https://kcl-lang.io) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 6s ``` @@ -289,25 +204,12 @@ function-kcl True True xpkg.crossplane.io/cross Create this composition function to install [Pythonic](https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -318,20 +220,7 @@ function-pythonic True True xpkg.crossplane.io/crossplane-contrib/fun This guide also uses `function-auto-ready`. This function automatically marks composed resources as ready when they're healthy: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-auto-ready -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 -``` - -Save this as `fn-auto-ready.yaml` and apply it: - -```shell -kubectl apply -f fn-auto-ready.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-auto-ready.yaml" >}} ### Configure the composition @@ -364,91 +253,7 @@ exposes their credentials as the composite resource's connection details `Secret {{< tab "YAML" >}} -```yaml {label="comp-pt"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-patch-and-transform -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: patch-and-transform - functionRef: - name: function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - writeConnectionSecretToRef: - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.writeConnectionSecretToRef.name - toFieldPath: name - resources: - - name: user - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - spec: - forProvider: {} - - name: accesskey-0 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-0 - connectionDetails: - - name: user-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-0" - - name: accesskey-1 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-1 - connectionDetails: - - name: user-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-1" - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-patch-and-transform.yaml" label="comp-pt" >}} @@ -482,78 +287,7 @@ spec: {{< tab "Templated YAML" >}} -```yaml {label="comp-gotmpl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-go-templating -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-templates - functionRef: - name: function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - metadata: - annotations: - {{ setResourceNameAnnotation "user" }} - spec: - forProvider: {} - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-0" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-1" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 - --- - apiVersion: v1 - kind: Secret - metadata: - name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} - annotations: - {{ setResourceNameAnnotation "connection-secret" }} - {{ if eq $.observed.resources nil }} - data: {} - {{ else }} - data: - user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} - user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} - password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} - password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} - {{ end }} - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-go-templating.yaml" label="comp-gotmpl" >}} @@ -584,109 +318,7 @@ spec: {{< tab "Python" >}} -```yaml {label="comp-python"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-python -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-python - functionRef: - name: function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - # Get observed composite resource - oxr = req.observed.composite.resource - oxr_name = oxr["metadata"]["name"] - - # IAM User - rsp.desired.resources["user"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "User", - "spec": { - "forProvider": {} - } - }) - - # Access Key 0 - rsp.desired.resources["accesskey-0"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-0" - } - } - }) - - # Access Key 1 - rsp.desired.resources["accesskey-1"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-1" - } - } - }) - - # Secret representing the composite resource's connection details - secret_resource = { - "apiVersion": "v1", - "kind": "Secret", - "metadata": {} - } - - # If a secret name was provided then use it - secret_name = "" - if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: - secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] - - secret_resource["metadata"]["name"] = secret_name - - # Only add data if we have connection details to populate - data = {} - if "accesskey-0" in req.observed.resources: - accesskey0_conn = req.observed.resources["accesskey-0"].connection_details - if "username" in accesskey0_conn: - data["user-0"] = accesskey0_conn["username"].decode("utf-8") - if "password" in accesskey0_conn: - data["password-0"] = accesskey0_conn["password"].decode("utf-8") - - if "accesskey-1" in req.observed.resources: - accesskey1_conn = req.observed.resources["accesskey-1"].connection_details - if "username" in accesskey1_conn: - data["user-1"] = accesskey1_conn["username"].decode("utf-8") - if "password" in accesskey1_conn: - data["password-1"] = accesskey1_conn["password"].decode("utf-8") - - if data: - secret_resource["stringData"] = data - - rsp.desired.resources["connection-secret"].resource.update(secret_resource) - - step: ready - functionRef: - name: function-auto-ready - -``` +{{< manifest path="guides/connection-details-composition/composition-python.yaml" label="comp-python" >}} @@ -717,77 +349,7 @@ spec: {{< tab "KCL" >}} -```yaml {label="comp-kcl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-kcl -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-kcl - functionRef: - name: function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - oxr = option("params").oxr - ocds = option("params").ocds - - user = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "User" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "user" - } - spec.forProvider = {} - } - - accesskey0 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-0" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" - } - - accesskey1 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-1" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" - } - - secret = { - apiVersion = "v1" - kind = "Secret" - metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "connection-secret" - } - data = { - "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" - "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" - "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" - "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" - } if ocds else {} - } - - items = [user, accesskey0, accesskey1, secret] - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-kcl.yaml" label="comp-kcl" >}} **How this Composition exposes connection details:** @@ -816,38 +378,7 @@ spec: {{< tab "Pythonic" >}} -```yaml {label="comp-pythonic"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-pythonic -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-pythonic - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - self.connectionSecret = self.spec.writeConnectionSecretToRef - - user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') - user.spec.forProvider = {} - - for ix in range(2): - key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') - key.spec.forProvider.user = user.status.atProvider.id - key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" - self.connection[f"user-{ix}"] = key.connection.username - self.connection[f"password-{ix}"] = key.connection.password -``` +{{< manifest path="guides/connection-details-composition/composition-pythonic.yaml" label="comp-pythonic" >}} @@ -877,12 +408,6 @@ spec: {{< /tabs >}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - ## Use the composite resource The Composition now specifies how to compose connection details for the @@ -890,27 +415,12 @@ The Composition now specifies how to compose connection details for the Create a `UserAccessKey` to see it in action: -```yaml -apiVersion: example.org/v1alpha1 -kind: UserAccessKey -metadata: - namespace: default - name: my-keys -spec: - writeConnectionSecretToRef: - name: my-keys-connection-details -``` - -Save the composite resource as `my-keys.yaml` and apply it: - -```shell -kubectl apply -f my-keys.yaml -``` +{{< manifest path="guides/connection-details-composition/my-keys.yaml" >}} Check that the composite resource is ready: ```shell {copy-lines="1"} -kubectl get -f my-keys.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-keys True True useraccesskeys-go-templating 45s ``` @@ -1128,7 +638,7 @@ namespace to the XR's namespace if left empty. Delete the composite resource to clean up: ```shell -kubectl delete -f my-keys.yaml +kubectl delete -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} ``` When you delete the composite resource, Crossplane deletes: diff --git a/content/v2.1/guides/disabling-unused-managed-resources.md b/content/v2.1/guides/disabling-unused-managed-resources.md index 3beb04a28..220c41544 100644 --- a/content/v2.1/guides/disabling-unused-managed-resources.md +++ b/content/v2.1/guides/disabling-unused-managed-resources.md @@ -86,20 +86,9 @@ kubectl delete managedresourceactivationpolicy default Install your provider as normal. Crossplane automatically converts the provider's CRDs to ManagedResourceDefinitions: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: provider-aws-ec2 -spec: - package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: +{{< manifest path="guides/disabling-unused-managed-resources/provider.yaml" >}} ```shell -kubectl apply -f provider.yaml - # Wait for provider to be ready kubectl wait --for=condition=Healthy provider/provider-aws-ec2 --timeout=5m ``` @@ -133,23 +122,7 @@ kubectl get crds | grep ec2.aws.m.crossplane.io Create a ManagedResourceActivationPolicy to selectively activate only the resources you need: -```yaml -apiVersion: apiextensions.crossplane.io/v1alpha1 -kind: ManagedResourceActivationPolicy -metadata: - name: my-app-resources -spec: - activate: - - instances.ec2.aws.m.crossplane.io # EC2 instances for compute - - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking - - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation -``` - -Save this as `activation-policy.yaml` and apply it: - -```shell -kubectl apply -f activation-policy.yaml -``` +{{< manifest path="guides/disabling-unused-managed-resources/activation-policy.yaml" >}} ## Step 5: Verify selective activation diff --git a/content/v2.1/manifests/get-started/composition/app.yaml b/content/v2.1/manifests/get-started/composition/app.yaml new file mode 100644 index 000000000..a24b2ecd9 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/app.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: App +metadata: + namespace: default + name: my-app +spec: + image: nginx diff --git a/content/v2.1/manifests/get-started/composition/composition-kcl.yaml b/content/v2.1/manifests/get-started/composition/composition-kcl.yaml new file mode 100644 index 000000000..f75c71504 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/composition-kcl.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-kcl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + observed_xr = option("params").oxr + + _desired_deployment = { + apiVersion = "apps/v1" + kind = "Deployment" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "deployment" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + replicas = 2 + selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} + template = { + metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + spec.containers = [{ + name = "app" + image = observed_xr.spec.image + ports = [{containerPort = 80}] + }] + } + } + } + + observed_deployment = option("params").ocds["deployment"]?.Resource + if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): + _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_service = { + apiVersion = "v1" + kind = "Service" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "service" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + selector = {"example.crossplane.io/app" = observed_xr.metadata.name} + ports = [{protocol = "TCP", port = 8080, targetPort = 80}] + } + } + + observed_service = option("params").ocds["service"]?.Resource + if observed_service?.spec?.clusterIP: + _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_xr = { + **option("params").dxr + + status.address = observed_service?.spec?.clusterIP or "" + status.replicas = observed_deployment?.status?.availableReplicas or 0 + } + + items = [_desired_deployment, _desired_service, _desired_xr] diff --git a/content/v2.1/manifests/get-started/composition/composition-python.yaml b/content/v2.1/manifests/get-started/composition/composition-python.yaml new file mode 100644 index 000000000..85a404f2d --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/composition-python.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-python +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + observed_xr = req.observed.composite.resource + + rsp.desired.resources["deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "replicas": 2, + "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, + "template": { + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "containers": [{ + "name": "app", + "image": observed_xr["spec"]["image"], + "ports": [{"containerPort": 80}] + }], + }, + }, + }, + }) + + observed_deployment = req.observed.resources["deployment"].resource + if "status" in observed_deployment: + if "availableReplicas" in observed_deployment["status"]: + rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] + if "conditions" in observed_deployment["status"]: + for condition in observed_deployment["status"]["conditions"]: + if condition["type"] == "Available" and condition["status"] == "True": + rsp.desired.resources["deployment"].ready = True + + rsp.desired.resources["service"].resource.update({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], + }, + }) + + observed_service = req.observed.resources["service"].resource + if "spec" in observed_service and "clusterIP" in observed_service["spec"]: + rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] + rsp.desired.resources["service"].ready = True diff --git a/content/v2.1/manifests/get-started/composition/composition-pythonic.yaml b/content/v2.1/manifests/get-started/composition/composition-pythonic.yaml new file mode 100644 index 000000000..9853a103e --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/composition-pythonic.yaml @@ -0,0 +1,39 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-pythonic +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/app': self.metadata.name} + + d = self.resources.deployment('apps/v1', 'Deployment') + d.metadata.labels = labels + d.spec.replicas = 2 + d.spec.selector.matchLabels = labels + d.spec.template.metadata.labels = labels + d.spec.template.spec.containers[0].name = 'app' + d.spec.template.spec.containers[0].image = self.spec.image + d.spec.template.spec.containers[0].ports[0].containerPort = 80 + + s = self.resources.service('v1', 'Service') + s.metadata.labels = labels + s.spec.selector = labels + s.spec.ports[0].protocol = 'TCP' + s.spec.ports[0].port = 8080 + s.spec.ports[0].targetPort = 80 + + self.status.replicas = d.status.availableReplicas + self.status.address = s.observed.spec.clusterIP diff --git a/content/v2.1/manifests/get-started/composition/composition-templated-yaml.yaml b/content/v2.1/manifests/get-started/composition/composition-templated-yaml.yaml new file mode 100644 index 000000000..4c1a30cd2 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/composition-templated-yaml.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-templated-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: deployment + {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + template: + metadata: + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + containers: + - name: app + image: {{ .observed.composite.resource.spec.image }} + ports: + - containerPort: 80 + --- + apiVersion: v1 + kind: Service + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: service + {{ if (get (getComposedResource . "service").spec "clusterIP") }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + selector: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + --- + apiVersion: example.crossplane.io/v1 + kind: App + status: + replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} + address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} diff --git a/content/v2.1/manifests/get-started/composition/composition-yaml.yaml b/content/v2.1/manifests/get-started/composition/composition-yaml.yaml new file mode 100644 index 000000000..9907d5a34 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/composition-yaml.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 2 + template: + spec: + containers: + - name: app + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: spec.image + toFieldPath: spec.template.spec.containers[0].image + - type: ToCompositeFieldPath + fromFieldPath: status.availableReplicas + toFieldPath: status.replicas + readinessChecks: + - type: MatchCondition + matchCondition: + type: Available + status: "True" + - name: service + base: + apiVersion: v1 + kind: Service + spec: + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector[example.crossplane.io/app] + - type: ToCompositeFieldPath + fromFieldPath: spec.clusterIP + toFieldPath: status.address + readinessChecks: + - type: NonEmpty + fieldPath: spec.clusterIP diff --git a/content/v2.1/manifests/get-started/composition/fn-go-templating.yaml b/content/v2.1/manifests/get-started/composition/fn-go-templating.yaml new file mode 100644 index 000000000..d11ea8e70 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 diff --git a/content/v2.1/manifests/get-started/composition/fn-kcl.yaml b/content/v2.1/manifests/get-started/composition/fn-kcl.yaml new file mode 100644 index 000000000..befb6f66d --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 diff --git a/content/v2.1/manifests/get-started/composition/fn-patch-and-transform.yaml b/content/v2.1/manifests/get-started/composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..aecb6cd0f --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v2.1/manifests/get-started/composition/fn-python.yaml b/content/v2.1/manifests/get-started/composition/fn-python.yaml new file mode 100644 index 000000000..b76bb3ee2 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 diff --git a/content/v2.1/manifests/get-started/composition/fn-pythonic.yaml b/content/v2.1/manifests/get-started/composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.1/manifests/get-started/composition/xrd.yaml b/content/v2.1/manifests/get-started/composition/xrd.yaml new file mode 100644 index 000000000..e2d3856de --- /dev/null +++ b/content/v2.1/manifests/get-started/composition/xrd.yaml @@ -0,0 +1,35 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: apps.example.crossplane.io +spec: + scope: Namespaced + group: example.crossplane.io + names: + kind: App + plural: apps + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + image: + description: The app's OCI container image. + type: string + required: + - image + status: + type: object + properties: + replicas: + description: The number of available app replicas. + type: integer + address: + description: The app's IP address. + type: string diff --git a/content/v2.1/manifests/get-started/managed-resources/bucket.yaml b/content/v2.1/manifests/get-started/managed-resources/bucket.yaml new file mode 100644 index 000000000..8c0ef2ecd --- /dev/null +++ b/content/v2.1/manifests/get-started/managed-resources/bucket.yaml @@ -0,0 +1,8 @@ +apiVersion: s3.aws.m.upbound.io/v1beta1 +kind: Bucket +metadata: + namespace: default + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 diff --git a/content/v2.1/manifests/get-started/managed-resources/provider.yaml b/content/v2.1/manifests/get-started/managed-resources/provider.yaml new file mode 100644 index 000000000..3bc07d336 --- /dev/null +++ b/content/v2.1/manifests/get-started/managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: crossplane-contrib-provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 diff --git a/content/v2.1/manifests/get-started/managed-resources/providerconfig.yaml b/content/v2.1/manifests/get-started/managed-resources/providerconfig.yaml new file mode 100644 index 000000000..2ad6259d4 --- /dev/null +++ b/content/v2.1/manifests/get-started/managed-resources/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v2.1/manifests/get-started/operations/function.yaml b/content/v2.1/manifests/get-started/operations/function.yaml new file mode 100644 index 000000000..a3ba5fbbc --- /dev/null +++ b/content/v2.1/manifests/get-started/operations/function.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.1/manifests/get-started/operations/ingress-rbac.yaml b/content/v2.1/manifests/get-started/operations/ingress-rbac.yaml new file mode 100644 index 000000000..63f54a463 --- /dev/null +++ b/content/v2.1/manifests/get-started/operations/ingress-rbac.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operations-ingress-access + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "patch", "update"] diff --git a/content/v2.1/manifests/get-started/operations/ingress.yaml b/content/v2.1/manifests/get-started/operations/ingress.yaml new file mode 100644 index 000000000..ba30f83b0 --- /dev/null +++ b/content/v2.1/manifests/get-started/operations/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example-app + namespace: default +spec: + rules: + - host: google.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nonexistent-service + port: + number: 80 diff --git a/content/v2.1/manifests/get-started/operations/operation.yaml b/content/v2.1/manifests/get-started/operations/operation.yaml new file mode 100644 index 000000000..aeb669f77 --- /dev/null +++ b/content/v2.1/manifests/get-started/operations/operation.yaml @@ -0,0 +1,75 @@ +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +spec: + mode: Pipeline + pipeline: + - step: check-ingress-certificate + functionRef: + name: crossplane-contrib-function-python + requirements: + requiredResources: + - requirementName: ingress + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: example-app + namespace: default + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + # Get the Ingress resource + ingress = request.get_required_resource(req, "ingress") + if not ingress: + response.set_output(rsp, {"error": "No ingress resource found"}) + return + + # Extract hostname from Ingress rules + hostname = ingress["spec"]["rules"][0]["host"] + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Add warning if certificate expires soon + if days_until_expiry < 30: + response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") + + # Annotate the Ingress with certificate expiry info + rsp.desired.resources["ingress"].resource.update({ + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", + "metadata": { + "name": ingress["metadata"]["name"], + "namespace": ingress["metadata"]["namespace"], + "annotations": { + "cert-monitor.crossplane.io/expires": cert['notAfter'], + "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), + "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" + } + } + }) + + # Return results in operation output for monitoring + response.set_output(rsp, { + "ingressName": ingress["metadata"]["name"], + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) diff --git a/content/v2.1/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml b/content/v2.1/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml new file mode 100644 index 000000000..9076ea60a --- /dev/null +++ b/content/v2.1/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml @@ -0,0 +1,28 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: enable-changelogs +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --enable-changelogs + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + - name: changelogs-sidecar + image: xpkg.crossplane.io/crossplane/changelogs-sidecar:v0.0.1 + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + volumes: + - name: changelogs-vol + emptyDir: {} + serviceAccountTemplate: + metadata: + name: provider-kubernetes diff --git a/content/v2.1/manifests/guides/change-logs/object-configmap-for-changelogs.yaml b/content/v2.1/manifests/guides/change-logs/object-configmap-for-changelogs.yaml new file mode 100644 index 000000000..a55e5b39e --- /dev/null +++ b/content/v2.1/manifests/guides/change-logs/object-configmap-for-changelogs.yaml @@ -0,0 +1,14 @@ +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: configmap-for-changelogs +spec: + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + name: configmap-for-changelogs + data: + key-1: cool-value-1 diff --git a/content/v2.1/manifests/guides/change-logs/provider-kubernetes.yaml b/content/v2.1/manifests/guides/change-logs/provider-kubernetes.yaml new file mode 100644 index 000000000..75b22830a --- /dev/null +++ b/content/v2.1/manifests/guides/change-logs/provider-kubernetes.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-kubernetes +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v0.18.0 + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: enable-changelogs diff --git a/content/v2.1/manifests/guides/change-logs/rbac.yaml b/content/v2.1/manifests/guides/change-logs/rbac.yaml new file mode 100644 index 000000000..2de5d1f8c --- /dev/null +++ b/content/v2.1/manifests/guides/change-logs/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: configmap-edit +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: provider-kubernetes-configmap-edit +subjects: + - kind: ServiceAccount + name: provider-kubernetes + namespace: crossplane-system +roleRef: + kind: ClusterRole + name: configmap-edit + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: kubernetes.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: InjectedIdentity diff --git a/content/v2.1/manifests/guides/connection-details-composition/composition-go-templating.yaml b/content/v2.1/manifests/guides/connection-details-composition/composition-go-templating.yaml new file mode 100644 index 000000000..7570332de --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/composition-go-templating.yaml @@ -0,0 +1,70 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-go-templating +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-templates + functionRef: + name: function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + metadata: + annotations: + {{ setResourceNameAnnotation "user" }} + spec: + forProvider: {} + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-0" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-1" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 + --- + apiVersion: v1 + kind: Secret + metadata: + name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} + annotations: + {{ setResourceNameAnnotation "connection-secret" }} + {{ if eq $.observed.resources nil }} + data: {} + {{ else }} + data: + user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} + user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} + password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} + password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} + {{ end }} + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.1/manifests/guides/connection-details-composition/composition-kcl.yaml b/content/v2.1/manifests/guides/connection-details-composition/composition-kcl.yaml new file mode 100644 index 000000000..fe80e07f5 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/composition-kcl.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-kcl +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-kcl + functionRef: + name: function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + oxr = option("params").oxr + ocds = option("params").ocds + + user = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "User" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "user" + } + spec.forProvider = {} + } + + accesskey0 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-0" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" + } + + accesskey1 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-1" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" + } + + secret = { + apiVersion = "v1" + kind = "Secret" + metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "connection-secret" + } + data = { + "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" + "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" + "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" + "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" + } if ocds else {} + } + + items = [user, accesskey0, accesskey1, secret] + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.1/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml b/content/v2.1/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml new file mode 100644 index 000000000..1d6c7e397 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml @@ -0,0 +1,83 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-patch-and-transform +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + writeConnectionSecretToRef: + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.writeConnectionSecretToRef.name + toFieldPath: name + resources: + - name: user + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + spec: + forProvider: {} + - name: accesskey-0 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-0 + connectionDetails: + - name: user-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-0" + - name: accesskey-1 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-1 + connectionDetails: + - name: user-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-1" + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.1/manifests/guides/connection-details-composition/composition-python.yaml b/content/v2.1/manifests/guides/connection-details-composition/composition-python.yaml new file mode 100644 index 000000000..bf49e9853 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/composition-python.yaml @@ -0,0 +1,101 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-python +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-python + functionRef: + name: function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + # Get observed composite resource + oxr = req.observed.composite.resource + oxr_name = oxr["metadata"]["name"] + + # IAM User + rsp.desired.resources["user"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "User", + "spec": { + "forProvider": {} + } + }) + + # Access Key 0 + rsp.desired.resources["accesskey-0"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-0" + } + } + }) + + # Access Key 1 + rsp.desired.resources["accesskey-1"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-1" + } + } + }) + + # Secret representing the composite resource's connection details + secret_resource = { + "apiVersion": "v1", + "kind": "Secret", + "metadata": {} + } + + # If a secret name was provided then use it + secret_name = "" + if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: + secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] + + secret_resource["metadata"]["name"] = secret_name + + # Only add data if we have connection details to populate + data = {} + if "accesskey-0" in req.observed.resources: + accesskey0_conn = req.observed.resources["accesskey-0"].connection_details + if "username" in accesskey0_conn: + data["user-0"] = accesskey0_conn["username"].decode("utf-8") + if "password" in accesskey0_conn: + data["password-0"] = accesskey0_conn["password"].decode("utf-8") + + if "accesskey-1" in req.observed.resources: + accesskey1_conn = req.observed.resources["accesskey-1"].connection_details + if "username" in accesskey1_conn: + data["user-1"] = accesskey1_conn["username"].decode("utf-8") + if "password" in accesskey1_conn: + data["password-1"] = accesskey1_conn["password"].decode("utf-8") + + if data: + secret_resource["stringData"] = data + + rsp.desired.resources["connection-secret"].resource.update(secret_resource) + - step: ready + functionRef: + name: function-auto-ready + diff --git a/content/v2.1/manifests/guides/connection-details-composition/composition-pythonic.yaml b/content/v2.1/manifests/guides/connection-details-composition/composition-pythonic.yaml new file mode 100644 index 000000000..15036a2e0 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/composition-pythonic.yaml @@ -0,0 +1,33 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-pythonic +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-pythonic + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + self.connectionSecret = self.spec.writeConnectionSecretToRef + + user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') + user.spec.forProvider = {} + + for ix in range(2): + key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') + key.spec.forProvider.user = user.status.atProvider.id + key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" + self.connection[f"user-{ix}"] = key.connection.username + self.connection[f"password-{ix}"] = key.connection.password + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-auto-ready.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-auto-ready.yaml new file mode 100644 index 000000000..9f15ef44c --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-auto-ready.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-auto-ready +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-go-templating.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-go-templating.yaml new file mode 100644 index 000000000..e6073c0e1 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-kcl.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-kcl.yaml new file mode 100644 index 000000000..3e81ce32b --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..c03e0f832 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-python.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-python.yaml new file mode 100644 index 000000000..f532a7b9a --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.1/manifests/guides/connection-details-composition/fn-pythonic.yaml b/content/v2.1/manifests/guides/connection-details-composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.1/manifests/guides/connection-details-composition/my-keys.yaml b/content/v2.1/manifests/guides/connection-details-composition/my-keys.yaml new file mode 100644 index 000000000..13191fc14 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/my-keys.yaml @@ -0,0 +1,8 @@ +apiVersion: example.org/v1alpha1 +kind: UserAccessKey +metadata: + namespace: default + name: my-keys +spec: + writeConnectionSecretToRef: + name: my-keys-connection-details diff --git a/content/v2.1/manifests/guides/connection-details-composition/xrd.yaml b/content/v2.1/manifests/guides/connection-details-composition/xrd.yaml new file mode 100644 index 000000000..a9ae5e2d6 --- /dev/null +++ b/content/v2.1/manifests/guides/connection-details-composition/xrd.yaml @@ -0,0 +1,26 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: useraccesskeys.example.org +spec: + group: example.org + names: + kind: UserAccessKey + plural: useraccesskeys + scope: Namespaced + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + writeConnectionSecretToRef: + type: object + properties: + name: + type: string diff --git a/content/v2.1/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml b/content/v2.1/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml new file mode 100644 index 000000000..62aa3ae51 --- /dev/null +++ b/content/v2.1/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml @@ -0,0 +1,9 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: ManagedResourceActivationPolicy +metadata: + name: my-app-resources +spec: + activate: + - instances.ec2.aws.m.crossplane.io # EC2 instances for compute + - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking + - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation diff --git a/content/v2.1/manifests/guides/disabling-unused-managed-resources/provider.yaml b/content/v2.1/manifests/guides/disabling-unused-managed-resources/provider.yaml new file mode 100644 index 000000000..a8c909540 --- /dev/null +++ b/content/v2.1/manifests/guides/disabling-unused-managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-ec2 +spec: + package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 diff --git a/content/v2.2/get-started/get-started-with-composition.md b/content/v2.2/get-started/get-started-with-composition.md index 5f407e341..95e163c26 100644 --- a/content/v2.2/get-started/get-started-with-composition.md +++ b/content/v2.2/get-started/get-started-with-composition.md @@ -121,54 +121,12 @@ A composite resource is a kind of custom resource. Create this _composite resource definition_ (XRD) to define the schema of the new `App` composite resource (XR). -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: apps.example.crossplane.io -spec: - scope: Namespaced - group: example.crossplane.io - names: - kind: App - plural: apps - versions: - - name: v1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - image: - description: The app's OCI container image. - type: string - required: - - image - status: - type: object - properties: - replicas: - description: The number of available app replicas. - type: integer - address: - description: The app's IP address. - type: string -``` - -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` +{{< manifest path="get-started/composition/xrd.yaml" >}} Check that Crossplane has established the XRD: ``` shell {copy-lines="1"} -kubectl get -f xrd.yaml +kubectl get -f {{< manifest-url path="get-started/composition/xrd.yaml" >}} NAME ESTABLISHED OFFERED AGE apps.example.crossplane.io True 21s ``` @@ -199,25 +157,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s ``` @@ -229,25 +174,12 @@ or conditionals. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s ``` @@ -260,25 +192,12 @@ resources automatically. Create this composition function to install YAML+CEL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kro -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kro.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kro.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kro True True xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 6s ``` @@ -290,25 +209,12 @@ full [Python standard library](https://docs.python.org/3/library/index.html). Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s ``` @@ -320,25 +226,12 @@ It's fast and sandboxed. Create this composition function to install KCL support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s ``` @@ -352,25 +245,12 @@ Crossplane function APIs. Create this composition function to install Pythonic support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="get-started/composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="get-started/composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -391,156 +271,13 @@ Create a composition to tell Crossplane what to do when you create or update an {{< tab "Templated YAML" >}} Create this composition to use templated YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-templated-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: deployment - {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - template: - metadata: - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - containers: - - name: app - image: {{ .observed.composite.resource.spec.image }} - ports: - - containerPort: 80 - --- - apiVersion: v1 - kind: Service - metadata: - annotations: - gotemplating.fn.crossplane.io/composition-resource-name: service - {{ if (get (getComposedResource . "service").spec "clusterIP") }} - gotemplating.fn.crossplane.io/ready: "True" - {{ end }} - labels: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - spec: - selector: - example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - --- - apiVersion: example.crossplane.io/v1 - kind: App - status: - replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} - address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} -``` +{{< manifest path="get-started/composition/composition-templated-yaml.yaml" >}} {{< /tab >}} {{< tab "YAML" >}} Create this composition to use YAML to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - resources: - - name: deployment - base: - apiVersion: apps/v1 - kind: Deployment - spec: - replicas: 2 - template: - spec: - containers: - - name: app - ports: - - containerPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: spec.image - toFieldPath: spec.template.spec.containers[0].image - - type: ToCompositeFieldPath - fromFieldPath: status.availableReplicas - toFieldPath: status.replicas - readinessChecks: - - type: MatchCondition - matchCondition: - type: Available - status: "True" - - name: service - base: - apiVersion: v1 - kind: Service - spec: - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: metadata.labels[example.crossplane.io/app] - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.selector[example.crossplane.io/app] - - type: ToCompositeFieldPath - fromFieldPath: spec.clusterIP - toFieldPath: status.address - readinessChecks: - - type: NonEmpty - fieldPath: spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-yaml.yaml" >}} {{< /tab >}} {{< tab "YAML+CEL" >}} @@ -548,68 +285,7 @@ Create this composition to use YAML and CEL to configure Crossplane. Define resources in YAML, wire them with CEL expressions, and let Crossplane handle the rest. -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-yaml-cel -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kro - input: - apiVersion: kro.fn.crossplane.io/v1beta1 - kind: ResourceGraph - status: - replicas: ${deployment.status.?availableReplicas.orValue(0)} - address: ${service.spec.?clusterIP.orValue("")} - resources: - - id: deployment - template: - apiVersion: apps/v1 - kind: Deployment - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - replicas: 2 - selector: - matchLabels: - example.crossplane.io/app: ${schema.metadata.name} - template: - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - containers: - - name: app - image: ${schema.spec.image} - ports: - - containerPort: 80 - readyWhen: - - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")} - - id: service - template: - apiVersion: v1 - kind: Service - metadata: - labels: - example.crossplane.io/app: ${schema.metadata.name} - spec: - selector: - example.crossplane.io/app: ${schema.metadata.name} - ports: - - protocol: TCP - port: 8080 - targetPort: 80 - readyWhen: - - ${service.spec.?clusterIP.hasValue()} -``` +{{< manifest path="get-started/composition/composition-yaml-cel.yaml" >}} {{}} This function uses the same resource graph syntax as @@ -621,77 +297,7 @@ resource definitions work without changes. {{< tab "Python" >}} Create this composition to use Python to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-python -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - observed_xr = req.observed.composite.resource - - rsp.desired.resources["deployment"].resource.update({ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "replicas": 2, - "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, - "template": { - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "containers": [{ - "name": "app", - "image": observed_xr["spec"]["image"], - "ports": [{"containerPort": 80}] - }], - }, - }, - }, - }) - - observed_deployment = req.observed.resources["deployment"].resource - if "status" in observed_deployment: - if "availableReplicas" in observed_deployment["status"]: - rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] - if "conditions" in observed_deployment["status"]: - for condition in observed_deployment["status"]["conditions"]: - if condition["type"] == "Available" and condition["status"] == "True": - rsp.desired.resources["deployment"].ready = True - - rsp.desired.resources["service"].resource.update({ - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - }, - "spec": { - "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, - "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], - }, - }) - - observed_service = req.observed.resources["service"].resource - if "spec" in observed_service and "clusterIP" in observed_service["spec"]: - rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] - rsp.desired.resources["service"].ready = True -``` +{{< manifest path="get-started/composition/composition-python.yaml" >}} {{}} You can write your own function in Python. @@ -707,138 +313,17 @@ Read the [guide to writing a composition function in Python]({{}} Create this composition to use KCL to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-kcl -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: crossplane-contrib-function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - observed_xr = option("params").oxr - - _desired_deployment = { - apiVersion = "apps/v1" - kind = "Deployment" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "deployment" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - replicas = 2 - selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} - template = { - metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - spec.containers = [{ - name = "app" - image = observed_xr.spec.image - ports = [{containerPort = 80}] - }] - } - } - } - - observed_deployment = option("params").ocds["deployment"]?.Resource - if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): - _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_service = { - apiVersion = "v1" - kind = "Service" - metadata = { - annotations = { - "krm.kcl.dev/composition-resource-name" = "service" - } - labels = {"example.crossplane.io/app" = observed_xr.metadata.name} - } - spec = { - selector = {"example.crossplane.io/app" = observed_xr.metadata.name} - ports = [{protocol = "TCP", port = 8080, targetPort = 80}] - } - } - - observed_service = option("params").ocds["service"]?.Resource - if observed_service?.spec?.clusterIP: - _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" - - _desired_xr = { - **option("params").dxr - - status.address = observed_service?.spec?.clusterIP or "" - status.replicas = observed_deployment?.status?.availableReplicas or 0 - } - - items = [_desired_deployment, _desired_service, _desired_xr] -``` +{{< manifest path="get-started/composition/composition-kcl.yaml" >}} {{< /tab >}} {{< tab "Pythonic" >}} Create this composition to use Pythonic to configure Crossplane: -```yaml -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: app-pythonic -spec: - compositeTypeRef: - apiVersion: example.crossplane.io/v1 - kind: App - mode: Pipeline - pipeline: - - step: create-deployment-and-service - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - labels = {'example.crossplane.io/app': self.metadata.name} - - d = self.resources.deployment('apps/v1', 'Deployment') - d.metadata.labels = labels - d.spec.replicas = 2 - d.spec.selector.matchLabels = labels - d.spec.template.metadata.labels = labels - d.spec.template.spec.containers[0].name = 'app' - d.spec.template.spec.containers[0].image = self.spec.image - d.spec.template.spec.containers[0].ports[0].containerPort = 80 - - s = self.resources.service('v1', 'Service') - s.metadata.labels = labels - s.spec.selector = labels - s.spec.ports[0].protocol = 'TCP' - s.spec.ports[0].port = 8080 - s.spec.ports[0].targetPort = 80 - - self.status.replicas = d.status.availableReplicas - self.status.address = s.observed.spec.clusterIP -``` +{{< manifest path="get-started/composition/composition-pythonic.yaml" >}} {{< /tab >}} {{}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - {{}} A composition can include multiple functions. @@ -859,26 +344,12 @@ Crossplane now understands `App` custom resources. Create an `App`: -```yaml -apiVersion: example.crossplane.io/v1 -kind: App -metadata: - namespace: default - name: my-app -spec: - image: nginx -``` - -Save the `App` as `app.yaml` and apply it: - -```shell -kubectl apply -f app.yaml -``` +{{< manifest path="get-started/composition/app.yaml" >}} Check that the `App` is ready: ```shell {copy-lines="1"} -kubectl get -f app.yaml +kubectl get -f {{< manifest-url path="get-started/composition/app.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-app True True app-yaml 56s ``` @@ -903,14 +374,19 @@ service/my-app-xfkzg ClusterIP 10.96.148.56 8080/TCP 11m ``` {{}} -Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates -the `Deployment`'s image to match. +Edit the `App`'s image: + +```shell +kubectl edit -f {{< manifest-url path="get-started/composition/app.yaml" >}} +``` + +Crossplane updates the `Deployment`'s image to match. {{}} Delete the `App`. ```shell {copy-lines="1"} -kubectl delete -f app.yaml +kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} ``` When you delete the `App`, Crossplane deletes the `Deployment` and `Service`. diff --git a/content/v2.2/get-started/get-started-with-managed-resources.md b/content/v2.2/get-started/get-started-with-managed-resources.md index fde7941ee..c378cc538 100644 --- a/content/v2.2/get-started/get-started-with-managed-resources.md +++ b/content/v2.2/get-started/get-started-with-managed-resources.md @@ -56,20 +56,7 @@ The AWS S3 provider installs support for all the AWS S3 managed resources. Create this provider to install the AWS S3 provider: -```yaml {label="provider",copy-lines="all"} -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: crossplane-contrib-provider-aws-s3 -spec: - package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: - -```shell {label="kube-apply-provider",copy-lines="all"} -kubectl apply -f provider.yaml -``` +{{< manifest path="get-started/managed-resources/provider.yaml" label="provider" >}} Check that Crossplane installed the provider: @@ -146,25 +133,7 @@ All providers need a configuration to tell them where to load credentials. Create this cluster-wide provider configuration: -```yaml {label="providerconfig",copy-lines="all"} -apiVersion: aws.m.upbound.io/v1beta1 -kind: ClusterProviderConfig -metadata: - name: default -spec: - credentials: - source: Secret - secretRef: - namespace: crossplane-system - name: aws-secret - key: creds -``` - -Save the provider configuration as `providerconfig.yaml` and apply it: - -```shell {label="kube-apply-providerconfig",copy-lines="all"} -kubectl apply -f providerconfig.yaml -``` +{{< manifest path="get-started/managed-resources/providerconfig.yaml" label="providerconfig" >}} This tells the provider to load credentials from [the secret](#save-the-providers-credentials). @@ -185,22 +154,7 @@ AWS S3 bucket names must be globally unique. This example uses `generateName` to generate a random name. Any unique name is acceptable. {{}} -```yaml {label="bucket"} -apiVersion: s3.aws.m.upbound.io/v1beta1 -kind: Bucket -metadata: - namespace: default - generateName: crossplane-bucket- -spec: - forProvider: - region: us-east-2 -``` - -Save the bucket to `bucket.yaml` and apply it: - -```shell {label="kube-create-bucket",copy-lines="all"} -kubectl create -f bucket.yaml -``` +{{< manifest path="get-started/managed-resources/bucket.yaml" label="bucket" command="kubectl create -f" >}} Check that Crossplane created the bucket: diff --git a/content/v2.2/get-started/get-started-with-operations.md b/content/v2.2/get-started/get-started-with-operations.md index 86a679c51..6abeb4ccc 100644 --- a/content/v2.2/get-started/get-started-with-operations.md +++ b/content/v2.2/get-started/get-started-with-operations.md @@ -105,55 +105,14 @@ Follow these steps to create your first `Operation`: Create an `Ingress` that references a real hostname but doesn't route actual traffic: -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: example-app - namespace: default -spec: - rules: - - host: google.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: nonexistent-service - port: - number: 80 -``` - -Save as `ingress.yaml` and apply it: - -```shell -kubectl apply -f ingress.yaml -``` +{{< manifest path="get-started/operations/ingress.yaml" >}} ### Grant Ingress permissions `Operations` need permission to access and change `Ingresses`. Create a `ClusterRole` that grants Crossplane access to `Ingresses`: -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: operations-ingress-access - labels: - rbac.crossplane.io/aggregate-to-crossplane: "true" -rules: -- apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch", "patch", "update"] -``` - -Save as `ingress-rbac.yaml` and apply it: - -```shell -kubectl apply -f ingress-rbac.yaml -``` +{{< manifest path="get-started/operations/ingress-rbac.yaml" >}} ### Install the function @@ -162,25 +121,12 @@ function, which supports both composition and operations. Create this function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: crossplane-contrib-function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `function.yaml` and apply it: - -```shell -kubectl apply -f function.yaml -``` +{{< manifest path="get-started/operations/function.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f function.yaml +kubectl get -f {{< manifest-url path="get-started/operations/function.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -189,97 +135,14 @@ crossplane-contrib-function-python True True xpkg.crossplane.io/cr Create this `Operation` that monitors the `Ingress` certificate: -```yaml -apiVersion: ops.crossplane.io/v1alpha1 -kind: Operation -metadata: - name: ingress-cert-monitor -spec: - mode: Pipeline - pipeline: - - step: check-ingress-certificate - functionRef: - name: crossplane-contrib-function-python - requirements: - requiredResources: - - requirementName: ingress - apiVersion: networking.k8s.io/v1 - kind: Ingress - name: example-app - namespace: default - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - import ssl - import socket - from datetime import datetime - - from crossplane.function import request, response - - def operate(req, rsp): - # Get the Ingress resource - ingress = request.get_required_resource(req, "ingress") - if not ingress: - response.set_output(rsp, {"error": "No ingress resource found"}) - return - - # Extract hostname from Ingress rules - hostname = ingress["spec"]["rules"][0]["host"] - port = 443 - - # Get SSL certificate info - context = ssl.create_default_context() - with socket.create_connection((hostname, port)) as sock: - with context.wrap_socket(sock, server_hostname=hostname) as ssock: - cert = ssock.getpeercert() - - # Parse expiration date - expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') - days_until_expiry = (expiry_date - datetime.now()).days - - # Add warning if certificate expires soon - if days_until_expiry < 30: - response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") - - # Annotate the Ingress with certificate expiry info - rsp.desired.resources["ingress"].resource.update({ - "apiVersion": "networking.k8s.io/v1", - "kind": "Ingress", - "metadata": { - "name": ingress["metadata"]["name"], - "namespace": ingress["metadata"]["namespace"], - "annotations": { - "cert-monitor.crossplane.io/expires": cert['notAfter'], - "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), - "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" - } - } - }) - - # Return results in operation output for monitoring - response.set_output(rsp, { - "ingressName": ingress["metadata"]["name"], - "hostname": hostname, - "certificateExpires": cert['notAfter'], - "daysUntilExpiry": days_until_expiry, - "status": "warning" if days_until_expiry < 30 else "ok" - }) -``` - - -Save the operation as `operation.yaml` and apply it: - -```shell -kubectl apply -f operation.yaml -``` +{{< manifest path="get-started/operations/operation.yaml" >}} ### Check the operation Check that the `Operation` runs successfully: ```shell {copy-lines="1"} -kubectl get -f operation.yaml +kubectl get -f {{< manifest-url path="get-started/operations/operation.yaml" >}} NAME SYNCED SUCCEEDED AGE ingress-cert-monitor True True 15s ``` @@ -346,10 +209,10 @@ information that other tools can use for monitoring and alerting. Delete the resources you created: ```shell -kubectl delete -f operation.yaml -kubectl delete -f ingress.yaml -kubectl delete -f ingress-rbac.yaml -kubectl delete -f function.yaml +kubectl delete -f {{< manifest-url path="get-started/operations/operation.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/ingress-rbac.yaml" >}} +kubectl delete -f {{< manifest-url path="get-started/operations/function.yaml" >}} ``` ## Next steps diff --git a/content/v2.2/guides/change-logs.md b/content/v2.2/guides/change-logs.md index c19bcd722..eecad0faa 100644 --- a/content/v2.2/guides/change-logs.md +++ b/content/v2.2/guides/change-logs.md @@ -80,38 +80,7 @@ steps: container{{}} and the {{}}sidecar container{{}}. -```yaml {label="drc",copy-lines="all"} -cat <}} ### Install the provider @@ -119,20 +88,7 @@ Install the {{}}provider{{}} and instruct it to use the {{}}DeploymentRuntimeConfig{{}} that was just created. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Configure permissions @@ -146,42 +102,7 @@ production environment. See more examples for configuring `provider-kubernetes` [examples directory](https://github.com/crossplane-contrib/provider-kubernetes/tree/main/examples/namespaced/provider). {{}} -```yaml {label="rbac",copy-lines="all"} -cat <}} ### Create a resource @@ -189,24 +110,7 @@ After installing and configuring the provider with change logs enabled, create a resource that generates change log entries that reflect the actions the control plane takes. -```yaml {label="provider",copy-lines="all"} -cat <}} ### Examine the change logs diff --git a/content/v2.2/guides/connection-details-composition.md b/content/v2.2/guides/connection-details-composition.md index d4eaca673..7a7827d70 100644 --- a/content/v2.2/guides/connection-details-composition.md +++ b/content/v2.2/guides/connection-details-composition.md @@ -113,34 +113,7 @@ A CompositeResourceDefinition (XRD) defines composite resources. For this example, create an XRD for the `UserAccessKey` composite resource: -```yaml -apiVersion: apiextensions.crossplane.io/v2 -kind: CompositeResourceDefinition -metadata: - name: useraccesskeys.example.org -spec: - group: example.org - names: - kind: UserAccessKey - plural: useraccesskeys - scope: Namespaced - versions: - - name: v1alpha1 - served: true - referenceable: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - writeConnectionSecretToRef: - type: object - properties: - name: - type: string -``` +{{< manifest path="guides/connection-details-composition/xrd.yaml" >}} {{}} This XRD schema defines a `.spec.writeConnectionSecretToRef.name` field that @@ -153,12 +126,6 @@ too. {{}} -Save the XRD as `xrd.yaml` and apply it: - -```shell -kubectl apply -f xrd.yaml -``` - The Kubernetes API is now serving requests for the `UserAccessKey` composite resource. @@ -175,25 +142,12 @@ from the tabs below. Create this composition function to install YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-patch-and-transform -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-patch-and-transform.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 8s ``` @@ -205,25 +159,12 @@ Templated YAML is a good choice if you're used to writing Create this composition function to install templated YAML support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-go-templating -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-go-templating.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-go-templating.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 15s ``` @@ -233,25 +174,12 @@ function-go-templating True True xpkg.crossplane.io/crossplane-con Create this composition function to install Python support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-python -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-python.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-python.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s ``` @@ -261,25 +189,12 @@ function-python True True xpkg.crossplane.io/cr Create this composition function to install [KCL](https://kcl-lang.io) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-kcl -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-kcl.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-kcl.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 6s ``` @@ -289,25 +204,12 @@ function-kcl True True xpkg.crossplane.io/cross Create this composition function to install [Pythonic](https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic) support: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-pythonic -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 -``` - -Save the function as `fn.yaml` and apply it: - -```shell -kubectl apply -f fn.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-pythonic.yaml" >}} Check that Crossplane installed the function: ```shell {copy-lines="1"} -kubectl get -f fn.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/fn-pythonic.yaml" >}} NAME INSTALLED HEALTHY PACKAGE AGE function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m ``` @@ -318,20 +220,7 @@ function-pythonic True True xpkg.crossplane.io/crossplane-contrib/fun This guide also uses `function-auto-ready`. This function automatically marks composed resources as ready when they're healthy: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Function -metadata: - name: function-auto-ready -spec: - package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 -``` - -Save this as `fn-auto-ready.yaml` and apply it: - -```shell -kubectl apply -f fn-auto-ready.yaml -``` +{{< manifest path="guides/connection-details-composition/fn-auto-ready.yaml" >}} ### Configure the composition @@ -364,91 +253,7 @@ exposes their credentials as the composite resource's connection details `Secret {{< tab "YAML" >}} -```yaml {label="comp-pt"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-patch-and-transform -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: patch-and-transform - functionRef: - name: function-patch-and-transform - input: - apiVersion: pt.fn.crossplane.io/v1beta1 - kind: Resources - writeConnectionSecretToRef: - patches: - - type: FromCompositeFieldPath - fromFieldPath: spec.writeConnectionSecretToRef.name - toFieldPath: name - resources: - - name: user - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - spec: - forProvider: {} - - name: accesskey-0 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-0 - connectionDetails: - - name: user-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-0 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-0" - - name: accesskey-1 - base: - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: accesskey-secret-1 - connectionDetails: - - name: user-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: username - - name: password-1 - type: FromConnectionSecretKey - fromConnectionSecretKey: password - patches: - - type: FromCompositeFieldPath - fromFieldPath: metadata.name - toFieldPath: spec.writeConnectionSecretToRef.name - transforms: - - type: string - string: - type: Format - fmt: "%s-accesskey-secret-1" - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-patch-and-transform.yaml" label="comp-pt" >}} @@ -482,78 +287,7 @@ spec: {{< tab "Templated YAML" >}} -```yaml {label="comp-gotmpl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-go-templating -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-templates - functionRef: - name: function-go-templating - input: - apiVersion: gotemplating.fn.crossplane.io/v1beta1 - kind: GoTemplate - source: Inline - inline: - template: | - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: User - metadata: - annotations: - {{ setResourceNameAnnotation "user" }} - spec: - forProvider: {} - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-0" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 - --- - apiVersion: iam.aws.m.upbound.io/v1beta1 - kind: AccessKey - metadata: - annotations: - {{ setResourceNameAnnotation "accesskey-1" }} - spec: - forProvider: - userSelector: - matchControllerRef: true - writeConnectionSecretToRef: - name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 - --- - apiVersion: v1 - kind: Secret - metadata: - name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} - annotations: - {{ setResourceNameAnnotation "connection-secret" }} - {{ if eq $.observed.resources nil }} - data: {} - {{ else }} - data: - user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} - user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} - password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} - password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} - {{ end }} - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-go-templating.yaml" label="comp-gotmpl" >}} @@ -584,109 +318,7 @@ spec: {{< tab "Python" >}} -```yaml {label="comp-python"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-python -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-python - functionRef: - name: function-python - input: - apiVersion: python.fn.crossplane.io/v1beta1 - kind: Script - script: | - def compose(req, rsp): - # Get observed composite resource - oxr = req.observed.composite.resource - oxr_name = oxr["metadata"]["name"] - - # IAM User - rsp.desired.resources["user"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "User", - "spec": { - "forProvider": {} - } - }) - - # Access Key 0 - rsp.desired.resources["accesskey-0"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-0" - } - } - }) - - # Access Key 1 - rsp.desired.resources["accesskey-1"].resource.update({ - "apiVersion": "iam.aws.m.upbound.io/v1beta1", - "kind": "AccessKey", - "spec": { - "forProvider": { - "userSelector": { - "matchControllerRef": True - } - }, - "writeConnectionSecretToRef": { - "name": f"{oxr_name}-accesskey-secret-1" - } - } - }) - - # Secret representing the composite resource's connection details - secret_resource = { - "apiVersion": "v1", - "kind": "Secret", - "metadata": {} - } - - # If a secret name was provided then use it - secret_name = "" - if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: - secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] - - secret_resource["metadata"]["name"] = secret_name - - # Only add data if we have connection details to populate - data = {} - if "accesskey-0" in req.observed.resources: - accesskey0_conn = req.observed.resources["accesskey-0"].connection_details - if "username" in accesskey0_conn: - data["user-0"] = accesskey0_conn["username"].decode("utf-8") - if "password" in accesskey0_conn: - data["password-0"] = accesskey0_conn["password"].decode("utf-8") - - if "accesskey-1" in req.observed.resources: - accesskey1_conn = req.observed.resources["accesskey-1"].connection_details - if "username" in accesskey1_conn: - data["user-1"] = accesskey1_conn["username"].decode("utf-8") - if "password" in accesskey1_conn: - data["password-1"] = accesskey1_conn["password"].decode("utf-8") - - if data: - secret_resource["stringData"] = data - - rsp.desired.resources["connection-secret"].resource.update(secret_resource) - - step: ready - functionRef: - name: function-auto-ready - -``` +{{< manifest path="guides/connection-details-composition/composition-python.yaml" label="comp-python" >}} @@ -717,77 +349,7 @@ spec: {{< tab "KCL" >}} -```yaml {label="comp-kcl"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-kcl -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-kcl - functionRef: - name: function-kcl - input: - apiVersion: krm.kcl.dev/v1alpha1 - kind: KCLInput - spec: - source: | - oxr = option("params").oxr - ocds = option("params").ocds - - user = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "User" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "user" - } - spec.forProvider = {} - } - - accesskey0 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-0" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" - } - - accesskey1 = { - apiVersion = "iam.aws.m.upbound.io/v1beta1" - kind = "AccessKey" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "accesskey-1" - } - spec.forProvider.userSelector.matchControllerRef = True - spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" - } - - secret = { - apiVersion = "v1" - kind = "Secret" - metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" - metadata.annotations = { - "krm.kcl.dev/composition-resource-name" = "connection-secret" - } - data = { - "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" - "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" - "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" - "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" - } if ocds else {} - } - - items = [user, accesskey0, accesskey1, secret] - - step: ready - functionRef: - name: function-auto-ready -``` +{{< manifest path="guides/connection-details-composition/composition-kcl.yaml" label="comp-kcl" >}} **How this Composition exposes connection details:** @@ -816,38 +378,7 @@ spec: {{< tab "Pythonic" >}} -```yaml {label="comp-pythonic"} -apiVersion: apiextensions.crossplane.io/v1 -kind: Composition -metadata: - name: useraccesskeys-pythonic -spec: - compositeTypeRef: - apiVersion: example.org/v1alpha1 - kind: UserAccessKey - mode: Pipeline - pipeline: - - step: render-pythonic - functionRef: - name: function-pythonic - input: - apiVersion: pythonic.fn.crossplane.io/v1alpha1 - kind: Composite - composite: | - class Composite(BaseComposite): - def compose(self): - self.connectionSecret = self.spec.writeConnectionSecretToRef - - user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') - user.spec.forProvider = {} - - for ix in range(2): - key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') - key.spec.forProvider.user = user.status.atProvider.id - key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" - self.connection[f"user-{ix}"] = key.connection.username - self.connection[f"password-{ix}"] = key.connection.password -``` +{{< manifest path="guides/connection-details-composition/composition-pythonic.yaml" label="comp-pythonic" >}} @@ -877,12 +408,6 @@ spec: {{< /tabs >}} -Save the composition as `composition.yaml` and apply it: - -```shell -kubectl apply -f composition.yaml -``` - ## Use the composite resource The Composition now specifies how to compose connection details for the @@ -890,27 +415,12 @@ The Composition now specifies how to compose connection details for the Create a `UserAccessKey` to see it in action: -```yaml -apiVersion: example.org/v1alpha1 -kind: UserAccessKey -metadata: - namespace: default - name: my-keys -spec: - writeConnectionSecretToRef: - name: my-keys-connection-details -``` - -Save the composite resource as `my-keys.yaml` and apply it: - -```shell -kubectl apply -f my-keys.yaml -``` +{{< manifest path="guides/connection-details-composition/my-keys.yaml" >}} Check that the composite resource is ready: ```shell {copy-lines="1"} -kubectl get -f my-keys.yaml +kubectl get -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} NAME SYNCED READY COMPOSITION AGE my-keys True True useraccesskeys-go-templating 45s ``` @@ -1128,7 +638,7 @@ namespace to the XR's namespace if left empty. Delete the composite resource to clean up: ```shell -kubectl delete -f my-keys.yaml +kubectl delete -f {{< manifest-url path="guides/connection-details-composition/my-keys.yaml" >}} ``` When you delete the composite resource, Crossplane deletes: diff --git a/content/v2.2/guides/disabling-unused-managed-resources.md b/content/v2.2/guides/disabling-unused-managed-resources.md index 3beb04a28..220c41544 100644 --- a/content/v2.2/guides/disabling-unused-managed-resources.md +++ b/content/v2.2/guides/disabling-unused-managed-resources.md @@ -86,20 +86,9 @@ kubectl delete managedresourceactivationpolicy default Install your provider as normal. Crossplane automatically converts the provider's CRDs to ManagedResourceDefinitions: -```yaml -apiVersion: pkg.crossplane.io/v1 -kind: Provider -metadata: - name: provider-aws-ec2 -spec: - package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 -``` - -Save this as `provider.yaml` and apply it: +{{< manifest path="guides/disabling-unused-managed-resources/provider.yaml" >}} ```shell -kubectl apply -f provider.yaml - # Wait for provider to be ready kubectl wait --for=condition=Healthy provider/provider-aws-ec2 --timeout=5m ``` @@ -133,23 +122,7 @@ kubectl get crds | grep ec2.aws.m.crossplane.io Create a ManagedResourceActivationPolicy to selectively activate only the resources you need: -```yaml -apiVersion: apiextensions.crossplane.io/v1alpha1 -kind: ManagedResourceActivationPolicy -metadata: - name: my-app-resources -spec: - activate: - - instances.ec2.aws.m.crossplane.io # EC2 instances for compute - - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking - - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation -``` - -Save this as `activation-policy.yaml` and apply it: - -```shell -kubectl apply -f activation-policy.yaml -``` +{{< manifest path="guides/disabling-unused-managed-resources/activation-policy.yaml" >}} ## Step 5: Verify selective activation diff --git a/content/v2.2/manifests/get-started/composition/app.yaml b/content/v2.2/manifests/get-started/composition/app.yaml new file mode 100644 index 000000000..a24b2ecd9 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/app.yaml @@ -0,0 +1,7 @@ +apiVersion: example.crossplane.io/v1 +kind: App +metadata: + namespace: default + name: my-app +spec: + image: nginx diff --git a/content/v2.2/manifests/get-started/composition/composition-kcl.yaml b/content/v2.2/manifests/get-started/composition/composition-kcl.yaml new file mode 100644 index 000000000..f75c71504 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-kcl.yaml @@ -0,0 +1,74 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-kcl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + observed_xr = option("params").oxr + + _desired_deployment = { + apiVersion = "apps/v1" + kind = "Deployment" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "deployment" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + replicas = 2 + selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name} + template = { + metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + spec.containers = [{ + name = "app" + image = observed_xr.spec.image + ports = [{containerPort = 80}] + }] + } + } + } + + observed_deployment = option("params").ocds["deployment"]?.Resource + if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]): + _desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_service = { + apiVersion = "v1" + kind = "Service" + metadata = { + annotations = { + "krm.kcl.dev/composition-resource-name" = "service" + } + labels = {"example.crossplane.io/app" = observed_xr.metadata.name} + } + spec = { + selector = {"example.crossplane.io/app" = observed_xr.metadata.name} + ports = [{protocol = "TCP", port = 8080, targetPort = 80}] + } + } + + observed_service = option("params").ocds["service"]?.Resource + if observed_service?.spec?.clusterIP: + _desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True" + + _desired_xr = { + **option("params").dxr + + status.address = observed_service?.spec?.clusterIP or "" + status.replicas = observed_deployment?.status?.availableReplicas or 0 + } + + items = [_desired_deployment, _desired_service, _desired_xr] diff --git a/content/v2.2/manifests/get-started/composition/composition-python.yaml b/content/v2.2/manifests/get-started/composition/composition-python.yaml new file mode 100644 index 000000000..85a404f2d --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-python.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-python +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + observed_xr = req.observed.composite.resource + + rsp.desired.resources["deployment"].resource.update({ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "replicas": 2, + "selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}}, + "template": { + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "containers": [{ + "name": "app", + "image": observed_xr["spec"]["image"], + "ports": [{"containerPort": 80}] + }], + }, + }, + }, + }) + + observed_deployment = req.observed.resources["deployment"].resource + if "status" in observed_deployment: + if "availableReplicas" in observed_deployment["status"]: + rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"] + if "conditions" in observed_deployment["status"]: + for condition in observed_deployment["status"]["conditions"]: + if condition["type"] == "Available" and condition["status"] == "True": + rsp.desired.resources["deployment"].ready = True + + rsp.desired.resources["service"].resource.update({ + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + }, + "spec": { + "selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}, + "ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}], + }, + }) + + observed_service = req.observed.resources["service"].resource + if "spec" in observed_service and "clusterIP" in observed_service["spec"]: + rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"] + rsp.desired.resources["service"].ready = True diff --git a/content/v2.2/manifests/get-started/composition/composition-pythonic.yaml b/content/v2.2/manifests/get-started/composition/composition-pythonic.yaml new file mode 100644 index 000000000..9853a103e --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-pythonic.yaml @@ -0,0 +1,39 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-pythonic +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + labels = {'example.crossplane.io/app': self.metadata.name} + + d = self.resources.deployment('apps/v1', 'Deployment') + d.metadata.labels = labels + d.spec.replicas = 2 + d.spec.selector.matchLabels = labels + d.spec.template.metadata.labels = labels + d.spec.template.spec.containers[0].name = 'app' + d.spec.template.spec.containers[0].image = self.spec.image + d.spec.template.spec.containers[0].ports[0].containerPort = 80 + + s = self.resources.service('v1', 'Service') + s.metadata.labels = labels + s.spec.selector = labels + s.spec.ports[0].protocol = 'TCP' + s.spec.ports[0].port = 8080 + s.spec.ports[0].targetPort = 80 + + self.status.replicas = d.status.availableReplicas + self.status.address = s.observed.spec.clusterIP diff --git a/content/v2.2/manifests/get-started/composition/composition-templated-yaml.yaml b/content/v2.2/manifests/get-started/composition/composition-templated-yaml.yaml new file mode 100644 index 000000000..4c1a30cd2 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-templated-yaml.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-templated-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: deployment + {{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + template: + metadata: + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + containers: + - name: app + image: {{ .observed.composite.resource.spec.image }} + ports: + - containerPort: 80 + --- + apiVersion: v1 + kind: Service + metadata: + annotations: + gotemplating.fn.crossplane.io/composition-resource-name: service + {{ if (get (getComposedResource . "service").spec "clusterIP") }} + gotemplating.fn.crossplane.io/ready: "True" + {{ end }} + labels: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + spec: + selector: + example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + --- + apiVersion: example.crossplane.io/v1 + kind: App + status: + replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }} + address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }} diff --git a/content/v2.2/manifests/get-started/composition/composition-yaml-cel.yaml b/content/v2.2/manifests/get-started/composition/composition-yaml-cel.yaml new file mode 100644 index 000000000..2dc93fb0c --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-yaml-cel.yaml @@ -0,0 +1,60 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml-cel +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-kro + input: + apiVersion: kro.fn.crossplane.io/v1beta1 + kind: ResourceGraph + status: + replicas: ${deployment.status.?availableReplicas.orValue(0)} + address: ${service.spec.?clusterIP.orValue("")} + resources: + - id: deployment + template: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + replicas: 2 + selector: + matchLabels: + example.crossplane.io/app: ${schema.metadata.name} + template: + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + containers: + - name: app + image: ${schema.spec.image} + ports: + - containerPort: 80 + readyWhen: + - ${deployment.status.?conditions.orValue([]).exists(c, c.type == "Available" && c.status == "True")} + - id: service + template: + apiVersion: v1 + kind: Service + metadata: + labels: + example.crossplane.io/app: ${schema.metadata.name} + spec: + selector: + example.crossplane.io/app: ${schema.metadata.name} + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + readyWhen: + - ${service.spec.?clusterIP.hasValue()} diff --git a/content/v2.2/manifests/get-started/composition/composition-yaml.yaml b/content/v2.2/manifests/get-started/composition/composition-yaml.yaml new file mode 100644 index 000000000..9907d5a34 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/composition-yaml.yaml @@ -0,0 +1,72 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: app-yaml +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: App + mode: Pipeline + pipeline: + - step: create-deployment-and-service + functionRef: + name: crossplane-contrib-function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + resources: + - name: deployment + base: + apiVersion: apps/v1 + kind: Deployment + spec: + replicas: 2 + template: + spec: + containers: + - name: app + ports: + - containerPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector.matchLabels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.template.metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: spec.image + toFieldPath: spec.template.spec.containers[0].image + - type: ToCompositeFieldPath + fromFieldPath: status.availableReplicas + toFieldPath: status.replicas + readinessChecks: + - type: MatchCondition + matchCondition: + type: Available + status: "True" + - name: service + base: + apiVersion: v1 + kind: Service + spec: + ports: + - protocol: TCP + port: 8080 + targetPort: 80 + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: metadata.labels[example.crossplane.io/app] + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.selector[example.crossplane.io/app] + - type: ToCompositeFieldPath + fromFieldPath: spec.clusterIP + toFieldPath: status.address + readinessChecks: + - type: NonEmpty + fieldPath: spec.clusterIP diff --git a/content/v2.2/manifests/get-started/composition/fn-go-templating.yaml b/content/v2.2/manifests/get-started/composition/fn-go-templating.yaml new file mode 100644 index 000000000..d11ea8e70 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 diff --git a/content/v2.2/manifests/get-started/composition/fn-kcl.yaml b/content/v2.2/manifests/get-started/composition/fn-kcl.yaml new file mode 100644 index 000000000..befb6f66d --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 diff --git a/content/v2.2/manifests/get-started/composition/fn-kro.yaml b/content/v2.2/manifests/get-started/composition/fn-kro.yaml new file mode 100644 index 000000000..501c5d5f0 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-kro.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-kro +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kro:v0.1.0 diff --git a/content/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml b/content/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..aecb6cd0f --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 diff --git a/content/v2.2/manifests/get-started/composition/fn-python.yaml b/content/v2.2/manifests/get-started/composition/fn-python.yaml new file mode 100644 index 000000000..b76bb3ee2 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 diff --git a/content/v2.2/manifests/get-started/composition/fn-pythonic.yaml b/content/v2.2/manifests/get-started/composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.2/manifests/get-started/composition/xrd.yaml b/content/v2.2/manifests/get-started/composition/xrd.yaml new file mode 100644 index 000000000..e2d3856de --- /dev/null +++ b/content/v2.2/manifests/get-started/composition/xrd.yaml @@ -0,0 +1,35 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: apps.example.crossplane.io +spec: + scope: Namespaced + group: example.crossplane.io + names: + kind: App + plural: apps + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + image: + description: The app's OCI container image. + type: string + required: + - image + status: + type: object + properties: + replicas: + description: The number of available app replicas. + type: integer + address: + description: The app's IP address. + type: string diff --git a/content/v2.2/manifests/get-started/managed-resources/bucket.yaml b/content/v2.2/manifests/get-started/managed-resources/bucket.yaml new file mode 100644 index 000000000..8c0ef2ecd --- /dev/null +++ b/content/v2.2/manifests/get-started/managed-resources/bucket.yaml @@ -0,0 +1,8 @@ +apiVersion: s3.aws.m.upbound.io/v1beta1 +kind: Bucket +metadata: + namespace: default + generateName: crossplane-bucket- +spec: + forProvider: + region: us-east-2 diff --git a/content/v2.2/manifests/get-started/managed-resources/provider.yaml b/content/v2.2/manifests/get-started/managed-resources/provider.yaml new file mode 100644 index 000000000..3bc07d336 --- /dev/null +++ b/content/v2.2/manifests/get-started/managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: crossplane-contrib-provider-aws-s3 +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v2.0.0 diff --git a/content/v2.2/manifests/get-started/managed-resources/providerconfig.yaml b/content/v2.2/manifests/get-started/managed-resources/providerconfig.yaml new file mode 100644 index 000000000..2ad6259d4 --- /dev/null +++ b/content/v2.2/manifests/get-started/managed-resources/providerconfig.yaml @@ -0,0 +1,11 @@ +apiVersion: aws.m.upbound.io/v1beta1 +kind: ClusterProviderConfig +metadata: + name: default +spec: + credentials: + source: Secret + secretRef: + namespace: crossplane-system + name: aws-secret + key: creds diff --git a/content/v2.2/manifests/get-started/operations/function.yaml b/content/v2.2/manifests/get-started/operations/function.yaml new file mode 100644 index 000000000..a3ba5fbbc --- /dev/null +++ b/content/v2.2/manifests/get-started/operations/function.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: crossplane-contrib-function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.2/manifests/get-started/operations/ingress-rbac.yaml b/content/v2.2/manifests/get-started/operations/ingress-rbac.yaml new file mode 100644 index 000000000..63f54a463 --- /dev/null +++ b/content/v2.2/manifests/get-started/operations/ingress-rbac.yaml @@ -0,0 +1,10 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: operations-ingress-access + labels: + rbac.crossplane.io/aggregate-to-crossplane: "true" +rules: +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch", "patch", "update"] diff --git a/content/v2.2/manifests/get-started/operations/ingress.yaml b/content/v2.2/manifests/get-started/operations/ingress.yaml new file mode 100644 index 000000000..ba30f83b0 --- /dev/null +++ b/content/v2.2/manifests/get-started/operations/ingress.yaml @@ -0,0 +1,17 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: example-app + namespace: default +spec: + rules: + - host: google.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nonexistent-service + port: + number: 80 diff --git a/content/v2.2/manifests/get-started/operations/operation.yaml b/content/v2.2/manifests/get-started/operations/operation.yaml new file mode 100644 index 000000000..aeb669f77 --- /dev/null +++ b/content/v2.2/manifests/get-started/operations/operation.yaml @@ -0,0 +1,75 @@ +apiVersion: ops.crossplane.io/v1alpha1 +kind: Operation +metadata: + name: ingress-cert-monitor +spec: + mode: Pipeline + pipeline: + - step: check-ingress-certificate + functionRef: + name: crossplane-contrib-function-python + requirements: + requiredResources: + - requirementName: ingress + apiVersion: networking.k8s.io/v1 + kind: Ingress + name: example-app + namespace: default + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + import ssl + import socket + from datetime import datetime + + from crossplane.function import request, response + + def operate(req, rsp): + # Get the Ingress resource + ingress = request.get_required_resource(req, "ingress") + if not ingress: + response.set_output(rsp, {"error": "No ingress resource found"}) + return + + # Extract hostname from Ingress rules + hostname = ingress["spec"]["rules"][0]["host"] + port = 443 + + # Get SSL certificate info + context = ssl.create_default_context() + with socket.create_connection((hostname, port)) as sock: + with context.wrap_socket(sock, server_hostname=hostname) as ssock: + cert = ssock.getpeercert() + + # Parse expiration date + expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z') + days_until_expiry = (expiry_date - datetime.now()).days + + # Add warning if certificate expires soon + if days_until_expiry < 30: + response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days") + + # Annotate the Ingress with certificate expiry info + rsp.desired.resources["ingress"].resource.update({ + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", + "metadata": { + "name": ingress["metadata"]["name"], + "namespace": ingress["metadata"]["namespace"], + "annotations": { + "cert-monitor.crossplane.io/expires": cert['notAfter'], + "cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry), + "cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok" + } + } + }) + + # Return results in operation output for monitoring + response.set_output(rsp, { + "ingressName": ingress["metadata"]["name"], + "hostname": hostname, + "certificateExpires": cert['notAfter'], + "daysUntilExpiry": days_until_expiry, + "status": "warning" if days_until_expiry < 30 else "ok" + }) diff --git a/content/v2.2/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml b/content/v2.2/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml new file mode 100644 index 000000000..9076ea60a --- /dev/null +++ b/content/v2.2/manifests/guides/change-logs/deploymentruntimeconfig-enable-changelogs.yaml @@ -0,0 +1,28 @@ +apiVersion: pkg.crossplane.io/v1beta1 +kind: DeploymentRuntimeConfig +metadata: + name: enable-changelogs +spec: + deploymentTemplate: + spec: + selector: {} + template: + spec: + containers: + - name: package-runtime + args: + - --enable-changelogs + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + - name: changelogs-sidecar + image: xpkg.crossplane.io/crossplane/changelogs-sidecar:v0.0.1 + volumeMounts: + - name: changelogs-vol + mountPath: /var/run/changelogs + volumes: + - name: changelogs-vol + emptyDir: {} + serviceAccountTemplate: + metadata: + name: provider-kubernetes diff --git a/content/v2.2/manifests/guides/change-logs/object-configmap-for-changelogs.yaml b/content/v2.2/manifests/guides/change-logs/object-configmap-for-changelogs.yaml new file mode 100644 index 000000000..a55e5b39e --- /dev/null +++ b/content/v2.2/manifests/guides/change-logs/object-configmap-for-changelogs.yaml @@ -0,0 +1,14 @@ +apiVersion: kubernetes.crossplane.io/v1alpha2 +kind: Object +metadata: + name: configmap-for-changelogs +spec: + forProvider: + manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: default + name: configmap-for-changelogs + data: + key-1: cool-value-1 diff --git a/content/v2.2/manifests/guides/change-logs/provider-kubernetes.yaml b/content/v2.2/manifests/guides/change-logs/provider-kubernetes.yaml new file mode 100644 index 000000000..75b22830a --- /dev/null +++ b/content/v2.2/manifests/guides/change-logs/provider-kubernetes.yaml @@ -0,0 +1,10 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-kubernetes +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-kubernetes:v0.18.0 + runtimeConfigRef: + apiVersion: pkg.crossplane.io/v1beta1 + kind: DeploymentRuntimeConfig + name: enable-changelogs diff --git a/content/v2.2/manifests/guides/change-logs/rbac.yaml b/content/v2.2/manifests/guides/change-logs/rbac.yaml new file mode 100644 index 000000000..2de5d1f8c --- /dev/null +++ b/content/v2.2/manifests/guides/change-logs/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: configmap-edit +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - "*" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: provider-kubernetes-configmap-edit +subjects: + - kind: ServiceAccount + name: provider-kubernetes + namespace: crossplane-system +roleRef: + kind: ClusterRole + name: configmap-edit + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: kubernetes.crossplane.io/v1alpha1 +kind: ProviderConfig +metadata: + name: default +spec: + credentials: + source: InjectedIdentity diff --git a/content/v2.2/manifests/guides/connection-details-composition/composition-go-templating.yaml b/content/v2.2/manifests/guides/connection-details-composition/composition-go-templating.yaml new file mode 100644 index 000000000..7570332de --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/composition-go-templating.yaml @@ -0,0 +1,70 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-go-templating +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-templates + functionRef: + name: function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + metadata: + annotations: + {{ setResourceNameAnnotation "user" }} + spec: + forProvider: {} + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-0" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-0 + --- + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + metadata: + annotations: + {{ setResourceNameAnnotation "accesskey-1" }} + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: {{ $.observed.composite.resource.metadata.name }}-accesskey-secret-1 + --- + apiVersion: v1 + kind: Secret + metadata: + name: {{ dig "spec" "writeConnectionSecretToRef" "name" "" $.observed.composite.resource}} + annotations: + {{ setResourceNameAnnotation "connection-secret" }} + {{ if eq $.observed.resources nil }} + data: {} + {{ else }} + data: + user-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.username }} + user-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.username }} + password-0: {{ ( index $.observed.resources "accesskey-0" ).connectionDetails.password }} + password-1: {{ ( index $.observed.resources "accesskey-1" ).connectionDetails.password }} + {{ end }} + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.2/manifests/guides/connection-details-composition/composition-kcl.yaml b/content/v2.2/manifests/guides/connection-details-composition/composition-kcl.yaml new file mode 100644 index 000000000..fe80e07f5 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/composition-kcl.yaml @@ -0,0 +1,69 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-kcl +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-kcl + functionRef: + name: function-kcl + input: + apiVersion: krm.kcl.dev/v1alpha1 + kind: KCLInput + spec: + source: | + oxr = option("params").oxr + ocds = option("params").ocds + + user = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "User" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "user" + } + spec.forProvider = {} + } + + accesskey0 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-0" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-0" + } + + accesskey1 = { + apiVersion = "iam.aws.m.upbound.io/v1beta1" + kind = "AccessKey" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "accesskey-1" + } + spec.forProvider.userSelector.matchControllerRef = True + spec.writeConnectionSecretToRef.name = "${oxr.metadata.name}-accesskey-secret-1" + } + + secret = { + apiVersion = "v1" + kind = "Secret" + metadata.name = oxr?.spec?.writeConnectionSecretToRef?.name or "" + metadata.annotations = { + "krm.kcl.dev/composition-resource-name" = "connection-secret" + } + data = { + "user-0" = ocds["accesskey-0"]?.ConnectionDetails?.username or "" + "user-1" = ocds["accesskey-1"]?.ConnectionDetails?.username or "" + "password-0" = ocds["accesskey-0"]?.ConnectionDetails?.password or "" + "password-1" = ocds["accesskey-1"]?.ConnectionDetails?.password or "" + } if ocds else {} + } + + items = [user, accesskey0, accesskey1, secret] + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.2/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml b/content/v2.2/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml new file mode 100644 index 000000000..1d6c7e397 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/composition-patch-and-transform.yaml @@ -0,0 +1,83 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-patch-and-transform +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: patch-and-transform + functionRef: + name: function-patch-and-transform + input: + apiVersion: pt.fn.crossplane.io/v1beta1 + kind: Resources + writeConnectionSecretToRef: + patches: + - type: FromCompositeFieldPath + fromFieldPath: spec.writeConnectionSecretToRef.name + toFieldPath: name + resources: + - name: user + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: User + spec: + forProvider: {} + - name: accesskey-0 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-0 + connectionDetails: + - name: user-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-0 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-0" + - name: accesskey-1 + base: + apiVersion: iam.aws.m.upbound.io/v1beta1 + kind: AccessKey + spec: + forProvider: + userSelector: + matchControllerRef: true + writeConnectionSecretToRef: + name: accesskey-secret-1 + connectionDetails: + - name: user-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: username + - name: password-1 + type: FromConnectionSecretKey + fromConnectionSecretKey: password + patches: + - type: FromCompositeFieldPath + fromFieldPath: metadata.name + toFieldPath: spec.writeConnectionSecretToRef.name + transforms: + - type: string + string: + type: Format + fmt: "%s-accesskey-secret-1" + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.2/manifests/guides/connection-details-composition/composition-python.yaml b/content/v2.2/manifests/guides/connection-details-composition/composition-python.yaml new file mode 100644 index 000000000..a1f5c0011 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/composition-python.yaml @@ -0,0 +1,100 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-python +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-python + functionRef: + name: function-python + input: + apiVersion: python.fn.crossplane.io/v1beta1 + kind: Script + script: | + def compose(req, rsp): + # Get observed composite resource + oxr = req.observed.composite.resource + oxr_name = oxr["metadata"]["name"] + + # IAM User + rsp.desired.resources["user"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "User", + "spec": { + "forProvider": {} + } + }) + + # Access Key 0 + rsp.desired.resources["accesskey-0"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-0" + } + } + }) + + # Access Key 1 + rsp.desired.resources["accesskey-1"].resource.update({ + "apiVersion": "iam.aws.m.upbound.io/v1beta1", + "kind": "AccessKey", + "spec": { + "forProvider": { + "userSelector": { + "matchControllerRef": True + } + }, + "writeConnectionSecretToRef": { + "name": f"{oxr_name}-accesskey-secret-1" + } + } + }) + + # Secret representing the composite resource's connection details + secret_resource = { + "apiVersion": "v1", + "kind": "Secret", + "metadata": {} + } + + # If a secret name was provided then use it + secret_name = "" + if "writeConnectionSecretToRef" in oxr["spec"] and "name" in oxr["spec"]["writeConnectionSecretToRef"]: + secret_name = oxr["spec"]["writeConnectionSecretToRef"]["name"] + + secret_resource["metadata"]["name"] = secret_name + + # Only add data if we have connection details to populate + data = {} + if "accesskey-0" in req.observed.resources: + accesskey0_conn = req.observed.resources["accesskey-0"].connection_details + if "username" in accesskey0_conn: + data["user-0"] = accesskey0_conn["username"].decode("utf-8") + if "password" in accesskey0_conn: + data["password-0"] = accesskey0_conn["password"].decode("utf-8") + + if "accesskey-1" in req.observed.resources: + accesskey1_conn = req.observed.resources["accesskey-1"].connection_details + if "username" in accesskey1_conn: + data["user-1"] = accesskey1_conn["username"].decode("utf-8") + if "password" in accesskey1_conn: + data["password-1"] = accesskey1_conn["password"].decode("utf-8") + + if data: + secret_resource["stringData"] = data + + rsp.desired.resources["connection-secret"].resource.update(secret_resource) + - step: ready + functionRef: + name: function-auto-ready diff --git a/content/v2.2/manifests/guides/connection-details-composition/composition-pythonic.yaml b/content/v2.2/manifests/guides/connection-details-composition/composition-pythonic.yaml new file mode 100644 index 000000000..5a59f8d3f --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/composition-pythonic.yaml @@ -0,0 +1,30 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: useraccesskeys-pythonic +spec: + compositeTypeRef: + apiVersion: example.org/v1alpha1 + kind: UserAccessKey + mode: Pipeline + pipeline: + - step: render-pythonic + functionRef: + name: function-pythonic + input: + apiVersion: pythonic.fn.crossplane.io/v1alpha1 + kind: Composite + composite: | + class Composite(BaseComposite): + def compose(self): + self.connectionSecret = self.spec.writeConnectionSecretToRef + + user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User') + user.spec.forProvider = {} + + for ix in range(2): + key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey') + key.spec.forProvider.user = user.status.atProvider.id + key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}" + self.connection[f"user-{ix}"] = key.connection.username + self.connection[f"password-{ix}"] = key.connection.password diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-auto-ready.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-auto-ready.yaml new file mode 100644 index 000000000..9f15ef44c --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-auto-ready.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-auto-ready +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0 diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-go-templating.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-go-templating.yaml new file mode 100644 index 000000000..e6073c0e1 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-go-templating.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-go-templating +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2 diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-kcl.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-kcl.yaml new file mode 100644 index 000000000..3e81ce32b --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-kcl.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-kcl +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml new file mode 100644 index 000000000..c03e0f832 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-patch-and-transform.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-patch-and-transform +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-python.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-python.yaml new file mode 100644 index 000000000..f532a7b9a --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-python.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-python +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 diff --git a/content/v2.2/manifests/guides/connection-details-composition/fn-pythonic.yaml b/content/v2.2/manifests/guides/connection-details-composition/fn-pythonic.yaml new file mode 100644 index 000000000..0ce36b981 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/fn-pythonic.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Function +metadata: + name: function-pythonic +spec: + package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 diff --git a/content/v2.2/manifests/guides/connection-details-composition/my-keys.yaml b/content/v2.2/manifests/guides/connection-details-composition/my-keys.yaml new file mode 100644 index 000000000..13191fc14 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/my-keys.yaml @@ -0,0 +1,8 @@ +apiVersion: example.org/v1alpha1 +kind: UserAccessKey +metadata: + namespace: default + name: my-keys +spec: + writeConnectionSecretToRef: + name: my-keys-connection-details diff --git a/content/v2.2/manifests/guides/connection-details-composition/xrd.yaml b/content/v2.2/manifests/guides/connection-details-composition/xrd.yaml new file mode 100644 index 000000000..a9ae5e2d6 --- /dev/null +++ b/content/v2.2/manifests/guides/connection-details-composition/xrd.yaml @@ -0,0 +1,26 @@ +apiVersion: apiextensions.crossplane.io/v2 +kind: CompositeResourceDefinition +metadata: + name: useraccesskeys.example.org +spec: + group: example.org + names: + kind: UserAccessKey + plural: useraccesskeys + scope: Namespaced + versions: + - name: v1alpha1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + writeConnectionSecretToRef: + type: object + properties: + name: + type: string diff --git a/content/v2.2/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml b/content/v2.2/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml new file mode 100644 index 000000000..62aa3ae51 --- /dev/null +++ b/content/v2.2/manifests/guides/disabling-unused-managed-resources/activation-policy.yaml @@ -0,0 +1,9 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: ManagedResourceActivationPolicy +metadata: + name: my-app-resources +spec: + activate: + - instances.ec2.aws.m.crossplane.io # EC2 instances for compute + - securitygroups.ec2.aws.m.crossplane.io # Security groups for networking + - vpcs.ec2.aws.m.crossplane.io # VPCs for isolation diff --git a/content/v2.2/manifests/guides/disabling-unused-managed-resources/provider.yaml b/content/v2.2/manifests/guides/disabling-unused-managed-resources/provider.yaml new file mode 100644 index 000000000..a8c909540 --- /dev/null +++ b/content/v2.2/manifests/guides/disabling-unused-managed-resources/provider.yaml @@ -0,0 +1,6 @@ +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-ec2 +spec: + package: xpkg.crossplane.io/provider-aws-ec2:v2.0.0 diff --git a/themes/geekboot/layouts/shortcodes/manifest-url.html b/themes/geekboot/layouts/shortcodes/manifest-url.html new file mode 100644 index 000000000..04a2a72b1 --- /dev/null +++ b/themes/geekboot/layouts/shortcodes/manifest-url.html @@ -0,0 +1,33 @@ +{{- /* + manifest-url shortcode + + Emits the absolute URL of an example manifest. Useful inside code + fences where the manifest shortcode itself would render too much + (e.g. a `kubectl delete -f ` line in a cleanup section). + + Usage (inside a shell code fence): + kubectl delete -f {{< manifest-url path="get-started/composition/app.yaml" >}} + + The version segment is auto-derived from the calling page's path, + so a page in content/v2.2/... resolves path="foo.yaml" to + https://docs.crossplane.io/v2.2/manifests/foo.yaml (or whatever + baseURL is configured for the build). + + See: docs/superpowers/specs/2026-04-28-inline-manifests-design.md +*/ -}} + +{{- $rel := .Get "path" -}} +{{- if not $rel -}} + {{- errorf "{{< manifest-url >}} requires a path= named argument at %s" .Position -}} +{{- end -}} + +{{- $segments := split .Page.File.Path "/" -}} +{{- $version := index $segments 0 -}} + +{{- $contentPath := printf "content/%s/manifests/%s" $version $rel -}} + +{{- if not (fileExists $contentPath) -}} + {{- errorf "{{< manifest-url >}}: file not found at %s (referenced from %s)" $contentPath .Position -}} +{{- end -}} + +{{- printf "%s/manifests/%s" $version $rel | absURL -}} diff --git a/themes/geekboot/layouts/shortcodes/manifest.html b/themes/geekboot/layouts/shortcodes/manifest.html new file mode 100644 index 000000000..08776627b --- /dev/null +++ b/themes/geekboot/layouts/shortcodes/manifest.html @@ -0,0 +1,58 @@ +{{- /* + manifest shortcode + + Renders an example manifest inline plus a `kubectl apply -f ` + command block pointing to the same file published at the docs site. + + Usage: + {{< manifest path="get-started/managed-resources/provider.yaml" >}} + {{< manifest path="get-started/managed-resources/provider.yaml" apply="false" >}} + + Required named args: + path= - relative path under manifests/ + + Optional named args: + apply="false" - omit the kubectl apply block + command="kubectl create -f" - override the verb (default: kubectl apply -f) + label="my-provider" - wraps the rendered block in
so + {{< hover label="..." >}} can target lines in this manifest + + See: docs/superpowers/specs/2026-04-28-inline-manifests-design.md +*/ -}} + +{{- $rel := .Get "path" -}} +{{- if not $rel -}} + {{- errorf "{{< manifest >}} requires a path= named argument at %s" .Position -}} +{{- end -}} + +{{- $segments := split .Page.File.Path "/" -}} +{{- $version := index $segments 0 -}} + +{{- $contentPath := printf "content/%s/manifests/%s" $version $rel -}} + +{{- if not (fileExists $contentPath) -}} + {{- errorf "{{< manifest >}}: file not found at %s (referenced from %s)" $contentPath .Position -}} +{{- end -}} + +{{- $body := readFile $contentPath -}} +{{- $label := .Get "label" -}} +{{- $apply := .Get "apply" | default "true" -}} +{{- $command := .Get "command" | default "kubectl apply -f" -}} + +{{- $url := printf "%s/manifests/%s" $version $rel | absURL -}} +{{- $applyCmd := printf "%s %s" $command $url -}} + +{{- $highlightOpts := "linenos=table,linenostart=1" -}} + +
+ {{- if $label -}} +
+ {{- highlight $body "yaml" $highlightOpts -}} +
+ {{- else -}} + {{- highlight $body "yaml" $highlightOpts -}} + {{- end -}} + {{- if not (eq $apply "false") -}} + {{- highlight $applyCmd "shell" "" -}} + {{- end -}} +
diff --git a/utils/vale/.vale.ini b/utils/vale/.vale.ini index d3923723d..8de46eb58 100644 --- a/utils/vale/.vale.ini +++ b/utils/vale/.vale.ini @@ -49,7 +49,7 @@ Microsoft.GeneralURL = NO # Ignore markdown image with alt: ![ # ignore v#.## # ignore ##px -TokenIgnores = (\(#.*\)),(\]\(),(http.*),({{<\s*\/?expand),(\!\[),(\d.*px),(\d\.\d\.\d) +TokenIgnores = (\(#.*\)),(\]\(),(http.*),({{<\s*\/?expand),(\!\[),(\d.*px),(\d\.\d\.\d),(\{\{<[^>]+>\}\}) # ignore whole tags: # Ignore hugo tags