{"id":16179137,"url":"https://github.com/jonashackt/crossplane-eks-cluster","last_synced_at":"2026-01-21T10:37:14.864Z","repository":{"id":229014964,"uuid":"775475338","full_name":"jonashackt/crossplane-eks-cluster","owner":"jonashackt","description":"Crossplane Configuration delivering CRDs to provision AWS EKS clusters","archived":false,"fork":false,"pushed_at":"2025-12-06T06:02:10.000Z","size":2040,"stargazers_count":5,"open_issues_count":11,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-09T23:42:42.610Z","etag":null,"topics":["aws","crossplane","crossplane-compositions","crossplane-configuration","eks-cluster"],"latest_commit_sha":null,"homepage":"","language":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-21T13:10:53.000Z","updated_at":"2025-08-21T21:01:43.000Z","dependencies_parsed_at":"2024-10-25T10:50:53.634Z","dependency_job_id":"6897d39d-c9bd-44b5-9ff7-188095c744f0","html_url":"https://github.com/jonashackt/crossplane-eks-cluster","commit_stats":null,"previous_names":["jonashackt/crossplane-eks-cluster"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jonashackt/crossplane-eks-cluster","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-eks-cluster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-eks-cluster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-eks-cluster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-eks-cluster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonashackt","download_url":"https://codeload.github.com/jonashackt/crossplane-eks-cluster/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonashackt%2Fcrossplane-eks-cluster/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28631937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-21T04:47:28.174Z","status":"ssl_error","status_checked_at":"2026-01-21T04:47:22.943Z","response_time":86,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","crossplane","crossplane-compositions","crossplane-configuration","eks-cluster"],"created_at":"2024-10-10T05:25:34.739Z","updated_at":"2026-01-21T10:37:14.849Z","avatar_url":"https://github.com/jonashackt.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# crossplane-eks-cluster\n[![test-composition-and-publish-to-ghcr](https://github.com/jonashackt/crossplane-eks-cluster/actions/workflows/test-composition-and-publish-to-ghcr.yml/badge.svg)](https://github.com/jonashackt/crossplane-eks-cluster/actions/workflows/test-composition-and-publish-to-ghcr.yml)\n![crossplane-version](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-eks-cluster%2Fmain%2Fcrossplane%2Finstall%2FChart.yaml\u0026query=%24.dependencies%5B%3A1%5D.version\u0026label=crossplane\u0026color=blue)\n![provider-aws-ec2](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-eks-cluster%2Fmain%2Fcrossplane%2Fprovider%2Fupbound-provider-aws-ec2.yaml\u0026query=%24.spec.package\u0026label=provider-aws-ec2\u0026color=rgb(109%2C%20100%2C%20245))\n![provider-aws-eks](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-eks-cluster%2Fmain%2Fcrossplane%2Fprovider%2Fupbound-provider-aws-eks.yaml\u0026query=%24.spec.package\u0026label=provider-aws-eks\u0026color=rgb(109%2C%20100%2C%20245))\n![provider-aws-iam](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fjonashackt%2Fcrossplane-eks-cluster%2Fmain%2Fcrossplane%2Fprovider%2Fupbound-provider-aws-iam.yaml\u0026query=%24.spec.package\u0026label=provider-aws-iam\u0026color=rgb(109%2C%20100%2C%20245))\n[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/jonashackt/crossplane-eks-cluster/blob/master/LICENSE)\n[![renovateenabled](https://img.shields.io/badge/renovate-enabled-yellow)](https://renovatebot.com)\n\nCrossplane Configuration delivering CRDs to provision AWS EKS clusters\n\nThis set of Crossplane (Nested) Compositions to provision a AWS EKS cluster was originally started in https://github.com/jonashackt/crossplane-argocd - but then scaled out to a separate repository using Crossplane's [Configuration Package feature](https://docs.crossplane.io/latest/concepts/packages/) to build a OCI image from the CRDs in this repo. \n\nThere's not really much documentation about Nested Compositions. There's [this section in the upbound docs about \"Layering composite resources\"](https://docs.upbound.io/xp-arch-framework/building-apis/building-apis-compositions/#layering-composite-resources).\n\nOnly some hints [like this about the role of the `XRD.status` field](https://docs.upbound.io/xp-arch-framework/building-apis/building-apis-xrds/#xrd-status).\n\nMost information [is provided by this blog post](https://vrelevant.net/crossplane-beyond-the-basics-nested-xrs-and-composition-selectors/) and some examples like [this (watch out, this is based on the crossplane aws provider!)](https://github.com/cem-altuner/crossplane-prod-ready-eks) and [this](https://github.com/upbound/configuration-eks).\n\n# How to use it\n\nAs described in https://docs.crossplane.io/latest/concepts/packages/#install-a-configuration use a manifest like the following to install the Configuration:\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Configuration\nmetadata:\n  name: crossplane-eks-cluster\nspec:\n  package: ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.2\n```\n\n`apply -f` the file and create a Claim like shown in the `examples` folder at []`examples/claim.yaml`](examples/claim.yaml):\n\n```yaml\napiVersion: k8s.crossplane.jonashackt.io/v1alpha1\nkind: KubernetesCluster\nmetadata:\n  namespace: default\n  name: deploy-target-eks\nspec:\n  id: deploy-target-eks\n  parameters:\n    region: eu-central-1\n    nodes:\n      count: 3\n  writeConnectionSecretToRef:\n    name: eks-cluster-kubeconfig\n```\n\nImagine a cluster (like the one bootstrapped in https://github.com/jonashackt/crossplane-argocd) and run `kubectl get crossplane`:\n\n```shell\n$ kubectl get crossplane\nNAME                                    AGE\nproviderconfig.aws.upbound.io/default   7d3h\n\nNAME                                                                          HEALTHY   REVISION   IMAGE                                                STATE    DEP-FOUND   DEP-INSTALLED   AGE\nproviderrevision.pkg.crossplane.io/provider-aws-ec2-150095bdd614              True      1          xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/provider-aws-eks-fbb6768e46c0              True      1          xpkg.upbound.io/upbound/provider-aws-eks:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/provider-aws-iam-9565c6312cd0              True      1          xpkg.upbound.io/upbound/provider-aws-iam:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/upbound-provider-family-aws-11fe5ecef831   True      1          xpkg.upbound.io/upbound/provider-family-aws:v1.1.1   Active                               7d4h\n\nNAME                                                     INSTALLED   HEALTHY   PACKAGE                                              AGE\nprovider.pkg.crossplane.io/provider-aws-ec2              True        True      xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.1      7d3h\nprovider.pkg.crossplane.io/provider-aws-eks              True        True      xpkg.upbound.io/upbound/provider-aws-eks:v1.1.1      7d3h\nprovider.pkg.crossplane.io/provider-aws-iam              True        True      xpkg.upbound.io/upbound/provider-aws-iam:v1.1.1      7d3h\nprovider.pkg.crossplane.io/upbound-provider-family-aws   True        True      xpkg.upbound.io/upbound/provider-family-aws:v1.1.1   7d4h\n\nNAME                                                AGE\ndeploymentruntimeconfig.pkg.crossplane.io/default   7d4h\n\nNAME                                        AGE    TYPE         DEFAULT-SCOPE\nstoreconfig.secrets.crossplane.io/default   7d4h   Kubernetes   crossplane-system\n```\n\nNow after applying this Configuration here, there should appear all the Compositions and XRDs:\n\n```shell\n$ kubectl get crossplane\nNAME                                                                                                       ESTABLISHED   OFFERED   AGE\ncompositeresourcedefinition.apiextensions.crossplane.io/xeksclusters.eks.aws.crossplane.jonashackt.io      True          True      4s\ncompositeresourcedefinition.apiextensions.crossplane.io/xkubernetesclusters.k8s.crossplane.jonashackt.io   True          True      4s\ncompositeresourcedefinition.apiextensions.crossplane.io/xnetworkings.net.aws.crossplane.jonashackt.io      True          True      4s\n\nNAME                                                                         REVISION   XR-KIND              XR-APIVERSION                               AGE\ncompositionrevision.apiextensions.crossplane.io/aws-eks-4c2092f              1          XEKSCluster          eks.aws.crossplane.jonashackt.io/v1alpha1   4s\ncompositionrevision.apiextensions.crossplane.io/kubernetes-cluster-2b6e754   1          XKubernetesCluster   k8s.crossplane.jonashackt.io/v1alpha1       4s\ncompositionrevision.apiextensions.crossplane.io/networking-3869153           1          XNetworking          net.aws.crossplane.jonashackt.io/v1alpha1   4s\n\nNAME                                                         XR-KIND              XR-APIVERSION                               AGE\ncomposition.apiextensions.crossplane.io/aws-eks              XEKSCluster          eks.aws.crossplane.jonashackt.io/v1alpha1   4s\ncomposition.apiextensions.crossplane.io/kubernetes-cluster   XKubernetesCluster   k8s.crossplane.jonashackt.io/v1alpha1       4s\ncomposition.apiextensions.crossplane.io/networking           XNetworking          net.aws.crossplane.jonashackt.io/v1alpha1   4s\n\nNAME                                    AGE\nproviderconfig.aws.upbound.io/default   7d3h\n\nNAME                                                                          HEALTHY   REVISION   IMAGE                                              STATE    DEP-FOUND   DEP-INSTALLED   AGE\nconfigurationrevision.pkg.crossplane.io/crossplane-eks-cluster-edf0e1ba1b0c   True      1          ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.2   Active   4           4               6s\n\nNAME                                                     INSTALLED   HEALTHY   PACKAGE                                            AGE\nconfiguration.pkg.crossplane.io/crossplane-eks-cluster   True        True      ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.2   6s\n\nNAME                                                                          HEALTHY   REVISION   IMAGE                                                STATE    DEP-FOUND   DEP-INSTALLED   AGE\nproviderrevision.pkg.crossplane.io/provider-aws-ec2-150095bdd614              True      1          xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/provider-aws-eks-fbb6768e46c0              True      1          xpkg.upbound.io/upbound/provider-aws-eks:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/provider-aws-iam-9565c6312cd0              True      1          xpkg.upbound.io/upbound/provider-aws-iam:v1.1.1      Active   1           1               7d3h\nproviderrevision.pkg.crossplane.io/upbound-provider-family-aws-11fe5ecef831   True      1          xpkg.upbound.io/upbound/provider-family-aws:v1.1.1   Active                               7d4h\n\nNAME                                                     INSTALLED   HEALTHY   PACKAGE                                              AGE\nprovider.pkg.crossplane.io/provider-aws-ec2              True        True      xpkg.upbound.io/upbound/provider-aws-ec2:v1.1.1      7d3h\nprovider.pkg.crossplane.io/provider-aws-eks              True        True      xpkg.upbound.io/upbound/provider-aws-eks:v1.1.1      7d3h\nprovider.pkg.crossplane.io/provider-aws-iam              True        True      xpkg.upbound.io/upbound/provider-aws-iam:v1.1.1      7d3h\nprovider.pkg.crossplane.io/upbound-provider-family-aws   True        True      xpkg.upbound.io/upbound/provider-family-aws:v1.1.1   7d4h\n\nNAME                                                AGE\ndeploymentruntimeconfig.pkg.crossplane.io/default   7d4h\n\nNAME                                        AGE    TYPE         DEFAULT-SCOPE\nstoreconfig.secrets.crossplane.io/default   7d4h   Kubernetes   crossplane-system\n```\n\n\n# Building a Nested Composition for EKS with Crossplane\n\n## Bootstrap a EKS cluster with Crossplane\n\nhttps://marketplace.upbound.io/providers/upbound/provider-aws-eks/\n\nInspiration taken from https://github.com/cem-altuner/crossplane-prod-ready-eks (watch out, this is based on the crossplane aws provider!), https://github.com/upbound/configuration-eks \n\n\n### Add EKS, ECS \u0026 IAM Providers\n\nWe first need to add 3 more Crossplane Providers from the upbound provider families: `provider-aws-eks` and `provider-aws-ec2`.\n\n[`upbound/provider-aws/provider/provider-aws-eks.yaml`](upbound/provider-aws/provider/provider-aws-eks.yaml):\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: upbound-provider-aws-eks\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-eks:v1.2.1\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\nthe [`upbound/provider-aws/provider/provider-aws-ec2.yaml`](upbound/provider-aws/provider/provider-aws-ec2.yaml):\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: upbound-provider-aws-ec2\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-ec2:v1.2.1\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\nand the [`crossplane/provider/upbound-provider-aws-iam.yaml`](crossplane/provider/upbound-provider-aws-iam.yaml):\n\n```yaml\napiVersion: pkg.crossplane.io/v1\nkind: Provider\nmetadata:\n  name: upbound-provider-aws-iam\nspec:\n  package: xpkg.upbound.io/upbound/provider-aws-iam:v1.2.1\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\nYou can also use the great Crossplane CLI command `crossplane beta trace` to see all the resources in the scope of your Claim:\n\n```shell\ncrossplane beta trace kubernetesclusters.k8s.crossplane.jonashackt.io/deploy-target-eks -o wide \nNAME                                                               RESOURCE                                SYNCED   READY   STATUS\nKubernetesCluster/deploy-target-eks (default)                                                              True     True    Available\n└─ XKubernetesCluster/deploy-target-eks-chzjb                                                              True     True    Available\n   ├─ XNetworking/deploy-target-eks-chzjb-7zw9c                    compositeNetworkEKS                     True     True    Available\n   │  ├─ VPC/deploy-target-eks                                     platform-vcp                            True     True    Available\n   │  ├─ InternetGateway/deploy-target-eks-chzjb-n2gx9             gateway                                 True     True    Available\n   │  ├─ Subnet/deploy-target-eks-chzjb-fdhpp                      subnet-public-eu-central-1a             True     True    Available\n   │  ├─ Subnet/deploy-target-eks-chzjb-tt4pb                      subnet-public-eu-central-1b             True     True    Available\n   │  ├─ Subnet/deploy-target-eks-chzjb-crx5m                      subnet-public-eu-central-1c             True     True    Available\n   │  ├─ SecurityGroup/deploy-target-eks                           securitygroup-cluster                   True     True    Available\n   │  ├─ SecurityGroupRule/deploy-target-eks-chzjb-8wlkv           securitygrouprule-cluster-inbound       True     True    Available\n   │  ├─ SecurityGroupRule/deploy-target-eks-chzjb-tjtxz           securitygrouprule-cluster-outbound      True     True    Available\n   │  ├─ Route/deploy-target-eks-chzjb-wh7gl                       route                                   True     True    Available\n   │  ├─ RouteTable/deploy-target-eks-chzjb-wc5lh                  routeTable                              True     True    Available\n   │  ├─ MainRouteTableAssociation/deploy-target-eks-chzjb-xsgss   mainRouteTableAssociation               True     True    Available\n   │  ├─ RouteTableAssociation/deploy-target-eks-chzjb-9gt7h       RouteTableAssociation-public-a          True     True    Available\n   │  ├─ RouteTableAssociation/deploy-target-eks-chzjb-px4g5       RouteTableAssociation-public-b          True     True    Available\n   │  └─ RouteTableAssociation/deploy-target-eks-chzjb-mzlqh       RouteTableAssociation-public-c          True     True    Available\n   └─ XEKSCluster/deploy-target-eks-chzjb-fcmp7                    compositeClusterEKS                     True     True    Available\n      ├─ Cluster/deploy-target-eks                                 eksCluster                              True     True    Available\n      ├─ ClusterAuth/deploy-target-eks-chzjb-4hq4d                 kubernetesClusterAuth                   True     True    Available\n      ├─ Role/deploy-target-eks-chzjb-mdmx9                        clusterRole                             True     True    Available\n      ├─ RolePolicyAttachment/deploy-target-eks-chzjb-wwvws        clusterRolePolicyAttachment             True     True    Available\n      ├─ NodeGroup/deploy-target-eks-chzjb-zt66c                   nodeGroupPublic                         True     True    Available\n      ├─ Role/deploy-target-eks-chzjb-ccvgp                        nodegroupRole                           True     True    Available\n      ├─ RolePolicyAttachment/deploy-target-eks-chzjb-bbgrr        workerNodeRolePolicyAttachment          True     True    Available\n      ├─ RolePolicyAttachment/deploy-target-eks-chzjb-nk2m4        cniRolePolicyAttachment                 True     True    Available\n      └─ RolePolicyAttachment/deploy-target-eks-chzjb-tdr2b        containerRegistryRolePolicyAttachment   True     True    Available\n```\n\n\n### The EC2 Networking Composition\n\nCan be found in `apis/networking/` directory:\n\n* XRD: [`apis/networking/definition.yaml`](apis/networking/definition.yaml)\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eexpand full yaml\u003c/summary\u003e\n\n  ```yaml\n  apiVersion: apiextensions.crossplane.io/v1\nkind: CompositeResourceDefinition\nmetadata:\n  name: xnetworkings.net.aws.crossplane.jonashackt.io\nspec:\n  group: net.aws.crossplane.jonashackt.io\n  names:\n    kind: XNetworking\n    plural: xnetworkings\n  claimNames:\n    kind: Networking\n    plural: networkings\n  versions:\n    - name: v1alpha1\n      served: true\n      referenceable: true\n      schema:\n        openAPIV3Schema:\n          type: object\n          properties:\n            # defining input parameters\n            spec:\n              type: object\n              properties:\n                id:\n                  type: string\n                  description: ID of this Network that other objects will use to refer to it.\n                parameters:\n                  type: object\n                  description: Network configuration parameters.\n                  properties:\n                    region:\n                      type: string\n                  required:\n                    - region\n              required:\n                - id\n                - parameters\n            # defining return values\n            status:\n              type: object\n              properties:\n                subnetIds:\n                  type: array\n                  items:\n                    type: string\n                securityGroupClusterIds:\n                  type: array\n                  items:\n                    type: string\n  ```\n\u003c/details\u003e\n\n* Composition: [`apis/networking/composition.yaml`](apis/networking/composition.yaml)\n\n\u003cdetails\u003e\n  \u003csummary\u003eexpand full yaml\u003c/summary\u003e\n  \n  ```yaml\napiVersion: apiextensions.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: networking\n  labels:\n    provider: aws\nspec:\n  compositeTypeRef:\n    apiVersion: net.aws.crossplane.jonashackt.io/v1alpha1\n    kind: XNetworking\n\n  writeConnectionSecretsToNamespace: crossplane-system\n\n  patchSets:\n  - name: networkconfig\n    patches:\n    - type: FromCompositeFieldPath\n      fromFieldPath: spec.id\n      toFieldPath: metadata.labels[net.aws.crossplane.jonashackt.io/network-id] # the network-id other Composition MRs (like EKSCluster) will use\n    - type: FromCompositeFieldPath\n      fromFieldPath: spec.parameters.region\n      toFieldPath: spec.forProvider.region\n\n  resources:\n    ### VPC and InternetGateway\n    - name: platform-vcp\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: VPC\n        spec:\n          forProvider:\n            cidrBlock: 10.0.0.0/16\n            enableDnsSupport: true\n            enableDnsHostnames: true\n            tags:\n              Owner: Platform Team\n              Name: platform-vpc\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        - fromFieldPath: spec.id\n          toFieldPath: metadata.name\n    \n    - name: gateway\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: InternetGateway\n        spec:\n          forProvider:\n            vpcIdSelector:\n              matchControllerRef: true\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n\n\n    ### Subnet Configuration\n    - name: subnet-public-eu-central-1a\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: Subnet\n        metadata:\n          labels:\n            access: public\n        spec:\n          forProvider:\n            mapPublicIpOnLaunch: true\n            cidrBlock: 10.0.0.0/24\n            vpcIdSelector:\n              matchControllerRef: true\n            tags:\n              kubernetes.io/role/elb: \"1\"\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        # define eu-central-1a as zone \u0026 availabilityZone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: metadata.labels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sa\"\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.availabilityZone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sa\"\n        # provide the subnetId for later use as status.subnetIds entry\n        - type: ToCompositeFieldPath\n          fromFieldPath: metadata.annotations[crossplane.io/external-name]\n          toFieldPath: status.subnetIds[0]\n    \n    - name: subnet-public-eu-central-1b\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: Subnet\n        metadata:\n          labels:\n            access: public\n        spec:\n          forProvider:\n            mapPublicIpOnLaunch: true\n            cidrBlock: 10.0.1.0/24\n            vpcIdSelector:\n              matchControllerRef: true\n            tags:\n              kubernetes.io/role/elb: \"1\"\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n          # define eu-central-1b as zone \u0026 availabilityZone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: metadata.labels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sb\"\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.availabilityZone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sb\"\n          # provide the subnetId for later use as status.subnetIds entry\n        - type: ToCompositeFieldPath\n          fromFieldPath: metadata.annotations[crossplane.io/external-name]\n          toFieldPath: status.subnetIds[1]\n\n    - name: subnet-public-eu-central-1c\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: Subnet\n        metadata:\n          labels:\n            access: public\n        spec:\n          forProvider:\n            mapPublicIpOnLaunch: true\n            cidrBlock: 10.0.2.0/24\n            vpcIdSelector:\n              matchControllerRef: true\n            tags:\n              kubernetes.io/role/elb: \"1\"\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n          # define eu-central-1c as zone \u0026 availabilityZone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: metadata.labels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sc\"\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.availabilityZone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sc\"\n          # provide the subnetId for later use as status.subnetIds entry\n        - type: ToCompositeFieldPath\n          fromFieldPath: metadata.annotations[crossplane.io/external-name]\n          toFieldPath: status.subnetIds[2]  \n\n    ### SecurityGroup \u0026 SecurityGroupRules Cluster API server\n    - name: securitygroup-cluster\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: SecurityGroup\n        metadata:\n          labels:\n            net.aws.crossplane.jonashackt.io: securitygroup-cluster\n        spec:\n          forProvider:\n            description: cluster API server access\n            name: securitygroup-cluster\n            vpcIdSelector:\n              matchControllerRef: true\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        - fromFieldPath: spec.id\n          toFieldPath: metadata.name\n          # provide the securityGroupId for later use as status.securityGroupClusterIds entry\n        - type: ToCompositeFieldPath\n          fromFieldPath: metadata.annotations[crossplane.io/external-name]\n          toFieldPath: status.securityGroupClusterIds[0]\n\n    - name: securitygrouprule-cluster-inbound\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: SecurityGroupRule\n        spec:\n          forProvider:\n            #description: Allow pods to communicate with the cluster API server \u0026 access API server from kubectl clients\n            type: ingress\n            cidrBlocks:\n              - 0.0.0.0/0\n            fromPort: 443\n            toPort: 443\n            protocol: tcp\n            securityGroupIdSelector:\n              matchLabels:\n                net.aws.crossplane.jonashackt.io: securitygroup-cluster\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n\n    - name: securitygrouprule-cluster-outbound\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: SecurityGroupRule\n        spec:\n          forProvider:\n            description: Allow internet access from the cluster API server\n            type: egress\n            cidrBlocks: # Destination\n              - 0.0.0.0/0\n            fromPort: 0\n            toPort: 0\n            protocol: tcp\n            securityGroupIdSelector:\n              matchLabels:\n                net.aws.crossplane.jonashackt.io: securitygroup-cluster\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n\n    ### Route, RouteTable \u0026 RouteTableAssociations\n    - name: route\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: Route\n        spec:\n          forProvider:\n            destinationCidrBlock: 0.0.0.0/0\n            gatewayIdSelector:\n              matchControllerRef: true\n            routeTableIdSelector:\n              matchControllerRef: true\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n\n    - name: routeTable\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: RouteTable\n        spec:\n          forProvider:\n            vpcIdSelector:\n              matchControllerRef: true\n      patches:\n      - type: PatchSet\n        patchSetName: networkconfig\n\n    - name: mainRouteTableAssociation\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: MainRouteTableAssociation\n        spec:\n          forProvider:\n            routeTableIdSelector:\n              matchControllerRef: true\n            vpcIdSelector:\n              matchControllerRef: true\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n\n    - name: RouteTableAssociation-public-a\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: RouteTableAssociation\n        spec:\n          forProvider:\n            routeTableIdSelector:\n              matchControllerRef: true\n            subnetIdSelector:\n              matchControllerRef: true\n              matchLabels:\n                access: public\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        # define eu-central-1a as subnetIdSelector.matchLabels.zone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.subnetIdSelector.matchLabels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sa\"\n\n    - name: RouteTableAssociation-public-b\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: RouteTableAssociation\n        spec:\n          forProvider:\n            routeTableIdSelector:\n              matchControllerRef: true\n            subnetIdSelector:\n              matchControllerRef: true\n              matchLabels:\n                access: public\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        # define eu-central-1b as subnetIdSelector.matchLabels.zone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.subnetIdSelector.matchLabels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sb\"\n\n    - name: RouteTableAssociation-public-c\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: RouteTableAssociation\n        spec:\n          forProvider:\n            routeTableIdSelector:\n              matchControllerRef: true\n            subnetIdSelector:\n              matchControllerRef: true\n              matchLabels:\n                access: public\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        # define eu-central-1c as subnetIdSelector.matchLabels.zone\n        - type: FromCompositeFieldPath\n          fromFieldPath: spec.parameters.region\n          toFieldPath: spec.forProvider.subnetIdSelector.matchLabels.zone\n          transforms:\n            - type: string\n              string:\n                fmt: \"%sc\"\n  ```\n\u003c/details\u003e\n\n\nFor the start, let's simply apply our first XRD, Composition and Claim manually like that:\n\n```shell\n# Networking XRD \u0026 Composition\nkubectl apply -f apis/networking/definition.yaml\nkubectl apply -f apis/networking/composition.yaml\n# Precheck if Network works\nkubectl apply -f examples/networking/claim.yaml\n```\n\nI found that the simplest way to follow what Crossplane is doing, is to look into the events ( via typing `:events`) in k9s:\n\n![](docs/follow-crossplane-events-in-k9s.png)\n\nAnd simply press `ENTER` to see the actual event message. This helped me a lot in the development process (no need to run `kubectl get crossplane` all the time and manually copy the CRD names to a `kubectl describe xyz-crd`).\n\n\n\nManaged Resources need to reference other Managed Resources. For example, a `SecurityGroupRule` needs to reference a `SecurityGroup`:\n\n```yaml\n...\n    ### SecurityGroups \u0026 Rules\n    - name: securitygroup-nodepool\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: SecurityGroup\n        spec:\n          forProvider:\n            description: Cluster communication with worker nodes\n            name: securitygroup-nodepool\n            vpcIdSelector:\n              matchControllerRef: true\n\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n        - fromFieldPath: spec.id\n          toFieldPath: metadata.name\n          # provide the securityGroupId for later use as status.securityGroupIds entry\n        - type: ToCompositeFieldPath\n          fromFieldPath: metadata.annotations[crossplane.io/external-name]\n          toFieldPath: status.securityGroupIds[0]\n\n    - name: securitygroup-nodepool-rule\n      base:\n        apiVersion: ec2.aws.upbound.io/v1beta1\n        kind: SecurityGroupRule\n        spec:\n          forProvider:\n            type: egress\n            cidrBlocks:\n              - 0.0.0.0/0\n            fromPort: 0\n            protocol: tcp\n            securityGroupIdSelector:\n              matchLabels:\n                net.aws.crossplane.jonashackt.io: securitygroup\n            toPort: 0\n      patches:\n        - type: PatchSet\n          patchSetName: networkconfig\n          ...\n```\n\nIn this example, we get the following error in our k8s events:\n\n```shell\ncannot resolve references: mg.Spec.ForProvider.SecurityGroupID: no resources matched selector\n```\n\nhttps://docs.crossplane.io/latest/concepts/managed-resources/#referencing-other-resources states\n\n\u003e Some fields in a managed resource may depend on values from other managed resources. For example a VM may need the name of a virtual network to use.\n\n\u003e Managed resources can reference other managed resources by external name, name reference or selector.\n\nThe problem is, we don't specify the `net.aws.crossplane.jonashackt.io: securitygroup` label on our `SecurityGroup`! Doing that the problem is gone:\n\n```yaml\n        kind: SecurityGroup\n        metadata:\n          labels:\n            net.aws.crossplane.jonashackt.io: securitygroup\n```\n\n\nThere should now be an event showing up containing `Successfully composed resources` in our `eks-vpc-j8s5k` XR.\n\n\n\n### The EKS Cluster Composition\n\nCan be found in `apis/eks/` directory:\n\n* XRD: [`apis/eks/definition.yaml`](apis/eks/definition.yaml)\n\n\u003cdetails\u003e\n  \u003csummary\u003eexpand full yaml\u003c/summary\u003e\n\n  ```yaml\n  apiVersion: apiextensions.crossplane.io/v1\nkind: CompositeResourceDefinition\nmetadata:\n  # XRDs must be named 'x\u003cplural\u003e.\u003cgroup\u003e'\n  name: xeksclusters.eks.aws.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: eks.aws.crossplane.jonashackt.io\n  \n  # XR names should always be prefixed with an 'X'\n  names:\n    kind: XEKSCluster\n    plural: xeksclusters\n  # This type of XR offers a claim, which should have the same name without the 'X' prefix\n  claimNames:\n    kind: EKSCluster\n    plural: ekscluster\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: aws-eks\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          # defining input parameters\n          spec:\n            type: object\n            properties:\n              id:\n                type: string\n                description: ID of this Cluster that other objects will use to refer to it.\n              parameters:\n                type: object\n                description: EKS configuration parameters.\n                properties:\n                  # Using subnetIds \u0026 securityGroupClusterIds from XNetworking to configure VPC\n                  subnetIds:\n                    type: array\n                    items:\n                      type: string\n                  securityGroupClusterIds:\n                    type: array\n                    items:\n                      type: string\n                  region:\n                    type: string\n                  nodes:\n                    type: object\n                    description: EKS node configuration parameters.\n                    properties:\n                      count:\n                        type: integer\n                        description: Desired node count, from 1 to 10.\n                    required:\n                    - count\n                required:\n                - subnetIds\n                - securityGroupClusterIds\n                - region\n                - nodes\n            required:\n            - id\n            - parameters\n          # defining return values\n          status:\n            type: object\n            properties:\n              clusterStatus:\n                description: The status of the control plane\n                type: string\n              nodePoolStatus:\n                description: The status of the node pool\n                type: string\n  ```\n\u003c/details\u003e\n\n* Composition: [`apis/eks/composition.yaml`](apis/eks/composition.yaml)\n\n\u003cdetails\u003e\n  \u003csummary\u003eexpand full yaml\u003c/summary\u003e\n\n  ```yaml\n  apiVersion: apiextensions.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: aws-eks\n  labels:\n    provider: aws\nspec:\n  compositeTypeRef:\n    apiVersion: eks.aws.crossplane.jonashackt.io/v1alpha1\n    kind: XEKSCluster\n  \n  writeConnectionSecretsToNamespace: crossplane-system\n\n  patchSets:\n  - name: clusterconfig\n    patches:\n    - fromFieldPath: spec.parameters.region\n      toFieldPath: spec.forProvider.region\n\n  resources:\n    ### Cluster Configuration\n    - name: eksCluster\n      base:\n        apiVersion: eks.aws.upbound.io/v1beta1\n        kind: Cluster\n        metadata:\n          annotations:\n            meta.upbound.io/example-id: eks/v1beta1/cluster\n            uptest.upbound.io/timeout: \"2400\"\n        spec:\n          forProvider:\n            roleArnSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: clusterRole\n            vpcConfig:\n              - endpointPrivateAccess: true\n                endpointPublicAccess: true\n      patches:\n        - type: PatchSet\n          patchSetName: clusterconfig\n        - fromFieldPath: spec.id\n          toFieldPath: metadata.name\n        # Using the XNetworking defined securityGroupClusterIds \u0026 subnetIds for the vpcConfig\n        - fromFieldPath: spec.parameters.securityGroupClusterIds\n          toFieldPath: spec.forProvider.vpcConfig[0].securityGroupIds\n        - fromFieldPath: spec.parameters.subnetIds\n          toFieldPath: spec.forProvider.vpcConfig[0].subnetIds\n\n        - type: ToCompositeFieldPath\n          fromFieldPath: status.atProvider.status\n          toFieldPath: status.clusterStatus    \n      readinessChecks:\n        - type: MatchString\n          fieldPath: status.atProvider.status\n          matchString: ACTIVE\n\n    - name: kubernetesClusterAuth\n      base:\n        apiVersion: eks.aws.upbound.io/v1beta1\n        kind: ClusterAuth\n        spec:\n          forProvider:\n            clusterNameSelector:\n              matchControllerRef: true\n      patches:\n        - type: PatchSet\n          patchSetName: clusterconfig\n        - fromFieldPath: spec.writeConnectionSecretToRef.namespace\n          toFieldPath: spec.writeConnectionSecretToRef.namespace\n        - fromFieldPath: spec.id\n          toFieldPath: spec.writeConnectionSecretToRef.name\n          transforms:\n            - type: string\n              string:\n                fmt: \"%s-access\"\n      connectionDetails:\n        - fromConnectionSecretKey: kubeconfig\n\n    ### Cluster Role and Policies\n    - name: clusterRole\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: Role\n        metadata:\n          labels:\n            role: clusterRole\n        spec:\n          forProvider:\n            assumeRolePolicy: |\n              {\n                \"Version\": \"2012-10-17\",\n                \"Statement\": [\n                    {\n                        \"Effect\": \"Allow\",\n                        \"Principal\": {\n                            \"Service\": [\n                                \"eks.amazonaws.com\"\n                            ]\n                        },\n                        \"Action\": [\n                            \"sts:AssumeRole\"\n                        ]\n                    }\n                ]\n              }\n      \n    \n    - name: clusterRolePolicyAttachment\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: RolePolicyAttachment\n        spec:\n          forProvider:\n            policyArn: arn:aws:iam::aws:policy/AmazonEKSClusterPolicy\n            roleSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: clusterRole\n\n\n    ### NodeGroup Configuration\n    - name: nodeGroupPublic\n      base:\n        apiVersion: eks.aws.upbound.io/v1beta1\n        kind: NodeGroup\n        spec:\n          forProvider:\n            clusterNameSelector:\n              matchControllerRef: true\n            nodeRoleArnSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: nodegroup\n            subnetIdSelector:\n              matchLabels:\n                access: public\n            scalingConfig:\n              - minSize: 1\n                maxSize: 10\n                desiredSize: 1\n            instanceTypes: # TODO: we can support to have that parameterized also\n              - t3.medium\n      patches:\n        - type: PatchSet\n          patchSetName: clusterconfig\n        - fromFieldPath: spec.parameters.nodes.count\n          toFieldPath: spec.forProvider.scalingConfig[0].desiredSize\n        - fromFieldPath: spec.id\n          toFieldPath: spec.forProvider.subnetIdSelector.matchLabels[net.aws.crossplane.jonashackt.io/network-id]\n        - type: ToCompositeFieldPath\n          fromFieldPath: status.atProvider.status\n          toFieldPath: status.nodePoolStatus  \n      readinessChecks:\n      - type: MatchString\n        fieldPath: status.atProvider.status\n        matchString: ACTIVE\n\n    ### Node Role and Policies\n    - name: nodegroupRole\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: Role\n        metadata:\n          labels:\n            role: nodegroup\n        spec:\n          forProvider:\n            assumeRolePolicy: |\n              {\n                \"Version\": \"2012-10-17\",\n                \"Statement\": [\n                    {\n                        \"Effect\": \"Allow\",\n                        \"Principal\": {\n                            \"Service\": [\n                                \"ec2.amazonaws.com\"\n                            ]\n                        },\n                        \"Action\": [\n                            \"sts:AssumeRole\"\n                        ]\n                    }\n                ]\n              }\n      \n\n    - name: workerNodeRolePolicyAttachment\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: RolePolicyAttachment\n        spec:\n          forProvider:\n            policyArn: arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy\n            roleSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: nodegroup\n      \n\n    - name: cniRolePolicyAttachment\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: RolePolicyAttachment\n        spec:\n          forProvider:\n            policyArn: arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy\n            roleSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: nodegroup\n      \n    - name: containerRegistryRolePolicyAttachment\n      base:\n        apiVersion: iam.aws.upbound.io/v1beta1\n        kind: RolePolicyAttachment\n        spec:\n          forProvider:\n            policyArn: arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly\n            roleSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: nodegroup\n      \n  ```\n\u003c/details\u003e\n\nFor testing we simply use `kubectl apply -f`:\n\n\n```shell\n# EKS XRD \u0026 Composition\nkubectl apply -f apis/eks/definition.yaml\nkubectl apply -f apis/eks/composition.yaml\n\n# If you choose this example (non-nested) claim, be sure to change the subnetIds and securitygroupid according the the Networking claim executed before!\n\n# Precheck if EKSCluster works\nkubectl apply -f examples/eks/claim.yaml \n```\n\nErrors in the events like this are normal, since the EKS Cluster needs it's time to be provisioned before NodeGroups etc. can be assigned:\n\n```shell\ncannot resolve references: mg.Spec.ForProvider.ClusterName: referenced field was empty (referenced resource may not yet be ready) \n```\n\nThis also shows up in the AWS console:\n\n![](docs/eks-cluster-initial-provisioning.png)\n\n\nNow if the `NodeGroup` comes up with the following\n\n```shell\ncannot resolve references: mg.Spec.ForProvider.SubnetIds: no resources matched selector \n```\n\nthere's a problem, where the NodeGroup can't find it's SubnetIds.\n\n```yaml\n    - name: nodeGroupPublic\n      base:\n        apiVersion: eks.aws.upbound.io/v1beta1\n        kind: NodeGroup\n        spec:\n          forProvider:\n            clusterNameSelector:\n              matchControllerRef: true\n            nodeRoleArnSelector:\n              matchControllerRef: true\n              matchLabels:\n                role: nodegroup\n            subnetIdSelector:\n              matchLabels:\n                access: public\n            scalingConfig:\n              - minSize: 1\n                maxSize: 10\n                desiredSize: 1\n            instanceTypes: # TODO: we can support to have that parameterized also\n              - t3.medium\n      patches:\n        - type: PatchSet\n          patchSetName: clusterconfig\n        - fromFieldPath: spec.parameters.nodes.count\n          toFieldPath: spec.forProvider.scalingConfig[0].desiredSize\n        - fromFieldPath: spec.id\n          toFieldPath: spec.forProvider.subnetIdSelector.matchLabels[aws.crossplane.jonashackt.io/network-id]\n          ...\n```\n\nThat's because the label of all networking components changed to `net.aws.crossplane.jonashackt.io/network-id`. So let's fix that!\n\nNow finally the NodeGroups are correctly assigned to the EKS cluster:\n\nThe `Successfully composed resources` message in the event `xekscluster/deploy-target-eks-cb87r` looks promising:\n\n![](docs/eks-cluster-with-nodegroups.png)\n\n\n\n### The nested XR for Networking \u0026 EKS Cluster Compositions\n\nCan be found in `apis/` directory:\n\n* XRD: [`apis/definition.yaml`](apis/definition.yaml)\n* Composition: [`apis/composition.yaml`](apis/composition.yaml)\n\nWith this Composition we're able to use both pre-defined Compositions `XNetworking` and `XEKSCluster` and thus implement a nested Composite Resource:\n\n```yaml\napiVersion: apiextensions.crossplane.io/v1\nkind: Composition\nmetadata:\n  name: kubernetes-cluster\nspec:\n  compositeTypeRef:\n    apiVersion: k8s.crossplane.jonashackt.io/v1alpha1\n    kind: XKubernetesCluster\n  \n  writeConnectionSecretsToNamespace: crossplane-system\n\n  resources:\n    ### Nested use of XNetworking XR\n    - name: compositeNetworkEKS\n      base:\n        apiVersion: net.aws.crossplane.jonashackt.io/v1alpha1\n        kind: XNetworking\n      patches:\n        - fromFieldPath: spec.id\n          toFieldPath: spec.id\n        - fromFieldPath: spec.parameters.region\n          toFieldPath: spec.parameters.region\n        # provide the subnetIds \u0026 securityGroupIds for later use\n        - type: ToCompositeFieldPath\n          fromFieldPath: status.subnetIds\n          toFieldPath: status.subnetIds\n          policy:\n            fromFieldPath: Required\n        - type: ToCompositeFieldPath\n          fromFieldPath: status.securityGroupIds\n          toFieldPath: status.securityGroupIds\n          policy:\n            fromFieldPath: Required\n    \n    ### Nested use of XEKSCluster XR\n    - name: compositeClusterEKS\n      base:\n        apiVersion: eks.aws.crossplane.jonashackt.io/v1alpha1\n        kind: XEKSCluster\n      connectionDetails:\n        - fromConnectionSecretKey: kubeconfig\n      patches:\n        - fromFieldPath: spec.id\n          toFieldPath: spec.id\n        - fromFieldPath: spec.id\n          toFieldPath: metadata.annotations[crossplane.io/external-name]\n        - fromFieldPath: metadata.uid\n          toFieldPath: spec.writeConnectionSecretToRef.name\n          transforms:\n            - type: string\n              string:\n                fmt: \"%s-eks\"\n        - fromFieldPath: spec.writeConnectionSecretToRef.namespace\n          toFieldPath: spec.writeConnectionSecretToRef.namespace\n        - fromFieldPath: spec.parameters.region\n          toFieldPath: spec.parameters.region\n        - fromFieldPath: spec.parameters.nodes.count\n          toFieldPath: spec.parameters.nodes.count\n        - fromFieldPath: status.subnetIds\n          toFieldPath: spec.parameters.subnetIds\n          policy:\n            fromFieldPath: Required\n        - fromFieldPath: status.securityGroupIds\n          toFieldPath: spec.parameters.securityGroupIds\n          policy:\n            fromFieldPath: Required\n```\n\nFor the start, let's simply apply our first XRD, Composition and Claim manually like that:\n\n```shell\n# Nested XRD \u0026 Composition\nkubectl apply -f apis/definition.yaml\nkubectl apply -f apis/composition.yaml\n\n# Check if full Cluster provisioning works\nkubectl apply -f examples/claim.yaml\n```\n\n\n\n### Accessing the Crossplane provisioned EKS cluster\n\nhttps://docs.crossplane.io/knowledge-base/guides/connection-details/\n\nIn our eks cluster [claim](upbound/provider-aws/apis/eks/claim.yaml) we defined a \n\n```yaml\n  writeConnectionSecretToRef:\n    name: eks-cluster-kubeconfig\n```\n\ninside our nested claim. This will create a k8s `Secret` called `eks-cluster-kubeconfig`, where the kubeconfig will be stored.\n\nLet's extract the kubeconfig:\n\n```shell\nkubectl get secret eks-cluster-kubeconfig -o jsonpath='{.data.kubeconfig}' | base64 --decode \u003e ekskubeconfig\n```\n\nNow integrate the contents of the `ekskubeconfig` file into your `~/.kube/config` (better with VSCode!) and switch over to the new kube context e.g. using https://github.com/ahmetb/kubectx. If you're on the new context of our Crossplane bootstrapped EKS cluster, check if everything works:\n\n```shell\n$ kubectl get nodes\nNAME                                          STATUS   ROLES    AGE   VERSION\nip-10-0-0-173.eu-central-1.compute.internal   Ready    \u003cnone\u003e   34m   v1.29.0-eks-5e0fdde\nip-10-0-1-149.eu-central-1.compute.internal   Ready    \u003cnone\u003e   34m   v1.29.0-eks-5e0fdde\nip-10-0-2-90.eu-central-1.compute.internal    Ready    \u003cnone\u003e   34m   v1.29.0-eks-5e0fdde\n```\n\n\n\n# Testing the Managed Resources Rendering with kuttl\n\nAs described in this project: https://github.com/jonashackt/crossplane-kuttl we'll use https://kuttl.dev to create tests for our EKS Cluster Composition.\n\n```shell\n# Run kuttl tests\nkubectl kuttl test\n```\n\nTo run multiple tests, you don't need to setup kind and Crossplane incl. it's Providers every time simply run:\n\n```shell\n# Only once:\nkubectl kuttl test --skip-cluster-delete\n# and the following runs:\nkubectl kuttl test --start-kind=false\n```\n\nTests can be found in the exact reflective order as in `apis` under `tests/compositions`.\n\nIf an error occurs like `key is missing from map`:\n\n```shell\ncase.go:366: resource VPC:/: .spec.forProvider.instanceTenancy: key is missing from map\n```\n\none needs to delete that entry from the `01-assert.yaml`.\n\nEven if something appears like \n\n```shell\nresource Subnet:/: .metadata.labels.zone: value mismatch, expected: eu-central-1a != actual: eu-central-1b\n```\n\nFix the `key is missing from map` first! Then the others might disappear.\n\n\nAlso for better readability, we run the kuttl tests one after another by using the `parallel: 1` configuration in the [`kuttl-test.yaml](kuttl-test.yaml):\n\n```yaml\n...\nparallel: 1 # use parallel: 1 to execute one test after another (e.g. for better readability in CI logs)\n```\n\n\n\n# Building a Configuration Package as OCI container\n\nhttps://docs.crossplane.io/latest/concepts/packages/#create-a-configuration\n\nhttps://morningspace.medium.com/build-publish-and-install-crossplane-package-5e4b74a3ee37\n\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 like `Failed to download Crossplane CLI. Please make sure version current exists on channel stable.`, try to manually craft the download link:\n\n```shell\ncurl --output crank \"https://releases.crossplane.io/stable/current/bin/linux_amd64/crank\"\nchmod +x crank\nsudo mv crank /usr/local/bin/crossplane\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### The crossplane.yaml\n\nAs [stated in the docs](https://docs.crossplane.io/latest/concepts/packages/#create-a-configuration) Crossplane has a feature where one can create Configuration Packages containing specific Compositions packaged in a OCI container. So let's build a Configuration Package from this EKS setup here.\n\nFirst we need a [`crossplane.yaml`](crossplane.yaml):\n\n```yaml\napiVersion: meta.pkg.crossplane.io/v1alpha1\nkind: Configuration\nmetadata:\n  name: crossplane-eks-cluster\n  annotations:\n    # Set the annotations defining the maintainer, source, license, and description of your Configuration\n    meta.crossplane.io/maintainer: Jonas Hecht iam@jonashackt.io\n    meta.crossplane.io/source: github.com/jonashackt/crossplane-eks-cluster\n    # Set the license of your Configuration\n    meta.crossplane.io/license: MIT\n    meta.crossplane.io/description: |\n      Crossplane Configuration delivering CRDs to provision AWS EKS clusters.\n    meta.crossplane.io/readme: |\n      It's a Nested Composition with separate AWS Networking \u0026 EKS cluster setups.\nspec:\n  dependsOn:\n    - provider: xpkg.upbound.io/upbound/provider-aws-ec2\n      version: \"\u003e=v1.1.1\"\n    - provider: xpkg.upbound.io/upbound/provider-aws-iam\n      version: \"\u003e=v1.1.1\"\n    - provider: xpkg.upbound.io/upbound/provider-aws-eks\n      version: \"\u003e=v1.1.1\"\n  crossplane:\n    version: \"\u003e=v1.15.1-0\"\n```\n\nDon't forget to add the `metadate.annotations` in order to prevent the error `crossplane: error: failed to build package: not exactly one package meta type` - see also https://stackoverflow.com/questions/78200917/crossplane-error-failed-to-build-package-not-exactly-one-package-meta-type\n\nAlso we should define on which providers our Configuration depends on - and also on which Crossplane version.\n\n\nThere's also a template one could use to create the crossplane.yaml using the crossplane CLI: https://docs.crossplane.io/latest/cli/command-reference/#beta-xpkg-init \n\n```shell\ncrossplane beta xpkg init crossplane-eks-cluster configuration-template\n```\n\nThe command uses the following template: https://github.com/crossplane/configuration-template (one could provide arbitrary repositories to the command).\n\n\n### Building the Package using Crossplane CLI\n\nThe Crossplane CLI [has the right command for us](https://docs.crossplane.io/latest/concepts/packages/#build-the-package):\n\n```shell\ncrossplane xpkg build --package-root=. --examples-root=\"./examples\" --ignore=\".github/workflows/*,crossplane/install/*,crossplane/provider/*,kuttl-test.yaml,tests/compositions/eks/*,tests/compositions/networking/*\" --verbose\n```\n\nNote that including YAML files that aren’t Compositions or CompositeResourceDefinitions, including Claims isn’t supported.\n\nThis can be done by appending `--ignore=\"file.xyz,directory/*\"`, which will ignore a file `file.xyz` and all files in directory `directory`. [Sadly, ingoring directories completely isn't supported right now](https://docs.crossplane.io/latest/concepts/packages/#build-the-package) - so we need to define all our kuttl test directories respectively.\n\nAlso appending `--verbose` makes a lot of sense to see what's going on.\n\n\n### Pushing the Package file .xpkg to GitHub Container Registry\n\nThere's also a `crossplane xpkg push` command to publish the Configuration package. So let's create a new GitHub package matching our repository:\n\n```shell\ncrossplane xpkg push ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.1\n```\n\nYou can leverage the Container image tag as version number for your Configuration here.\n\nIf the command gives the following error, we need to setup Authentication for your Docker Registry:\n\n```shell\ncrossplane: error: failed to push package file crossplane-eks-cluster-7badc365c06a.xpkg: Post \"https://ghcr.io/v2/jonashackt/crossplane-eks-cluster/blobs/uploads/\": GET https://ghcr.io/token?scope=repository%3Ajonashackt%2Fcrossplane-eks-cluster%3Apull\u0026scope=repository%3Ajonashackt%2Fcrossplane-eks-cluster%3Apush%2Cpull\u0026service=ghcr.io: DENIED: requested access to the resource is denied\n```\n\nThe ` crossplane xpkg push --help` helps us:\n\n\u003e Credentials for the registry are automatically retrieved from xpkg\nlogin and dockers configuration as fallback.\n\nSo we need to login to GitHub Container Registry first in order to be able to push our OCI image:\n\n```shell\necho $CR_PAT | docker login ghcr.io -u YourAccountOrGHOrgaNameHere --password-stdin\n```\n\nMake sure to use a Personal Access Token as described in this post https://www.codecentric.de/wissens-hub/blog/github-container-registry with the following scopes (`repo`, `write:packages` and `delete:packages`):\n\n![](docs/github-container-registry-pat-scopes.png)\n\nAdditionally we need to add the domain configuration like this: `--domain=https://ghcr.io`. Otherwise the default domain is `upbound.io` which will lead to non pushed Configurations - only visible via the `verbose` flag.\n\nWith this our `crossplane xpkg push` command should work as expected:\n\n```shell\n$ crossplane xpkg push ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.1 --domain=https://ghcr.io --verbose\n\n2024-03-21T16:39:48+01:00\tDEBUG\tFound package in directory\t{\"path\": \"crossplane-eks-cluster-7badc365c06a.xpkg\"}\n2024-03-21T16:39:48+01:00\tDEBUG\tGetting credentials for server\t{\"serverURL\": \"ghcr.io\"}\n2024-03-21T16:39:48+01:00\tDEBUG\tNo profile specified, using default profile\n2024-03-21T16:39:49+01:00\tDEBUG\tPushed package\t{\"path\": \"crossplane-eks-cluster-7badc365c06a.xpkg\", \"ref\": \"ghcr.io/jonashackt/crossplane-eks-cluster:v0.0.1\"}\n```\n\nNow head over to your GitHub Organisation's `Packages` tab and search for the newly created package:\n\n![](docs/github-container-registry-package-connect-repository.png)\n\nClick onto the package and connect the GitHub Repository.\n\nAlso - on the right - click on `Package settings` and scroll down to the `Danger Zone`. There click on `Change visibility` and change it to public. Now your Crossplane Configuration should be available for download without login.\n\nIf everything went fine, the package / OCI image should now be visible at your repository:\n\n![](docs/github-container-registry-package-visible.png)\n\n\n### Build \u0026 Publish Crossplane Configuration Packages automatically with GitHub Actions\n\nhttps://docs.upbound.io/xp-arch-framework/building-apis/building-apis-configurations/#set-up-a-build-pipeline-with-github\n\nSo let's finally do it all automatically on Composition code changes (git commit/push) using GitHub Actions. We simply extend our workflow at [.github/workflows/test-composition-and-publish-to-ghcr.yml](.github/workflows/test-composition-and-publish-to-ghcr.yml) and do all the steps from above:\n\n```yaml\nname: publish\n\non: [push]\n\nenv:\n  GHCR_PAT: ${{ secrets.GHCR_PAT }}\n  CONFIGURATION_VERSION: \"v0.0.2\"\n\njobs:\n  resouces-rendering-test:\n    ...\n\njobs:\n  build-configuration-and-publish-to-ghcr:\n    needs: resouces-rendering-test\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GHCR_PAT }}\n\n      - name: Install Crossplane CLI\n        run: |\n          curl -sL \"https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh\" |sh\n          sudo mv crossplane /usr/local/bin\n\n      - name: Build Crossplane Configuration package \u0026 publish it to GitHub Container Registry\n        run: |\n          echo \"### Build Configuration .xpkg file\"\n          crossplane xpkg build --package-root=. --examples-root=\"./examples\" --ignore=\".github/workflows/*,crossplane/install/*,crossplane/provider/*,kuttl-test.yaml,tests/compositions/eks/*,tests/compositions/networking/*\" --verbose\n\n          echo \"### Publish as OCI image to GHCR\"\n          crossplane xpkg push \"ghcr.io/jonashackt/crossplane-eks-cluster:$CONFIGURATION_VERSION\" --domain=https://ghcr.io --verbose\n```\n\nAs we added the `.github/workflows` directory with a workflow yaml file, the `crossplane xpkg build` command also tries to include it. Therefore the command locally need to exclude the workflow file also:\n\n```shell\ncrossplane xpkg build --package-root=. --examples-root=\"./examples\" --ignore=\".github/workflows/*,crossplane/install/*,crossplane/provider/*,kuttl-test.yaml,tests/compositions/eks/*,tests/compositions/networking/*\" --verbose\n```\n\n`--ignore=\".github/*` won't work, since the command doesn't support to exclude directories - only wildcards IN directories.\n\nAlso to prevent the following error:\n\n```shell\ncrossplane: error: failed to push package file crossplane-eks-cluster-7badc365c06a.xpkg: PUT https://ghcr.io/v2/jonashackt/crossplane-eks-cluster/manifests/v0.0.2: DENIED: installation not allowed to Write organization package\n```\n\nwe use the Personal Access Token (PAT) we already created above also in our GitHub Actions Workflow instead of the default `GITHUB_TOKEN` in order to have the correct permissions. Therefore create it as a new Repository Secret:\n\n![](docs/create-repository-secret.png)\n\nWith this we should also be able to use a ENV var for our Configuration version or even `latest`.\n\nTo prevent the `build-configuration-and-publish-to-ghcr` step from running before the test job successfully finished, [we use the `needs: resouces-rendering-test` keyword as described here](https://stackoverflow.com/a/65698892/4964553).\n\nNow our Pipeline should look like this:\n\n![](docs/only-publish-when-test-successful-pipeline.png)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fcrossplane-eks-cluster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonashackt%2Fcrossplane-eks-cluster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonashackt%2Fcrossplane-eks-cluster/lists"}