{"id":16179138,"url":"https://github.com/jonashackt/crossplane-aws-azure","last_synced_at":"2025-03-19T01:30:55.095Z","repository":{"id":37411564,"uuid":"505728419","full_name":"jonashackt/crossplane-aws-azure","owner":"jonashackt","description":"Example project showing how to get started with Crossplane to e.g. provision a AWS S3Bucket \u0026 Azure StorageAccount through K8s CRDs","archived":false,"fork":false,"pushed_at":"2024-10-23T17:27:21.000Z","size":3996,"stargazers_count":15,"open_issues_count":5,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-24T01:17:48.152Z","etag":null,"topics":["aws","azure","crossplane","kubernetes","kubernetes-crd","s3-bucket","storageaccount"],"latest_commit_sha":null,"homepage":"https://blog.codecentric.de/en/2022/07/crossplane/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonashackt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-21T07:05:20.000Z","updated_at":"2024-09-03T17:07:06.000Z","dependencies_parsed_at":"2023-01-22T22:45:52.220Z","dependency_job_id":"bde89708-17a6-44d1-a63c-96574ca281a7","html_url":"https://github.com/jonashackt/crossplane-aws-azure","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-aws-azure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-aws-azure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-aws-azure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-aws-azure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonashackt","download_url":"https://codeload.github.com/jonashackt/crossplane-aws-azure/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243960361,"owners_count":20375102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","azure","crossplane","kubernetes","kubernetes-crd","s3-bucket","storageaccount"],"created_at":"2024-10-10T05:25:34.784Z","updated_at":"2025-03-19T01:30:55.088Z","avatar_url":"https://github.com/jonashackt.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# crossplane-aws-azure\n[![provision-aws](https://github.com/jonashackt/crossplane-aws-azure/actions/workflows/provision-aws.yml/badge.svg)](https://github.com/jonashackt/crossplane-aws-azure/actions/workflows/provision-aws.yml)\n[![provision-azure](https://github.com/jonashackt/crossplane-aws-azure/actions/workflows/provision-azure.yml/badge.svg)](https://github.com/jonashackt/crossplane-aws-azure/actions/workflows/provision-azure.yml)\n![crossplane-version](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-aws-azure%2Fmain%2Fcrossplane-install%2FChart.yaml\u0026query=%24.dependencies%5B%3A1%5D.version\u0026label=crossplane\u0026color=blue)\n![provider-aws-s3](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-aws-azure%2Fmain%2Fupbound%2Fprovider-aws-s3%2Fconfig%2Fprovider-aws-s3.yaml\u0026query=%24.spec.package\u0026label=provider-aws-s3\u0026color=rgb(109%2C%20100%2C%20245))\n![provider-azure-storage](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-aws-azure%2Fmain%2Fupbound%2Fprovider-azure-storage%2Fconfig%2Fprovider-azure-storage.yaml\u0026query=%24.spec.package\u0026label=provider-azure-storage\u0026color=rgb(109%2C%20100%2C%20245))\n[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/jonashackt/crossplane-aws-azure/blob/master/LICENSE)\n[![renovateenabled](https://img.shields.io/badge/renovate-enabled-yellow)](https://renovatebot.com)\n\nExample project showing how to get started with Crossplane, connect it to multiple providers like AWS, Azure - and provision some resources like a S3Bucket or a StorageAccount through K8s CRDs\n\n\u003e This repo is accompanied by this blog post https://blog.codecentric.de/en/2022/07/crossplane/\n\nAlso it is used as an example in these three [iX Magazin](https://www.heise.de/ix/) issues (simply click on the magazine covers to access the full articles):\n\n[![](screenshots/ixmagazin_frontcover_02_2023.png)](https://www.codecentric.de/wissens-hub/fachmedien/crossplane-gitops-fuer-die-multi-cloud) [![](screenshots/ixmagazin_frontcover_03_2023.png)](https://www.codecentric.de/wissen/publikation/crossplane-provisionierung-in-aws-und-azure) ![](screenshots/ix_developer_autumn_2023.png)\n\nCrossplane https://crossplane.io/ claims to be the \"The cloud native control plane framework\". It introduces a new way how to manage any cloud resource (beeing it Kubernetes-native or not). It's an alternative Infrastructure-as-Code tooling to Terraform, AWS CDK/Bicep or Pulumi and introduces a higher level of abstraction - based on Kubernetes CRDs. \n\nLiterally the best intro post to Crossplane for me was https://blog.crossplane.io/crossplane-vs-cloud-infrastructure-addons/ - here the real key benefits especially compared to other tools are described. Without marketing blabla. If you love deep dives, I can also recommend Nate Reid's blog https://vrelevant.net/ who works as Staff Solutions Engineer at Upbound.\n\n\n# TLDR;\n\nHere are the brief steps to spin up Crossplane and provision an S3 Bucket on AWS (Azure is also possible):\n\n```shell\n## DEMO No.1\n\n# Create a kind cluster\nkind create cluster --image kindest/node:v1.31.1 --wait 5m\nkubectl get crd\n\n# Install Crossplane\nhelm dependency update crossplane-install\nhelm upgrade --install crossplane --namespace crossplane-system crossplane-install --create-namespace\nkubectl wait --for=condition=ready pod -l app=crossplane --namespace crossplane-system --timeout=120s\n\n# Check Crossplane is there \u0026 get CRDs before the Provider installation\nkubectl get all -n crossplane-system\nkubectl get crd\n\n# Install AWS Provider (Official Upbound Provider Family-based)\nkubectl apply -f upbound/provider-aws-s3/config/provider-aws-s3.yaml\nkubectl wait --for=condition=healthy --timeout=180s provider/upbound-provider-aws-s3\nkubectl get crd\nkubectl get crossplane\n\n# Configure AWS Provider \necho \"[default]\naws_access_key_id = $(aws configure get aws_access_key_id)\naws_secret_access_key = $(aws configure get aws_secret_access_key)\n\" \u003e aws-creds.conf\n\nkubectl create secret generic aws-creds -n crossplane-system --from-file=creds=./aws-creds.conf\n\nkubectl apply -f upbound/provider-aws-s3/config/provider-config-aws.yaml\n\n# Create Simple S3 Bucket (based on Crossplane Managed Resources only)\nkubectl apply -f upbound/provider-aws-s3/resources/simple-bucket.yaml\nkubectl get crossplane\nkubectl delete -f upbound/provider-aws-s3/resources/simple-bucket.yaml\n\n\n\n## DEMO No.2: \n\n# Create XRD\nkubectl apply -f upbound/provider-aws-s3/definition.yaml\nkubectl wait --for=condition=Established --timeout=120s xrd xobjectstorages.crossplane.jonashackt.io  \nkubectl get xrd\n\n# Create Composition\nkubectl apply -f upbound/provider-aws-s3/composition.yaml\nkubectl get composition\n\n# Create Claim - which will provision the S3 Bucket\nkubectl apply -f upbound/provider-aws-s3/claim.yaml\ncrossplane beta trace objectstorage.crossplane.jonashackt.io/managed-upbound-s3 -o wide\nkubectl get crossplane\nkubectl get claim\nkubectl get composite\n\n# Upload a static website (e.g. \"App\")\naws s3 sync static s3://container-conf-bucket --acl public-read\n# Open Browser at http://crossplane-meetup-softwerkskammer.s3-website.eu-central-1.amazonaws.com\naws s3 rm s3://container-conf-bucket/index.html\n\n# don't forget to delete the Claim\nkubectl delete -f upbound/provider-aws-s3/claim.yaml\n```\n\n\n# Crossplane basic concepts\n\nhttps://docs.crossplane.io/latest/concepts/\n\n* [Managed Resourced (MR)](https://crossplane.io/docs/v1.8/concepts/managed-resources.html): Kubernetes custom resources (CRDs) that represent infrastructure primitives (mostly in cloud providers). All Crossplane Managed Resources could be found via https://doc.crds.dev/ \n* [Composite Resources (XR)](https://crossplane.io/docs/v1.8/concepts/composition.html): compose Managed Resources into higher level infrastructure units (especially interesting for platform teams). They are defined by:\n    * a `CompositeResourceDefinition` (XRD) (which defines an OpenAPI schema the `Composition` needs to be conform to)\n    * (optional) `CompositeResourceClaims` (XRC) (which is an abstraction of the XR for the application team to consume) - but is fantastic to hold the exact configuration parameters for the concrete resources you want to provision\n    * a `Composition` that describes the actual infrastructure primitives aka `Managed Resources` used to build the Composite Resource. One XRD could have multiple Compositions - e.g. to one for every environment like development, stating and production\n    * and configured by a `Configuration`\n* [Packages](https://crossplane.io/docs/v1.8/concepts/packages.html): OCI container images to handle distribution, version updates, dependency management \u0026 permissions for Providers \u0026 Configurations. Packages were formerly named `Stacks`.\n    * [Providers](https://crossplane.io/docs/v1.8/concepts/providers.html): are Packages that bundle a set of Managed Resources \u0026 __a Controller to provision infrastructure resources__ - all providers can be found on GitHub, e.g. [provider-aws](https://github.com/crossplane-contrib/provider-aws) or on [docs.crds.dev](https://doc.crds.dev/github.com/crossplane/provider-aws). A [list of all available Providers](https://github.com/orgs/crossplane-contrib/repositories?type=all) can also be found on GitHub.\n    * [Configuration](https://crossplane.io/docs/v1.8/getting-started/create-configuration.html): define your own Composite Resources (XRs) \u0026 package them via `kubectl crossplane build configuration` (now they are a Package) - and push them to an OCI registry via `kubectl crossplane push configuration`. With this Configurations can also be easily installed into other Crossplane clusters.\n\n\n![composition-how-it-works](screenshots/composition-how-it-works.svg)\n\n\n# Getting started with Crossplane\n\nIn order to use Crossplane we'll need any kind of Kubernetes cluster to let it operate in. This management cluster with Crossplane installed will then provision the defined infrastructure. Using any managed Kubernetes cluster like EKS, AKS and so on is possible - or even a local [Minikube](https://minikube.sigs.k8s.io/docs/start/), [kind](https://kind.sigs.k8s.io) or [k3d](https://k3d.io/).\n\n\n## Install prerequisites \u0026 fire up a K8s cluster with kind\n\nhttps://crossplane.io/docs/v1.8/getting-started/install-configure.html\n\nBe sure to have kind, the package manager Helm and kubectl installed:\n\n```shell\nbrew install kind helm kubectl\n```\n\nhttps://docs.crossplane.io/latest/cli/\n\nAlso we should install the crossplane CLI\n\n```shell\ncurl -sL \"https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh\" | sh\nsudo mv crossplane /usr/local/bin\n```\n\nNow the `kubectl crossplane --help` command should be ready to use.\n\n\nNow spin up a local kind cluster\n\n```shell\nkind create cluster --image kindest/node:v1.29.2 --wait 5m\n```\n\n\n## Install Crossplane with Helm\n\n### Plain via Helm\n\nThe Crossplane docs [tell us to use Helm for installation](https://docs.crossplane.io/latest/software/install/):\n\n```shell\nhelm repo add crossplane-stable https://charts.crossplane.io/stable\nhelm repo update\n\nhelm upgrade --install crossplane --namespace crossplane-system --create-namespace crossplane-stable/crossplane\n```\n\nUsing the appended `--create-namespace`, we don't need to explicitely create the namespace before running `helm upgrade`.\n\n\n### Renovate-powered installation via local Helm Chart\n\nAs an Renovate-powered alternative we can [create our own simple [Chart.yaml](crossplane-install/Chart.yaml) to enable automatic updates](https://stackoverflow.com/a/71765472/4964553) of our installation if new crossplane versions get released:\n\n```yaml\napiVersion: v2\ntype: application\nname: crossplane\nversion: 0.0.0 # unused\nappVersion: 0.0.0 # unused\ndependencies:\n  - name: crossplane\n    repository: https://charts.crossplane.io/stable\n    version: 1.15.1\n```\n\nTo install Crossplane using our own `Chart.yaml` simply run:\n\n```shell\nhelm dependency update crossplane-install\nhelm upgrade --install crossplane --namespace crossplane-system crossplane-install --create-namespace\n```\n\nBe sure to exclude `charts` and `Chart.lock` files via [.gitignore](.gitignore).\n\n```shell\n# Exclude Helm charts lock and packages\n**/**/charts\n**/**/Chart.lock\n```\n\n\n### Check Crossplane installation\n\nCheck Crossplane version installed with `helm list -n crossplane-system` :\n\n```shell\n$ helm list -n crossplane-system\nNAME      \tNAMESPACE        \tREVISION\tUPDATED                              \tSTATUS  \tCHART           \tAPP VERSION\ncrossplane\tcrossplane-system\t1       \t2022-06-21 09:28:21.178357 +0200 CEST\tdeployed\tcrossplane-1.8.1\t1.8.1\n```\n\nBefore we can actually apply a Provider we have to make sure that crossplane is actually healthy and running. Therefore we can use the `kubectl wait` command like this:\n\n```shell\nkubectl wait --for=condition=ready pod -l app=crossplane --namespace crossplane-system --timeout=120s\n```\n\nOtherwise we will run into errors like this when applying a `Provider`:\n\n```shell\nerror: resource mapping not found for name: \"provider-aws\" namespace: \"\" from \"provider-aws.yaml\": no matches for kind \"Provider\" in version \"pkg.crossplane.io/v1\"\nensure CRDs are installed first\n```\n\nFinally check crossplane status with `kubectl get all -n crossplane-system`:\n\n```shell\n$ kubectl get all -n crossplane-system\nNAME                                           READY   STATUS    RESTARTS   AGE\npod/crossplane-7c88c45998-d26wl                1/1     Running   0          69s\npod/crossplane-rbac-manager-8466dfb7fc-db9rb   1/1     Running   0          69s\n\nNAME                                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE\nservice/crossplane-webhooks              ClusterIP   10.96.160.156   \u003cnone\u003e        9443/TCP   4d\n\nNAME                                      READY   UP-TO-DATE   AVAILABLE   AGE\ndeployment.apps/crossplane                1/1     1            1           69s\ndeployment.apps/crossplane-rbac-manager   1/1     1            1           69s\n\nNAME                                                 DESIRED   CURRENT   READY   AGE\nreplicaset.apps/crossplane-7c88c45998                1         1         1       69s\nreplicaset.apps/crossplane-rbac-manager-8466dfb7fc   1         1         1       69s\n```\n\n\n\u003e \"The base Crossplane installation consists of two pods, the crossplane pod and the crossplane-rbac-manager pod. Before starting the core Crossplane container an init container runs. The init container installs the core Crossplane Custom Resource Definitions (CRDs), configures Crossplane webhooks and installs any supplied Providers or Configurations.\"\" See https://docs.crossplane.io/latest/concepts/pods/\n\n\nNow we should be able to find some new Kubernetes API objects:\n\n```shell\n$ kubectl api-resources  | grep crossplane\ncompositeresourcedefinitions               xrd,xrds     apiextensions.crossplane.io/v1         false        CompositeResourceDefinition\ncompositionrevisions                       comprev      apiextensions.crossplane.io/v1         false        CompositionRevision\ncompositions                               comp         apiextensions.crossplane.io/v1         false        Composition\nenvironmentconfigs                         envcfg       apiextensions.crossplane.io/v1alpha1   false        EnvironmentConfig\nusages                                                  apiextensions.crossplane.io/v1alpha1   false        Usage\nconfigurationrevisions                                  pkg.crossplane.io/v1                   false        ConfigurationRevision\nconfigurations                                          pkg.crossplane.io/v1                   false        Configuration\ncontrollerconfigs                                       pkg.crossplane.io/v1alpha1             false        ControllerConfig\ndeploymentruntimeconfigs                                pkg.crossplane.io/v1beta1              false        DeploymentRuntimeConfig\nfunctionrevisions                                       pkg.crossplane.io/v1beta1              false        FunctionRevision\nfunctions                                               pkg.crossplane.io/v1beta1              false        Function\nlocks                                                   pkg.crossplane.io/v1beta1              false        Lock\nproviderrevisions                                       pkg.crossplane.io/v1                   false        ProviderRevision\nproviders                                               pkg.crossplane.io/v1                   false        Provider\nstoreconfigs                                            secrets.crossplane.io/v1alpha1         false        StoreConfig\n```\n\n\n\n\n\n# Configure Crossplane to access AWS\n\nhttps://docs.crossplane.io/latest/getting-started/provider-aws/\n\n\n### Choosing a Crossplane AWS Provider\n\nCurrently there are two crossplane providers for AWS:\n\n* https://github.com/crossplane-contrib/provider-aws (legacy now)\n* https://github.com/upbound/provider-aws\n\nThe first is written \"by hand\" and supports around 200 Managed Resources and the other is generated using Upjet supporting the full AWS API with 924 Managed Resources. \n\nThe second provider, which is also called the Upbound \"official\" provider has been donated to the OpenSource community in September 2023 - so there are now 2 OpenSource providers for AWS. \n\nWhich one should you choose? [This issue clarifies it](https://github.com/crossplane-contrib/provider-aws/issues/1954#issuecomment-1862593913):\n\n\u003e \"Upbound (the company behind crossplane) has moved to its own Terraform-based set of providers. This means that https://github.com/crossplane-contrib/provider-aws is now only maintained by community volunteers since there is a number of people out there who are still using it. __It is kind of legacy but it will receive further updates as long as the community is willing to contribute to it.__ \n\nUntil June 2023 the Upbound providers had a problem. As Managed Resources are CRDs, the Upbound provider introduced a problem to most control planes, since Kubernetes wasn't designed for that amount of CRDs. I gave [a talk about crossplane at our local DevOps Meetup](https://www.meetup.com/de-DE/devopsthde/events/293211158/) and already struggled with the Upbound AWS provider.\n\nTherefore in June 2023 the Upjet generated official Upbound provider has been split into Provider Families: https://blog.crossplane.io/crd-scaling-provider-families/ These split up Upbound providers are the way to go if you start using crossplane today:\n\n![](screenshots/provider-family-transition.png)\n\nThe [migration guide](https://docs.upbound.io/providers/migration/) also states that the monolithic provider is already deprecated:\n\n\u003e \"Warning: The monolithic AWS provider (upbound/provider-aws) has been deprecated in favor of the AWS provider family. You can read more about the provider families in our blog post and the official documentation for the provider families is here. We will continue support for the monolithic AWS provider until June 12, 2024. https://marketplace.upbound.io/providers/upbound/provider-aws/\"\n\nSo head over to [the Upbound marketplace](https://marketplace.upbound.io/) and search for your provider familiy. As we want to use AWS S3 for example, here we went with https://marketplace.upbound.io/providers/upbound/provider-aws-s3.\n\n\n\n### Using the official Upbound (Upjet generated) AWS Provider\n\nhttps://marketplace.upbound.io/providers/upbound/provider-aws-s3\n\nNow we can install the Upbound AWS Provider. Therefore the [upbound/provider-aws-s3/config/provider-aws-s3.yaml](upbound/provider-aws-s3/config/provider-aws-s3.yaml) looks only slightly different compared to the classic AWS Provider - only the `spec.package` changed:\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: upbound-provider-aws-s3\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-s3:v0.46.0\n  packagePullPolicy: Always\n  revisionActivationPolicy: Automatic\n  revisionHistoryLimit: 1\n```\n\nInstall it via `kubectl`:\n\n\n```shell\nkubectl apply -f upbound/provider-aws-s3/config/provider-aws-s3.yaml\n```\n\nThe `package` version in combination with the `packagePullPolicy` configuration here is crucial, since we can configure an update strategy for the Provider here. ~~I'am not sure, if the Crossplane team will provide an installation method where we can use tools like Renovate to keep our Crossplane providers up to date~~ (now Renovate supports Crossplane, see paragraph `Provider \u0026 Configuration Package Upgrades with Renovate`). A full table of all possible fields can be found in the docs: https://crossplane.io/docs/v1.8/concepts/packages.html#specpackagepullpolicy We can also let crossplane itself manage new versions for us. But we also leave the GitOps way here!\n\nMultiple Provider Package revisions can also be installed at the same time (especially when using `packagePullPolicy: Always`). If you installed multiple package versions, you'll see them as `providerrevision.pkg.x` when running `kubectl get crossplane`:\n\n```shell\n$ kubectl get crossplane\n...\nNAME                                                                             HEALTHY   REVISION   IMAGE                                                   STATE      DEP-FOUND   DEP-INSTALLED   AGE\nproviderrevision.pkg.crossplane.io/upbound-provider-aws-s3-4c95b368de88          True      1          xpkg.upbound.io/upbound/provider-aws-s3:v1.2.1          Active     1           1               3d23h\nproviderrevision.pkg.crossplane.io/upbound-provider-aws-s3-d6a6663caff3          True      1          xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0          Inactive   1           1               5d22h\n...\n```\n\nAs our first Crossplane Provider has been installed. We need to wait for the Provider to become healthy:\n\n```shell\nkubectl wait \"providers.pkg.crossplane.io/upbound-provider-aws-s3\" --for=condition=Healthy --timeout=180s\n```\n\nObtain the status via `kubectl get provider.pkg.crossplane.io`:\n\n```shell\n$ kubectl get provider.pkg.crossplane.io\nNAME                          INSTALLED   HEALTHY   PACKAGE                                               AGE\nupbound-provider-aws-s3       True        True      xpkg.upbound.io/upbound/provider-aws-s3:v0.46.0       113s\nupbound-provider-family-aws   True        True      xpkg.upbound.io/upbound/provider-family-aws:v0.46.0   108s\n```\n\nOtherwise we may run into errors like this when applying the `ProviderConfig` right after the Provider.\n\n\n\n\n### Create aws-creds.conf file\n\nI assume here that you have [aws CLI installed and configured](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). So that the command `aws configure` should work on your system.\n\nWith this prepared [we can create an `aws-creds.conf` file](https://docs.crossplane.io/latest/getting-started/provider-aws/#generate-an-aws-key-pair-file):\n\n```shell\necho \"[default]\naws_access_key_id = $(aws configure get aws_access_key_id)\naws_secret_access_key = $(aws configure get aws_secret_access_key)\n\" \u003e aws-creds.conf\n```\n\n\u003e Don't ever check this file into source control - it holds your AWS credentials! Add it to your [.gitignore](.gitignore) file right now:\n\n```shell\n# Exclude credential configuration files like aws-creds.conf\n*-creds.conf\n```\n\nIf you're using a CI system like GitHub Actions (as this repository is based on), you need to have both `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` configured as Repository Secrets:\n\n![github-actions-secrets](screenshots/github-actions-secrets.png)\n\nAlso make sure to have your `Default region` configured locally - or as a `env:` variable in your CI system. All three needed variables [in GitHub Actions](.github/workflows/provision.yml) for example look like this:\n\n```yaml\nenv:\n  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n  AWS_DEFAULT_REGION: 'eu-central-1'\n```\n\n\n### Create AWS Provider secret\n\nNow we need to use the `aws-creds.conf` file to create the Crossplane AWS Provider secret:\n\n```shell\nkubectl create secret generic aws-creds -n crossplane-system --from-file=creds=./aws-creds.conf\n```\n\nIf everything went well there should be a new `aws-creds` Secret ready:\n\n![provider-aws-secret](screenshots/provider-aws-secret.png)\n\n\n\n\n### Create ProviderConfig to consume the Secret containing AWS credentials\n\nTo get our Provider finally working we also need to create a `ProviderConfig` accordingly that will tell the Provider where to find it's AWS credentials. Therefore we create a [upbound/provider-aws-s3/config/provider-config-aws.yaml](upbound/provider-aws-s3/config/provider-config-aws.yaml):\n\n```yaml\napiVersion: aws.upbound.io/v1beta1\nkind: ProviderConfig\nmetadata:\n  name: default\nspec:\n  credentials:\n    source: Secret\n    secretRef:\n      namespace: crossplane-system\n      name: aws-creds\n      key: creds\n```\n\n\u003e Crossplane resources use the `ProviderConfig` named `default` if no specific ProviderConfig is specified, so this ProviderConfig will be the default for all AWS resources.\n\nThe `secretRef.name` and `secretRef.key` has to match the fields of the already created Secret.\n\nApply it via `kubectl`:\n\n\n```shell\nkubectl apply -f upbound/provider-aws-s3/config/provider-config-aws.yaml\n```\n\nNow we should have everything in place to use the Upbound AWS Provider! We can double check via `kubectl get crossplane`:\n\n```shell\n$ kubectl get crossplane\nNAME                                    AGE\nproviderconfig.aws.upbound.io/default   55s\n\nNAME                                                                             HEALTHY   REVISION   IMAGE                                                   STATE      DEP-FOUND   DEP-INSTALLED   AGE\nproviderrevision.pkg.crossplane.io/upbound-provider-aws-s3-4c95b368de88          True      1          xpkg.upbound.io/upbound/provider-aws-s3:v1.2.1          Active     1           1               3d23h\n\nNAME                                                        INSTALLED   HEALTHY   PACKAGE                                                 AGE\nprovider.pkg.crossplane.io/upbound-provider-aws-s3          True        True      xpkg.upbound.io/upbound/provider-aws-s3:v1.2.1          3d23h\n\nNAME                                        AGE   TYPE         DEFAULT-SCOPE\nstoreconfig.secrets.crossplane.io/default   4d    Kubernetes   crossplane-system\n```\n\n\n\nNow we should have some more Kubernetes API resources available:\n\n```shell\n$ kubectl api-resources | grep aws\nproviderconfigs                                         aws.upbound.io/v1beta1                 false        ProviderConfig\nproviderconfigusages                                    aws.upbound.io/v1beta1                 false        ProviderConfigUsage\nstoreconfigs                                            aws.upbound.io/v1alpha1                false        StoreConfig\nbucketaccelerateconfigurations                          s3.aws.upbound.io/v1beta1              false        BucketAccelerateConfiguration\nbucketacls                                              s3.aws.upbound.io/v1beta1              false        BucketACL\nbucketanalyticsconfigurations                           s3.aws.upbound.io/v1beta1              false        BucketAnalyticsConfiguration\nbucketcorsconfigurations                                s3.aws.upbound.io/v1beta1              false        BucketCorsConfiguration\nbucketintelligenttieringconfigurations                  s3.aws.upbound.io/v1beta1              false        BucketIntelligentTieringConfiguration\nbucketinventories                                       s3.aws.upbound.io/v1beta1              false        BucketInventory\nbucketlifecycleconfigurations                           s3.aws.upbound.io/v1beta1              false        BucketLifecycleConfiguration\nbucketloggings                                          s3.aws.upbound.io/v1beta1              false        BucketLogging\nbucketmetrics                                           s3.aws.upbound.io/v1beta1              false        BucketMetric\nbucketnotifications                                     s3.aws.upbound.io/v1beta1              false        BucketNotification\nbucketobjectlockconfigurations                          s3.aws.upbound.io/v1beta1              false        BucketObjectLockConfiguration\nbucketobjects                                           s3.aws.upbound.io/v1beta1              false        BucketObject\nbucketownershipcontrols                                 s3.aws.upbound.io/v1beta1              false        BucketOwnershipControls\nbucketpolicies                                          s3.aws.upbound.io/v1beta1              false        BucketPolicy\nbucketpublicaccessblocks                                s3.aws.upbound.io/v1beta1              false        BucketPublicAccessBlock\nbucketreplicationconfigurations                         s3.aws.upbound.io/v1beta1              false        BucketReplicationConfiguration\nbucketrequestpaymentconfigurations                      s3.aws.upbound.io/v1beta1              false        BucketRequestPaymentConfiguration\nbuckets                                                 s3.aws.upbound.io/v1beta1              false        Bucket\nbucketserversideencryptionconfigurations                s3.aws.upbound.io/v1beta1              false        BucketServerSideEncryptionConfiguration\nbucketversionings                                       s3.aws.upbound.io/v1beta1              false        BucketVersioning\nbucketwebsiteconfigurations                             s3.aws.upbound.io/v1beta1              false        BucketWebsiteConfiguration\nobjectcopies                                            s3.aws.upbound.io/v1beta1              false        ObjectCopy\nobjects                                                 s3.aws.upbound.io/v1beta1              false        Object\n```\n\n\n\n\n\n\n# Provision a S3 Bucket with Crossplane\n\n__The crossplane core Controller and the Provider AWS Controller should now be ready to provision any infrastructure component in AWS!__\n\nYou heard right: we don't create a Kubernetes based infrastructure component - but we start with a simple S3 Bucket.\n\nTherefore we can have a look into the Crossplane AWS provider API docs:https://doc.crds.dev/github.com/upbound/provider-aws/s3.aws.upbound.io/Bucket/v1beta1@v0.45.0 \n\n\n## CompositeResourceDefinitions (or XRDs)\n\nhttps://docs.crossplane.io/latest/concepts/composite-resource-definitions/\n\n\u003e A CompositeResourceDefinition (or XRD) defines the type and schema of your XR. It lets Crossplane know that you want a particular kind of XR to exist, and what fields that XR should have.\n\nSince defining your own CompositeResourceDefinitions and Compositions is the main work todo with Crossplane, it's always good to know the full Reference documentation which can be found here https://docs.crossplane.io/latest/concepts/compositions/\n\nOne of the things to know is that Crossplane automatically injects some common 'machinery' into the manifests of the XRDs and Compositions: https://docs.crossplane.io/latest/concepts/composite-resources/ \n\n\n\n### Defining a CompositeResourceDefinition (XRD) for our S3 Bucket\n\nAll possible fields an XRD can have [are documented in the docs](https://docs.crossplane.io/latest/concepts/composite-resource-definitions/).\n\nThe field `spec.versions.schema` must contain a OpenAPI schema, which is similar to the ones used by any Kubernetes CRDs. They determine what fields the XR (and Claim) will have. The full CRD documentation and a guide on how to write OpenAPI schemas [could be found in the Kubernetes docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/).\n\nNote that Crossplane will be automatically extended this section. Therefore the following fields are used by Crossplane and will be ignored if they're found in the schema:\n\n    spec.resourceRef\n    spec.resourceRefs\n    spec.claimRef\n    spec.writeConnectionSecretToRef\n    status.conditions\n    status.connectionDetails\n\n\nSo our Composite Resource Definition (XRD) for our S3 Bucket could look like [crossplane-contrib/provider-aws/s3/definition.yaml](crossplane-contrib/provider-aws/s3/definition.yaml):\n\n```yaml\napiVersion: apiextensions.crossplane.io/v1\nkind: CompositeResourceDefinition\nmetadata:\n  # XRDs must be named 'x\u003cplural\u003e.\u003cgroup\u003e'\n  name: xobjectstorages.crossplane.jonashackt.io\nspec:\n  # This XRD defines an XR in the 'crossplane.jonashackt.io' API group.\n  # The XR or Claim must use this group together with the spec.versions[0].name as it's apiVersion, like this:\n  # 'crossplane.jonashackt.io/v1alpha1'\n  group: crossplane.jonashackt.io\n  \n  # XR names should always be prefixed with an 'X'\n  names:\n    kind: XObjectStorage\n    plural: xobjectstorages\n  # This type of XR offers a claim, which should have the same name without the 'X' prefix\n  claimNames:\n    kind: ObjectStorage\n    plural: objectstorages\n  \n  # default Composition when none is specified (must match metadata.name of a provided Composition)\n  # e.g. in composition.yaml\n  defaultCompositionRef:\n    name: objectstorage-composition\n\n  versions:\n  - name: v1alpha1\n    served: true\n    referenceable: true\n    # OpenAPI schema (like the one used by Kubernetes CRDs). Determines what fields\n    # the XR (and claim) will have. Will be automatically extended by crossplane.\n    # See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/\n    # for full CRD documentation and guide on how to write OpenAPI schemas\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            # We define 2 needed parameters here one has to provide as XR or Claim spec.parameters\n            properties:\n              parameters:\n                type: object\n                properties:\n                  bucketName:\n                    type: string\n                  region:\n                    type: string\n                required:\n                  - bucketName\n                  - region\n```\n\nInstall the XRD into our cluster with:\n\n```shell\nkubectl apply -f crossplane-contrib/provider-aws/s3/definition.yaml\n```\n\nWe can double check the CRDs beeing created with `kubectl get crds` and filter them using `grep` to our group name `crossplane.jonashackt.io`:\n\n```shell\n$ kubectl get crds | grep crossplane.jonashackt.io\nobjectstorages.crossplane.jonashackt.io                         2022-06-27T09:54:18Z\nxobjectstorages.crossplane.jonashackt.io                        2022-06-27T09:54:18Z\n```\n\n\n### Craft a Composition to manage our needed cloud resources\n\nThe main work in Crossplane has to be done crafting the Compositions. This is because they interact with the infrastructure primitives the cloud provider APIs provide.\n\nDetailled docs to many of the possible manifest configurations can be found here https://docs.crossplane.io/latest/concepts/compositions/\n\n\nhttps://github.com/aws/aws-cdk/issues/25288#issuecomment-1522011311\n\nhttps://doc.crds.dev/github.com/crossplane/provider-aws/s3.aws.crossplane.io/Bucket/v1beta1@v0.39.0\n\nhttps://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html\n\nhttps://stackoverflow.com/questions/76097031/aws-s3-bucket-cannot-have-acls-set-with-objectownerships-bucketownerenforced-s \n\nhttps://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/BucketPublicAccessBlock/v1beta1 \n\n\nAccording to https://github.com/hashicorp/terraform-provider-aws/issues/28353 and https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl we need to separate `Bucket` creation from `BucketPublicAccessBlock`, `BucketOwnershipControls` and `BucketACL` - which should now be available finally leveraging the Upbound AWS Provider:\n\n```yaml\napiVersion: apiextensions.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: objectstorage-composition\n  labels:\n    # An optional convention is to include a label of the XRD. This allows\n    # easy discovery of compatible Compositions.\n    crossplane.io/xrd: xobjectstorages.crossplane.jonashackt.io\n    # The following label marks this Composition for AWS. This label can \n    # be used in 'compositionSelector' in an XR or Claim.\n    provider: aws\nspec:\n  # Each Composition must declare that it is compatible with a particular type\n  # of Composite Resource using its 'compositeTypeRef' field. The referenced\n  # version must be marked 'referenceable' in the XRD that defines the XR.\n  compositeTypeRef:\n    apiVersion: crossplane.jonashackt.io/v1alpha1\n    kind: XObjectStorage\n  \n  # When an XR is created in response to a claim Crossplane needs to know where\n  # it should create the XR's connection secret. This is configured using the\n  # 'writeConnectionSecretsToNamespace' field.\n  writeConnectionSecretsToNamespace: crossplane-system\n  \n  # Each Composition must specify at least one composed resource template.\n  resources:\n    # Providing a unique name for each entry is good practice.\n    # Only identifies the resources entry within the Composition. Required in future crossplane API versions.\n    - name: bucket\n      base:\n        # see https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/Bucket/v1beta1\n        apiVersion: s3.aws.upbound.io/v1beta1\n        kind: Bucket\n        metadata: {}\n        spec:\n          deletionPolicy: Delete\n      \n      patches:\n        - fromFieldPath: \"spec.parameters.bucketName\"\n          toFieldPath: \"metadata.name\"\n        - fromFieldPath: \"spec.parameters.region\"\n          toFieldPath: \"spec.forProvider.region\"\n\n    - name: bucketpublicaccessblock\n      base:\n        # see https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/BucketPublicAccessBlock/v1beta1\n        apiVersion: s3.aws.upbound.io/v1beta1\n        kind: BucketPublicAccessBlock\n        spec:\n          forProvider:\n            blockPublicAcls: false\n            blockPublicPolicy: false\n            ignorePublicAcls: false\n            restrictPublicBuckets: false\n\n      patches:\n        - fromFieldPath: \"spec.parameters.bucketPABName\"\n          toFieldPath: \"metadata.name\"\n        - fromFieldPath: \"spec.parameters.bucketName\"\n          toFieldPath: \"spec.forProvider.bucketRef.name\"\n        - fromFieldPath: \"spec.parameters.region\"\n          toFieldPath: \"spec.forProvider.region\"\n\n    - name: bucketownershipcontrols\n      base:\n        # see https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/BucketOwnershipControls/v1beta1#doc:spec-forProvider-rule-objectOwnership\n        apiVersion: s3.aws.upbound.io/v1beta1\n        kind: BucketOwnershipControls\n        spec:\n          forProvider:\n            rule:\n              - objectOwnership: ObjectWriter\n\n      patches:\n        - fromFieldPath: \"spec.parameters.bucketOSCName\"\n          toFieldPath: \"metadata.name\"\n        - fromFieldPath: \"spec.parameters.bucketName\"\n          toFieldPath: \"spec.forProvider.bucketRef.name\"\n        - fromFieldPath: \"spec.parameters.region\"\n          toFieldPath: \"spec.forProvider.region\"\n\n    - name: bucketacl\n      base:\n        # see https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/BucketACL/v1beta1\n        apiVersion: s3.aws.upbound.io/v1beta1\n        kind: BucketACL\n        spec:\n          forProvider:\n            acl: \"public-read\"\n\n      patches:\n        - fromFieldPath: \"spec.parameters.bucketAclName\"\n          toFieldPath: \"metadata.name\"\n        - fromFieldPath: \"spec.parameters.bucketName\"\n          toFieldPath: \"spec.forProvider.bucketRef.name\"\n        - fromFieldPath: \"spec.parameters.region\"\n          toFieldPath: \"spec.forProvider.region\"\n  \n    - name: bucketwebsiteconfiguration\n      base:\n        # see https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/resources/s3.aws.upbound.io/BucketWebsiteConfiguration/v1beta1\n        apiVersion: s3.aws.upbound.io/v1beta1\n        kind: BucketWebsiteConfiguration\n        spec:\n          forProvider:\n            indexDocument:\n              - suffix: index.html\n\n      patches:\n        - fromFieldPath: \"spec.parameters.bucketWebConfName\"\n          toFieldPath: \"metadata.name\"\n        - fromFieldPath: \"spec.parameters.bucketName\"\n          toFieldPath: \"spec.forProvider.bucketRef.name\"\n        - fromFieldPath: \"spec.parameters.region\"\n          toFieldPath: \"spec.forProvider.region\"\n\n  # If you find yourself repeating patches a lot you can group them as a named\n  # 'patch set' then use a PatchSet type patch to reference them.\n  #patchSets:\n```\n\n\nLet's create Composition via:\n\n```shell\nkubectl apply -f upbound/provider-aws-s3/composition.yaml\n```\n\n\n\n\n### Craft a Composite Resource (XR) or Claim (XRC)\n\nCrossplane could look quite intimidating when having a first look. There are few guides around to show how to approach a setup when using Crossplane the first time. You can choose between writing an XR __OR__ XRC! You don't need both, since the XR will be generated from the XRC, if you choose to craft a XRC.\n\nhttps://docs.crossplane.io/v1.14/concepts/composite-resources/\n\nSince we want to create a S3 Bucket, here's an suggestion for an [claim.yaml](crossplane-contrib/provider-aws/s3/claim.yaml):\n\n```yaml\n# Use the spec.group/spec.versions[0].name defined in the XRD\napiVersion: crossplane.jonashackt.io/v1alpha1\nkind: ObjectStorage\nmetadata:\n  # Only claims are namespaced, unlike XRs.\n  namespace: default\n  name: managed-s3\nspec:\n  # The compositionRef specifies which Composition this XR will use to compose\n  # resources when it is created, updated, or deleted.\n  compositionRef:\n    name: objectstorage-composition\n  \n  # Parameters for the Composition to provide the Managed Resources (MR) with\n  # to create the actual infrastructure components\n  parameters:\n    bucketName: microservice-ui-nuxt-js-static-bucket2\n    region: eu-central-1\n```\n\n\nTestdrive with:\n\n```shell\nkubectl apply -f crossplane-contrib/provider-aws/s3/claim.yaml\n```\n\nWhen somthing goes wrong with the validation, this could look like this:\n\n```shell\n$ kubectl apply -f claim.yaml\nerror: error validating \"claim.yaml\": error validating data: [ValidationError(S3Bucket.metadata): unknown field \"crossplane.io/external-name\" in io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta_v2, ValidationError(S3Bucket.spec): unknown field \"parameters\" in io.jonashackt.crossplane.v1alpha1.S3Bucket.spec, ValidationError(S3Bucket.spec.writeConnectionSecretToRef): missing required field \"namespace\" in io.jonashackt.crossplane.v1alpha1.S3Bucket.spec.writeConnectionSecretToRef, ValidationError(S3Bucket.spec): missing required field \"bucketName\" in io.jonashackt.crossplane.v1alpha1.S3Bucket.spec, ValidationError(S3Bucket.spec): missing required field \"region\" in io.jonashackt.crossplane.v1alpha1.S3Bucket.spec]; if you choose to ignore these errors, turn validation off with --validate=false\n```\n\nThe Crossplane validation is a great way to debug your yaml configuration - it hints you to the actual problems that are still present.\n\n\n\n\n### Troubleshooting your crossplane configuration\n\nhttps://docs.crossplane.io/knowledge-base/guides/troubleshoot/\n\n\u003e Per Kubernetes convention, Crossplane keeps errors close to the place they happen. This means that if your claim is not becoming ready due to an issue with your Composition or with a composed resource you’ll need to “follow the references” to find out why. Your claim will only tell you that the XR is not yet ready.\n\n\nThe docs also tell us what they mean by \"follow the references\":\n\n* Find your XR by running `kubectl describe \u003cclaim-kind\u003e \u003cclaim-metadata.name\u003e` and look for its “Resource Ref” (aka `spec.resourceRef`).\n* Run `kubectl describe` on your XR. This is where you’ll find out about issues with the Composition you’re using, if any.\n* If there are no issues but your XR doesn’t seem to be becoming ready, take a look for the “Resource Refs” (or `spec.resourceRefs`) to find your composed resources.\n* Run `kubectl describe` on each referenced composed resource to determine whether it is ready and what issues, if any, it is encountering.\n\n\n\n\n\n### Waiting for resources to become ready\n\nThere are some possible things to check while your resources (may) get deployed after running a `kubectl apply -f claim.yaml` (see https://crossplane.io/docs/v1.8/getting-started/provision-infrastructure.html#claim-your-infrastructure).\n\nThe best overview gives a `kubectl get crossplane` which will simply list all the crossplane resources:\n\n```shell\n$ kubectl get crossplane\nWarning: Please use v1beta1 version of this resource that has identical schema.\nNAME                                                                                          ESTABLISHED   OFFERED   AGE\ncompositeresourcedefinition.apiextensions.crossplane.io/xs3buckets.crossplane.jonashackt.io   True          True      23m\n\nNAME                                               AGE\ncomposition.apiextensions.crossplane.io/s3bucket   2d17h\n\nNAME                                      INSTALLED   HEALTHY   PACKAGE                           AGE\nprovider.pkg.crossplane.io/provider-aws   True        True      crossplanecontrib/provider-aws:v0.34.0   4d21h\n\nNAME                                                           HEALTHY   REVISION   IMAGE                             STATE    DEP-FOUND   DEP-INSTALLED   AGE\nproviderrevision.pkg.crossplane.io/provider-aws-2189bc61e0bd   True      1          crossplanecontrib/provider-aws:v0.34.0   Active                               4d21h\n\nNAME                                        AGE     TYPE         DEFAULT-SCOPE\nstoreconfig.secrets.crossplane.io/default   5d23h   Kubernetes   crossplane-system\n```\n\n* `kubectl get claim`: get all resources of all claim kinds, like PostgreSQLInstance.\n* `kubectl get composite`: get all resources that are of composite kind, like XPostgreSQLInstance.\n* `kubectl get managed`: get all resources that represent a unit of external infrastructure.\n* `kubectl get \u003cname-of-provider\u003e`: get all resources related to \u003cprovider\u003e.\n* `kubectl get crossplane`: get all resources related to Crossplane.\n\n\n\nWe can also check our claim with `kubectl get \u003cclaim-kind\u003e \u003cclaim-metadata.name\u003e` like this:\n\n```shell\n$ kubectl get ObjectStorage managed-s3\nNAME         READY   CONNECTION-SECRET               AGE\nmanaged-s3           managed-s3-connection-details   5s\n```\n\nTo watch the provisioned resources become ready we can run `kubectl get crossplane -l crossplane.io/claim-name=\u003cclaim-metadata.name\u003e`:\n\n```shell\nkubectl get crossplane -l crossplane.io/claim-name=managed-s3\n```\n\n\nCheck if the S3 Bucket has been created successfully via aws CLI with `aws s3 ls | grep microservice-ui-nuxt-js-static-bucket2`.\n\n```shell\n$ aws s3 ls | grep microservice-ui-nuxt-js-static-bucket2\n2022-06-27 11:56:26 microservice-ui-nuxt-js-static-bucket2\n```\n\nOur bucket should be there! We can also double check in the AWS console:\n\n![aws-console-s3-bucket-created](screenshots/aws-console-s3-bucket-created.png)\n\n\n\n### Deploy our app\n\nLet's deploy our app (a simple [index.html](static/index.html)) to our S3 Bucket using the aws CLI like this:\n\n```shell\naws s3 sync static s3://devopsthde-bucket --acl public-read\n```\n\nNow we can open up http://microservice-ui-nuxt-js-static-bucket2.s3-website.eu-central-1.amazonaws.com/ in our Browser and should see our app beeing deployed:\n\n![s3-static-webseite-deployed](screenshots/s3-static-webseite-deployed.png)\n\n\n\n\nBefore removing the claim, we should remove our `index.html` - otherwise we'll run into errors like this:\n\n```shell\n  Warning  CannotDeleteExternalResource  37s (x16 over 57s)  managed/bucket.s3.aws.crossplane.io  (combined from similar events): operation error S3: DeleteBucket, https response error StatusCode: 409, RequestID: 0WHR906YZRF0YDSH, HostID: x7cz2iYF/8Ag2wKtKRZUy1j3hPk67tBUOTFeR//+grrD7plqQ5Zo6EecO70KOOgHKbY7hUyp9vU=, api error BucketNotEmpty: The bucket you tried to delete is not empty\n```\n\nSo first delete the `index.html`:\n\n```shell\naws s3 rm s3://devopsthde-bucket/index.html\n```\n\nFinally remove our S3 Bucket again with \n\n```shell\nkubectl delete -f upbound/provider-aws-s3/claim.yaml\n```\n\nNow also the S3 Bucket should be removed by crossplane.\n\n\n\n\n\n\n\n\n# Configure Crossplane to access Azure\n\nhttps://docs.crossplane.io/v1.14/getting-started/provider-azure/\n\nhttps://marketplace.upbound.io/providers/upbound/provider-azure-storage/v0.39.0\n\n\n### Create crossplane-azure-provider-key.json\n\nhttps://docs.crossplane.io/latest/getting-started/provider-azure/#create-a-kubernetes-secret-for-azure\n\nI assume here that you have [azure CLI installed](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and you're logged in to your Azure subscription via `az login`. So that the command `az account show` should work on your system.\n\n\u003e The current Crossplane docs on Azure propose a `az ad sp create-for-rbac` command that isn't working: `ERROR: Usage error: To create role assignments, specify both --role and --scopes.` When using `--role` we must use `--scopes` also. The documentation about the latter isn't easy to grasp https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create-for-rbac at first sight. As there are examples like `--scopes /subscriptions/{subscriptionId}/resourceGroups/{resourceGroup1}` I thought I need to create a resourceGroup before even connecting to Azure to create a resourceGroup with Crossplane (that doesn't make any sense). BUT: It's possible to use `--scopes /subscriptions/{subscriptionId}` here without any resourceGroups, which will create a scope for your whole subscription.\n\nSo prepare the `SUBSCRIPTION_ID` variable like this:\n\n```shell\nexport SUBSCRIPTION_ID=$(az account show --query id --output tsv)\n```\n\nWith this prepared we can create a service principal in Azure AD using the following command:\n\n```shell\naz ad sp create-for-rbac --sdk-auth --role Owner --scopes /subscriptions/$SUBSCRIPTION_ID --name servicePrincipalCrossplaneGHActions \u003e crossplane-azure-provider-key.json\n```\n\n\u003e Although this produces a warning message like `WARNING: Option '--sdk-auth' has been deprecated and will be removed in a future release.` we definitely need to use the `--sdk-auth` parameter. Otherwise our Managed Resources will run into errors like `connect failed: cannot get authorizer from client credentials config: failed to get SPT from client credentials: parameter 'activeDirectoryEndpoint' cannot be empty` because there are configuration entries missing in the file like `\"activeDirectoryEndpointUrl\": \"https://login.microsoftonline.com\",`.\n\nThis produces a `crossplane-azure-provider-key.json` file you should never ever check into version control! For this repository I added it to the [.gitignore](.gitignore) file. \n\n\nIf you're using a CI system like GitHub Actions (as this repository is based on), define 3 repository secrets from the output. Choose the `appId` as the `ARM_CLIENT_ID`, the `password` as the `ARM_CLIENT_SECRET` and the `tenant` as the `ARM_TENANT_ID`. Additionally we need to define the `ARM_SUBSCRIPTION_ID` secret. Therefore run a `az account show` (after you logged your CLI into your Azure subscription via `azure login`) and use the value of `\"id\":`.\n\n![github-actions-secrets-azure](screenshots/github-actions-secrets-azure.png)\n\nOn GitHub Actions we need to \n\n```shell\necho \"{\n  \\\"clientId\\\": \\\"$ARM_CLIENT_ID\\\",\n  \\\"clientSecret\\\": \\\"$ARM_CLIENT_SECRET\\\",\n  \\\"subscriptionId\\\": \\\"$ARM_SUBSCRIPTION_ID\\\",\n  \\\"tenantId\\\": \\\"$ARM_TENANT_ID\\\",\n  \\\"activeDirectoryEndpointUrl\\\": \\\"https://login.microsoftonline.com\\\",\n  \\\"resourceManagerEndpointUrl\\\": \\\"https://management.azure.com/\\\",\n  \\\"activeDirectoryGraphResourceId\\\": \\\"https://graph.windows.net/\\\",\n  \\\"sqlManagementEndpointUrl\\\": \\\"https://management.core.windows.net:8443/\\\",\n  \\\"galleryEndpointUrl\\\": \\\"https://gallery.azure.com/\\\",\n  \\\"managementEndpointUrl\\\": \\\"https://management.core.windows.net/\\\"\n}\" \u003e crossplane-azure-provider-key.json\n```\n\n\nAll three needed variables [in GitHub Actions](.github/workflows/provision-azure.yml) for example look like this:\n\n```yaml\nenv:\n  ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}\n  ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}\n  ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}\n  ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}\n```\n\n\n### Create Azure Provider secret\n\nNow we need to use the `crossplane-azure-provider-key.json` file to create the Crossplane Azure Provider secret:\n\n```shell\nkubectl create secret generic azure-account-creds -n crossplane-system --from-file=creds=./crossplane-azure-provider-key.json\n```\n\nIf everything went well there should be a new `azure-account-creds` Secret ready.\n\n\n\n### Install the Crossplane Azure Provider\n\nhttps://docs.crossplane.io/latest/getting-started/provider-azure/#install-the-azure-provider\n\nLet's create a [provider-azure-storage.yaml](upbound/provider-azure-storage/config/provider-azure-storage.yaml) file like this:\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: provider-azure-storage\nspec:\n  package: xpkg.upbound.io/upbound/provider-azure-storage:v0.39.0\n  packagePullPolicy: Always\n  revisionActivationPolicy: Automatic\n  revisionHistoryLimit: 1\n```\n\nInstall the Azure provider:\n\n```shell\nkubectl apply -f upbound/provider-azure-storage/config/provider-azure-storage.yaml\n```\n\nNow our first Crossplane Provider has been installed. You may check it with `kubectl get provider`:\n\n```shell\n$ kubectl get provider\nNAME             INSTALLED   HEALTHY   PACKAGE                            AGE\nprovider-azure   True        Unknown   crossplane/provider-azure:v0.19.0  13s\n```\n\nBefore we can actually apply a `ProviderConfig` to our Azure provider we have to make sure that it's actually healthy and running. Therefore we can use the `kubectl wait` command like this:\n\n```shell\nkubectl wait --for=condition=healthy --timeout=120s provider/provider-azure\n```\n\nOtherwise we may run into errors like this when applying the `ProviderConfig` right after the Provider.\n\n\n### Create ProviderConfig to consume the Secret containing Azure credentials\n\nhttps://docs.crossplane.io/latest/getting-started/provider-azure/#create-a-providerconfig\n\nNow we need to create `ProviderConfig` object that will tell the AWS Provider where to find it's AWS credentials. Therefore we create a [provider-config-azure.yaml](crossplane-contrib/provider-azure/config/provider-config-azure.yaml):\n\n```yaml\napiVersion: azure.upbound.io/v1beta1\nkind: ProviderConfig\nmetadata:\n  name: default\nspec:\n  credentials:\n    source: Secret\n    secretRef:\n      namespace: crossplane-system\n      name: azure-account-creds\n      key: creds\n```\n\n\u003e Crossplane resources use the `ProviderConfig` named `default` if no specific ProviderConfig is specified, so this ProviderConfig will be the default for all Azure resources.\n\nThe `secretRef.name` and `secretRef.key` has to match the fields of the already created Secret.\n\nApply it with:\n\n```shell\nkubectl apply -f upbound/provider-azure-storage/config/provider-config-azure.yaml\n```\n\n__The crossplane core Controller and the Provider Azure Controller should now be ready to provision any infrastructure component in Azure!__\n\n\n\n# Provision a StorageAccount in Azure with Crossplane\n\nhttps://doc.crds.dev/github.com/upbound/provider-azure/storage.azure.upbound.io/Account/v1beta1@v0.38.2\n\nhttps://doc.crds.dev/github.com/upbound/provider-azure/azure.upbound.io/ResourceGroup/v1beta1@v0.38.2\n\n\n\n### Defining a CompositeResourceDefinition (XRD) for our Storage Account\n\nSo our Composite Resource Definition (XRD) for our Storage Account could look like [upbound/provider-azure-storage/definition.yaml](upbound/provider-azure-storage/definition.yaml):\n\n```yaml\n---\napiVersion: apiextensions.crossplane.io/v1\nkind: CompositeResourceDefinition\nmetadata:\n  name: xstoragesazure.crossplane.jonashackt.io\nspec:\n  group: crossplane.jonashackt.io\n  names:\n    kind: XStorageAzure\n    plural: xstoragesazure\n  claimNames:\n    kind: StorageAzure\n    plural: storagesazure\n  \n  defaultCompositionRef:\n    name: storageazure-composition\n\n  versions:\n  - name: v1alpha1\n    served: true\n    referenceable: true\n    # See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/\n    schema:\n      openAPIV3Schema:\n        type: object\n        properties:\n          spec:\n            type: object\n            properties:\n              parameters:\n                type: object\n                properties:\n                  location:\n                    type: string\n                  resourceGroupName:\n                    type: string\n                  storageAccountName:\n                    type: string\n                required:\n                  - location\n                  - resourceGroupName\n                  - storageAccountName\n```\n\nInstall the XRD into our cluster with:\n\n```shell\nkubectl apply -f upbound/provider-azure-storage/definition.yaml\n```\n\nLet's wait for the XRD to become `Offered`:\n\n```shell\nkubectl wait --for=condition=Offered --timeout=120s xrd xstoragesazure.crossplane.jonashackt.io  \n```\n\n\n### Craft a Composition to manage our needed cloud resources\n\nA Composite to manage an Storage Account in Azure with public access for static website hosting could for example look like this [upbound/provider-azure-storage/composition.yaml](upbound/provider-azure-storage/composition.yaml):\n\n```yaml\n---\napiVersion: apiextensions.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: storageazure-composition\n  labels:\n    crossplane.io/xrd: xstoragesazure.crossplane.jonashackt.io\n    provider: azure\nspec:\n  compositeTypeRef:\n    apiVersion: crossplane.jonashackt.io/v1alpha1\n    kind: XStorageAzure\n  \n  writeConnectionSecretsToNamespace: crossplane-system\n  \n  resources:\n    - name: storageaccount\n      base:\n        # see https://doc.crds.dev/github.com/crossplane/provider-azure/storage.azure.crossplane.io/Account/v1alpha3@v0.19.0\n        apiVersion: storage.azure.upbound.io/v1beta1\n        kind: Account\n        metadata: {}\n        spec:\n          forProvider:\n            accountKind: StorageV2\n            accountTier: Standard\n            accountReplicationType: LRS\n      patches:\n        - fromFieldPath: spec.parameters.storageAccountName\n          toFieldPath: metadata.name\n        - fromFieldPath: spec.parameters.resourceGroupName\n          toFieldPath: spec.forProvider.resourceGroupName\n        - fromFieldPath: spec.parameters.location\n          toFieldPath: spec.forProvider.location\n          \n    - name: resourcegroup\n      base:\n        # see https://doc.crds.dev/github.com/crossplane/provider-azure/azure.crossplane.io/ResourceGroup/v1alpha3@v0.19.0\n        apiVersion: azure.upbound.io/v1beta1\n        kind: ResourceGroup\n        metadata: {}\n      patches:\n        - fromFieldPath: spec.parameters.resourceGroupName\n          toFieldPath: metadata.name\n        - fromFieldPath: spec.parameters.location\n          toFieldPath: spec.forProvider.location\n```\n\nInstall our Composition with \n\n```shell\nkubectl apply -f upbound/provider-azure-storage/composition.yaml\n```\n\n\n\n### Craft a Composite Resource (XR) or Claim (XRC)\n\nSince we want to create a Storage Account, here's an suggestion for an [claim.yaml](upbound/provider-azure-storage/claim.yaml):\n\n```yaml\n---\napiVersion: crossplane.jonashackt.io/v1alpha1\nkind: StorageAzure\nmetadata:\n  namespace: default\n  name: managed-storage-account\nspec:\n  compositionRef:\n    name: storageazure-composition\n  parameters:\n    location: West Europe\n    resourceGroupName: rg-crossplane\n    storageAccountName: account4c8672f\n```\n\n\nTestdrive with \n\n```shell\nkubectl apply -f upbound/provider-azure-storage/claim.yaml\n```\n\n\n\n### Wait for the Azure ResourceGroup \u0026 Storage Account to become ready\n\nWe can use the new `trace` command of the [`crossplane` CLI introduced in 1.14](https://blog.crossplane.io/crossplane-v1-14/) to have a look, what's going on with our Azure resources:\n\n```shell\n$ crossplane beta trace storageazure.crossplane.jonashackt.io/managed-storage-account\nNAME                                SYNCED   READY   STATUS                                                                                \nStorageAzure/account (default)      True     False   Waiting: ...resource claim is waiting for composite resource to become Ready          \n└─ XStorageAzure/account-g97s8      False    -       ReconcileError: ... \"object\": spec.forProvider.accountTier is a required parameter]   \n   ├─ Account/account4c8672f        -        -       Error: accounts.storage.azure.upbound.io \"account4c8672f\" not found                   \n   └─ ResourceGroup/rg-crossplane   -        -       Error: resourcegroups.azure.upbound.io \"rg-crossplane\" not found    \n```\n\nAhh, so our Composition isn't crafted correctly. \n\nTo fix this, we need to have a look into the API docs at https://doc.crds.dev/github.com/upbound/provider-azure/storage.azure.upbound.io/Account/v1beta1@v0.38.2. After fixing our Composition and reapplying it together with the Claim, we can have a look into the Azure Portal. Our Resource Group should show up:\n\n![azure-console-resourcegroup](screenshots/azure-console-resourcegroup.png)\n\nAnd also our Storage account should be visible inside the group:\n\n![azure-console-storageaccount](screenshots/azure-console-storageaccount.png)\n\n\n\n\n\n\n\n\n\n# TODO:\n\n\n## Publish Crossplane Configuration Package into GitHub Container Registry\n\nLet's package our Composite Resource definitions as a Configuration. Therefore we need to use the Crossplane CLI via \u003ccode\u003ekubectl crossplane build configuration\u003c/code\u003e (now they are a Package) - and push them to an OCI registry via \u003ccode\u003ekubectl crossplane push configuration\u003c/code\u003e. With this Configurations can also be easily installed into other Crossplane clusters.\n\nTherefore we need a `crossplane.yaml` file as described in https://crossplane.io/docs/v1.8/getting-started/create-configuration.html#build-and-push-the-configuration \n\nSee also https://crossplane.io/docs/v1.8/concepts/packages.html#configuration-packages\n\nOur [crossplane-contrib/provider-aws/s3/crossplane.yaml](crossplane-contrib/provider-aws/s3/crossplane.yaml) is of `kind: Configuration` and defines the minimum crossplane version needed alongside the crossplane AWS provider:\n\n```yaml\napiVersion: meta.pkg.crossplane.io/v1\nkind: Configuration\nmetadata:\n  name: s3-bucket-composition\nspec:\n  crossplane:\n    version: \"\u003e=v1.8\"\n  dependsOn:\n    - provider: crossplanecontrib/provider-aws\n      version: \"\u003e=v0.33.0\"\n```\n\nHaving this `crossplane.yaml` in place we can build our Configuration at last. On a command line go into the directory where the `crossplane.yaml` resides and run the `kubectl crossplane build` command:\n\n```shell\n$ cd crossplane-s3\n$ kubectl crossplane build configuration\n```\n\n\n\nReally strange, getting\n\n```shell\nkubectl crossplane build configuration\nkubectl crossplane: error: failed to build package: failed to parse package: {path:/Users/jonashecht/dev/kubernetes/crossplane-awws-azure/crossplane-contrib/provider-aws/s3/composition.yaml position:0}: no kind \"S3Bucket\" is registered for version \"crossplane.jonashackt.io/v1alpha1\" in scheme \"/home/runner/work/crossplane/crossplane/internal/xpkg/scheme.go:47\"\n```\n\n\n\n\n\n\n\n\n# Provision an EKS cluster with crossplane\n\n__The crossplane core Controller and the Provider AWS Controller should now be ready to provision any infrastructure component in AWS!__\n\nSo in order to maximise the Inception let's provision an EKS based Kubernetes cluster in AWS with our crossplane Kubernetes cluster :) \n\nA EKS cluster is a complex AWS construct leveraging different basic AWS services. That's exactly what a crossplane Composite Resource (XR) is all about. So let's create our own Composite Resource.\n\n\n\n\n\n## Higher level abstractions - or why it is so hard to write your own Compositions\n\nLooking only at the Crossplane docs and blog I thought something is missing: A curated library of higher level abstractions (like Pulumi Crosswalk). First initiatives from cloud vendors like AWS Blueprints for Crossplane: https://aws.amazon.com/blogs/opensource/introducing-aws-blueprints-for-crossplane/\n\nBut then I found the Upbound blog - and finally stumbled upon the __Upbound Platform Reference Architectures__ https://blog.upbound.io/azure-reference-platform/ which are intended as\n\n\u003e foundations to help accelerate understanding and application of Crossplane within your organization\n\n\n### Use Upbound Platform Reference Architecture AWS to provision a EKS cluster\n\n__What I didn't wanted to do is to duplicate some code__ into my `Composition` from all those sources I found (mainly this https://www.kloia.com/blog/production-ready-eks-cluster-with-crossplane, this https://aws.amazon.com/blogs/containers/gitops-model-for-provisioning-and-bootstrapping-amazon-eks-clusters-using-crossplane-and-argo-cd/ and https://aws.amazon.com/blogs/opensource/introducing-aws-blueprints-for-crossplane/) about provisioning a EKS cluster with crossplane. \n\n\n\n\n### Install platform-ref-aws\n\nhttps://github.com/upbound/platform-ref-aws#install-the-platform-configuration\n\n\n\n### The problem with Open Source Crossplane: Provider Coverage Problem\n\nSee this blog post https://blog.upbound.io/first-official-providers/ and the paragraph `Provider Coverage Problem`:\n\n\u003e The Crossplane community has been adding CustomResourceDefinitions (CRDs) to cover the API surface, but the pace is unable to catch up with users who are blocked from getting into production with Crossplane due to the lack of coverage. To give you an idea, AWS has ~1000 resources and the classic AWS provider has yet to cover ~200 as of writing.\n\nNow with the classic AWS provider https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/v0.39.0/crds you have around 180 CRDs available. If you only need the CRDs that are provided by this classic provider, everything is okay. But I recently stumbled upon a new security default implementation of AWS S3: https://github.com/jonashackt/crossplane-aws-azure/issues/12\n\nThis project stopped to work, simply since AWS doesn't support Access Control Lists (ACLs) for all new S3 Buckets anymore. See https://github.com/hashicorp/terraform-provider-aws/issues/28353\n\n\u003e Starting in April 2023, Amazon S3 will introduce two new default bucket security settings by automatically enabling S3 Block Public Access and disabling S3 access control lists (ACLs) for all new S3 buckets.\n\nThis in fact means, that we not only need the `Bucket` CRD, but we additionally need `BucketACL`, `BucketPublicAccessBlock` and `BucketOwnershipControls`, which are only available in the so called official Upbound providers with their 901 CRDs: https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/crds\n\n\nUp until now I didn't really know the difference between the classic providers and the official providers. But the official providers are based on the tool Upjet https://github.com/upbound/upjet/ which is the successor of the former Terrajet https://github.com/crossplane/terrajet, where Terraform modules are used to generate Crossplane CRDs. With this approach, every API Terraform provides is also available in Crossplane. Upjet is currently used to generate all CRDs for the official AWS, Azure and GCP providers.\n\nAnd with Upjet it's possible to generate really any Crossplane provider from Terraform, meaning one can rely on every Provider Terraform already has: https://registry.terraform.io/browse/providers\n\nWhy not use Terraform then you may ask. Theres a good post here https://blog.crossplane.io/crossplane-vs-terraform/ which makes the point, that Terraform can only be used from a bash script - but Crossplane delivers a control plane based approach, which continously reconciles all infrastructure configured inside of it.\n\nSo TLDR: To fix the error https://github.com/jonashackt/crossplane-aws-azure/issues/12\n\n```shell\nS3 creation error: CannotCreateExternalResource managed/bucket.s3.aws.crossplane.io failed to create the Bucket: InvalidBucketAclWithObjectOwnership: Bucket cannot have ACLs set with ObjectOwnership's BucketOwnerEnforced setting\n```\n\nwe need to switch over to the official AWS provider based on Upjet. Do we need to use the UXP (Universal Control Plane) flavour of Crossplane for that? No, the docs https://marketplace.upbound.io/providers/upbound/provider-aws/v0.34.0/docs/configuration state:\n\n\u003e The Upbound AWS official provider may also be used with upstream Crossplane.\n\nSo let's use the provider inside our project!\n\n\n\n### IDE plugins\n\nhttps://blog.upbound.io/moving-crossplane-package-authoring-from-plain-yaml-to-ide-aided-development/\n\nUpbound VS Code plugin https://blog.upbound.io/crossplane-vscode-plugin-announcement/\n\n![upbound-vscode-extension](screenshots/upbound-vscode-extension.png)\n\n\n\n\n\n\n# Links\n\nhttps://crossplane.io/\n\nIntro post https://medium.com/@khelge/crossplane-7197ad200013\n\nCrossplane is based on OAM (3 roles: Developer, application operator \u0026 infrastructure operator) https://github.com/oam-dev/spec\n\nhttps://www.kloia.com/blog/production-ready-eks-cluster-with-crossplane\n\nhttps://www.forbes.com/sites/janakirammsv/2021/09/15/how-crossplane-transforms-kubernetes-into-a-universal-control-plane/\n\n\u003e Crossplane essentially transforms Kubernetes into a universal control plane that can orchestrate the lifecycle of public cloud-based services such as virtual machines, database instances, Big Data clusters, machine learning jobs, and other managed services offered by the hyperscalers. \n\n\u003e Crossplane takes the concept of Infrastructure as Code (IaC) to the next level through its tight integration with Kubernetes.\n\nIntro Slides: https://docs.google.com/presentation/d/1PxZweRpB6HElxd9qGK1McboGZ1kluCDCS5qxgYnX5f0/edit#slide=id.g8801599ecb_2_169\n\n\nPromoted from sandbox to incubation by the CNCF: https://blog.crossplane.io/crossplane-cncf-incubation/ \n\nCrossplane vs. Terraform: https://blog.crossplane.io/crossplane-vs-terraform/\n\nCrossplane 101 https://vrelevant.net/crossplane-bringing-the-basics-together/\n\n\n\nhttps://www.techtarget.com/searchitoperations/news/252508176/Crossplane-project-could-disrupt-infrastructure-as-code\n\n\nArgo + crossplane https://morningspace.medium.com/using-crossplane-in-gitops-what-to-check-in-git-76c08a5ff0c4\n\n\nInfrastructure-as-Apps https://codefresh.io/blog/infrastructure-as-apps-the-gitops-future-of-infra-as-code/\n\n\n\nNew Charter from Sep 2023 on: https://github.com/crossplane/crossplane/pull/4643 Developer Experience and Providers are added to the scope of crossplane\n\nEven Upjet and the major Upbound developed (aka Upjet generated) Providers are donated to the OS project \u0026 CNCF: https://blog.crossplane.io/charter-expansion-upjet-donation/\n\nSee also https://github.com/crossplane-contrib/provider-aws/issues/1954\n\n\n\n\n### Crossplane + AWS\n\nhttps://aws.amazon.com/blogs/containers/gitops-model-for-provisioning-and-bootstrapping-amazon-eks-clusters-using-crossplane-and-argo-cd/\n\n\u003e Crossplane’s infrastructure provider for AWS relies on code generated by the AWS Go Code Generator, which is also used by [AWS Controllers for Kubernetes (ACK)](https://github.com/aws-controllers-k8s/community). \n\nhttps://www.kloia.com/blog/production-ready-eks-cluster-with-crossplane\n\n\n\n\nAWS Blueprints for Crossplane: \n\nhttps://aws.amazon.com/blogs/opensource/introducing-aws-blueprints-for-crossplane/\n\n\u003e Writing your first Composition and debugging can be intimidating.\n\nAnd it's quite a doublicate effort IMHO compared to other tools like Pulumi that provide higher level abstractions including well-architectured best practices (like Pulumi https://www.pulumi.com/docs/guides/crosswalk/aws/)\n\nhttps://github.com/aws-samples/crossplane-aws-blueprints\n\n\nThoughworks Tech Radar: Assess https://www.thoughtworks.com/de-de/radar/tools/crossplane\n\n\nIONOS and crossplane https://docs.ionos.com/crossplane-provider/example\n\n\n\n### CRD examples\n\nhttps://github.com/upbound/platform-ref-s3-website\n\n\n\n### Show status of infrastructure components\n\nk8s event-log! `:events` and then scroll down to the newest events and have a (constant) look\n\n\n### Opt out of automatic Composition Updates in XRs/Claims\n\nComposition Updates are applied automatically to all XRs/Claims by default. [As the docs state](https://docs.crossplane.io/knowledge-base/guides/composition-revisions/):\n\n\u003e If you have 10 PlatformDB XRs all using the big-platform-db Composition, all 10 of those XRs will be instantly updated in accordance with any updates you make to the big-platform-db Composition.\n\nSo if we change a Composition, all the XRs will be updated - which we can see in the Kubernetes Events:\n\n\u003e[](screenshots/update-composition-updates-external-resources.png)\n\nThis can be a wanted behavior - or not at all.\n\nBut be aware of this error:\n\n```shell\ncannot compose resources: cannot parse base template of composed resource \"securitygrouprule-cluster-inbound\": cannot change the kind or group of a composed resource from ec2.aws.upbound .io/v1beta1, Kind=SecurityGroupRule to ec2.aws.upbound.io/v1beta1, Kind=SecurityGroupIngressRule (possible composed resource template mismatch) \n```\n\nSee https://github.com/crossplane/crossplane/issues/1909\n\n\nIn the first steps with Crossplane you might encounter the issue already:\n\n```shell\ncannot compose resources: cannot parse base template of composed resource \"securitygrouprule-cluster-inbound\": cannot change the kind or group of a composed resource from ec2.aws.upbound .io/v1beta1, Kind=SecurityGroupRule to ec2.aws.upbound.io/v1beta1, Kind=SecurityGroupIngressRule (possible composed resource template mismatch) \n```\n\n\u003e Composition Revisions allow XRs to opt out of automatic updates.\n\nIf you don't want Composition to do automatic updates, you need to [use Composition Revisions](https://docs.crossplane.io/knowledge-base/guides/composition-revisions/) in your XRs/XRCs via the `compositionUpdatePolicy: Manual` keyword:\n\n```yaml\napiVersion: example.org/v1alpha1\nkind: PlatformDB\nmetadata:\n  name: example\nspec:\n  parameters:\n    storageGB: 20\n  # The Manual policy specifies that you do not want this XR to update to the\n  # current CompositionRevision automatically.\n  compositionUpdatePolicy: Manual\n  compositionRef:\n    name: example\n  writeConnectionSecretToRef:\n    name: db-conn\n```\n\n\n\n### Troubleshooting\n\nhttps://docs.crossplane.io/knowledge-base/guides/troubleshoot/\n\n\n\n\n### Crossplane CRD Validation\n\n##### Install crossplane CLI\n\nhttps://github.com/crossplane/crossplane/releases/tag/v1.15.0\n\n\u003e Enhancements to the Crossplane CLI: New subcommands like `crossplane beta validate` for schema validation, `crossplane beta top` for resource utilization views similar to `kubectl top pods`, and `crossplane beta convert` for converting resources to newer formats or configurations.\n\nInstall crossplane CLI:\n\n```shell\ncurl -sL \"https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh\" |sh\n```\n\nIf that produces an error, try to manually craft the download link:\n\n```shell\ncurl -sfLo crossplane \"https://releases.crossplane.io/stable/v1.15.0/bin/linux_amd64/crank\"\nsudo mv crossplane /usr/local/bin\n```\n\nBe sure to have the `v1.15.0` version installed as a minimum, otherwise the `crossplane beta validate` command won't work:\n\n```shell\ncrossplane --version\nv1.15.0\n```\n\n### Validate XR or Claim against Composite Resource Definition\n\nBefore using the command have a look at the command reference: https://docs.crossplane.io/latest/cli/command-reference/#beta-validate:\n\n\u003e The crossplane beta validate command validates compositions against provider or XRD schemas using the Kubernetes API server’s validation library.\n\nSo let's grab a `definition.yaml` and validate a `claim.yaml` against it:\n\n```shell\ncrossplane beta validate --cache-dir ~/.crossplane definition.yaml claim.yaml\n[✓] crossplane.jonashackt.io/v1alpha1, Kind=ObjectStorage, managed-upbound-s3 validated successfully\nTotal 1 resources: 0 missing schemas, 1 success cases, 0 failure cases\n```\n\nTo prevent the command from polluting our projects with `.crossplane` directories, we should also provide a `--cache-dir ~/.crossplane` flag, which will deposit the directory in the user profile folder.\n\n\n### Validate Managed Resources against a Crossplane provider (family)\n\nWe need to provide the provider's schemes.\n\nFor example grab a Composition and validate it against the AWS provider:\n\n```shell\ncrossplane beta validate --cache-dir ~/.crossplane config/provider-aws-s3.yaml resources/public-bucket.yaml\npackage schemas does not exist, downloading:  xpkg.upbound.io/upbound/provider-aws-s3:v1.1.0\n[✓] s3.aws.upbound.io/v1beta1, Kind=Bucket, crossplane-argocd-s3-bucket validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketPublicAccessBlock, crossplane-argocd-s3-bucket-pab validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketOwnershipControls, crossplane-argocd-s3-bucket-osc validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketACL, crossplane-argocd-s3-bucket-acl validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketWebsiteConfiguration, crossplane-argocd-s3-bucket-websiteconf validated successfully\nTotal 5 resources: 0 missing schemas, 5 success cases, 0 failure cases\n```\n\n\n##### Validate a full directory against XRDs or Provider schema\n\nWe can also validate a full directory:\n\n```shell\ncrossplane beta validate --cache-dir ~/.crossplane provider-aws-s3/definition.yaml provider-aws-s3\n[✓] crossplane.jonashackt.io/v1alpha1, Kind=ObjectStorage, managed-upbound-s3 validated successfully\n[!] could not find CRD/XRD for: apiextensions.crossplane.io/v1, Kind=Composition\n[!] could not find CRD/XRD for: pkg.crossplane.io/v1, Kind=Provider\n[!] could not find CRD/XRD for: aws.upbound.io/v1beta1, Kind=ProviderConfig\n[!] could not find CRD/XRD for: apiextensions.crossplane.io/v1, Kind=CompositeResourceDefinition\n[✓] s3.aws.upbound.io/v1beta1, Kind=Bucket, crossplane-argocd-s3-bucket validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketPublicAccessBlock, crossplane-argocd-s3-bucket-pab validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketOwnershipControls, crossplane-argocd-s3-bucket-osc validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketACL, crossplane-argocd-s3-bucket-acl validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketWebsiteConfiguration, crossplane-argocd-s3-bucket-websiteconf validated successfully\nTotal 10 resources: 4 missing schemas, 6 success cases, 0 failure cases\n```\n\n\n```shell\ncrossplane beta validate --cache-dir ~/.crossplane provider-aws-s3/config/provider-aws-s3.yaml provider-aws-s3\n[!] could not find CRD/XRD for: crossplane.jonashackt.io/v1alpha1, Kind=ObjectStorage\n[!] could not find CRD/XRD for: apiextensions.crossplane.io/v1, Kind=Composition\n[!] could not find CRD/XRD for: pkg.crossplane.io/v1, Kind=Provider\n[!] could not find CRD/XRD for: aws.upbound.io/v1beta1, Kind=ProviderConfig\n[!] could not find CRD/XRD for: apiextensions.crossplane.io/v1, Kind=CompositeResourceDefinition\n[✓] s3.aws.upbound.io/v1beta1, Kind=Bucket, crossplane-argocd-s3-bucket validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketPublicAccessBlock, crossplane-argocd-s3-bucket-pab validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketOwnershipControls, crossplane-argocd-s3-bucket-osc validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketACL, crossplane-argocd-s3-bucket-acl validated successfully\n[✓] s3.aws.upbound.io/v1beta1, Kind=BucketWebsiteConfiguration, crossplane-argocd-s3-bucket-websiteconf validated successfully\nTotal 10 resources: 5 missing schemas, 5 success cases, 0 failure cases\n```\n\n\n\n\n##### beta render: kind of Unit tests\n\nhttps://docs.crossplane.io/latest/cli/command-reference/#beta-render\n\nhttps://blog.crossplane.io/building-crossplane-composition-functions-to-empower-your-control-plane/\n\n\n\n\n##### Composition Validation\n\nTo be able to validate Compositions \u0026 XRs, we need another command in the game: `crossplane beta render`:\n\nhttps://docs.crossplane.io/latest/cli/command-reference/#validate-render-command-output\n\n\u003e You can pipe the output of `crossplane beta render` into `crossplane beta validate` to validate complete Crossplane resource pipelines, including XRs, compositions and composition functions.\n\nTherefore we need to use the `--include-full-xr` command with `crossplane beta render` and the `-` option with `crossplane beta validate` like that:\n\n```shell\ncrossplane beta render composition.yaml --include-full-xr | crossplane beta validate config/provider-aws-s3.yaml -\n```\n\n\n### Provider Upgrades\n\nImagine a provider beeing configured like this:\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: provider-aws-ec2\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.0\n  packagePullPolicy: Always\n  revisionActivationPolicy: Automatic\n  revisionHistoryLimit: 1\n```\n\nNow if `provider-aws-ec2:v1.1.1` gets released, the `revisionActivationPolicy: Automatic` will lead to a automatically upgraded provider.\n\nThis can be great - or it can lead to a sitation, where the new provider version doesn't work as expected (e.g. because it presents a https://docs.crossplane.io/latest/concepts/providers/#unhealthypackagerevision) - and thus beeing rolled back, also automatically. This could lead to a situation, where the provider gets unusable from time to time.\n\nIf we want to do Provider upgrades in a GitOps fashion through a git commit to a git repository, we should configure the `packagePullPolicy` to `IfNotPresent` instead of `Always` (which means \" Check for new packages every minute and download any matching package that isn’t in the cache\", see https://docs.crossplane.io/master/concepts/packages/#configuration-package-pull-policy) - BUT leave the `revisionActivationPolicy` to `Automatic`! Since otherwise, the Provider will never get active and healty! See https://docs.crossplane.io/master/concepts/packages/#revision-activation-policy), but I didn't find it documented that way!\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: provider-aws-ec2\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.0\n  packagePullPolicy: IfNotPresent # Only download the package if it isn’t in the cache.\n  revisionActivationPolicy: Automatic # Otherwise our Provider never gets activate \u0026 healthy\n  revisionHistoryLimit: 1\n```\n\n#### Provider \u0026 Configuration Package Upgrades with Renovate\n\nRenovate supports Crossplane by the end of November 2023:\n\n* https://github.com/renovatebot/renovate/discussions/22363\n* merged PR https://github.com/renovatebot/renovate/pull/25911 \u0026 docs https://github.com/renovatebot/renovate/pull/25911/commits/d224eaeaee0283282d54bc86e2b7f3e7100de455\n\nThe Renovate docs tell us how we can configure Crossplane support:\n\nhttps://docs.renovatebot.com/modules/manager/crossplane/\n\n\u003e To use the crossplane manager you must set your own fileMatch pattern. The crossplane manager has no default fileMatch pattern, because there is no common filename or directory name convention for Crossplane YAML files. The crossplane manager supports these depTypes: configuration, function, provider\n\nSo we always need to explicitely configure Renovate to let it handle our Crossplane Provider \u0026 Configuration Package updates for us!\n\nThe simplest configuration would be:\n\n```yaml\n\"crossplane\": {\n    \"fileMatch\": [\"\\\\.yaml$\"]\n  }\n```\n\nIt makes sense, if most of the files are for Crossplane.\n\n\n\n### Readiness checks\n\nhttps://docs.crossplane.io/latest/concepts/compositions/#resource-readiness-checks\n\nhttps://docs.crossplane.io/latest/concepts/compositions/#match-a-boolean\n\n\n\n### End-2-End tests \n\nWith `uptest` and `kuttl`:\n\nhttps://github.com/upbound/platform-ref-s3-website/blob/main/Makefile\n\n\n\n### How does Patching work?\n\nhttps://github.com/awslabs/crossplane-on-eks?tab=readme-ov-file#features\n\nhttps://github.com/awslabs/crossplane-on-eks/blob/main/doc/patching-101.md\n\n\n\n### Stacks now called Packages\n\nhttps://github.com/crossplane/crossplane/blob/master/design/design-doc-composition.md\n\n\u003e  Note that Crossplane packages were formerly known as 'Stacks'. This term has been rescoped to refer specifically to a package of infrastructure configuration but is frequently synonymous with packages in historical design documents and some remaining Crossplane APIs.\n\n\n## Others\n\n#### KUTTL - KUbernetes Test TooL\n\nhttps://kuttl.dev/\n\nexample: https://github.com/jonashackt/crossplane-kuttl\n\n\n# GitHub Actions recommendations\n\nA good advice is to use unique bucket names based on the branch to prevent interfering jobs with each other. Otherwise a Renovate enabled Crossplane project using real Cloud resources will create multiple crashed jobs in a row. \n\nBut we also need to split out the branch name, see https://stackoverflow.com/a/73467112/4964553\notherwise we'll run into errors like: \n\n```shell\ncompose resources: cannot associate composed resources with Composition resource templates: cannot get composed resource: invalid resource name \"spring2024-bucket-renovate/xpkg.upbound.io-upbound-provider-aws-s3-1.x\": [may not contain '/']'\n```\n\nTherefore I created a `BUCKET_NAME` variable and ` Split branch name` step which uses the predefined `{{ github.ref_name }}' containing the branch name with often with a slash (when they are opened by Renovate for example):\n\n```yaml\nname: provision-aws\n\non: [push]\n\nenv:\n  KIND_NODE_VERSION: v1.29.2\n  BUCKET_NAME: spring2024-bucket\n  # AWS\n  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n  AWS_DEFAULT_REGION: 'eu-central-1'\n\njobs:\n  crossplane-provision-aws:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@master\n\n      # Using branch unique name for our bucket to prevent interfering jobs\n      # But we need to split out the branch name, see https://stackoverflow.com/a/73467112/4964553\n      # otherwise we'll run into errors like: 'compose resources: cannot associate composed resources with Composition resource templates: cannot get composed resource: invalid resource name \"spring2024-bucket-renovate/xpkg.upbound.io-upbound-provider-aws-s3-1.x\": [may not contain '/']'\n      - name: Split branch name\n        env:\n          BRANCH: ${{ github.ref_name }}\n        id: split\n        run: echo \"::set-output name=branchbucketsuffix::${BRANCH##*/}\"\n```\n\nLater I create a inline Claim instead of using the yaml file to be able to override the bucket name dynamically:\n\n```yaml\n      # Not using kubectl apply -f upbound/provider-aws-s3/claim.yaml currently to prevent jobs from interfering with each other\n      - name: Create XRD, Composition \u0026 Claim to create S3 Bucket (now using Upbound official AWS Provider Families)\n        run: |\n          ...\n\n          echo \"### Create Claim, which should create S3 Bucket (inline here to prevent jobs from interfering with each other)\"\n          kubectl apply -f - \u003c\u003cEOF\n          apiVersion: crossplane.jonashackt.io/v1alpha1\n          kind: ObjectStorage\n          metadata:\n            namespace: default\n            name: managed-upbound-s3\n          spec:\n            compositionRef:\n              name: objectstorage-composition\n            parameters:\n              bucketName: \"$BUCKET_NAME-${{ steps.split.outputs.branchbucketsuffix }}\"\n              region: eu-central-1\n          EOF\n\n          ...\n```\n\nNow in AWS console we can watch Renovate doing it's work without interfering with other branches:\n\n![](screenshots/multiple-buckets-based-on-branch-names.png)\n\n\nAnd finally we need to use the bucket name also when we interact with AWS S3:\n\n```yaml\n      - name: Upload index.html to S3 and check deployment works\n        run: |\n          echo \"### Upload index.html to Bucket via AWS CLI\"\n          aws s3 sync static \"s3://$BUCKET_NAME-${{ steps.split.outputs.branchbucketsuffix }}\" --acl public-read\n\n          echo \"### Access S3 Bucket static website\"\n          curl \"http://$BUCKET_NAME-${{ steps.split.outputs.branchbucketsuffix }}.s3-website.eu-central-1.amazonaws.com\"\n\n      - name: Delete index.html and remove Claim for S3 Bucket deletion\n        run: |\n          echo \"### Delete index.html from S3 Bucket\"\n          aws s3 rm \"s3://$BUCKET_NAME-${{ steps.split.outputs.branchbucketsuffix }}/index.html\"\n\n          ...\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fcrossplane-aws-azure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonashackt%2Fcrossplane-aws-azure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fcrossplane-aws-azure/lists"}