From 247b7c3111d559d0047ab3a0379f3f5c9bdcba29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Wed, 23 Oct 2024 16:28:31 +0200 Subject: [PATCH 1/5] Introduce kcl-version of composition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Kässer --- .gitignore | 5 +- .gitmodules | 2 +- Makefile | 150 ++++++++++------ apis/kcl/generate.k | 44 +++++ apis/kcl/main.k | 313 +++++++++++++++++++++++++++++++++ build | 2 +- crossplane.yaml | 3 + examples/functions.yaml | 11 +- examples/gotpl/network-xr.yaml | 2 +- examples/kcl/network-xr.yaml | 28 +++ project.mk | 3 + 11 files changed, 500 insertions(+), 63 deletions(-) create mode 100644 apis/kcl/generate.k create mode 100644 apis/kcl/main.k create mode 100644 examples/kcl/network-xr.yaml create mode 100644 project.mk diff --git a/.gitignore b/.gitignore index fe34fe6..2a90c50 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ *.xpkg kubeconfig -devbox* \ No newline at end of file +devbox* + +# generated by kcl +apis/kcl/composition.yaml diff --git a/.gitmodules b/.gitmodules index 8f84209..394e1ee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "build"] path = build - url = https://github.com/crossplane/build + url = https://github.com/crossplane/build.git diff --git a/Makefile b/Makefile index 027db85..d6ff9a7 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,47 @@ +# Usage +# ==================================================================================== +# Generic Makefile to be used across repositories building a crossplane configuration +# package +# +# Available targets: +# +# - `yamllint` +# Runs yamllint for all files in `api`-folder recursively +# +# - `render` +# Runs crossplane render to render the output of the composition. Usefule for quick +# feedback in order to test templating. +# Important note: +# Claims need following annotations in order for render to work (adjust the paths +# if necessary): +# render.crossplane.io/composition-path: apis/pat/composition.yaml +# render.crossplane.io/function-path: examples/functions.yaml +# +# - `e2e` +# Runs full end-to-end test, including creating cluster, setting up the configuration +# and testing if create, import and delete work as expected. +# Available options: +# UPTEST_SKIP_DELETE (default `false`) skips the deletion of any resources created during the test +# UPTEST_SKIP_UPDATE (default `false`) skips testing the update of the claims +# UPTEST_SKIP_IMPORT (default `true`) skips testing the import of resources +# Example: +# `make e2e UPTEST_SKIP_DELETE=true` +# +# Language specific options: +# +# - `KCL` +# KCL_COMPOSITION_PATH can be set to the kcl-file creating composition.yaml. Default: `apis/kcl/generate.k` + # Project Setup -PROJECT_NAME := configuration-aws-network +# ==================================================================================== + +# Include project.mk for project specific settings +include project.mk + +ifndef PROJECT_NAME + $(error PROJECT_NAME is not set. Please create `project.mk` and set it there.) +endif + PROJECT_REPO := github.com/upbound/$(PROJECT_NAME) # NOTE(hasheddan): the platform is insignificant here as Configuration package @@ -11,10 +53,9 @@ PLATFORMS ?= linux_amd64 # ==================================================================================== # Setup Kubernetes tools -UP_VERSION = v0.32.1 +UP_VERSION = v0.34.0 UP_CHANNEL = stable -UPTEST_VERSION = v0.13.1 -CROSSPLANE_CLI_VERSION=v1.16.0 +CROSSPLANE_CLI_VERSION = v1.17.1 -include build/makelib/k8s_tools.mk # ==================================================================================== @@ -28,15 +69,25 @@ XPKG_REG_ORGS_NO_PROMOTE ?= xpkg.upbound.io/upbound XPKGS = $(PROJECT_NAME) -include build/makelib/xpkg.mk -CROSSPLANE_VERSION = 1.16.0-up.1 +CROSSPLANE_VERSION = v1.17.1-up.1 CROSSPLANE_CHART_REPO = https://charts.upbound.io/stable CROSSPLANE_CHART_NAME = universal-crossplane CROSSPLANE_NAMESPACE = upbound-system CROSSPLANE_ARGS = "--enable-usages" -KIND_CLUSTER_NAME = uptest-$(PROJECT_NAME) +KIND_CLUSTER_NAME ?= uptest-$(PROJECT_NAME) + -include build/makelib/local.xpkg.mk -include build/makelib/controlplane.mk +# ==================================================================================== +# Testing + +UPTEST_VERSION = v1.1.2 +UPTEST_LOCAL_DEPLOY_TARGET = local.xpkg.deploy.configuration.$(PROJECT_NAME) +UPTEST_DEFAULT_TIMEOUT = 2400s + +-include build/makelib/uptest.mk + # ==================================================================================== # Targets @@ -59,59 +110,44 @@ submodules: ## Update the submodules, such as the common build scripts. # machinery sets UP to point to tool cache. build.init: $(UP) -# ==================================================================================== -# End to End Testing - -check: -ifndef UPTEST_CLOUD_CREDENTIALS - @$(INFO) Please export UPTEST_CLOUD_CREDENTIALS, e.g. \`export UPTEST_CLOUD_CREDENTIALS=\$\(cat \~/.aws/credentials\)\` - @$(FAIL) +# In case of building a composition with kcl, we must first render the composition-file +KCL_COMPOSITION_PATH ?= apis/kcl/generate.k +LANG_KCL := $(shell find ./apis -type f -name '*.k') +ifdef LANG_KCL +kcl: $(KCL) ## Generate KCL-based Composition + @$(INFO) Generating kcl composition + $(KCL) $(KCL_COMPOSITION_PATH) + @$(OK) Generated kcl composition + +render: kcl +build.init: kcl +.PHONY: kcl endif -# This target requires the following environment variables to be set: -# - UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) -# - UPTEST_DATASOURCE_PATH (optional), see https://github.com/upbound/uptest#injecting-dynamic-values-and-datasource -SKIP_DELETE ?= -uptest: $(UPTEST) $(KUBECTL) $(KUTTL) - @$(INFO) running automated tests - @KUBECTL=$(KUBECTL) KUTTL=$(KUTTL) CROSSPLANE_NAMESPACE=$(CROSSPLANE_NAMESPACE) CROSSPLANE_CLI=$(CROSSPLANE_CLI) $(UPTEST) e2e "${UPTEST_EXAMPLE_LIST}" --data-source="${UPTEST_DATASOURCE_PATH}" --setup-script=test/setup.sh --default-timeout=2400 $(SKIP_DELETE) || $(FAIL) - @$(OK) running automated tests - -# This target requires the following environment variables to be set: -# - UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials) -e2e: check build controlplane.down controlplane.up local.xpkg.deploy.configuration.$(PROJECT_NAME) uptest ## Run uptest together with all dependencies. Use `make e2e SKIP_DELETE=--skip-delete` to skip deletion of resources. - -render: $(CROSSPLANE_CLI) ${YQ} - @indir="./examples"; \ - for file in $$(find $$indir -type f -name '*.yaml' ); do \ - doc_count=$$(grep -c '^---' "$$file"); \ - if [[ $$doc_count -gt 0 ]]; then \ - continue; \ - fi; \ - COMPOSITION=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/composition-path"' $$file); \ - FUNCTION=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/function-path"' $$file); \ - ENVIRONMENT=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/environment-path"' $$file); \ - OBSERVE=$$(${YQ} eval '.metadata.annotations."render.crossplane.io/observe-path"' $$file); \ - if [[ "$$ENVIRONMENT" == "null" ]]; then \ - ENVIRONMENT=""; \ - fi; \ - if [[ "$$OBSERVE" == "null" ]]; then \ - OBSERVE=""; \ - fi; \ - if [[ "$$COMPOSITION" == "null" || "$$FUNCTION" == "null" ]]; then \ - continue; \ - fi; \ - ENVIRONMENT=$${ENVIRONMENT=="null" ? "" : $$ENVIRONMENT}; \ - OBSERVE=$${OBSERVE=="null" ? "" : $$OBSERVE}; \ - $(CROSSPLANE_CLI) beta render $$file $$COMPOSITION $$FUNCTION $${ENVIRONMENT:+-e $$ENVIRONMENT} $${OBSERVE:+-o $$OBSERVE} -x; \ - done - -yamllint: ## Static yamllint check - @$(INFO) running yamllint - @yamllint ./apis || $(FAIL) - @$(OK) running yamllint +.PHONY: check-examples +check-examples: ## Check examples for sanity + @$(INFO) Checking if package versions in dependencies match examples + @FN_EXAMPLES=$$( \ + find examples -type f -name "*.yaml" | \ + xargs $(YQ) -r -o=json 'select(.kind == "Function" and (.apiVersion | test("^pkg.crossplane.io/"))) | .spec.package' | \ + sort -u); \ + FN_DEPS=$$( \ + $(YQ) '.spec.dependsOn[] | select(.function != null) | (.function + ":" + .version)' crossplane.yaml | \ + sort -u \ + ); \ + if [ "$$FN_EXAMPLES" != "$$FN_DEPS" ]; then \ + echo "Function package versions in examples and in crossplane.yaml don't match!"; \ + echo "" ; \ + echo "Versions in dependencies:"; \ + echo "---" ; \ + echo "$$FN_DEPS"; \ + echo "" ; \ + echo "Versions in examples:"; \ + echo "---" ; \ + echo "$$FN_EXAMPLES"; \ + exit 1; \ + fi; + @$(OK) Package versions are sane help.local: @grep -E '^[a-zA-Z_-]+.*:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: uptest e2e render yamllint help.local diff --git a/apis/kcl/generate.k b/apis/kcl/generate.k new file mode 100644 index 0000000..4228134 --- /dev/null +++ b/apis/kcl/generate.k @@ -0,0 +1,44 @@ +import file +import yaml + +_composition = { + apiVersion = "apiextensions.crossplane.io/v1" + kind = "Composition" + metadata = { + name = "kcl.xnetworks.aws.platform.upbound.io" + labels = { + provider = "aws" + function = "kcl" + } + } + spec = { + compositeTypeRef = { + apiVersion = "aws.platform.upbound.io/v1alpha1" + kind = "XNetwork" + } + mode = "Pipeline" + pipeline = [ + { + step = "kcl" + functionRef = { + name = "crossplane-contrib-function-kcl" + } + input = { + apiVersion = "krm.kcl.dev/v1alpha1" + kind = "KCLRun" + spec = { + source = (file.read("apis/kcl/main.k")) + } + } + }, + { + step = "automatically-detect-ready-composed-resources" + functionRef = { + name = "crossplane-contrib-function-auto-ready" + } + } + ] + } +} + +file.write("apis/kcl/composition.yaml", yaml.encode(_composition)) diff --git a/apis/kcl/main.k b/apis/kcl/main.k new file mode 100644 index 0000000..f1a9fc4 --- /dev/null +++ b/apis/kcl/main.k @@ -0,0 +1,313 @@ +import regex + +xrApiVersion = option("params")?.oxr?.apiVersion +xrKind = option("params")?.oxr?.kind +xrName = option("params")?.oxr?.metadata.name +region = option("params")?.oxr?.spec.parameters.region or "" +id = option("params")?.oxr?.spec.parameters.id or "" +vpcCidrBlock = option("params")?.oxr?.spec.parameters.vpcCidrBlock or "" +subnets = option("params")?.oxr?.spec.parameters.subnets or "" +providerConfigRefName = option("params")?.oxr?.spec.parameters.providerConfigName or None + +_metadata = lambda name = str -> any { + { + annotations = {"krm.kcl.dev/composition-resource-name" = name} + } +} + +_defaults = { + deletionPolicy = option("params")?.oxr?.spec.parameters.deletionPolicy or "Delete" + if providerConfigRefName: + providerConfigRef.name = providerConfigRefName +} + +_cidrEscaped = lambda cidr = str -> str { + regex.replace(cidr, "\.|\/", "-") +} + +_formatSubnet = lambda s = dict -> str { + "{}-{}-{}".format(s.availabilityZone, _cidrEscaped(s.cidrBlock), s.type) +} + +_getExternalName = lambda resourceName = str -> str { + id = option("params")?.ocds?[resourceName]?.Resource?.metadata?.annotations?["crossplane.io/external-name"] or None +} + +_items = [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "VPC" + metadata = _metadata("vpc") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + cidrBlock = vpcCidrBlock + enableDnsHostnames = True + enableDnsSupport = True + tags = { + Name = xrName + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "InternetGateway" + metadata = _metadata("igw") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + vpcIdSelector = { + matchControllerRef = True + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "Subnet" + metadata = _metadata("subnet-" + _formatSubnet(s)) | { + labels = { + zone = s.availabilityZone + if s.type == "private": + access = "private" + else: + access = "public" + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + cidrBlock = s.cidrBlock + if s.type == "public": + mapPublicIpOnLaunch = True + tags = { + if s.type == "private": + "kubernetes.io/role/internal-elb" = "1" + else: + "kubernetes.io/role/elb" = "1" + "networks.aws.platform.upbound.io/network-id" = id + } + region = region + vpcIdSelector = { + matchControllerRef = True + } + availabilityZone = s.availabilityZone + } + } +} for s in subnets] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "RouteTable" + metadata = _metadata("rt") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + vpcIdSelector = { + matchControllerRef = True + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "Route" + metadata = _metadata("route") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + destinationCidrBlock = "0.0.0.0/0" + gatewayIdSelector = { + matchControllerRef = True + } + routeTableIdSelector = { + matchControllerRef = True + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "MainRouteTableAssociation" + metadata = _metadata("mrt") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + routeTableIdSelector = { + matchControllerRef = True + } + vpcIdSelector = { + matchControllerRef = True + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "RouteTableAssociation" + metadata = _metadata("rta-" + _formatSubnet(s)) | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + region = region + routeTableIdSelector = { + matchControllerRef = True + } + subnetIdSelector = { + matchControllerRef = True + matchLabels = { + if s.type == "private": + access = "private" + else: + access = "public" + zone = s.availabilityZone + } + } + } + } +} for s in subnets] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "SecurityGroup" + metadata = _metadata("sg") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + description = "Allow access to databases" + name = "platform-ref-aws-cluster" + vpcIdSelector = { + matchControllerRef = True + } + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "SecurityGroupRule" + metadata = _metadata("sgr-postgres") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + cidrBlocks = [ + "0.0.0.0/0" + ] + description = "Everywhere" + fromPort = 5432 + protocol = "tcp" + securityGroupIdSelector = { + matchControllerRef = True + } + toPort = 5432 + type = "ingress" + region = region + } + } +}] + +_items += [{ + apiVersion = "ec2.aws.upbound.io/v1beta1" + kind = "SecurityGroupRule" + metadata = _metadata("sgr-mysql") | { + labels = { + "networks.aws.platform.upbound.io/network-id" = id + } + } + spec = _defaults | { + forProvider = { + cidrBlocks = [ + "0.0.0.0/0" + ] + description = "Everywhere" + fromPort = 3306 + protocol = "tcp" + securityGroupIdSelector = { + matchControllerRef = True + } + toPort = 3306 + type = "ingress" + region = region + } + } +}] + +vpcId = option("params")?.ocds?.vpc?.Resource?.status?.atProvider?.id or False + +createdSubnets = [ + createdSubnet + for createdSubnet in [ + { + id = _getExternalName(subnetResource.name) + type = subnetResource.type + } + for subnetResource in [ + { + name = "subnet-" + _formatSubnet(s) + type = s.type + } + for s in subnets + ] + ] if createdSubnet.id != None +] + +securityGroupIds = [ + sgId + for sgId in [ + _getExternalName(sgResource) + for sgResource in [ + "sg-" + _formatSubnet(s) + for s in subnets + ] + ] if sgId != None +] + +_items += [{ + apiVersion = xrApiVersion + kind = xrKind + status = { + if vpcId: + vpcId = vpcId + subnetIds = [s.id for s in createdSubnets] + publicSubnetIds = [s.id for s in createdSubnets if s.type == "public"] + privateSubnetIds = [s.id for s in createdSubnets if s.type == "private"] + securityGroupIds = securityGroupIds + } +}] + +items = _items diff --git a/build b/build index 3cf6663..6f2b7c5 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 3cf6663fafcf22f5cb3e7b90cf21d981faa52230 +Subproject commit 6f2b7c5f0a88eac426439d6d9b40abd919d89fbe diff --git a/crossplane.yaml b/crossplane.yaml index 390d0b6..6902fb5 100644 --- a/crossplane.yaml +++ b/crossplane.yaml @@ -25,3 +25,6 @@ spec: - function: xpkg.upbound.io/crossplane-contrib/function-go-templating # renovate: datasource=github-releases depName=crossplane-contrib/function-go-templating version: "v0.5.0" + - function: xpkg.upbound.io/crossplane-contrib/function-kcl + # renovate: datasource=github-releases depName=crossplane-contrib/function-kcl + version: "v0.10.3" diff --git a/examples/functions.yaml b/examples/functions.yaml index 779f6df..a414fda 100644 --- a/examples/functions.yaml +++ b/examples/functions.yaml @@ -1,13 +1,20 @@ apiVersion: pkg.crossplane.io/v1beta1 kind: Function +metadata: + name: crossplane-contrib-function-kcl +spec: + package: xpkg.upbound.io/crossplane-contrib/function-kcl:v0.10.3 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function metadata: name: crossplane-contrib-function-go-templating spec: - package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.4.1 + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.5.0 --- apiVersion: pkg.crossplane.io/v1beta1 kind: Function metadata: name: crossplane-contrib-function-auto-ready spec: - package: xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.2.1 \ No newline at end of file + package: xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.2.1 diff --git a/examples/gotpl/network-xr.yaml b/examples/gotpl/network-xr.yaml index 7019d0b..e0ea37c 100644 --- a/examples/gotpl/network-xr.yaml +++ b/examples/gotpl/network-xr.yaml @@ -1,7 +1,7 @@ apiVersion: aws.platform.upbound.io/v1alpha1 kind: XNetwork metadata: - name: configuration-aws-network + name: configuration-aws-network-gotpl annotations: render.crossplane.io/composition-path: apis/gotpl/composition.yaml render.crossplane.io/function-path: examples/functions.yaml diff --git a/examples/kcl/network-xr.yaml b/examples/kcl/network-xr.yaml new file mode 100644 index 0000000..73f904a --- /dev/null +++ b/examples/kcl/network-xr.yaml @@ -0,0 +1,28 @@ +apiVersion: aws.platform.upbound.io/v1alpha1 +kind: XNetwork +metadata: + name: configuration-aws-network-kcl + annotations: + render.crossplane.io/composition-path: apis/kcl/composition.yaml + render.crossplane.io/function-path: examples/functions.yaml +spec: + compositionSelector: + matchLabels: + function: kcl + parameters: + id: configuration-aws-network + region: us-west-2 + vpcCidrBlock: 192.168.0.0/16 + subnets: + - availabilityZone: us-west-2a + type: public + cidrBlock: 192.168.0.0/18 + - availabilityZone: us-west-2b + type: public + cidrBlock: 192.168.64.0/18 + - availabilityZone: us-west-2a + type: private + cidrBlock: 192.168.128.0/18 + - availabilityZone: us-west-2b + type: private + cidrBlock: 192.168.192.0/18 diff --git a/project.mk b/project.mk new file mode 100644 index 0000000..b3e9949 --- /dev/null +++ b/project.mk @@ -0,0 +1,3 @@ +PROJECT_NAME := configuration-aws-network +UPTEST_INPUT_MANIFESTS := examples/kcl/network-xr.yaml,examples/gotpl/network-xr.yaml +UPTEST_SKIP_UPDATE := true From fc2e9f26dc7d2f59c114cf60e45df3d7e8b850d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Fri, 25 Oct 2024 10:27:52 +0200 Subject: [PATCH 2/5] PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Kässer --- .gitmodules | 2 +- Makefile | 2 ++ apis/kcl/generate.k | 2 +- crossplane.yaml | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 394e1ee..8f84209 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "build"] path = build - url = https://github.com/crossplane/build.git + url = https://github.com/crossplane/build diff --git a/Makefile b/Makefile index d6ff9a7..c5dd083 100644 --- a/Makefile +++ b/Makefile @@ -151,3 +151,5 @@ check-examples: ## Check examples for sanity help.local: @grep -E '^[a-zA-Z_-]+.*:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: uptest e2e render yamllint help.local diff --git a/apis/kcl/generate.k b/apis/kcl/generate.k index 4228134..f006eba 100644 --- a/apis/kcl/generate.k +++ b/apis/kcl/generate.k @@ -25,7 +25,7 @@ _composition = { } input = { apiVersion = "krm.kcl.dev/v1alpha1" - kind = "KCLRun" + kind = "KCLInput" spec = { source = (file.read("apis/kcl/main.k")) } diff --git a/crossplane.yaml b/crossplane.yaml index 6902fb5..46b0b7c 100644 --- a/crossplane.yaml +++ b/crossplane.yaml @@ -27,4 +27,4 @@ spec: version: "v0.5.0" - function: xpkg.upbound.io/crossplane-contrib/function-kcl # renovate: datasource=github-releases depName=crossplane-contrib/function-kcl - version: "v0.10.3" + version: "v0.10.6" From a069ceed994ed49f9e53c19ea44ec88cd1911551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Mon, 28 Oct 2024 11:37:47 +0100 Subject: [PATCH 3/5] Adding tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tobias Kässer --- Makefile | 22 ++++++++++++++--- apis/gotpl/composition.yaml | 4 ++-- build | 2 +- crossplane.yaml | 2 +- examples/gotpl/network-xr.yaml | 3 +++ test/render_test.k | 44 ++++++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 test/render_test.k diff --git a/Makefile b/Makefile index c5dd083..4684781 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,13 @@ # - `e2e` # Runs full end-to-end test, including creating cluster, setting up the configuration # and testing if create, import and delete work as expected. +# In case the configuration creates resources on a cloud provider, it requires the following +# environment variables to be set: +# UPTEST_CLOUD_CREDENTIALS, cloud credentials for the provider being tested, e.g. +# - for aws: `export UPTEST_CLOUD_CREDENTIALS=$(cat ~/.aws/credentials)` +# - for gcp: `export UPTEST_CLOUD_CREDENTIALS=$(cat gcp-sa.json)` +# - for azure: `export UPTEST_CLOUD_CREDENTIALS=$(cat azure.json)` +# # Available options: # UPTEST_SKIP_DELETE (default `false`) skips the deletion of any resources created during the test # UPTEST_SKIP_UPDATE (default `false`) skips testing the update of the claims @@ -30,7 +37,7 @@ # Language specific options: # # - `KCL` -# KCL_COMPOSITION_PATH can be set to the kcl-file creating composition.yaml. Default: `apis/kcl/generate.k` +# KCL_COMPOSITION_PATH can be set to the kcl-file creating composition.yaml. Default: `apis/kcl/generate.k` # Project Setup # ==================================================================================== @@ -61,7 +68,7 @@ CROSSPLANE_CLI_VERSION = v1.17.1 # ==================================================================================== # Setup XPKG XPKG_DIR = $(shell pwd) -XPKG_IGNORE = .github/workflows/*.yaml,.github/workflows/*.yml,examples/*.yaml,.work/uptest-datasource.yaml +XPKG_IGNORE = .github/workflows/*.yaml,.github/workflows/*.yml,examples/*.yaml,.work/uptest-datasource.yaml,.cache/render/* XPKG_REG_ORGS ?= xpkg.upbound.io/upbound # NOTE(hasheddan): skip promoting on xpkg.upbound.io as channel tags are # inferred. @@ -116,7 +123,7 @@ LANG_KCL := $(shell find ./apis -type f -name '*.k') ifdef LANG_KCL kcl: $(KCL) ## Generate KCL-based Composition @$(INFO) Generating kcl composition - $(KCL) $(KCL_COMPOSITION_PATH) + @$(KCL) $(KCL_COMPOSITION_PATH) 1>/dev/null @$(OK) Generated kcl composition render: kcl @@ -124,6 +131,15 @@ build.init: kcl .PHONY: kcl endif +render.test: $(CROSSPLANE_CLI) $(KCL) render + @for RENDERED_COMPOSITION in $$(find .cache/render -depth 1 -type f -name "*.yaml"); do \ + $(INFO) "Testing $${RENDERED_COMPOSITION}"; \ + export RENDERED_COMPOSITION; \ + $(KCL) test test/ && \ + $(OK) "Success testing \"$${RENDERED_COMPOSITION}\"!" || \ + $(ERR) "Failure testing \"$${RENDERED_COMPOSITION}\"!"; \ + done; \ + .PHONY: check-examples check-examples: ## Check examples for sanity @$(INFO) Checking if package versions in dependencies match examples diff --git a/apis/gotpl/composition.yaml b/apis/gotpl/composition.yaml index eab698b..56db12f 100644 --- a/apis/gotpl/composition.yaml +++ b/apis/gotpl/composition.yaml @@ -75,7 +75,7 @@ spec: {{- end }} networks.aws.platform.upbound.io/network-id: {{ $params.id }} annotations: - {{ setResourceNameAnnotation (printf "subnet-%s-%s" $subnet.availabilityZone $subnet.type) }} + {{ setResourceNameAnnotation (printf "subnet-%s-%s-%s" $subnet.availabilityZone (regexReplaceAll "\\.|\\/" $subnet.cidrBlock "-") $subnet.type) }} spec: forProvider: cidrBlock: {{ $subnet.cidrBlock }} @@ -160,7 +160,7 @@ spec: labels: networks.aws.platform.upbound.io/network-id: {{ $params.id }} annotations: - {{ setResourceNameAnnotation (printf "rta-%s-%s" $subnet.availabilityZone $subnet.type) }} + {{ setResourceNameAnnotation (printf "rta-%s-%s-%s" $subnet.availabilityZone (regexReplaceAll "\\.|\\/" $subnet.cidrBlock "-") $subnet.type) }} spec: forProvider: region: {{ $region }} diff --git a/build b/build index 6f2b7c5..420d07d 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 6f2b7c5f0a88eac426439d6d9b40abd919d89fbe +Subproject commit 420d07dfa6022dc38812e3140a41666644b533ec diff --git a/crossplane.yaml b/crossplane.yaml index 46b0b7c..4805a96 100644 --- a/crossplane.yaml +++ b/crossplane.yaml @@ -27,4 +27,4 @@ spec: version: "v0.5.0" - function: xpkg.upbound.io/crossplane-contrib/function-kcl # renovate: datasource=github-releases depName=crossplane-contrib/function-kcl - version: "v0.10.6" + version: "v0.10.8" diff --git a/examples/gotpl/network-xr.yaml b/examples/gotpl/network-xr.yaml index e0ea37c..430d9e8 100644 --- a/examples/gotpl/network-xr.yaml +++ b/examples/gotpl/network-xr.yaml @@ -6,6 +6,9 @@ metadata: render.crossplane.io/composition-path: apis/gotpl/composition.yaml render.crossplane.io/function-path: examples/functions.yaml spec: + compositionSelector: + matchLabels: + function: go-templating parameters: id: configuration-aws-network region: us-west-2 diff --git a/test/render_test.k b/test/render_test.k new file mode 100644 index 0000000..1a3ddc3 --- /dev/null +++ b/test/render_test.k @@ -0,0 +1,44 @@ +import file +import yaml + +getByCompositeResourceName = lambda name: str -> any { + render = yaml.decode_all(file.read(file.read_env("RENDERED_COMPOSITION"))) + resource = [ el for el in render if el.metadata.annotations?["crossplane.io/composition-resource-name"] == name][0] + resource +} + +test_subnets = lambda { + subnetPublic1 = getByCompositeResourceName("subnet-us-west-2a-192-168-0-0-18-public") + assert subnetPublic1.metadata.labels.access == "public" + assert subnetPublic1.spec.forProvider.mapPublicIpOnLaunch == True + assert subnetPublic1.spec.forProvider.tags["networks.aws.platform.upbound.io/network-id"] == "configuration-aws-network" + assert subnetPublic1.spec.forProvider.tags["kubernetes.io/role/elb"] == "1" + + subnetPublic2 = getByCompositeResourceName("subnet-us-west-2b-192-168-64-0-18-public") + assert subnetPublic1.metadata.labels.access == "public" + assert subnetPublic1.spec.forProvider.mapPublicIpOnLaunch == True + assert subnetPublic1.spec.forProvider.tags["networks.aws.platform.upbound.io/network-id"] == "configuration-aws-network" + assert subnetPublic1.spec.forProvider.tags["kubernetes.io/role/elb"] == "1" + + subnetPrivate1 = getByCompositeResourceName("subnet-us-west-2a-192-168-128-0-18-private") + assert subnetPrivate1.metadata.labels.access == "private" + assert subnetPrivate1.spec.forProvider.tags["kubernetes.io/role/internal-elb"] == "1" + + subnetPrivate2 = getByCompositeResourceName("subnet-us-west-2b-192-168-192-0-18-private") + assert subnetPrivate2.metadata.labels.access == "private" + assert subnetPrivate2.spec.forProvider.tags["kubernetes.io/role/internal-elb"] == "1" +} + +test_rtas = lambda { + rtaPublic1 = getByCompositeResourceName("rta-us-west-2a-192-168-0-0-18-public") + assert rtaPublic1.spec.forProvider.subnetIdSelector.matchLabels.access == "public" + + rtaPublic2 = getByCompositeResourceName("rta-us-west-2b-192-168-64-0-18-public") + assert rtaPublic2.spec.forProvider.subnetIdSelector.matchLabels.access == "public" + + rtaPrivate1 = getByCompositeResourceName("rta-us-west-2a-192-168-128-0-18-private") + assert rtaPrivate1.spec.forProvider.subnetIdSelector.matchLabels.access == "private" + + rtaPrivate2 = getByCompositeResourceName("rta-us-west-2b-192-168-192-0-18-private") + assert rtaPrivate2.spec.forProvider.subnetIdSelector.matchLabels.access == "private" +} From febe05fa88c3ddddcbc5023b2303264ebbd2ba02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Tue, 5 Nov 2024 12:34:27 +0100 Subject: [PATCH 4/5] Add docs for render.show --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 4684781..c92dca2 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,12 @@ # if necessary): # render.crossplane.io/composition-path: apis/pat/composition.yaml # render.crossplane.io/function-path: examples/functions.yaml +# This will only populate the cache. Use `render.show` to use the output +# +# - `render.show` +# Outputs the rendered yaml, useful for validating the rendered output manually +# or together with 'crossplane validate', e.g.: +# `make render.show | crossplane beta validate crossplane.yaml -` # # - `e2e` # Runs full end-to-end test, including creating cluster, setting up the configuration From 19f9b77b4250102663306b923b2e3027aca01be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=A4sser?= Date: Tue, 5 Nov 2024 13:04:19 +0100 Subject: [PATCH 5/5] Add render.test to ci and remove not needed qemu-part --- .github/workflows/ci.yaml | 5 ----- .github/workflows/render_test.yaml | 17 +++++++++++++++++ Makefile | 9 +++++++-- build | 2 +- 4 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/render_test.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9ea1978..49bdee4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,11 +31,6 @@ jobs: if: needs.detect-noop.outputs.noop != 'true' steps: - - name: Setup QEMU - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3 - with: - platforms: all - - name: Setup Docker Buildx uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3 with: diff --git a/.github/workflows/render_test.yaml b/.github/workflows/render_test.yaml new file mode 100644 index 0000000..d6ac606 --- /dev/null +++ b/.github/workflows/render_test.yaml @@ -0,0 +1,17 @@ +name: render-test +on: + - pull_request + - push +jobs: + test: + name: runner / render-test + runs-on: e2-standard-8 + steps: + - name: Checkout + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 + - name: Run KCL Unit-Tests + id: run-kcl-test + run: | + set -eo pipefail + make submodules + make render.test diff --git a/Makefile b/Makefile index c92dca2..2fbfbd4 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,10 @@ # or together with 'crossplane validate', e.g.: # `make render.show | crossplane beta validate crossplane.yaml -` # +# - `render.test` +# Executes kcl-unit tests on rendered manifests. Tests live in `test`-folder and +# can be written as normal kcl-tests +# # - `e2e` # Runs full end-to-end test, including creating cluster, setting up the configuration # and testing if create, import and delete work as expected. @@ -138,12 +142,13 @@ build.init: kcl endif render.test: $(CROSSPLANE_CLI) $(KCL) render - @for RENDERED_COMPOSITION in $$(find .cache/render -depth 1 -type f -name "*.yaml"); do \ + @echo find .cache/render -depth 1 -type f -name '*.yaml' + @for RENDERED_COMPOSITION in $$(find .cache/render -maxdepth 1 -type f -name '*.yaml'); do \ $(INFO) "Testing $${RENDERED_COMPOSITION}"; \ export RENDERED_COMPOSITION; \ $(KCL) test test/ && \ $(OK) "Success testing \"$${RENDERED_COMPOSITION}\"!" || \ - $(ERR) "Failure testing \"$${RENDERED_COMPOSITION}\"!"; \ + ($(ERR) "Failure testing \"$${RENDERED_COMPOSITION}\"!" && exit 1); \ done; \ .PHONY: check-examples diff --git a/build b/build index 420d07d..cc14f9c 160000 --- a/build +++ b/build @@ -1 +1 @@ -Subproject commit 420d07dfa6022dc38812e3140a41666644b533ec +Subproject commit cc14f9cdac034e0eaaeb43479f57ee85d5490473