diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c05dd..995bed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- [#4] Add CRD-Release to Jenkinsfile ## [v0.1.0] - 2023-09-05 diff --git a/Jenkinsfile b/Jenkinsfile index 47b8aac..e087224 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -187,9 +187,8 @@ void stageStaticAnalysisSonarQube() { void stageAutomaticRelease(Makefile makefile) { if (gitflow.isReleaseBranch()) { - String releaseVersion = git.getSimpleBranchName() - String dockerReleaseVersion = releaseVersion.split("v")[1] String controllerVersion = makefile.getVersion() + String releaseVersion = "v${controllerVersion}".toString() stage('Build & Push Image') { withCredentials([usernamePassword(credentialsId: 'cesmarvin', @@ -200,10 +199,10 @@ void stageAutomaticRelease(Makefile makefile) { "login ${CES_MARVIN_USERNAME}\n" + "password ${CES_MARVIN_PASSWORD}\" >> ~/.netrc" } - def dockerImage = docker.build("cloudogu/${repositoryName}:${dockerReleaseVersion}") + def dockerImage = docker.build("cloudogu/${repositoryName}:${controllerVersion}") sh "rm ~/.netrc" docker.withRegistry('https://registry.hub.docker.com/', 'dockerHubCredentials') { - dockerImage.push("${dockerReleaseVersion}") + dockerImage.push("${controllerVersion}") } } @@ -238,11 +237,16 @@ void stageAutomaticRelease(Makefile makefile) { .mountJenkinsUser() .inside("--volume ${WORKSPACE}:/go/src/${project} -w /go/src/${project}") { - make 'k8s-helm-package-release' + // Package operator-chart & crd-chart + make 'helm-package-release' + make 'crd-helm-package' + // Push charts withCredentials([usernamePassword(credentialsId: 'harborhelmchartpush', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD')]) { sh ".bin/helm registry login ${registry} --username '${HARBOR_USERNAME}' --password '${HARBOR_PASSWORD}'" + sh ".bin/helm push target/helm/${repositoryName}-${controllerVersion}.tgz oci://${registry}/${registry_namespace}/" + sh ".bin/helm push target/helm-crd/${repositoryName}-crd-${controllerVersion}.tgz oci://${registry}/${registry_namespace}/" } } } diff --git a/Makefile b/Makefile index 1751f18..c76b740 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ VERSION=0.1.0 IMAGE_DEV=${K3CES_REGISTRY_URL_PREFIX}/${ARTIFACT_ID}:${VERSION} IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} GOTAG?=1.21 -MAKEFILES_VERSION=7.13.1 +MAKEFILES_VERSION=8.5.0 LINT_VERSION=v1.52.1 STAGE?=production diff --git a/build/make/bats/Dockerfile b/build/make/bats/Dockerfile index f75afe1..428ee05 100644 --- a/build/make/bats/Dockerfile +++ b/build/make/bats/Dockerfile @@ -4,4 +4,4 @@ ARG BATS_TAG FROM ${BATS_BASE_IMAGE}:${BATS_TAG} # Make bash more findable by scripts and tests -RUN apk add make git bash \ No newline at end of file +RUN apk add make git bash diff --git a/build/make/coder-lib.sh b/build/make/coder-lib.sh new file mode 100755 index 0000000..5be68fc --- /dev/null +++ b/build/make/coder-lib.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# a collection of helpful functions to update coder workspaces for rapid development +set -e -u -x -o pipefail + +function getCoderUser() { + coder users show me -o json | jq -r '.username' +} + +function getAllWorkspaces() { + coder list -c workspace | tail -n+2 +} + +function doesWorkspaceExist() { + coderUser="$1" + workspaceName="$2" + + workspace=$(coder list -a -o json | jq -r "select(.[].owner_name == \"${coderUser}\" and .[].name == \"${workspaceName}\") | .[0].name") + if [ -z "$workspace" ]; then + return 1 #workspace does not exist + else + return 0 + fi +} + +function generateUniqueWorkspaceName() { + local wantedWorkspacePrefix="$1" + # use time to make name unique + local time + time=$(date +'%H-%M-%S') + local lengthOfTime=${#time} + local delimiter='-' + local lengthOfDelimiter=${#delimiter} + # trim prefix, as workspace names are limited to 32 chars + local trimmedPrefix="${wantedWorkspacePrefix:0:$((32 - lengthOfDelimiter - lengthOfTime))}" + local uniqueName="${trimmedPrefix}${delimiter}${time}" + # '--' is forbidden in coder, replace multiple '-' with a single one. + echo "${uniqueName}" | awk '{gsub(/[-]+/,"-")}1' + # returns sth like 'myPrefix-12-45-23' +} + +function buildImage() { + local tag="$1" + local buildDir="${2:-./build}" + local secretDir="${3:-./secretArgs}" + local containerExec="${4:-podman}" + local secretArgs=() + # include build-secrets if there are any + # shellcheck disable=SC2231 + for secretPath in $secretDir/*; do + # do not match .sh scripts + [[ $secretPath == *.sh ]] && continue + local secretName + secretName=$(basename "$secretPath") + secretArgs+=("--secret=id=$secretName,src=$secretDir/$secretName") + done + if [ "$containerExec" = "podman" ]; then + $containerExec build -t "$tag" --pull=newer "$buildDir" "${secretArgs[@]}" + else + $containerExec build -t "$tag" --pull "$buildDir" "${secretArgs[@]}" + fi +} + +function doTrivyConvert() { + local trivyFlags=$1 + local outputFile=$2 + local containerExec=$3 + local jsonScanToConvert=$4 + + local containerJsonScanFile="/tmp/scan.json" + + # shellcheck disable=SC2086 + # as globbing is what we want here + "$containerExec" run --rm --pull=always \ + -v trivy-cache:/root/.cache \ + -v "$jsonScanToConvert:$containerJsonScanFile" \ + aquasec/trivy -q \ + convert $trivyFlags "$containerJsonScanFile" > "$outputFile" +} + +function uploadTemplate() { + local templateDir="${1:?"Error. you need to add the template directory as the first parameter"}" + local templateName="${2:?"Error. you need to add the template name as the second parameter"}" + # for terraform variables (not editable by workspace users) + local variablesFile="${templateDir}/variables.yaml" + if [ -f "$variablesFile" ]; then + local doesVariablesFileExist=1 + fi + if ! coder template push -y -d "$templateDir" ${doesVariablesFileExist:+--variables-file "$variablesFile"} "$templateName"; then + # if template does not exist yet, create it in coder + coder template create -y -d "$templateDir" ${doesVariablesFileExist:+--variables-file "$variablesFile"} "$templateName" + fi +} + +function createNewWorkspace() { + local templateName="$1" + local workspaceName="$2" + # 3. param is optional, set it to autofill prompts for coder params + local templateDir="${3-unset}" + local richParametersFile="${templateDir}/rich-parameters.yaml" + if [ -n "${templateDir+x}" ] && [ -f "$richParametersFile" ]; then + local doesRichParametersFileExist=1 + fi + coder create -t "$templateName" -y "$workspaceName" ${doesRichParametersFileExist:+--rich-parameter-file "$richParametersFile"} +} + +function removeAllOtherWorkspaces() { + local CODER_USER="$1" + local WORKSPACE_PREFIX="$2" + local IGNORED_WORKSPACE="$3" + WORKSPACES="$(getAllWorkspaces)" + for ws in $WORKSPACES; do + if [ "$ws" != "$CODER_USER/$IGNORED_WORKSPACE" ] && [[ "$ws" =~ ^"$CODER_USER/$WORKSPACE_PREFIX" ]]; then + echo "delete $ws" + if ! coder delete "$ws" -y; then + #do it twice as podman always throws an error at the first time + coder delete "$ws" -y + fi + fi + done +} + +function updateWorkspace() { + local coderUser="$1" + local workspaceName="$2" + local qualifiedWorkspaceName="$coderUser/$workspaceName" + if ! coder stop "$qualifiedWorkspaceName" -y; then + #do it twice as podman always throws an error at the first time + coder stop "$qualifiedWorkspaceName" -y + fi + coder update "$qualifiedWorkspaceName" +} + +function startTestWorkspace() { + local coderUser="$1" + local templateDir="$2" + local workspacePrefix="$3" + local templateName="$4" + local reuseTestWorkspace="$5" + + local newWorkspaceName + if [ "$reuseTestWorkspace" = false ]; then + newWorkspaceName="$(generateUniqueWorkspaceName "$workspacePrefix")" + # do that before deleting others, so that i don't need to wait + createNewWorkspace "$templateName" "$newWorkspaceName" "$templateDir" + # trim prefix as the name of the workspace can also get trimmed + removeAllOtherWorkspaces "$coderUser" "${workspacePrefix:0:22}" "$newWorkspaceName" + else + newWorkspaceName="$workspacePrefix" + if ! doesWorkspaceExist "$coderUser" "$newWorkspaceName"; then + createNewWorkspace "$templateName" "$newWorkspaceName" "$templateDir" + else + updateWorkspace "$coderUser" "$newWorkspaceName" + fi + fi +} + +function uploadToNexus() { + local fileToUpload="$1" + local fileNameNexus="${fileToUpload##*/}" + local templateName="$2" + local releaseVersion="$3" + local nexusUrl="${4:-https://ecosystem.cloudogu.com/nexus/repository/itz-bund/coder}" + set +x #disable command printing because of the password + curl --progress-bar -u "$(cat secrets/nexus-user):$(cat secrets/nexus-pw)" --upload-file "$fileToUpload" \ + "$nexusUrl/$templateName/$releaseVersion/$fileNameNexus" + set -x +} \ No newline at end of file diff --git a/build/make/coder.mk b/build/make/coder.mk new file mode 100644 index 0000000..f80d991 --- /dev/null +++ b/build/make/coder.mk @@ -0,0 +1,160 @@ +SHELL := /bin/bash + +IMAGE_TAG?=${IMAGE_REGISTRY}/coder/coder-${TEMPLATE_NAME}:${VERSION} +REUSE_TEST_WORKSPACE?=false + +#BUILD_DIR given via variables.mk +TEMPLATE_DIR=${WORKDIR}/template +CONTAINER_BUILD_DIR=${WORKDIR}/container +SECRETS_DIR=${WORKDIR}/secrets +CODER_LIB_PATH=${BUILD_DIR}/make/coder-lib.sh + +RELEASE_DIR=${WORKDIR}/release +MAKE_CHANGE_TOKEN_DIR=${RELEASE_DIR}/make +CONTAINER_FILE?=${CONTAINER_BUILD_DIR}/Dockerfile +CONTAINER_IMAGE_CHANGE_TOKEN?=${MAKE_CHANGE_TOKEN_DIR}/${TEMPLATE_NAME}_image_id.txt +CONTAINER_IMAGE_TAR?=${RELEASE_DIR}/${TEMPLATE_NAME}.tar +CONTAINER_IMAGE_TARGZ?=${RELEASE_DIR}/${TEMPLATE_NAME}.tar.gz +CONTAINER_IMAGE_TRIVY_SCAN_JSON?=${RELEASE_DIR}/trivy.json +CONTAINER_IMAGE_TRIVY_SCAN_TABLE?=${RELEASE_DIR}/trivy.txt +CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE?=${RELEASE_DIR}/trivy_critical.txt +CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON?=${RELEASE_DIR}/trivy_critical.json + +IMAGE_REGISTRY?=registry.cloudogu.com +IMAGE_REGISTRY_USER_FILE?=${SECRETS_DIR}/harbor-user +IMAGE_REGISTRY_PW_FILE?=${SECRETS_DIR}/harbor-pw + +CHANGELOG_FILE=${WORKDIR}/CHANGELOG.md +TEMPLATE_RELEASE_TAR_GZ=${RELEASE_DIR}/${TEMPLATE_NAME}-template.tar.gz + +TEST_WORKSPACE_PREFIX?=test-${TEMPLATE_NAME} +# only set if coder is installed, so that there is no problem with build and release targets +CODER_USER?=$(shell if [ -x "$(command -v coder)" ]; then coder users show me -o json | jq -r '.username'; else echo ""; fi ) + +CONTAINER_BIN?=$(shell if [ -x "$(command -v podman)" ]; then echo "podman"; else echo "docker"; fi) +GOPASS_BIN?=$(shell command -v gopass 2> /dev/null) + +EXCLUDED_TEMPLATE_FILES?=rich-parameters.yaml variables.yaml + + +##@ Coder template development + +${SECRETS_DIR}: + mkdir -p ${SECRETS_DIR} + +${IMAGE_REGISTRY_USER_FILE}: ${SECRETS_DIR} +ifeq ($(ENVIRONMENT), local) + @echo "Found developer environment. creating secret ${IMAGE_REGISTRY_USER_FILE}" + @${GOPASS_BIN} show ces/websites/registry.cloudogu.com/robot_coder_jenkins | tail -n 1 | sed -e "s/^username: //" > ${IMAGE_REGISTRY_USER_FILE}; +else + @echo "Found CI environment. Please create secrets yourself" +endif + +${IMAGE_REGISTRY_PW_FILE}: ${SECRETS_DIR} +ifeq ($(ENVIRONMENT), local) + @echo "Found developer environment. creating secret ${IMAGE_REGISTRY_PW_FILE}" + @${GOPASS_BIN} show ces/websites/registry.cloudogu.com/robot_coder_jenkins | head -n 1 > ${IMAGE_REGISTRY_PW_FILE}; +else + @echo "Found CI environment. Please create secrets yourself" +endif + +.PHONY: loadGopassSecrets +loadGopassSecrets: ${IMAGE_REGISTRY_USER_FILE} ${IMAGE_REGISTRY_PW_FILE} ${ADDITIONAL_SECRETS_TARGET} ## load secrets from gopass into secret files, so that the build process works locally + +.PHONY: imageRegistryLogin +imageRegistryLogin: loadGopassSecrets ${IMAGE_REGISTRY_USER_FILE} ${IMAGE_REGISTRY_PW_FILE} ## log in to the registry + @${CONTAINER_BIN} login -u "$$(cat ${IMAGE_REGISTRY_USER_FILE})" --password-stdin '${IMAGE_REGISTRY}' < ${IMAGE_REGISTRY_PW_FILE} + +.PHONY: imageRegistryLogout +imageRegistryLogout: ## log out of the registry + @${CONTAINER_BIN} logout '${IMAGE_REGISTRY}' + +.PHONY: buildImage +buildImage: buildImage-$(ENVIRONMENT) ## build the container image + +.PHONY: buildImage-local +buildImage-local: imageRegistryLogin ${CONTAINER_IMAGE_CHANGE_TOKEN} ## build the container image locally + @echo "if the build is not triggered without a change in the dockerfile, try to delete ${CONTAINER_IMAGE_CHANGE_TOKEN}" + +.PHONY: buildImage-ci +buildImage-ci: ${CONTAINER_IMAGE_CHANGE_TOKEN} ## build the container image without automatic secret management + +${CONTAINER_IMAGE_CHANGE_TOKEN}: ${CONTAINER_FILE} + @. ${CODER_LIB_PATH} && buildImage ${IMAGE_TAG} ${CONTAINER_BUILD_DIR} ${SECRETS_DIR} ${CONTAINER_BIN} + @mkdir -p ${MAKE_CHANGE_TOKEN_DIR} + @${CONTAINER_BIN} image ls --format="{{.ID}}" ${IMAGE_TAG} > ${CONTAINER_IMAGE_CHANGE_TOKEN} + +.PHONY: uploadTemplate +uploadTemplate: ## upload template to coder server + @. ${CODER_LIB_PATH} && uploadTemplate ${TEMPLATE_DIR} ${TEMPLATE_NAME} + +.PHONY: startTestWorkspace +startTestWorkspace: ## start a test workspace with coder + @. ${CODER_LIB_PATH} && startTestWorkspace ${CODER_USER} ${TEMPLATE_DIR} ${TEST_WORKSPACE_PREFIX} ${TEMPLATE_NAME} ${REUSE_TEST_WORKSPACE} + +.PHONY: createImageRelease +createImageRelease: ${CONTAINER_IMAGE_TARGZ} ## export the container image as a tar.gz + +${CONTAINER_IMAGE_TAR}: ${CONTAINER_IMAGE_CHANGE_TOKEN} + ${CONTAINER_BIN} save "${IMAGE_TAG}" -o ${CONTAINER_IMAGE_TAR} + +${CONTAINER_IMAGE_TARGZ}: ${CONTAINER_IMAGE_TAR} + gzip -f --keep "${CONTAINER_IMAGE_TAR}" + +.PHONY: trivyscanImage +trivyscanImage: ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} ${CONTAINER_IMAGE_TRIVY_SCAN_TABLE} ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE} ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON} ## do a trivy scan for the workspace image in various output formats + +${CONTAINER_IMAGE_TRIVY_SCAN_JSON}: ${CONTAINER_IMAGE_TAR} + ${CONTAINER_BIN} run --rm --pull=always \ + -v "trivy-cache:/root/.cache" \ + -v "${CONTAINER_IMAGE_TAR}:/tmp/image.tar" \ + aquasec/trivy -q \ + image --scanners vuln --input /tmp/image.tar -f json --timeout 15m \ + > ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + +${CONTAINER_IMAGE_TRIVY_SCAN_TABLE}: ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + @. ${CODER_LIB_PATH} && \ + doTrivyConvert "--format table" ${CONTAINER_IMAGE_TRIVY_SCAN_TABLE} ${CONTAINER_BIN} ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + +${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE}: ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + @. ${CODER_LIB_PATH} && \ + doTrivyConvert "--format table --severity CRITICAL" ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE} ${CONTAINER_BIN} ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + +${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON}: ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + @. ${CODER_LIB_PATH} && \ + doTrivyConvert "--format json --severity CRITICAL" ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON} ${CONTAINER_BIN} ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} + +.PHONY: createTemplateRelease +createTemplateRelease: ## generate template.tar.gz with all files needed for customers + # remove release dir first as 'cp' cannot merge and will place the source dir inside the target dir if it already exists + rm -rf "${RELEASE_DIR}/${TEMPLATE_NAME}" + cp -r "${TEMPLATE_DIR}" "${RELEASE_DIR}/${TEMPLATE_NAME}/" + #copy changelog + cp "${CHANGELOG_FILE}" "${RELEASE_DIR}/${TEMPLATE_NAME}/" + # remove excludes + for file in "${EXCLUDED_TEMPLATE_FILES}"; do \ + rm -f "${RELEASE_DIR}/${TEMPLATE_NAME}/$$file"; \ + done + tar -czf "${RELEASE_DIR}/${TEMPLATE_NAME}-template.tar.gz" -C "${RELEASE_DIR}" "${TEMPLATE_NAME}" + +.PHONY: createRelease ## generate template- and container archives and the trivy scans +createRelease: createTemplateRelease ${CONTAINER_IMAGE_TARGZ} trivyscanImage ## create the image.tar.gz, template.tar.gz and trivy scans + +.PHONY: cleanCoderRelease +cleanCoderRelease: ## clean release directory + rm -rf "${RELEASE_DIR}" + mkdir -p "${RELEASE_DIR}" + +.PHONY: pushImage +pushImage: ## push the container image into the registry + ${CONTAINER_BIN} push ${IMAGE_TAG} + +.PHONY: uploadRelease +uploadRelease: createTemplateRelease ${CONTAINER_IMAGE_TARGZ} ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} ${CONTAINER_IMAGE_TRIVY_SCAN_TABLE} ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE} ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON} ## upload release artifacts to nexus + @. ${CODER_LIB_PATH} && uploadToNexus ${TEMPLATE_RELEASE_TAR_GZ} ${TEMPLATE_NAME} ${VERSION} + @. ${CODER_LIB_PATH} && uploadToNexus ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} ${TEMPLATE_NAME} ${VERSION} + @. ${CODER_LIB_PATH} && uploadToNexus ${CONTAINER_IMAGE_TRIVY_SCAN_TABLE} ${TEMPLATE_NAME} ${VERSION} + @. ${CODER_LIB_PATH} && uploadToNexus ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_TABLE} ${TEMPLATE_NAME} ${VERSION} + @. ${CODER_LIB_PATH} && uploadToNexus ${CONTAINER_IMAGE_TRIVY_SCAN_CRITICAL_JSON} ${TEMPLATE_NAME} ${VERSION} + @. ${CODER_LIB_PATH} && uploadToNexus ${CONTAINER_IMAGE_TARGZ} ${TEMPLATE_NAME} ${VERSION} + diff --git a/build/make/k8s-component.mk b/build/make/k8s-component.mk new file mode 100644 index 0000000..2ec3811 --- /dev/null +++ b/build/make/k8s-component.mk @@ -0,0 +1,128 @@ +DEV_VERSION?=${VERSION}-dev +## Image URL to use all building/pushing image targets +IMAGE_DEV?=${K3CES_REGISTRY_URL_PREFIX}/${ARTIFACT_ID}:${DEV_VERSION} + +include $(WORKDIR)/build/make/k8s.mk + +BINARY_HELM = $(UTILITY_BIN_PATH)/helm +BINARY_HELM_VERSION?=v3.12.0-dev.1.0.20230817154107-a749b663101d +BINARY_HELM_ADDITIONAL_PUSH_ARGS?=--plain-http +BINARY_HELM_ADDITIONAL_PACK_ARGS?= +BINARY_HELM_ADDITIONAL_UNINST_ARGS?= +BINARY_HELM_ADDITIONAL_UPGR_ARGS?= + +K8S_HELM_TARGET ?= $(K8S_RESOURCE_TEMP_FOLDER)/helm +K8S_HELM_RESSOURCES ?= k8s/helm +K8S_HELM_RELEASE_TGZ=${K8S_HELM_TARGET}/${ARTIFACT_ID}-${VERSION}.tgz +K8S_HELM_DEV_RELEASE_TGZ=${K8S_HELM_TARGET}/${ARTIFACT_ID}-${DEV_VERSION}.tgz +K8S_HELM_ARTIFACT_NAMESPACE?=k8s + +K8S_RESOURCE_COMPONENT ?= "${K8S_RESOURCE_TEMP_FOLDER}/component-${ARTIFACT_ID}-${VERSION}.yaml" +K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML ?= $(WORKDIR)/build/make/k8s-component.tpl + +##@ K8s - Helm general +.PHONY: helm-init-chart +helm-init-chart: ${BINARY_HELM} ## Creates a Chart.yaml-template with zero values + @echo "Initialize ${K8S_HELM_RESSOURCES}/Chart.yaml..." + @mkdir -p ${K8S_HELM_RESSOURCES}/tmp/ + @${BINARY_HELM} create ${K8S_HELM_RESSOURCES}/tmp/${ARTIFACT_ID} + @cp ${K8S_HELM_RESSOURCES}/tmp/${ARTIFACT_ID}/Chart.yaml ${K8S_HELM_RESSOURCES}/ + @rm -dr ${K8S_HELM_RESSOURCES}/tmp + @sed -i 's/appVersion: ".*"/appVersion: "0.0.0-replaceme"/' ${K8S_HELM_RESSOURCES}/Chart.yaml + @sed -i 's/version: .*/version: 0.0.0-replaceme/' ${K8S_HELM_RESSOURCES}/Chart.yaml + +.PHONY: helm-generate-chart +helm-generate-chart: k8s-generate ${K8S_HELM_TARGET}/Chart.yaml ## Generates the final helm chart. + +.PHONY: ${K8S_HELM_TARGET}/Chart.yaml +${K8S_HELM_TARGET}/Chart.yaml: $(K8S_RESOURCE_TEMP_FOLDER) k8s-generate + @echo "Generate helm chart..." + @rm -drf ${K8S_HELM_TARGET} # delete folder, so the chart is newly created. + @mkdir -p ${K8S_HELM_TARGET}/templates + @cp $(K8S_RESOURCE_TEMP_YAML) ${K8S_HELM_TARGET}/templates + @${BINARY_YQ} 'select(document_index != (select(.kind == "CustomResourceDefinition") | document_index))' $(K8S_RESOURCE_TEMP_YAML) > ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml # select all documents without the CRD + @sed -i "s/'{{ .Namespace }}'/'{{ .Release.Namespace }}'/" ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml + @cp -r ${K8S_HELM_RESSOURCES}/** ${K8S_HELM_TARGET} + @if [[ ${STAGE} == "development" ]]; then \ + sed -i 's/appVersion: "0.0.0-replaceme"/appVersion: '$(DEV_VERSION)'/' ${K8S_HELM_TARGET}/Chart.yaml; \ + sed -i 's/version: 0.0.0-replaceme/version: '$(DEV_VERSION)'/' ${K8S_HELM_TARGET}/Chart.yaml; \ + else \ + sed -i 's/appVersion: "0.0.0-replaceme"/appVersion: "${VERSION}"/' ${K8S_HELM_TARGET}/Chart.yaml; \ + sed -i 's/version: 0.0.0-replaceme/version: ${VERSION}/' ${K8S_HELM_TARGET}/Chart.yaml; \ + fi + +##@ K8s - Helm dev targets + +.PHONY: helm-generate +helm-generate: helm-generate-chart ## Generates the final helm chart with dev-urls. + +.PHONY: helm-apply +helm-apply: ${BINARY_HELM} check-k8s-namespace-env-var image-import helm-generate $(K8S_POST_GENERATE_TARGETS) ## Generates and installs the helm chart. + @echo "Apply generated helm chart" + @${BINARY_HELM} upgrade -i ${ARTIFACT_ID} ${K8S_HELM_TARGET} ${BINARY_HELM_ADDITIONAL_UPGR_ARGS} --namespace ${NAMESPACE} + +.PHONY: helm-delete +helm-delete: ${BINARY_HELM} check-k8s-namespace-env-var ## Uninstalls the current helm chart. + @echo "Uninstall helm chart" + @${BINARY_HELM} uninstall ${ARTIFACT_ID} --namespace=${NAMESPACE} ${BINARY_HELM_ADDITIONAL_UNINST_ARGS} || true + +.PHONY: helm-reinstall +helm-reinstall: helm-delete helm-apply ## Uninstalls the current helm chart and reinstalls it. + +.PHONY: helm-chart-import +helm-chart-import: check-all-vars check-k8s-artifact-id helm-generate-chart helm-package-release image-import ## Imports the currently available chart into the cluster-local registry. + @if [[ ${STAGE} == "development" ]]; then \ + echo "Import ${K8S_HELM_DEV_RELEASE_TGZ} into K8s cluster ${K3CES_REGISTRY_URL_PREFIX}..."; \ + ${BINARY_HELM} push ${K8S_HELM_DEV_RELEASE_TGZ} oci://${K3CES_REGISTRY_URL_PREFIX}/${K8S_HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + else \ + echo "Import ${K8S_HELM_RELEASE_TGZ} into K8s cluster ${K3CES_REGISTRY_URL_PREFIX}..."; \ + ${BINARY_HELM} push ${K8S_HELM_RELEASE_TGZ} oci://${K3CES_REGISTRY_URL_PREFIX}/${K8S_HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + fi + @echo "Done." + +##@ K8s - Helm release targets + +.PHONY: helm-generate-release +helm-generate-release: ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml ## Generates the final helm chart with release urls. + +${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml: $(K8S_PRE_GENERATE_TARGETS) ${K8S_HELM_TARGET}/Chart.yaml + @sed -i "s/'{{ .Namespace }}'/'{{ .Release.Namespace }}'/" ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml + +.PHONY: helm-package-release +helm-package-release: ${BINARY_HELM} helm-delete-existing-tgz ${K8S_HELM_RELEASE_TGZ} ## Generates and packages the helm chart with release urls. + +.PHONY: helm-delete-existing-tgz +helm-delete-existing-tgz: ## Remove an existing Helm package. +# remove + @rm -f ${K8S_HELM_RELEASE_TGZ}* + +${K8S_HELM_RELEASE_TGZ}: ${BINARY_HELM} ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml helm-generate-chart $(K8S_POST_GENERATE_TARGETS) ## Generates and packages the helm chart with release urls. + @echo "Package generated helm chart" + @${BINARY_HELM} package ${K8S_HELM_TARGET} -d ${K8S_HELM_TARGET} ${BINARY_HELM_ADDITIONAL_PACK_ARGS} + +${BINARY_HELM}: $(UTILITY_BIN_PATH) ## Download helm locally if necessary. + $(call go-get-tool,$(BINARY_HELM),helm.sh/helm/v3/cmd/helm@${BINARY_HELM_VERSION}) + +##@ K8s - Component dev targets + +.PHONY: component-generate +component-generate: ${K8S_RESOURCE_TEMP_FOLDER} ## Generate the component yaml resource. + @echo "Generating temporary K8s component resource: ${K8S_RESOURCE_COMPONENT}" + @if [[ ${STAGE} == "development" ]]; then \ + sed "s|NAMESPACE|$(K8S_HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_ID)|g" | sed "s|VERSION|$(DEV_VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ + else \ + sed "s|NAMESPACE|$(K8S_HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_ID)|g" | sed "s|VERSION|$(VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ + fi + +.PHONY: component-apply +component-apply: check-k8s-namespace-env-var image-import helm-generate helm-chart-import component-generate $(K8S_POST_GENERATE_TARGETS) ## Applies the component yaml resource to the actual defined context. + @kubectl apply -f "${K8S_RESOURCE_COMPONENT}" --namespace="${NAMESPACE}" + @echo "Done." + +.PHONY: component-delete +component-delete: check-k8s-namespace-env-var component-generate $(K8S_POST_GENERATE_TARGETS) ## Deletes the component yaml resource from the actual defined context. + @kubectl delete -f "${K8S_RESOURCE_COMPONENT}" --namespace="${NAMESPACE}" || true + @echo "Done." + +.PHONY: component-reinstall +component-reinstall: component-delete component-apply ## Reinstalls the component yaml resource from the actual defined context. \ No newline at end of file diff --git a/build/make/k8s-component.tpl b/build/make/k8s-component.tpl new file mode 100644 index 0000000..0192eec --- /dev/null +++ b/build/make/k8s-component.tpl @@ -0,0 +1,10 @@ +apiVersion: k8s.cloudogu.com/v1 +kind: Component +metadata: + name: NAME + labels: + app: ces +spec: + name: NAME + namespace: NAMESPACE + version: VERSION \ No newline at end of file diff --git a/build/make/k8s-controller.mk b/build/make/k8s-controller.mk index 5bdd885..ea4334a 100644 --- a/build/make/k8s-controller.mk +++ b/build/make/k8s-controller.mk @@ -14,16 +14,11 @@ # @$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." # This script requires the k8s.mk script -include $(WORKDIR)/build/make/k8s.mk +include $(WORKDIR)/build/make/k8s-component.mk +include $(WORKDIR)/build/make/k8s-crd.mk ## Variables -# Setting SHELL to bash allows bash commands to be executed by recipes. -# This is a requirement for 'setup-envtest.sh' in the test target. -# Options are set to exit when a recipe line exits non-zero or a piped command fails. -SHELL = /usr/bin/env bash -o pipefail -.SHELLFLAGS = -ec - # make sure to create a statically linked binary otherwise it may quit with # "exec user process caused: no such file or directory" GO_BUILD_FLAGS=-mod=vendor -a -tags netgo,osusergo $(LDFLAGS) -o $(BINARY) @@ -38,7 +33,7 @@ K8S_INTEGRATION_TEST_DIR=${TARGET_DIR}/k8s-integration-test ##@ K8s - EcoSystem .PHONY: build -build: k8s-helm-apply ## Builds a new version of the dogu and deploys it into the K8s-EcoSystem. +build: helm-apply ## Builds a new version of the dogu and deploys it into the K8s-EcoSystem. ##@ Release diff --git a/build/make/k8s-crd.mk b/build/make/k8s-crd.mk new file mode 100644 index 0000000..639a941 --- /dev/null +++ b/build/make/k8s-crd.mk @@ -0,0 +1,78 @@ +ARTIFACT_CRD_ID=$(ARTIFACT_ID)-crd +DEV_CRD_VERSION?=${VERSION}-dev +K8S_HELM_CRD_TARGET ?= $(K8S_RESOURCE_TEMP_FOLDER)/helm-crd +K8S_HELM_CRD_RESSOURCES ?= k8s/helm-crd +K8S_HELM_CRD_RELEASE_TGZ=${K8S_HELM_CRD_TARGET}/${ARTIFACT_CRD_ID}-${VERSION}.tgz +K8S_HELM_CRD_DEV_RELEASE_TGZ=${K8S_HELM_CRD_TARGET}/${ARTIFACT_CRD_ID}-${DEV_CRD_VERSION}.tgz + +K8S_RESOURCE_CRD_COMPONENT ?= "${K8S_RESOURCE_TEMP_FOLDER}/component-${ARTIFACT_CRD_ID}-${VERSION}.yaml" +K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML ?= $(WORKDIR)/build/make/k8s-component.tpl + +##@ K8s - CRD targets + +.PHONY: crd-helm-generate-chart ## Generates the helm crd-chart +crd-helm-generate-chart: ${BINARY_YQ} $(K8S_RESOURCE_TEMP_FOLDER) k8s-generate + @echo "Generate helm crd-chart..." + @rm -drf ${K8S_HELM_CRD_TARGET} # delete folder, so the chart is newly created. + @mkdir -p ${K8S_HELM_CRD_TARGET}/templates + @cp -r ${K8S_HELM_CRD_RESSOURCES}/** ${K8S_HELM_CRD_TARGET} + @${BINARY_YQ} 'select(.kind == "CustomResourceDefinition")' $(K8S_RESOURCE_TEMP_YAML) > ${K8S_HELM_CRD_TARGET}/templates/$(ARTIFACT_CRD_ID)_$(VERSION).yaml + @sed -i 's/name: artifact-crd-replaceme/name: ${ARTIFACT_CRD_ID}/' ${K8S_HELM_CRD_TARGET}/Chart.yaml + @if [[ ${STAGE} == "development" ]]; then \ + sed -i 's/appVersion: "0.0.0-replaceme"/appVersion: "${DEV_CRD_VERSION}"/' ${K8S_HELM_CRD_TARGET}/Chart.yaml; \ + sed -i 's/version: 0.0.0-replaceme/version: ${DEV_CRD_VERSION}/' ${K8S_HELM_CRD_TARGET}/Chart.yaml; \ + else \ + sed -i 's/appVersion: "0.0.0-replaceme"/appVersion: "${VERSION}"/' ${K8S_HELM_CRD_TARGET}/Chart.yaml; \ + sed -i 's/version: 0.0.0-replaceme/version: ${VERSION}/' ${K8S_HELM_CRD_TARGET}/Chart.yaml; \ + fi + +.PHONY: crd-helm-apply +crd-helm-apply: ${BINARY_HELM} check-k8s-namespace-env-var crd-helm-generate-chart $(K8S_POST_GENERATE_TARGETS) ## Generates and installs the helm crd-chart. + @echo "Apply generated helm crd-chart" + @${BINARY_HELM} upgrade -i ${ARTIFACT_CRD_ID} ${K8S_HELM_CRD_TARGET} ${BINARY_HELM_ADDITIONAL_UPGR_ARGS} --namespace ${NAMESPACE} + +.PHONY: crd-helm-delete +crd-helm-delete: ${BINARY_HELM} check-k8s-namespace-env-var ## Uninstalls the current helm crd-chart. + @echo "Uninstall helm crd-chart" + @${BINARY_HELM} uninstall ${ARTIFACT_CRD_ID} --namespace=${NAMESPACE} ${BINARY_HELM_ADDITIONAL_UNINST_ARGS} || true + +.PHONY: crd-helm-package +crd-helm-package: ${BINARY_HELM} crd-helm-delete-existing-tgz ${K8S_HELM_CRD_RELEASE_TGZ} ## Generates and packages the helm crd-chart. + +.PHONY: crd-helm-delete-existing-tgz +crd-helm-delete-existing-tgz: ## Remove an existing Helm crd-package. + @rm -f ${K8S_HELM_CRD_RELEASE_TGZ}* + +${K8S_HELM_CRD_RELEASE_TGZ}: ${BINARY_HELM} crd-helm-generate-chart $(K8S_POST_GENERATE_TARGETS) ## Generates and packages the helm crd-chart. + @echo "Package generated helm crd-chart" + @${BINARY_HELM} package ${K8S_HELM_CRD_TARGET} -d ${K8S_HELM_CRD_TARGET} ${BINARY_HELM_ADDITIONAL_PACK_ARGS} + +.PHONY: crd-helm-chart-import +crd-helm-chart-import: check-all-vars check-k8s-artifact-id crd-helm-generate-chart crd-helm-package ## Imports the currently available crd-chart into the cluster-local registry. + @if [[ ${STAGE} == "development" ]]; then \ + echo "Import ${K8S_HELM_CRD_DEV_RELEASE_TGZ} into K8s cluster ${K3CES_REGISTRY_URL_PREFIX}..."; \ + ${BINARY_HELM} push ${K8S_HELM_CRD_DEV_RELEASE_TGZ} oci://${K3CES_REGISTRY_URL_PREFIX}/${K8S_HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + else \ + echo "Import ${K8S_HELM_CRD_RELEASE_TGZ} into K8s cluster ${K3CES_REGISTRY_URL_PREFIX}..."; \ + ${BINARY_HELM} push ${K8S_HELM_CRD_RELEASE_TGZ} oci://${K3CES_REGISTRY_URL_PREFIX}/${K8S_HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + fi + @echo "Done." + +.PHONY: crd-component-generate +crd-component-generate: ${K8S_RESOURCE_TEMP_FOLDER} ## Generate the crd-component yaml resource. + @echo "Generating temporary K8s crd-component resource: ${K8S_RESOURCE_CRD_COMPONENT}" + @if [[ ${STAGE} == "development" ]]; then \ + sed "s|NAMESPACE|$(K8S_HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_CRD_ID)|g" | sed "s|VERSION|$(DEV_CRD_VERSION)|g" > "${K8S_RESOURCE_CRD_COMPONENT}"; \ + else \ + sed "s|NAMESPACE|$(K8S_HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_CRD_ID)|g" | sed "s|VERSION|$(VERSION)|g" > "${K8S_RESOURCE_CRD_COMPONENT}"; \ + fi + +.PHONY: crd-component-apply +crd-component-apply: check-k8s-namespace-env-var crd-helm-chart-import crd-component-generate $(K8S_POST_GENERATE_TARGETS) ## Applies the crd-component yaml resource to the actual defined context. + @kubectl apply -f "${K8S_RESOURCE_CRD_COMPONENT}" --namespace="${NAMESPACE}" + @echo "Done." + +.PHONY: crd-component-delete +crd-component-delete: check-k8s-namespace-env-var crd-component-generate $(K8S_POST_GENERATE_TARGETS) ## Deletes the crd-component yaml resource from the actual defined context. + @kubectl delete -f "${K8S_RESOURCE_CRD_COMPONENT}" --namespace="${NAMESPACE}" || true + @echo "Done." diff --git a/build/make/k8s-helm-temp-chart.yaml b/build/make/k8s-helm-temp-chart.yaml deleted file mode 100644 index 3611257..0000000 --- a/build/make/k8s-helm-temp-chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -name: replaceme -version: 0.0.0 \ No newline at end of file diff --git a/build/make/k8s.mk b/build/make/k8s.mk index 8a7af58..63ba3c5 100644 --- a/build/make/k8s.mk +++ b/build/make/k8s.mk @@ -6,17 +6,19 @@ endif ## Variables +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + BINARY_YQ = $(UTILITY_BIN_PATH)/yq -BINARY_HELM = $(UTILITY_BIN_PATH)/helm -BINARY_HELM_VERSION?=v3.12.0-dev.1.0.20230817154107-a749b663101d -BINARY_HELM_ADDITIONAL_PUSH_ARGS?=--plain-http -BINARY_HELM_ADDITIONAL_PACK_ARGS?= -BINARY_HELM_ADDITIONAL_UNINST_ARGS?= -BINARY_HELM_ADDITIONAL_UPGR_ARGS?= # The productive tag of the image IMAGE ?= +# Set production as default stage. Use "development" as stage in your .env file to generate artifacts +# with development images pointing to K3S_CLUSTER_FQDN. +STAGE?=production K3S_CLUSTER_FQDN?=k3ces.local K3S_LOCAL_REGISTRY_PORT?=30099 K3CES_REGISTRY_URL_PREFIX="${K3S_CLUSTER_FQDN}:${K3S_LOCAL_REGISTRY_PORT}" @@ -25,10 +27,6 @@ K3CES_REGISTRY_URL_PREFIX="${K3S_CLUSTER_FQDN}:${K3S_LOCAL_REGISTRY_PORT}" # the current namespace and the dev image. K8S_RESOURCE_TEMP_FOLDER ?= $(TARGET_DIR)/make/k8s K8S_RESOURCE_TEMP_YAML ?= $(K8S_RESOURCE_TEMP_FOLDER)/$(ARTIFACT_ID)_$(VERSION).yaml -K8S_HELM_TARGET ?= $(K8S_RESOURCE_TEMP_FOLDER)/helm -K8S_HELM_RESSOURCES ?= k8s/helm -K8S_HELM_RELEASE_TGZ=${K8S_HELM_TARGET}/${ARTIFACT_ID}-${VERSION}.tgz -K8S_HELM_TARGET_DEP_DIR=charts ##@ K8s - Variables @@ -76,103 +74,30 @@ K8S_PRE_GENERATE_TARGETS ?= k8s-create-temporary-resource .PHONY: k8s-generate k8s-generate: ${BINARY_YQ} $(K8S_RESOURCE_TEMP_FOLDER) $(K8S_PRE_GENERATE_TARGETS) ## Generates the final resource yaml. @echo "Applying general transformations..." - @sed -i "s/'{{ .Namespace }}'/$(NAMESPACE)/" $(K8S_RESOURCE_TEMP_YAML) - @$(BINARY_YQ) -i e "(select(.kind == \"Deployment\").spec.template.spec.containers[]|select(.image == \"*$(ARTIFACT_ID)*\").image)=\"$(IMAGE_DEV)\"" $(K8S_RESOURCE_TEMP_YAML) + @if [[ ${STAGE} == "development" ]]; then \ + $(BINARY_YQ) -i e "(select(.kind == \"Deployment\").spec.template.spec.containers[]|select(.image == \"*$(ARTIFACT_ID)*\").image)=\"$(IMAGE_DEV)\"" $(K8S_RESOURCE_TEMP_YAML); \ + else \ + $(BINARY_YQ) -i e "(select(.kind == \"Deployment\").spec.template.spec.containers[]|select(.image == \"*$(ARTIFACT_ID)*\").image)=\"$(IMAGE)\"" $(K8S_RESOURCE_TEMP_YAML); \ + fi @echo "Done." .PHONY: k8s-apply -k8s-apply: k8s-generate $(K8S_POST_GENERATE_TARGETS) ## Applies all generated K8s resources to the current cluster and namespace. +k8s-apply: k8s-generate image-import $(K8S_POST_GENERATE_TARGETS) ## Applies all generated K8s resources to the current cluster and namespace. @echo "Apply generated K8s resources..." + @sed -i "s/'{{ .Namespace }}'/$(NAMESPACE)/" $(K8S_RESOURCE_TEMP_YAML) @kubectl apply -f $(K8S_RESOURCE_TEMP_YAML) --namespace=${NAMESPACE} -##@ K8s - Helm general -.PHONY: k8s-helm-init-chart -k8s-helm-init-chart: ${BINARY_HELM} ## Creates a Chart.yaml-template with zero values - @echo "Initialize ${K8S_HELM_RESSOURCES}/Chart.yaml..." - @mkdir -p ${K8S_HELM_RESSOURCES}/tmp/ - @${BINARY_HELM} create ${K8S_HELM_RESSOURCES}/tmp/${ARTIFACT_ID} - @cp ${K8S_HELM_RESSOURCES}/tmp/${ARTIFACT_ID}/Chart.yaml ${K8S_HELM_RESSOURCES}/ - @rm -dr ${K8S_HELM_RESSOURCES}/tmp - @sed -i 's/appVersion: ".*"/appVersion: "0.0.0-replaceme"/' ${K8S_HELM_RESSOURCES}/Chart.yaml - @sed -i 's/version: .*/version: 0.0.0-replaceme/' ${K8S_HELM_RESSOURCES}/Chart.yaml - -.PHONY: k8s-helm-delete -k8s-helm-delete: ${BINARY_HELM} check-k8s-namespace-env-var ## Uninstalls the current helm chart. - @echo "Uninstall helm chart" - @${BINARY_HELM} uninstall ${ARTIFACT_ID} --namespace=${NAMESPACE} ${BINARY_HELM_ADDITIONAL_UNINST_ARGS} || true - -.PHONY: k8s-helm-generate-chart -k8s-helm-generate-chart: ${K8S_HELM_TARGET}/Chart.yaml k8s-helm-create-temp-dependencies ## Generates the final helm chart. - -${K8S_HELM_TARGET}/Chart.yaml: $(K8S_RESOURCE_TEMP_FOLDER) - @echo "Generate helm chart..." - @rm -drf ${K8S_HELM_TARGET} # delete folder, so the chart is newly created. - @mkdir -p ${K8S_HELM_TARGET}/templates - @cp $(K8S_RESOURCE_TEMP_YAML) ${K8S_HELM_TARGET}/templates - @cp -r ${K8S_HELM_RESSOURCES}/** ${K8S_HELM_TARGET} - @sed -i 's/appVersion: "0.0.0-replaceme"/appVersion: "${VERSION}"/' ${K8S_HELM_TARGET}/Chart.yaml - @sed -i 's/version: 0.0.0-replaceme/version: ${VERSION}/' ${K8S_HELM_TARGET}/Chart.yaml - -##@ K8s - Helm dev targets - -.PHONY: k8s-helm-generate -k8s-helm-generate: k8s-generate k8s-helm-generate-chart ## Generates the final helm chart with dev-urls. - -.PHONY: k8s-helm-apply -k8s-helm-apply: ${BINARY_HELM} check-k8s-namespace-env-var image-import k8s-helm-generate $(K8S_POST_GENERATE_TARGETS) ## Generates and installs the helm chart. - @echo "Apply generated helm chart" - @${BINARY_HELM} upgrade -i ${ARTIFACT_ID} ${K8S_HELM_TARGET} ${BINARY_HELM_ADDITIONAL_UPGR_ARGS} --namespace ${NAMESPACE} - -.PHONY: k8s-helm-reinstall -k8s-helm-reinstall: k8s-helm-delete k8s-helm-apply ## Uninstalls the current helm chart and reinstalls it. - -##@ K8s - Helm release targets - -.PHONY: k8s-helm-generate-release -k8s-helm-generate-release: ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml ## Generates the final helm chart with release urls. - -${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml: $(K8S_PRE_GENERATE_TARGETS) ${K8S_HELM_TARGET}/Chart.yaml - @sed -i "s/'{{ .Namespace }}'/'{{ .Release.Namespace }}'/" ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml - -.PHONY: k8s-helm-package-release -k8s-helm-package-release: ${BINARY_HELM} k8s-helm-delete-existing-tgz ${K8S_HELM_RELEASE_TGZ} ## Generates and packages the helm chart with release urls. - -.PHONY: k8s-helm-delete-existing-tgz -k8s-helm-delete-existing-tgz: ## Remove an existing Helm package. -# remove - @rm -f ${K8S_HELM_RELEASE_TGZ}* - -${K8S_HELM_RELEASE_TGZ}: ${BINARY_HELM} ${K8S_HELM_TARGET}/templates/$(ARTIFACT_ID)_$(VERSION).yaml k8s-helm-generate-chart $(K8S_POST_GENERATE_TARGETS) ## Generates and packages the helm chart with release urls. - @echo "Package generated helm chart" - @${BINARY_HELM} package ${K8S_HELM_TARGET} -d ${K8S_HELM_TARGET} ${BINARY_HELM_ADDITIONAL_PACK_ARGS} - -.PHONY: k8s-helm-create-temp-dependencies -k8s-helm-create-temp-dependencies: ${BINARY_YQ} ${K8S_HELM_TARGET}/Chart.yaml -# we use helm dependencies internally but never use them as "official" dependency because the namespace may differ -# instead we create empty dependencies to satisfy the helm package call and delete the whole directory from the chart.tgz later-on. - @echo "Create helm temp dependencies (if they exist)" - @for dep in `${BINARY_YQ} -e '.dependencies[].name // ""' ${K8S_HELM_TARGET}/Chart.yaml`; do \ - mkdir -p ${K8S_HELM_TARGET}/${K8S_HELM_TARGET_DEP_DIR}/$${dep} ; \ - sed "s|replaceme|$${dep}|g" $(BUILD_DIR)/make/k8s-helm-temp-chart.yaml > ${K8S_HELM_TARGET}/${K8S_HELM_TARGET_DEP_DIR}/$${dep}/Chart.yaml ; \ - done - -.PHONY: chart-import -chart-import: check-all-vars check-k8s-artifact-id k8s-helm-generate-chart k8s-helm-package-release image-import ## Imports the currently available image into the cluster-local registry. - @echo "Import ${K8S_HELM_RELEASE_TGZ} into K8s cluster ${K3CES_REGISTRY_URL_PREFIX}..." - @${BINARY_HELM} push ${K8S_HELM_RELEASE_TGZ} oci://${K3CES_REGISTRY_URL_PREFIX}/k8s ${BINARY_HELM_ADDITIONAL_PUSH_ARGS} - @echo "Done." - ##@ K8s - Docker .PHONY: docker-build docker-build: check-k8s-image-env-var ## Builds the docker image of the K8s app. - @echo "Building docker image..." - DOCKER_BUILDKIT=1 docker build . -t $(IMAGE) + @echo "Building docker image $(IMAGE)..." + @DOCKER_BUILDKIT=1 docker build . -t $(IMAGE) .PHONY: docker-dev-tag docker-dev-tag: check-k8s-image-dev-var docker-build ## Tags a Docker image for local K3ces deployment. - @echo "Tagging image with dev tag..." - DOCKER_BUILDKIT=1 docker tag ${IMAGE} ${IMAGE_DEV} + @echo "Tagging image with dev tag $(IMAGE_DEV)..." + @DOCKER_BUILDKIT=1 docker tag ${IMAGE} $(IMAGE_DEV) .PHONY: check-k8s-image-dev-var check-k8s-image-dev-var: @@ -183,8 +108,8 @@ endif .PHONY: image-import image-import: check-all-vars check-k8s-artifact-id docker-dev-tag ## Imports the currently available image into the cluster-local registry. - @echo "Import ${IMAGE_DEV} into K8s cluster ${K3S_CLUSTER_FQDN}..." - @docker push ${IMAGE_DEV} + @echo "Import $(IMAGE_DEV) into K8s cluster ${K3S_CLUSTER_FQDN}..." + @docker push $(IMAGE_DEV) @echo "Done." ## Functions @@ -202,8 +127,8 @@ __check_defined = \ $(if $(value $1),, \ $(error Undefined $1$(if $2, ($2)))) +.PHONY: install-yq ## Installs the yq YAML editor. +install-yq: ${BINARY_YQ} + ${BINARY_YQ}: $(UTILITY_BIN_PATH) ## Download yq locally if necessary. $(call go-get-tool,$(BINARY_YQ),github.com/mikefarah/yq/v4@v4.25.1) - -${BINARY_HELM}: $(UTILITY_BIN_PATH) ## Download helm locally if necessary. - $(call go-get-tool,$(BINARY_HELM),helm.sh/helm/v3/cmd/helm@${BINARY_HELM_VERSION}) diff --git a/build/make/release.mk b/build/make/release.mk index 11dde9a..82ba1ba 100644 --- a/build/make/release.mk +++ b/build/make/release.mk @@ -8,4 +8,8 @@ dogu-release: ## Start a dogu release .PHONY: go-release go-release: ## Start a go tool release - build/make/release.sh go-tool \ No newline at end of file + build/make/release.sh go-tool + +.PHONY: dogu-cve-release +dogu-cve-release: ## Start a dogu release of a new build if the local build fixes critical CVEs + @bash -c "build/make/release_cve.sh \"${REGISTRY_USERNAME}\" \"${REGISTRY_PASSWORD}\" \"${TRIVY_IMAGE_SCAN_FLAGS}\" \"${DRY_RUN}\"" diff --git a/build/make/release.sh b/build/make/release.sh index 4fd4569..ae9a722 100755 --- a/build/make/release.sh +++ b/build/make/release.sh @@ -15,7 +15,7 @@ sourceCustomReleaseArgs() { if [[ -f "${RELEASE_ARGS_FILE}" ]]; then echo "Using custom release args file ${RELEASE_ARGS_FILE}" - sourceCustomReleaseExitCode=0 + local sourceCustomReleaseExitCode=0 # shellcheck disable=SC1090 source "${RELEASE_ARGS_FILE}" || sourceCustomReleaseExitCode=$? if [[ ${sourceCustomReleaseExitCode} -ne 0 ]]; then @@ -30,13 +30,16 @@ RELEASE_ARGS_FILE="${PROJECT_DIR}/release_args.sh" sourceCustomReleaseArgs "${RELEASE_ARGS_FILE}" +# shellcheck disable=SC1090 source "$(pwd)/build/make/release_functions.sh" TYPE="${1}" +FIXED_CVE_LIST="${2:-""}" +DRY_RUN="${3:-""}" echo "=====Starting Release process=====" -if [ "${TYPE}" == "dogu" ];then +if [[ "${TYPE}" == "dogu" || "${TYPE}" == "dogu-cve-release" ]];then CURRENT_TOOL_VERSION=$(get_current_version_by_dogu_json) else CURRENT_TOOL_VERSION=$(get_current_version_by_makefile) @@ -45,10 +48,20 @@ fi NEW_RELEASE_VERSION="$(read_new_version)" validate_new_version "${NEW_RELEASE_VERSION}" -start_git_flow_release "${NEW_RELEASE_VERSION}" +if [[ -n "${DRY_RUN}" ]]; then + start_dry_run_release "${NEW_RELEASE_VERSION}" +else + start_git_flow_release "${NEW_RELEASE_VERSION}" +fi + update_versions "${NEW_RELEASE_VERSION}" -update_changelog "${NEW_RELEASE_VERSION}" +update_changelog "${NEW_RELEASE_VERSION}" "${FIXED_CVE_LIST}" show_diff -finish_release_and_push "${CURRENT_TOOL_VERSION}" "${NEW_RELEASE_VERSION}" + +if [[ -n "${DRY_RUN}" ]]; then + abort_dry_run_release "${NEW_RELEASE_VERSION}" +else + finish_release_and_push "${CURRENT_TOOL_VERSION}" "${NEW_RELEASE_VERSION}" +fi echo "=====Finished Release process=====" diff --git a/build/make/release_cve.sh b/build/make/release_cve.sh new file mode 100755 index 0000000..21738cb --- /dev/null +++ b/build/make/release_cve.sh @@ -0,0 +1,154 @@ +#!/bin/bash +set -o errexit +set -o pipefail +set -o nounset + +function readCredentialsIfUnset() { + if [ -z "${USERNAME}" ]; then + echo "username is unset" + while [[ -z ${USERNAME} ]]; do + read -r -p "type username for ${REGISTRY_URL}: " USERNAME + done + fi + if [ -z "${PASSWORD}" ]; then + echo "password is unset" + while [[ -z ${PASSWORD} ]]; do + read -r -s -p "type password for ${REGISTRY_URL}: " PASSWORD + done + fi +} + +function diffArrays() { + local cveListX=("$1") + local cveListY=("$2") + local result=() + + local cveX + # Disable the following shellcheck because the arrays are sufficiently whitespace delimited because of the jq parsing result. + # shellcheck disable=SC2128 + for cveX in ${cveListX}; do + local found=0 + local cveY + for cveY in ${cveListY}; do + [[ "${cveY}" == "${cveX}" ]] && { + found=1 + break + } + done + + [[ "${found}" == 0 ]] && result+=("${cveX}") + done + + echo "${result[@]}" +} + +function dockerLogin() { + docker login "${REGISTRY_URL}" -u "${USERNAME}" -p "${PASSWORD}" +} + +function dockerLogout() { + docker logout "${REGISTRY_URL}" +} + +function nameFromDogu() { + jsonPropertyFromDogu ".Name" +} + +function imageFromDogu() { + jsonPropertyFromDogu ".Image" +} + +function versionFromDogu() { + jsonPropertyFromDogu ".Version" +} + +function jsonPropertyFromDogu() { + local property="${1}" + jq -r "${property}" "${DOGU_JSON_FILE}" +} + +function pullRemoteImage() { + docker pull "$(imageFromDogu):$(versionFromDogu)" +} + +function buildLocalImage() { + docker build . -t "$(imageFromDogu):$(versionFromDogu)" +} + +function scanImage() { + docker run -v "${TRIVY_CACHE_DIR}":"${TRIVY_DOCKER_CACHE_DIR}" -v /var/run/docker.sock:/var/run/docker.sock -v "${TRIVY_PATH}":/result aquasec/trivy --cache-dir "${TRIVY_DOCKER_CACHE_DIR}" -f json -o /result/results.json image ${TRIVY_IMAGE_SCAN_FLAGS:+"${TRIVY_IMAGE_SCAN_FLAGS}"} "$(imageFromDogu):$(versionFromDogu)" +} + +function parseTrivyJsonResult() { + local severity="${1}" + local trivy_result_file="${2}" + + # First select results which have the property "Vulnerabilities". Filter the vulnerability ids with the given severity and afterward put the values in an array. + # This array is used to format the values with join(" ") in a whitespace delimited string list. + jq -rc "[.Results[] | select(.Vulnerabilities) | .Vulnerabilities | .[] | select(.Severity == \"${severity}\") | .VulnerabilityID] | join(\" \")" "${trivy_result_file}" +} + +RELEASE_SH="build/make/release.sh" + +REGISTRY_URL="registry.cloudogu.com" +DOGU_JSON_FILE="dogu.json" + +CVE_SEVERITY="CRITICAL" + +TRIVY_PATH= +TRIVY_RESULT_FILE= +TRIVY_CACHE_DIR= +TRIVY_DOCKER_CACHE_DIR=/tmp/db +TRIVY_IMAGE_SCAN_FLAGS= + +USERNAME="" +PASSWORD="" +DRY_RUN= + +function runMain() { + readCredentialsIfUnset + dockerLogin + + mkdir -p "${TRIVY_PATH}" # Cache will not be removed after release. rm requires root because the trivy container only runs with root. + pullRemoteImage + scanImage + local remote_trivy_cve_list + remote_trivy_cve_list=$(parseTrivyJsonResult "${CVE_SEVERITY}" "${TRIVY_RESULT_FILE}") + + buildLocalImage + scanImage + local local_trivy_cve_list + local_trivy_cve_list=$(parseTrivyJsonResult "${CVE_SEVERITY}" "${TRIVY_RESULT_FILE}") + + dockerLogout + + local cve_in_local_but_not_in_remote + cve_in_local_but_not_in_remote=$(diffArrays "${local_trivy_cve_list}" "${remote_trivy_cve_list}") + if [[ -n "${cve_in_local_but_not_in_remote}" ]]; then + echo "Abort release. Added new vulnerabilities:" + echo "${cve_in_local_but_not_in_remote[@]}" + exit 2 + fi + + local cve_in_remote_but_not_in_local + cve_in_remote_but_not_in_local=$(diffArrays "${remote_trivy_cve_list}" "${local_trivy_cve_list}") + if [[ -z "${cve_in_remote_but_not_in_local}" ]]; then + echo "Abort release. Fixed no new vulnerabilities" + exit 3 + fi + + "${RELEASE_SH}" "dogu-cve-release" "${cve_in_remote_but_not_in_local}" "${DRY_RUN}" +} + +# make the script only runMain when executed, not when sourced from bats tests +if [[ -n "${BASH_VERSION}" && "${BASH_SOURCE[0]}" == "${0}" ]]; then + USERNAME="${1:-""}" + PASSWORD="${2:-""}" + TRIVY_IMAGE_SCAN_FLAGS="${3:-""}" + DRY_RUN="${4:-""}" + + TRIVY_PATH="/tmp/trivy-dogu-cve-release-$(nameFromDogu)" + TRIVY_RESULT_FILE="${TRIVY_PATH}/results.json" + TRIVY_CACHE_DIR="${TRIVY_PATH}/db" + runMain +fi diff --git a/build/make/release_functions.sh b/build/make/release_functions.sh index 528806b..499c248 100755 --- a/build/make/release_functions.sh +++ b/build/make/release_functions.sh @@ -3,15 +3,15 @@ set -o errexit set -o nounset set -o pipefail -wait_for_ok(){ +wait_for_ok() { printf "\n" - OK=false - while [[ ${OK} != "ok" ]] ; do + local OK="false" + while [[ "${OK}" != "ok" ]]; do read -r -p "${1} (type 'ok'): " OK done } -ask_yes_or_no(){ +ask_yes_or_no() { local ANSWER="" while [ "${ANSWER}" != "y" ] && [ "${ANSWER}" != "n" ]; do @@ -21,49 +21,51 @@ ask_yes_or_no(){ echo "${ANSWER}" } -get_current_version_by_makefile(){ +get_current_version_by_makefile() { grep '^VERSION=[0-9[:alpha:].-]*$' Makefile | sed s/VERSION=//g } -get_current_version_by_dogu_json(){ +get_current_version_by_dogu_json() { jq ".Version" --raw-output dogu.json } -read_new_version(){ +read_new_version() { local NEW_RELEASE_VERSION read -r -p "Current Version is v${CURRENT_TOOL_VERSION}. Please provide the new version: v" NEW_RELEASE_VERSION echo "${NEW_RELEASE_VERSION}" } -validate_new_version(){ +validate_new_version() { local NEW_RELEASE_VERSION="${1}" # Validate that release version does not start with vv if [[ ${NEW_RELEASE_VERSION} = v* ]]; then echo "WARNING: The new release version (v${NEW_RELEASE_VERSION}) starts with 'vv'." echo "You must not enter the v when defining the new version." + local ANSWER ANSWER=$(ask_yes_or_no "Should the first v be removed?") if [ "${ANSWER}" == "y" ]; then NEW_RELEASE_VERSION="${NEW_RELEASE_VERSION:1}" echo "Release version now is: ${NEW_RELEASE_VERSION}" fi - fi; + fi } -start_git_flow_release(){ +start_git_flow_release() { local NEW_RELEASE_VERSION="${1}" # Do gitflow git flow init --defaults --force + local mainBranchExists mainBranchExists="$(git show-ref refs/remotes/origin/main || echo "")" if [ -n "$mainBranchExists" ]; then - echo 'Using "main" branch for production releases' - git flow config set master main - git checkout main - git pull origin main + echo 'Using "main" branch for production releases' + git flow config set master main + git checkout main + git pull origin main else - echo 'Using "master" branch for production releases' - git checkout master - git pull origin master + echo 'Using "master" branch for production releases' + git checkout master + git pull origin master fi git checkout develop @@ -71,17 +73,30 @@ start_git_flow_release(){ git flow release start v"${NEW_RELEASE_VERSION}" } +start_dry_run_release() { + local NEW_RELEASE_VERSION="${1}" + + git checkout -b dryrun/v"${NEW_RELEASE_VERSION}" +} + +abort_dry_run_release() { + local NEW_RELEASE_VERSION="${1}" + + git checkout develop + git branch -D dryrun/v"${NEW_RELEASE_VERSION}" +} + # update_versions updates files with the new release version and interactively asks the user for verification. If okay # the updated files will be staged to git and finally committed. # # extension points: # - update_versions_modify_files - update a file with the new version number # - update_versions_stage_modified_files - stage a modified file to prepare the file for the up-coming commit -update_versions(){ +update_versions() { local NEW_RELEASE_VERSION="${1}" if [[ $(type -t update_versions_modify_files) == function ]]; then - preSkriptExitCode=0 + local preSkriptExitCode=0 update_versions_modify_files "${NEW_RELEASE_VERSION}" || preSkriptExitCode=$? if [[ ${preSkriptExitCode} -ne 0 ]]; then echo "ERROR: custom update_versions_modify_files() exited with exit code ${preSkriptExitCode}" @@ -92,7 +107,7 @@ update_versions(){ # Update version in dogu.json if [ -f "dogu.json" ]; then echo "Updating version in dogu.json..." - jq ".Version = \"${NEW_RELEASE_VERSION}\"" dogu.json > dogu2.json && mv dogu2.json dogu.json + jq ".Version = \"${NEW_RELEASE_VERSION}\"" dogu.json >dogu2.json && mv dogu2.json dogu.json fi # Update version in Dockerfile @@ -110,7 +125,7 @@ update_versions(){ # Update version in package.json if [ -f "package.json" ]; then echo "Updating version in package.json..." - jq ".version = \"${NEW_RELEASE_VERSION}\"" package.json > package2.json && mv package2.json package.json + jq ".version = \"${NEW_RELEASE_VERSION}\"" package.json >package2.json && mv package2.json package.json fi # Update version in pom.xml @@ -133,7 +148,7 @@ update_versions(){ fi if [ -f "dogu.json" ]; then - git add dogu.json + git add dogu.json fi if [ -f "Dockerfile" ]; then @@ -155,12 +170,14 @@ update_versions(){ git commit -m "Bump version" } -update_changelog(){ +update_changelog() { local NEW_RELEASE_VERSION="${1}" + local FIXED_CVE_LIST="${2}" # Changelog update + local CURRENT_DATE CURRENT_DATE=$(date --rfc-3339=date) - NEW_CHANGELOG_TITLE="## [v${NEW_RELEASE_VERSION}] - ${CURRENT_DATE}" + local NEW_CHANGELOG_TITLE="## [v${NEW_RELEASE_VERSION}] - ${CURRENT_DATE}" # Check if "Unreleased" tag exists while ! grep --silent "## \[Unreleased\]" CHANGELOG.md; do echo "" @@ -169,6 +186,10 @@ update_changelog(){ wait_for_ok "Please insert a \"## [Unreleased]\" line into CHANGELOG.md now." done + if [[ -n "${FIXED_CVE_LIST}" ]]; then + addFixedCVEListFromReRelease "${FIXED_CVE_LIST}" + fi + # Add new title line to changelog sed -i "s|## \[Unreleased\]|## \[Unreleased\]\n\n${NEW_CHANGELOG_TITLE}|g" CHANGELOG.md @@ -186,8 +207,36 @@ update_changelog(){ git commit -m "Update changelog" } -show_diff(){ - if ! git diff --exit-code > /dev/null; then +# addFixedCVEListFromReRelease is used in dogu cve releases. The method adds the fixed CVEs under the ### Fixed header +# in the unreleased section. +addFixedCVEListFromReRelease() { + local fixed_cve_list="${1}" + + local cve_sed_search="" + local cve_sed_replace="" + local fixed_exists_in_unreleased + fixed_exists_in_unreleased=$(awk '/^\#\# \[Unreleased\]$/{flag=1;next}/^\#\# \[/{flag=0}flag' CHANGELOG.md | grep -e "^### Fixed$" || true) + if [[ -n "${fixed_exists_in_unreleased}" ]]; then + # extend fixed header with CVEs. + cve_sed_search="^\#\#\# Fixed$" + cve_sed_replace="\#\#\# Fixed\n- Fixed ${fixed_cve_list}" + else + # extend unreleased header with fixed header and CVEs. + cve_sed_search="^\#\# \[Unreleased\]$" + cve_sed_replace="\#\# \[Unreleased\]\n\#\#\# Fixed\n- Fixed ${fixed_cve_list}" + + local any_exists_unreleased + any_exists_unreleased=$(awk '/^\#\# \[Unreleased\]$/{flag=1;next}/^\#\# \[/{flag=0}flag' CHANGELOG.md | grep -e "^\#\#\# Added$" -e "^\#\#\# Fixed$" -e "^\#\#\# Changed$" || true) + if [[ -n ${any_exists_unreleased} ]]; then + cve_sed_replace+="\n" + fi + fi + + sed -i "0,/${cve_sed_search}/s//${cve_sed_replace}/" CHANGELOG.md +} + +show_diff() { + if ! git diff --exit-code >/dev/null; then echo "There are still uncommitted changes:" echo "" echo "# # # # # # # # # #" @@ -206,7 +255,7 @@ show_diff(){ echo "# # # # # # # # # #" } -finish_release_and_push(){ +finish_release_and_push() { local CURRENT_VERSION="${1}" local NEW_RELEASE_VERSION="${2}" diff --git a/k8s/helm-crd/Chart.yaml b/k8s/helm-crd/Chart.yaml new file mode 100644 index 0000000..7777386 --- /dev/null +++ b/k8s/helm-crd/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: artifact-crd-replaceme +description: A Helm chart for the Backup-CRDs + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.0.0-replaceme + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.0.0-replaceme" \ No newline at end of file diff --git a/k8s/helm/Chart.yaml b/k8s/helm/Chart.yaml index e7044f4..cdef2e9 100644 --- a/k8s/helm/Chart.yaml +++ b/k8s/helm/Chart.yaml @@ -22,3 +22,8 @@ version: 0.0.0-replaceme # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. appVersion: "0.0.0-replaceme" + +annotations: + # Dependency for the Backup-CRDs. + # Allow all versions up to next major version to avoid breaking changes + "k8s.cloudogu.com/ces-dependency/k8s-backup-operator-crd": "0.x.x-0" \ No newline at end of file