diff --git a/Dockerfile b/Dockerfile index 01a6557..8a78482 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,6 +54,8 @@ RUN set -x -o errexit -o nounset -o pipefail \ # copy resources after package installation, that we can override package defaults COPY ./resources / +COPY dogu.json / + # set permissions RUN mkdir -p ${SCM_HOME} \ && chown scm:scm ${SCM_HOME} \ diff --git a/Makefile b/Makefile index 5b65bf1..2c84fc5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -MAKEFILES_VERSION=10.6.0 +MAKEFILES_VERSION=10.10.0 .DEFAULT_GOAL:=dogu-release include build/make/variables.mk @@ -6,4 +6,31 @@ include build/make/self-update.mk include build/make/clean.mk include build/make/release.mk include build/make/bats.mk -include build/make/k8s-dogu.mk \ No newline at end of file +include build/make/k8s-dogu.mk + +SCM_V3_RELEASE ?= scm +SCM_V3_HELM_SOURCE ?= k8s/helm + +# The chart composes the image as "/:", so the dev pull ref (IMAGE_DEV) must be split +SCM_V3_IMAGE_REPOSITORY = $(patsubst $(CES_REGISTRY_HOST)/%,%,$(IMAGE_DEV)) + +.PHONY: scm-v3-install +scm-v3-install: IMAGE = $(IMAGE_DEV_VERSION) +scm-v3-install: image-import $(BINARY_HELM) ## DoguV3 dev: build+push the image and helm upgrade/install the chart. + @echo "Installing DoguV3 release '$(SCM_V3_RELEASE)' into namespace '$(NAMESPACE)' (context '$(KUBE_CONTEXT_NAME)')..." + @echo " image: $(CES_REGISTRY_HOST)/$(SCM_V3_IMAGE_REPOSITORY):$(VERSION)" + @$(BINARY_HELM) upgrade --install $(SCM_V3_RELEASE) $(SCM_V3_HELM_SOURCE) \ + --kube-context="$(KUBE_CONTEXT_NAME)" \ + --namespace $(NAMESPACE) \ + --set-string fullnameOverride=$(SCM_V3_RELEASE) \ + --set-string scm.image.registry="$(CES_REGISTRY_HOST)" \ + --set-string scm.image.repository="$(SCM_V3_IMAGE_REPOSITORY)" \ + --set-string scm.image.tag="$(VERSION)" \ + --set-string scm.imagePullPolicy=Always + @echo "Done. Watch rollout: kubectl -n $(NAMESPACE) get pods -w" + +.PHONY: scm-v3-uninstall +scm-v3-uninstall: $(BINARY_HELM) ## DoguV3 dev: uninstall the chart (keeps PVCs). + @$(BINARY_HELM) --kube-context="$(KUBE_CONTEXT_NAME)" uninstall $(SCM_V3_RELEASE) --namespace $(NAMESPACE) || true + @echo "Note: PVCs are retained. Delete them manually to reset data:" + @echo " kubectl -n $(NAMESPACE) delete pvc -l app.kubernetes.io/name=scm" diff --git a/build/make/coder-lib.sh b/build/make/coder-lib.sh index 2b5d198..2bcc740 100755 --- a/build/make/coder-lib.sh +++ b/build/make/coder-lib.sh @@ -88,7 +88,7 @@ function doTrivyConvert() { "$containerExec" run --rm --pull=always \ -v trivy-cache:/root/.cache \ -v "$jsonScanToConvert:$containerJsonScanFile" \ - aquasec/trivy -q \ + "${TRIVY_IMAGE}" -q \ convert $trivyFlags "$containerJsonScanFile" > "$outputFile" } diff --git a/build/make/coder.mk b/build/make/coder.mk index 07f4d43..5a82d6a 100644 --- a/build/make/coder.mk +++ b/build/make/coder.mk @@ -35,6 +35,10 @@ GOPASS_BIN?=$(shell command -v gopass 2> /dev/null) EXCLUDED_TEMPLATE_FILES?=rich-parameters.yaml variables.yaml +TRIVY_VERSION ?= latest +TRIVY_IMAGE = aquasec/trivy:$(TRIVY_VERSION) + +export TRIVY_IMAGE ##@ Coder template development @@ -107,7 +111,7 @@ ${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 \ + $(TRIVY_IMAGE) -q \ image --scanners vuln --input /tmp/image.tar -f json --timeout 15m \ > ${CONTAINER_IMAGE_TRIVY_SCAN_JSON} diff --git a/build/make/k8s-component.mk b/build/make/k8s-component.mk index 4efe639..18a196f 100644 --- a/build/make/k8s-component.mk +++ b/build/make/k8s-component.mk @@ -1,26 +1,37 @@ +COMPONENT_ARTIFACT_ID?=$(ARTIFACT_ID) COMPONENT_BUILD_VERSION := $(shell date +%s) COMPONENT_DEV_VERSION?=${VERSION}-dev.${COMPONENT_BUILD_VERSION} -include ${BUILD_DIR}/make/k8s.mk +ifeq (${K8S_MK_INCLUDE_MARKER}, ) + include ${BUILD_DIR}/make/k8s.mk +endif ifeq (${RUNTIME_ENV}, local) BINARY_HELM_ADDITIONAL_PUSH_ARGS?=--plain-http endif +ifeq (${RUNTIME_ENV}, k3d) + BINARY_HELM_ADDITIONAL_PUSH_ARGS?=--plain-http +endif BINARY_HELM_ADDITIONAL_PACK_ARGS?= BINARY_HELM_ADDITIONAL_UNINST_ARGS?= BINARY_HELM_ADDITIONAL_UPGR_ARGS?= HELM_TARGET_DIR ?= $(K8S_RESOURCE_TEMP_FOLDER)/helm HELM_SOURCE_DIR ?= k8s/helm -HELM_RELEASE_TGZ=${HELM_TARGET_DIR}/${ARTIFACT_ID}-${VERSION}.tgz -HELM_DEV_RELEASE_TGZ=${HELM_TARGET_DIR}/${ARTIFACT_ID}-${COMPONENT_DEV_VERSION}.tgz +HELM_RELEASE_TGZ=${HELM_TARGET_DIR}/${COMPONENT_ARTIFACT_ID}-${VERSION}.tgz +HELM_DEV_RELEASE_TGZ=${HELM_TARGET_DIR}/${COMPONENT_ARTIFACT_ID}-${COMPONENT_DEV_VERSION}.tgz HELM_ARTIFACT_NAMESPACE?=k8s +HELM_PUSH_REGISTRY_HOST?=${CES_REGISTRY_HOST} ifeq (${RUNTIME_ENV}, remote) HELM_ARTIFACT_NAMESPACE=testing/k8s endif +ifeq (${RUNTIME_ENV}, k3d) + HELM_PUSH_REGISTRY_HOST=$(IMAGE_PUSH_REGISTRY_HOST) +endif $(info HELM_ARTIFACT_NAMESPACE=$(HELM_ARTIFACT_NAMESPACE)) +$(info HELM_PUSH_REGISTRY_HOST=$(HELM_PUSH_REGISTRY_HOST)) -K8S_RESOURCE_COMPONENT ?= "${K8S_RESOURCE_TEMP_FOLDER}/component-${ARTIFACT_ID}-${VERSION}.yaml" +K8S_RESOURCE_COMPONENT ?= "${K8S_RESOURCE_TEMP_FOLDER}/component-${COMPONENT_ARTIFACT_ID}-${VERSION}.yaml" K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML ?= $(BUILD_DIR)/make/k8s-component.tpl # HELM_PRE_GENERATE_TARGETS allows to execute targets that affect Helm source files AND Helm target files. HELM_PRE_GENERATE_TARGETS ?= @@ -38,8 +49,8 @@ IMAGE_IMPORT_TARGET?= helm-init-chart: ${BINARY_HELM} ## Creates a Chart.yaml-template with zero values @echo "Initialize ${HELM_SOURCE_DIR}/Chart.yaml..." @mkdir -p ${HELM_SOURCE_DIR}/tmp/ - @${BINARY_HELM} create ${HELM_SOURCE_DIR}/tmp/${ARTIFACT_ID} - @cp ${HELM_SOURCE_DIR}/tmp/${ARTIFACT_ID}/Chart.yaml ${HELM_SOURCE_DIR}/ + @${BINARY_HELM} create ${HELM_SOURCE_DIR}/tmp/${COMPONENT_ARTIFACT_ID} + @cp ${HELM_SOURCE_DIR}/tmp/${COMPONENT_ARTIFACT_ID}/Chart.yaml ${HELM_SOURCE_DIR}/ @rm -dr ${HELM_SOURCE_DIR}/tmp @sed -i 's/appVersion: ".*"/appVersion: "0.0.0-replaceme"/' ${HELM_SOURCE_DIR}/Chart.yaml @sed -i 's/version: .*/version: 0.0.0-replaceme/' ${HELM_SOURCE_DIR}/Chart.yaml @@ -82,12 +93,12 @@ helm-update-dependencies: ${BINARY_HELM} ## Update Helm chart dependencies .PHONY: helm-apply helm-apply: ${BINARY_HELM} check-k8s-namespace-env-var ${IMAGE_IMPORT_TARGET} helm-generate ${HELM_PRE_APPLY_TARGETS} ## Generates and installs the Helm chart. @echo "Apply generated helm chart" - @${BINARY_HELM} --kube-context="${KUBE_CONTEXT_NAME}" upgrade -i ${ARTIFACT_ID} ${HELM_TARGET_DIR} ${BINARY_HELM_ADDITIONAL_UPGR_ARGS} --namespace ${NAMESPACE} + @${BINARY_HELM} --kube-context="${KUBE_CONTEXT_NAME}" upgrade -i ${COMPONENT_ARTIFACT_ID} ${HELM_TARGET_DIR} ${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} --kube-context="${KUBE_CONTEXT_NAME}" uninstall ${ARTIFACT_ID} --namespace=${NAMESPACE} ${BINARY_HELM_ADDITIONAL_UNINST_ARGS} || true + @${BINARY_HELM} --kube-context="${KUBE_CONTEXT_NAME}" uninstall ${COMPONENT_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. @@ -95,11 +106,11 @@ helm-reinstall: helm-delete helm-apply ## Uninstalls the current helm chart and .PHONY: helm-chart-import helm-chart-import: ${CHECK_VAR_TARGETS} helm-generate helm-package ${IMAGE_IMPORT_TARGET} ## Imports the currently available chart into the cluster-local registry. @if [[ ${STAGE} == "development" ]]; then \ - echo "Import ${HELM_DEV_RELEASE_TGZ} into K8s cluster ${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ - ${BINARY_HELM} push ${HELM_DEV_RELEASE_TGZ} oci://${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + echo "Import ${HELM_DEV_RELEASE_TGZ} into K8s cluster ${HELM_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ + ${BINARY_HELM} push ${HELM_DEV_RELEASE_TGZ} oci://${HELM_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ else \ - echo "Import ${HELM_RELEASE_TGZ} into K8s cluster ${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ - ${BINARY_HELM} push ${HELM_RELEASE_TGZ} oci://${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + echo "Import ${HELM_RELEASE_TGZ} into K8s cluster ${HELM_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ + ${BINARY_HELM} push ${HELM_RELEASE_TGZ} oci://${HELM_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ fi @echo "Done." @@ -122,7 +133,7 @@ ${HELM_RELEASE_TGZ}: ${BINARY_HELM} ${HELM_TARGET_DIR}/Chart.yaml ${HELM_POST_GE .PHONY: helm-delete-existing-tgz helm-delete-existing-tgz: ## Remove an existing Helm package from the target directory. @echo "Delete ${HELM_RELEASE_TGZ}*" - @rm -f ${HELM_TARGET_DIR}/${ARTIFACT_ID}-*.tgz + @rm -f ${HELM_TARGET_DIR}/${COMPONENT_ARTIFACT_ID}-*.tgz ##@ K8s - Helm lint targets @@ -138,9 +149,9 @@ component-generate: ${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML} ${COMPONENT_POST_ ${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}: ${K8S_RESOURCE_TEMP_FOLDER} @echo "Generating temporary K8s component resource: ${K8S_RESOURCE_COMPONENT}" @if [[ ${STAGE} == "development" ]]; then \ - sed "s|NAMESPACE|$(HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_ID)|g" | sed "s|VERSION|$(COMPONENT_DEV_VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ + sed "s|NAMESPACE|$(HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(COMPONENT_ARTIFACT_ID)|g" | sed "s|VERSION|$(COMPONENT_DEV_VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ else \ - sed "s|NAMESPACE|$(HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(ARTIFACT_ID)|g" | sed "s|VERSION|$(VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ + sed "s|NAMESPACE|$(HELM_ARTIFACT_NAMESPACE)|g" "${K8S_RESOURCE_COMPONENT_CR_TEMPLATE_YAML}" | sed "s|NAME|$(COMPONENT_ARTIFACT_ID)|g" | sed "s|VERSION|$(VERSION)|g" > "${K8S_RESOURCE_COMPONENT}"; \ fi .PHONY: component-apply diff --git a/build/make/k8s-crd.mk b/build/make/k8s-crd.mk index aca625d..b9a9c1c 100644 --- a/build/make/k8s-crd.mk +++ b/build/make/k8s-crd.mk @@ -90,11 +90,11 @@ ${HELM_CRD_RELEASE_TGZ}: ${BINARY_HELM} crd-helm-generate ## Generates and packa .PHONY: crd-helm-chart-import crd-helm-chart-import: ${CHECK_VAR_TARGETS} check-k8s-artifact-id crd-helm-generate crd-helm-package ## Imports the currently available Helm CRD chart into the cluster-local registry. @if [[ ${STAGE} == "development" ]]; then \ - echo "Import ${HELM_CRD_DEV_RELEASE_TGZ} into K8s cluster ${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ - ${BINARY_HELM} push ${HELM_CRD_DEV_RELEASE_TGZ} oci://${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + echo "Import ${HELM_CRD_DEV_RELEASE_TGZ} into K8s cluster ${IMAGE_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ + ${BINARY_HELM} push ${HELM_CRD_DEV_RELEASE_TGZ} oci://${IMAGE_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ else \ - echo "Import ${HELM_CRD_RELEASE_TGZ} into K8s cluster ${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ - ${BINARY_HELM} push ${HELM_CRD_RELEASE_TGZ} oci://${CES_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ + echo "Import ${HELM_CRD_RELEASE_TGZ} into K8s cluster ${IMAGE_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE}..."; \ + ${BINARY_HELM} push ${HELM_CRD_RELEASE_TGZ} oci://${IMAGE_PUSH_REGISTRY_HOST}/${HELM_ARTIFACT_NAMESPACE} ${BINARY_HELM_ADDITIONAL_PUSH_ARGS}; \ fi @echo "Done." diff --git a/build/make/k8s-dogu.mk b/build/make/k8s-dogu.mk index e656b17..30e7a03 100644 --- a/build/make/k8s-dogu.mk +++ b/build/make/k8s-dogu.mk @@ -11,12 +11,16 @@ VERSION=$(shell $(BINARY_YQ) -oy -e ".Version" $(DOGU_JSON_FILE)) # Image of the dogu is extracted from the dogu.json IMAGE=$(shell $(BINARY_YQ) -oy -e ".Image" $(DOGU_JSON_FILE)):$(VERSION) -include $(BUILD_DIR)/make/k8s.mk +PRE_BUILD_TARGETS ?= + +ifeq (${K8S_MK_INCLUDE_MARKER}, ) + include ${BUILD_DIR}/make/k8s.mk +endif ##@ K8s - EcoSystem .PHONY: build -build: image-import install-dogu-descriptor create-dogu-resource apply-dogu-resource ## Builds a new version of the dogu and deploys it into the K8s-EcoSystem. +build: ${PRE_BUILD_TARGETS} image-import install-dogu-descriptor create-dogu-resource apply-dogu-resource ## Builds a new version of the dogu and deploys it into the K8s-EcoSystem. ##@ K8s - Dogu - Resource diff --git a/build/make/k8s.mk b/build/make/k8s.mk index 073c6b1..f9f8648 100644 --- a/build/make/k8s.mk +++ b/build/make/k8s.mk @@ -6,18 +6,39 @@ endif ## Variables +K8S_MK_INCLUDE_MARKER="k8s.mk" + BINARY_YQ = $(UTILITY_BIN_PATH)/yq BINARY_YQ_4_VERSION?=v4.40.3 + BINARY_HELM = $(UTILITY_BIN_PATH)/helm -BINARY_HELM_VERSION?=v3.13.0 +BINARY_HELM_VERSION?=v3.20.2 +BINARY_HELM_URL?=https://get.helm.sh/helm-${BINARY_HELM_VERSION}-linux-amd64.tar.gz +BINARY_HELM_SUM?=258e830a9e613c8a7a302d6059b4bb3b9758f2f3e1bb8ea0d707ce10a9a72fea +BINARY_HELM_ARCHIVE_PATH?=linux-amd64/helm +BINARY_HELM_ARCHIVE_STRIP?=1 + CONTROLLER_GEN = $(UTILITY_BIN_PATH)/controller-gen CONTROLLER_GEN_VERSION?=v0.19.0 +BINARY_CRANE_VERSION=v0.21.4 +BINARY_CRANE=$(UTILITY_BIN_PATH)/crane +BINARY_CRANE_URL?=https://github.com/google/go-containerregistry/releases/download/${BINARY_CRANE_VERSION}/go-containerregistry_Linux_x86_64.tar.gz +BINARY_CRANE_SUM?=3b6032bcf412e14cf3baf964a4065f2966af906ec947ab22478df5f74705c892 +BINARY_CRANE_ARCHIVE_PATH?=crane +BINARY_CRANE_ARCHIVE_STRIP?=0 + # 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 +ifneq (${KUBECONFIG},) + # Values from the repo-local .env become plain make variables first. Export KUBECONFIG so + # recipe shells and nested kubectl/helm calls use the same kubeconfig file as the make logic. + export KUBECONFIG +endif + # The productive tag of the image IMAGE ?= @@ -25,37 +46,65 @@ IMAGE ?= # with development images pointing to CES_REGISTRY_URL_PREFIX. STAGE?=production -# Set the "local" as runtime-environment, to push images to the container-registry of the local cluster and to apply resources to the local cluster. -# Use "remote" as runtime-environment in your .env file to push images to the container-registry at "registry.cloudogu.com/testing" and to apply resources to the configured kubernetes-context in KUBE_CONTEXT_NAME. +# Set "local" as runtime-environment to use the legacy in-cluster registry of the local cluster. +# Set "k3d" as runtime-environment for local k3d development with local registry push/pull: +# - push from host to ${K3D_PUSH_REGISTRY_HOST}${K3D_PUSH_REGISTRY_NAMESPACE} +# - pull in-cluster via ${K3D_PULL_REGISTRY_HOST}${K3D_PULL_REGISTRY_NAMESPACE} +# Use "remote" as runtime-environment in your .env file to push images to the container-registry at +# "registry.cloudogu.com/testing" and to apply resources to the configured kubernetes-context in KUBE_CONTEXT_NAME. RUNTIME_ENV?=local $(info RUNTIME_ENV=$(RUNTIME_ENV)) # The host and port of the local cluster -K3S_CLUSTER_FQDN?=k3ces.local +K3S_CLUSTER_FQDN?=k3ces.localdomain K3S_LOCAL_REGISTRY_PORT?=30099 - -# The URL of the container-registry to use. Defaults to the registry of the local-cluster. -# If RUNTIME_ENV is "remote" it is "registry.cloudogu.com/testing", if ENVIRONMENT is "ci" it is "registry.cloudogu.com/ci" -# if run on ci (jenkins) the images must be pushed to a separate namespace in order to free space every night after the build. +K3D_PULL_REGISTRY_HOST?=k3d-registry-proxy.localhost:5000 +K3D_PULL_REGISTRY_NAMESPACE?=/local-dev +K3D_PUSH_REGISTRY_HOST?=localhost:5001 +K3D_PUSH_REGISTRY_NAMESPACE?=$(K3D_PULL_REGISTRY_NAMESPACE) + +# The URL or image-prefix host to use for development images. +# If RUNTIME_ENV is "remote" it is "registry.cloudogu.com/testing", if ENVIRONMENT is "ci" it is "registry.cloudogu.com/ci". +# If run on ci (jenkins) the images must be pushed to a separate namespace in order to free space every night after the build. CES_REGISTRY_HOST?=${K3S_CLUSTER_FQDN}:${K3S_LOCAL_REGISTRY_PORT} CES_REGISTRY_NAMESPACE ?= +IMAGE_PUSH_REGISTRY_HOST ?= $(CES_REGISTRY_HOST) +IMAGE_PUSH_REGISTRY_NAMESPACE ?= $(CES_REGISTRY_NAMESPACE) ifeq (${RUNTIME_ENV}, remote) CES_REGISTRY_HOST=registry.cloudogu.com CES_REGISTRY_NAMESPACE=/testing + IMAGE_PUSH_REGISTRY_HOST=$(CES_REGISTRY_HOST) + IMAGE_PUSH_REGISTRY_NAMESPACE=$(CES_REGISTRY_NAMESPACE) ifeq ($(ENVIRONMENT), ci) CES_REGISTRY_NAMESPACE=/ci + IMAGE_PUSH_REGISTRY_NAMESPACE=$(CES_REGISTRY_NAMESPACE) endif endif +ifeq (${RUNTIME_ENV}, k3d) + CES_REGISTRY_HOST=$(K3D_PULL_REGISTRY_HOST) + CES_REGISTRY_NAMESPACE=$(K3D_PULL_REGISTRY_NAMESPACE) + IMAGE_PUSH_REGISTRY_HOST=$(K3D_PUSH_REGISTRY_HOST) + IMAGE_PUSH_REGISTRY_NAMESPACE=$(K3D_PUSH_REGISTRY_NAMESPACE) +endif $(info CES_REGISTRY_HOST=$(CES_REGISTRY_HOST)) # The name of the kube-context to use for applying resources. +# If KUBECONFIG is set and KUBE_CONTEXT_NAME is empty, the current context from this kubeconfig is used. # If KUBE_CONTEXT_NAME is empty and RUNTIME_ENV is "remote" the currently configured kube-context is used. -# If KUBE_CONTEXT_NAME is empty and RUNTIME_ENV is not "remote" the "k3ces.local" is used as kube-context. +# If KUBE_CONTEXT_NAME is empty and RUNTIME_ENV is "k3d" the currently configured kube-context is used. +# Set KUBE_CONTEXT_NAME explicitly if the current kube-context does not point to the desired local k3d cluster. +# If KUBE_CONTEXT_NAME is empty and RUNTIME_ENV is neither "remote" nor "k3d" the "k3ces.localdomain" is used as kube-context. ifeq (${KUBE_CONTEXT_NAME}, ) - ifeq (${RUNTIME_ENV}, remote) + ifneq (${KUBECONFIG}, ) + # Resolve the current context from the explicitly configured kubeconfig instead of the + # user's default ~/.kube/config. This keeps repo-local .env settings self-contained. + KUBE_CONTEXT_NAME = $(shell KUBECONFIG="${KUBECONFIG}" kubectl config current-context) + else ifeq (${RUNTIME_ENV}, remote) + KUBE_CONTEXT_NAME = $(shell kubectl config current-context) + else ifeq (${RUNTIME_ENV}, k3d) KUBE_CONTEXT_NAME = $(shell kubectl config current-context) else - KUBE_CONTEXT_NAME = k3ces.local + KUBE_CONTEXT_NAME = k3ces.localdomain endif endif $(info KUBE_CONTEXT_NAME=$(KUBE_CONTEXT_NAME)) @@ -68,6 +117,8 @@ GIT_HASH := $(shell git rev-parse --short HEAD) ## Image URL to use all building/pushing image targets IMAGE_DEV?=$(CES_REGISTRY_HOST)$(CES_REGISTRY_NAMESPACE)/$(ARTIFACT_ID)/$(GIT_BRANCH) IMAGE_DEV_VERSION=$(IMAGE_DEV):$(VERSION) +IMAGE_DEV_PUSH?=$(IMAGE_PUSH_REGISTRY_HOST)$(IMAGE_PUSH_REGISTRY_NAMESPACE)/$(ARTIFACT_ID)/$(GIT_BRANCH) +IMAGE_DEV_PUSH_VERSION=$(IMAGE_DEV_PUSH):$(VERSION) # Variables for the temporary yaml files. These are used as template to generate a development resource containing # the current namespace and the dev image. @@ -159,9 +210,17 @@ ifeq (${IMAGE_DEV},) endif .PHONY: image-import -image-import: check-all-vars check-k8s-artifact-id docker-dev-tag ## Imports the currently available image into the configured ces-registry. - @echo "Import $(IMAGE_DEV_VERSION) into K8s cluster ${KUBE_CONTEXT_NAME}..." - @docker push $(IMAGE_DEV_VERSION) +image-import: check-all-vars check-k8s-artifact-id docker-dev-tag ## Imports the currently available image into the configured runtime target. + @if [[ "${RUNTIME_ENV}" == "k3d" ]]; then \ + echo "Push $(IMAGE_DEV_VERSION) for k3d registry workflow..."; \ + echo "Push target: $(IMAGE_DEV_PUSH_VERSION)"; \ + echo "Pull target: $(IMAGE_DEV_VERSION)"; \ + DOCKER_BUILDKIT=1 docker tag $(IMAGE_DEV_VERSION) $(IMAGE_DEV_PUSH_VERSION); \ + docker push $(IMAGE_DEV_PUSH_VERSION); \ + else \ + echo "Import $(IMAGE_DEV_VERSION) into K8s cluster ${KUBE_CONTEXT_NAME}..."; \ + docker push $(IMAGE_DEV_VERSION); \ + fi @echo "Done." ## Functions @@ -193,7 +252,13 @@ ${BINARY_YQ}: $(UTILITY_BIN_PATH) install-helm: ${BINARY_HELM} ${BINARY_HELM}: $(UTILITY_BIN_PATH) - $(call go-get-tool,$(BINARY_HELM),helm.sh/helm/v3/cmd/helm@${BINARY_HELM_VERSION}) + $(call curl-get-tool-from-tar,$(BINARY_HELM),$(BINARY_HELM_URL),$(BINARY_HELM_SUM),$(BINARY_HELM_ARCHIVE_PATH),$(BINARY_HELM_ARCHIVE_STRIP)) + +.PHONY: install-crane ## Installs crane. +install-crane: ${BINARY_CRANE} + +${BINARY_CRANE}: $(UTILITY_BIN_PATH) + $(call curl-get-tool-from-tar,$(BINARY_CRANE),$(BINARY_CRANE_URL),$(BINARY_CRANE_SUM),$(BINARY_CRANE_ARCHIVE_PATH),$(BINARY_CRANE_ARCHIVE_STRIP)) .PHONY: controller-gen controller-gen: ${CONTROLLER_GEN} ## Download controller-gen locally if necessary. @@ -216,5 +281,3 @@ isProduction: else \ echo "Command executed in development stage. Continuing."; \ fi - - diff --git a/build/make/static-analysis.mk b/build/make/static-analysis.mk index 262bc0d..1c72452 100644 --- a/build/make/static-analysis.mk +++ b/build/make/static-analysis.mk @@ -47,7 +47,7 @@ $(STATIC_ANALYSIS_DIR)/static-analysis.log: $(STATIC_ANALYSIS_DIR) $(STATIC_ANALYSIS_DIR)/static-analysis-cs.log: $(STATIC_ANALYSIS_DIR) @echo "run static analysis with export to checkstyle format" - @$(LINT) $(LINTFLAGS) --output.checkstyle.path stdout run ./... $(ADDITIONAL_LINTER) > $@ + @$(LINT) $(LINTFLAGS) --output.checkstyle.path $@ run ./... $(ADDITIONAL_LINTER) $(STATIC_ANALYSIS_DIR): $(LINT) @mkdir -p $(STATIC_ANALYSIS_DIR) diff --git a/build/make/test-common.mk b/build/make/test-common.mk index efc0bc7..1946f83 100644 --- a/build/make/test-common.mk +++ b/build/make/test-common.mk @@ -1,3 +1,5 @@ +TEST_COMMON_MK_INCLUDE_MARKER="test-common.mk" + GO_JUNIT_REPORT=$(UTILITY_BIN_PATH)/go-junit-report GO_JUNIT_REPORT_VERSION=v2.1.0 diff --git a/build/make/test-integration.mk b/build/make/test-integration.mk index 9a3103c..cefad99 100644 --- a/build/make/test-integration.mk +++ b/build/make/test-integration.mk @@ -1,3 +1,8 @@ +# this also works with older main Makefiles which include all test*.mk files on top-level. +ifeq (${TEST_COMMON_MK_INCLUDE_MARKER}, ) + include ${BUILD_DIR}/make/test-common.mk +endif + ##@ Integration testing INTEGRATION_TEST_DIR=$(TARGET_DIR)/integration-tests diff --git a/build/make/test-unit.mk b/build/make/test-unit.mk index c6994a4..03cce8a 100644 --- a/build/make/test-unit.mk +++ b/build/make/test-unit.mk @@ -1,3 +1,7 @@ +ifeq (${TEST_COMMON_MK_INCLUDE_MARKER}, ) + include ${BUILD_DIR}/make/test-common.mk +endif + ##@ Unit testing UNIT_TEST_DIR=$(TARGET_DIR)/unit-tests diff --git a/build/make/variables.mk b/build/make/variables.mk index 639a8b1..5558626 100644 --- a/build/make/variables.mk +++ b/build/make/variables.mk @@ -95,3 +95,18 @@ define go-get-tool rm -rf $$TMP_DIR ;\ } endef + +# curl-get-tool-from-tar 'curl get' any source tar $2, sha256 checks with $3 and installs the file path $4 to $1. The intermediate folders from the archive can be stripped with $5 (Use 0 if the binary is in root). +define curl-get-tool-from-tar + @[ -f $(1) ] || { \ + set -e ;\ + echo "Downloading $(2) to $(1)" ;\ + TMP_FILE_PATH="$(TMP_DIR)/$$(basename "$(1)")" ;\ + mkdir -p "$(TMP_DIR)" ;\ + curl -L -s -o "$$TMP_FILE_PATH" "$(2)" ;\ + echo "Checking with sum: $3" ;\ + echo "$(3) $$TMP_FILE_PATH" | sha256sum -c ;\ + echo "Extracting $(4) to $$(dirname $(1))" ;\ + tar -xf $$TMP_FILE_PATH -C $$(dirname $(1)) --strip-components=$(5) $(4) ;\ + } +endef diff --git a/docs/development/doguv3.md b/docs/development/doguv3.md new file mode 100644 index 0000000..d50050c --- /dev/null +++ b/docs/development/doguv3.md @@ -0,0 +1,43 @@ +# scm - Dogu v3 + +## Einleitung +Die wesentliche Dogu v3-Unterstützung für den SCM-Manager wurde in den folgenden Bereichen hinzugefügt: + +### resources/dogu_v3 +- Fügt Dogu v3-spezifische Start- und Dienstkonto-Hook-Skripte hinzu. +- Bereitet das von `doguctl` in Kubernetes erwartete `dogu.json`-Layout vor. +- Stellt sicher, dass persistente SCM-Verzeichnisse dem SCM-Laufzeitbenutzer gehören. +- Passt die Erstellung, Löschung, Existenzprüfung und Rotationsverwaltung von Dienstkonten für den Dogu v3-Dienstkonto-Sidecar an. + +### resources/var/tmp/scm/init.script.d +- Passen die SCM-Initialisierungsskripte an die Dogu v3-Laufzeitumgebung an. +- Stellt sicher, dass die SCM-Konfiguration beim Start für das Ökosystem verfügbar ist. +- Enthält die Konfigurationsverwaltung für Integrationen, die nun innerhalb des SCM-Deployments bereitgestellt werden. + +### scm/k8s/helm +- Implementiert das Helm-basierte Dogu v3-Deployment für SCM. +- Definiert Kubernetes-Ressourcen wie StatefulSet, Services, Exposure, Konfiguration, Netzwerkrichtlinien und die Integration von Service-Konten. +- Fügt Chart-Werte und Metadaten für die SCM-Image-Konfiguration, Persistenz, Sicherheitskontexte, Ressourcen und die Dogu-Konfiguration hinzu. +- Bündelt Gotenberg als Teil des SCM-Helm-Releases, wodurch ein separates Gotenberg-Dogu überflüssig wird. + +Darüber hinaus wurden die Dateien `Makefile` und `Dockerfile` aktualisiert, um das Erstellen, Veröffentlichen, Installieren und Deinstallieren der Dogu v3-Variante zu unterstützen. + +## Entwicklung + +### Setup: + `.env` aus `.env.template` befüllen. +### Deployen/Aktualisieren: + ``` + make scm-v3-install + ``` + Baut und pusht das Image in die Dev-Registry und macht `helm upgrade --install` mit + `--set-string scm.image.*` + `imagePullPolicy=Always`. + +### Deinstallieren (PVCs bleiben erhalten): + ``` + make scm-v3-uninstall + ``` + +Hinweis: Um sicherzustellen, dass die SCM-Änderungen nach einer Neuinstallation erhalten bleiben, +wird „pvc“ (scm-data-scm-0) nicht automatisch deinstalliert. +Bei Bedarf kann es manuell entfernt werden. \ No newline at end of file diff --git a/k8s/helm/.helmignore b/k8s/helm/.helmignore new file mode 100644 index 0000000..438a5da --- /dev/null +++ b/k8s/helm/.helmignore @@ -0,0 +1,5 @@ +# Patterns to ignore when building packages. +.git/ +.gitignore +*.md +tmp/ \ No newline at end of file diff --git a/k8s/helm/Chart.yaml b/k8s/helm/Chart.yaml new file mode 100644 index 0000000..5436fc7 --- /dev/null +++ b/k8s/helm/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +name: scm +description: SCM Manager as a Cloudogu EcoSystem Dogu (V3) +type: application +version: 0.0.1 +appVersion: "3.11.10-1" +icon: https://cloudogu.com/images/dogus/scm-manager.png +annotations: + dogu.cloudogu.com/api-version: v3 + dogu.cloudogu.com/application.gotenberg: "8.34.0" diff --git a/k8s/helm/chart-patch-tpl.yaml b/k8s/helm/chart-patch-tpl.yaml new file mode 100644 index 0000000..0ffd0ff --- /dev/null +++ b/k8s/helm/chart-patch-tpl.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +values: + images: + scm: registry.cloudogu.com/official/scm:3.11.10-1 + scmSaManager: registry.cloudogu.com/k8s/service-account-producer-sidecar:0.1.2 + gotenberg: docker.io/gotenberg/gotenberg:8.34.0 +patches: + values.yaml: + scm: + image: + registry: "{{ registryFrom .images.scm }}" + repository: "{{ repositoryFrom .images.scm }}" + tag: "{{ tagFrom .images.scm }}" + saManager: + image: + registry: "{{ registryFrom .images.scmSaManager }}" + repository: "{{ repositoryFrom .images.scmSaManager }}" + tag: "{{ tagFrom .images.scmSaManager }}" + gotenberg: + image: + registry: "{{ registryFrom .images.gotenberg }}" + repository: "{{ repositoryFrom .images.gotenberg }}" + tag: "{{ tagFrom .images.gotenberg }}" diff --git a/k8s/helm/dogu-values-metadata.yaml b/k8s/helm/dogu-values-metadata.yaml new file mode 100644 index 0000000..9cdf7f2 --- /dev/null +++ b/k8s/helm/dogu-values-metadata.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +metavalues: + mainLogLevel: + name: Log-Level + description: Root log level for SCM-Manager + keys: + - path: scm.configuration.normal.logging.root + mapping: + debug: DEBUG + info: INFO + warn: WARN + error: ERROR + trace: TRACE diff --git a/k8s/helm/templates/_helpers.tpl b/k8s/helm/templates/_helpers.tpl new file mode 100644 index 0000000..bf17c59 --- /dev/null +++ b/k8s/helm/templates/_helpers.tpl @@ -0,0 +1,48 @@ +{{- define "scm.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "scm.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name (include "scm.name" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* All-in-one labels */}} +{{- define "scm.labels" -}} +app: ces +{{ include "scm.selectorLabels" . }} +helm.sh/chart: {{- printf " %s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.extraLabels }} +{{ toYaml .Values.extraLabels }} +{{- end }} +{{- end }} + +{{- define "scm.selectorLabels" -}} +app.kubernetes.io/name: {{ include "scm.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: {{ include "scm.name" . }} +{{- end }} + +{{- define "scm.scmSelectorLabels" -}} +{{ include "scm.selectorLabels" . }} +app.kubernetes.io/component: scm +{{- end }} + +{{- define "scm.backupLabels" -}} +k8s.cloudogu.com/backup-scope: scm +{{- end }} + +{{- define "scm.gotenbergName" -}} +{{- printf "%s-gotenberg" (include "scm.fullname" .) -}} +{{- end -}} + +{{- define "scm.gotenbergSelectorLabels" -}} +app.kubernetes.io/name: {{ include "scm.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/component: gotenberg +{{- end }} diff --git a/k8s/helm/templates/auth-registration.yaml b/k8s/helm/templates/auth-registration.yaml new file mode 100644 index 0000000..39aba34 --- /dev/null +++ b/k8s/helm/templates/auth-registration.yaml @@ -0,0 +1,9 @@ +apiVersion: k8s.cloudogu.com/v1 +kind: AuthRegistration +metadata: + name: {{ include "scm.fullname" . }}-authregistration + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + consumer: {{ include "scm.name" . }} + protocol: CAS diff --git a/k8s/helm/templates/config.yaml b/k8s/helm/templates/config.yaml new file mode 100644 index 0000000..615700e --- /dev/null +++ b/k8s/helm/templates/config.yaml @@ -0,0 +1,13 @@ +# Non-sensitive doguctl config. Rendered into the "normal" key and mounted at /etc/ces/config/normal/config.yaml via the projected ces-config volume (see deployment). +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "scm.fullname" . }}-config + labels: + {{- include "scm.labels" . | nindent 4 }} +data: + normal: | + {{- toYaml .Values.scm.configuration.normal | nindent 4 }} + # set sensitive to empty here because we don't have any sensitive values + # if sensitive values are ever added, they should be added into a secret, not this configmap + sensitive: "{}" diff --git a/k8s/helm/templates/exposition.yaml b/k8s/helm/templates/exposition.yaml new file mode 100644 index 0000000..f16a4b2 --- /dev/null +++ b/k8s/helm/templates/exposition.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.cloudogu.com/v1 +kind: Exposition +metadata: + name: {{ include "scm.name" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + http: + - name: {{ include "scm.name" . }} + service: {{ include "scm.name" . }} + port: {{ .Values.scm.service.ports.web.port }} + path: /{{ include "scm.name" . }} + tcp: + - name: {{ include "scm.name" . }}-ssh + service: {{ include "scm.name" . }} + port: {{ .Values.scm.service.ports.ssh.port }} + requestedExternalPort: {{ .Values.scm.exposition.externalPort }} + protocol: ssh diff --git a/k8s/helm/templates/gotenberg/deployment.yaml b/k8s/helm/templates/gotenberg/deployment.yaml new file mode 100644 index 0000000..0285266 --- /dev/null +++ b/k8s/helm/templates/gotenberg/deployment.yaml @@ -0,0 +1,42 @@ +{{- if .Values.gotenberg.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "scm.gotenbergName" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} + app.kubernetes.io/component: gotenberg +spec: + replicas: {{ .Values.gotenberg.replicas | default 1 }} + selector: + matchLabels: + {{- include "scm.gotenbergSelectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "scm.gotenbergSelectorLabels" . | nindent 8 }} + spec: + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + {{- with .Values.gotenberg.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: gotenberg + image: "{{ .Values.gotenberg.image.registry }}/{{ .Values.gotenberg.image.repository }}:{{ .Values.gotenberg.image.tag }}" + imagePullPolicy: {{ .Values.gotenberg.imagePullPolicy }} + ports: + - name: api + containerPort: 3000 + protocol: TCP + volumeMounts: + {{- with .Values.scm.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + requests: {{- toYaml .Values.gotenberg.resourceRequests | nindent 14 }} + limits: {{- toYaml .Values.gotenberg.resourceLimits | nindent 14 }} +{{- end }} \ No newline at end of file diff --git a/k8s/helm/templates/gotenberg/network-policy.yaml b/k8s/helm/templates/gotenberg/network-policy.yaml new file mode 100644 index 0000000..8a583eb --- /dev/null +++ b/k8s/helm/templates/gotenberg/network-policy.yaml @@ -0,0 +1,26 @@ +{{- if and .Values.networkPolicies.enabled .Values.gotenberg.enabled }} +# Allow scm to reach gotenberg +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "scm.gotenbergName" . }}-allow-scm + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "scm.gotenbergSelectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + {{- include "scm.scmSelectorLabels" . | nindent 14 }} + ports: + - protocol: TCP + port: 3000 +{{- end }} \ No newline at end of file diff --git a/k8s/helm/templates/gotenberg/service.yaml b/k8s/helm/templates/gotenberg/service.yaml new file mode 100644 index 0000000..51011a8 --- /dev/null +++ b/k8s/helm/templates/gotenberg/service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.gotenberg.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "scm.gotenbergName" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} + app.kubernetes.io/component: gotenberg +spec: + type: ClusterIP + ports: + {{- range $name, $value := .Values.gotenberg.service.ports }} + - name: {{ $name | quote }} + {{- toYaml $value | nindent 6 }} + {{- end }} + selector: + {{- include "scm.gotenbergSelectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/k8s/helm/templates/network-policies.yaml b/k8s/helm/templates/network-policies.yaml new file mode 100644 index 0000000..2f98f13 --- /dev/null +++ b/k8s/helm/templates/network-policies.yaml @@ -0,0 +1,48 @@ +{{- if .Values.networkPolicies.enabled }} +# Allow the CES gateway to reach scm +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "scm.fullname" . }}-gateway + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "scm.scmSelectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + {{- toYaml .Values.networkPolicies.gateway.matchLabels | nindent 14 }} +--- +# Allow the service-account-operator to reach the scm-sa-manager sidecar (:8090). +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "scm.fullname" . }}-sa-manager + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "scm.scmSelectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} + podSelector: + matchLabels: + {{- toYaml .Values.networkPolicies.serviceAccountOperator.matchLabels | nindent 14 }} + ports: + - protocol: TCP + port: {{ (index .Values.scm.service.ports "sa-manager").port }} +{{- end }} \ No newline at end of file diff --git a/k8s/helm/templates/sa-manager-secret.yaml b/k8s/helm/templates/sa-manager-secret.yaml new file mode 100644 index 0000000..57c6873 --- /dev/null +++ b/k8s/helm/templates/sa-manager-secret.yaml @@ -0,0 +1,22 @@ +# API key shared between the scm-sa-manager sidecar (API_KEY env var) and the +# ServiceAccountProducer CR (spec.http.authSecret), which the service-account-operator reads to +# authenticate its requests via the X-CES-SA-API-KEY header. +# +# Generated once and kept stable across `helm upgrade` via lookup. +{{ $secretName := printf "%s-sa-manager-auth" (include "scm.fullname" .) -}} +{{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName -}} +{{- $apiKey := "" -}} +{{- if and $existing $existing.data (index $existing.data "apiKey") -}} +{{- $apiKey = index $existing.data "apiKey" | b64dec -}} +{{- else -}} +{{- $apiKey = randAlphaNum 32 -}} +{{- end -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + labels: + {{- include "scm.labels" . | nindent 4 }} +type: Opaque +stringData: + apiKey: {{ $apiKey | quote }} diff --git a/k8s/helm/templates/service-account-producer.yaml b/k8s/helm/templates/service-account-producer.yaml new file mode 100644 index 0000000..d33518a --- /dev/null +++ b/k8s/helm/templates/service-account-producer.yaml @@ -0,0 +1,26 @@ +# Registers scm as a ServiceAccountProducer +apiVersion: k8s.cloudogu.com/v2 +kind: ServiceAccountProducer +metadata: + name: {{ include "scm.name" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + producer: {{ include "scm.name" . }} + http: + endpoint: "http://{{ include "scm.name" . }}.{{ .Release.Namespace }}.svc:{{ (index .Values.scm.service.ports "sa-manager").port }}/serviceaccounts" + authSecret: + name: {{ include "scm.fullname" . }}-sa-manager-auth + key: apiKey + priority: 1 + params: + attributes: + permission: + type: string + enum: [ "ro", "rw", "admin" ] + description: The permission to grant to the service account. Either "ro", "rw", or "admin". + return: + username: + description: Generated technical username for the service account. + password: + description: Password for the generated service account. diff --git a/k8s/helm/templates/service.yaml b/k8s/helm/templates/service.yaml new file mode 100644 index 0000000..824cd94 --- /dev/null +++ b/k8s/helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "scm.name" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + type: ClusterIP + selector: + {{- include "scm.scmSelectorLabels" . | nindent 4 }} + ports: + {{- range $name, $value := .Values.scm.service.ports }} + - name: {{ $name | quote }} + {{- toYaml $value | nindent 6 }} + {{- end }} diff --git a/k8s/helm/templates/statefulset.yaml b/k8s/helm/templates/statefulset.yaml new file mode 100644 index 0000000..6727e6a --- /dev/null +++ b/k8s/helm/templates/statefulset.yaml @@ -0,0 +1,306 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "scm.fullname" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} + {{- include "scm.backupLabels" . | nindent 4 }} +spec: + serviceName: {{ include "scm.name" . }} + replicas: {{ .Values.scm.replicas | default 1 }} + selector: + matchLabels: + {{- include "scm.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: scm + config/checksum: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }} + labels: + {{- include "scm.scmSelectorLabels" . | nindent 8 }} + spec: + # No spec.hostname: the StatefulSet controller sets it to the pod name (e.g. scm-0) + # and overrides any value here. doguctl's descriptor lookup follows ${HOSTNAME} accordingly + # (see dogu_v3/init.sh). + enableServiceLinks: false + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: {{- toYaml .Values.scm.podSecurityContext | nindent 8 }} + initContainers: + - name: dogu-init + image: "{{ .Values.scm.image.registry }}/{{ .Values.scm.image.repository }}:{{ .Values.scm.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.scm.imagePullPolicy }} + command: ["bash", "/dogu_v3/init.sh"] + env: + - name: ECOSYSTEM_MULTINODE + value: "true" + securityContext: + runAsUser: 0 + runAsNonRoot: false + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + add: + - CHOWN + - DAC_OVERRIDE + - FOWNER + volumeMounts: + - name: scm-data + mountPath: /var/lib/scm + subPath: data + - name: scm-data + mountPath: /var/ces/config + subPath: localConfig + - name: ces-config + mountPath: /etc/ces/config + readOnly: true + - name: scm-dogu-json + mountPath: /etc/ces/dogu_json + - name: sa-manager-hooks-init + # Copies create-sa.sh/remove-sa.sh, the sa-hook-*.sh adapters and doguctl out of the scm image + image: "{{ .Values.scm.image.registry }}/{{ .Values.scm.image.repository }}:{{ .Values.scm.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.scm.imagePullPolicy }} + command: ["sh", "-c", "cp /usr/local/bin/doguctl /create-sa.sh /remove-sa.sh /dogu_v3/sa-hook-common.sh /dogu_v3/sa-hook-create.sh /dogu_v3/sa-hook-remove.sh /dogu_v3/sa-hook-exists.sh /shared/ && chmod 0555 /shared/*"] + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + volumeMounts: + - name: sa-manager-hooks + mountPath: /shared + {{- with .Values.scm.extraInitContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: scm + image: "{{ .Values.scm.image.registry }}/{{ .Values.scm.image.repository }}:{{ .Values.scm.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.scm.imagePullPolicy }} + args: ["/startup.sh"] + securityContext: {{- toYaml .Values.scm.securityContext | nindent 12 }} + ports: + - name: ssh + containerPort: 2222 + protocol: TCP + - name: web + containerPort: 8080 + protocol: TCP + env: + - name: GOTENBERG_PLUGIN_ENABLED + value: {{ .Values.gotenberg.enabled | quote }} + - name: HOME + value: /var/lib/scm + - name: GOTENBERG_SERVICE_NAME + value: {{ include "scm.gotenbergName" . }} + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: ECOSYSTEM_MULTINODE + value: "true" + {{- with .Values.scm.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: scm-data + mountPath: /var/lib/scm + subPath: data + # Writable local doguctl config store (successfulInitialConfiguration, admin_user, ...). + - name: scm-data + mountPath: /var/ces/config + subPath: localConfig + # Ephemeral doguctl state (doguctl state ready). + - name: scm-ephemeral + mountPath: /var/ces/state + subPath: state + # Ephemeral cache and temp dir + - name: scm-ephemeral + mountPath: /var/cache + subPath: cache + - name: scm-ephemeral + mountPath: /tmp + subPath: tmp + # Read-only platform config: global + normal + sensitive. + - name: ces-config + mountPath: /etc/ces/config + readOnly: true + # dogu.json for doguctl config-key validation. + - name: scm-dogu-json + mountPath: /etc/ces/dogu_json + readOnly: true + {{- with .Values.scm.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + startupProbe: + # startup.sh ends with `doguctl state ready`; give first start plenty of time (incl. its restart). + exec: + command: + - /bin/sh + - -c + - '[ "$(doguctl state)" = "ready" ]' + periodSeconds: 10 + failureThreshold: 180 + timeoutSeconds: 5 + livenessProbe: + tcpSocket: + port: web + periodSeconds: 15 + failureThreshold: 5 + readinessProbe: + tcpSocket: + port: web + periodSeconds: 10 + failureThreshold: 5 + resources: + requests: {{- toYaml .Values.scm.resourceRequests | nindent 14 }} + limits: {{- toYaml .Values.scm.resourceLimits | nindent 14 }} + - name: scm-sa-manager + # Generic Go HTTP sidecar implementing the ServiceAccountProducer HTTP API + image: "{{ .Values.scm.saManager.image.registry }}/{{ .Values.scm.saManager.image.repository }}:{{ .Values.scm.saManager.image.tag }}" + imagePullPolicy: {{ .Values.scm.saManager.imagePullPolicy }} + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + ports: + - name: sa-manager + containerPort: 8090 + protocol: TCP + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: ECOSYSTEM_MULTINODE + value: "true" + - name: CREATE_HOOK + value: /hooks/sa-hook-create.sh + - name: DELETE_HOOK + value: /hooks/sa-hook-remove.sh + - name: EXISTS_HOOK + value: /hooks/sa-hook-exists.sh + - name: LOG_LEVEL + value: {{ .Values.scm.saManager.logLevel | quote }} + - name: ADDR + value: ":8090" + - name: API_KEY + valueFrom: + secretKeyRef: + name: {{ include "scm.fullname" . }}-sa-manager-auth + key: apiKey + # The hook scripts call bare "doguctl ..." - it's copied into /hooks by sa-manager-hooks-init, + # so it needs to be on PATH. The sidecar's own Alpine base image doesn't provide doguctl itself. + - name: PATH + value: "/hooks:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + # These variables are usually set in the scm Dockerfile. + # They are necessary here to execute the service account scripts. + - name: CES_TOKEN_HEADER + value: X-CES-Token + - name: CES_TOKEN_CONFIGURATION_KEY + value: serviceaccount_token + volumeMounts: + # Writable local doguctl config + - name: scm-data + mountPath: /var/ces/config + subPath: localConfig + # Read-only platform config: global + normal + sensitive + - name: ces-config + mountPath: /etc/ces/config + readOnly: true + # create-sa.sh/remove-sa.sh/scm_api.sh and the sa-hook-*.sh adapters + - name: sa-manager-hooks + mountPath: /hooks + readOnly: true + # dogu.json for doguctl's component-mode registry lookup. + - name: scm-dogu-json + mountPath: /etc/ces/dogu_json + readOnly: true + # GET /serviceaccounts is the sidecar's unauthenticated readiness route (httpGet probes + # can only send GET, never HEAD - the method the real service-account-operator client + # uses for its own Ready() check). + readinessProbe: + httpGet: + path: /serviceaccounts + port: sa-manager + periodSeconds: 10 + failureThreshold: 5 + livenessProbe: + httpGet: + path: /serviceaccounts + port: sa-manager + periodSeconds: 15 + failureThreshold: 5 + resources: + requests: {{- toYaml .Values.scm.saManager.resourceRequests | nindent 14 }} + limits: {{- toYaml .Values.scm.saManager.resourceLimits | nindent 14 }} + volumes: + {{- if .Values.scm.persistence.existingClaim }} + - name: scm-data + persistentVolumeClaim: + claimName: {{ .Values.scm.persistence.existingClaim }} + {{- end }} + - name: scm-ephemeral + emptyDir: {} + - name: sa-manager-hooks + emptyDir: {} + - name: ces-config + projected: + sources: + - configMap: + name: {{ .Values.scm.configuration.meta.global.name }} + items: + - key: {{ .Values.scm.configuration.meta.global.key }} + path: global/config.yaml + - configMap: + name: {{ include "scm.fullname" . }}-config + items: + - key: {{ .Values.scm.configuration.meta.normal.key }} + path: normal/config.yaml + - key: {{ .Values.scm.configuration.meta.sensitive.key }} + path: sensitive/config.yaml + - name: scm-dogu-json + emptyDir: {} + {{- with .Values.scm.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.scm.tolerations }} + tolerations: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.scm.affinity }} + affinity: {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.scm.nodeSelector }} + nodeSelector: {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.scm.persistence.existingClaim }} + volumeClaimTemplates: + # Holds the SCM work dir (/var/lib/scm) and the writable doguctl local config (/var/ces/config). + - metadata: + name: scm-data + labels: + {{- include "scm.labels" . | nindent 10 }} + {{- include "scm.backupLabels" . | nindent 10 }} + spec: + accessModes: + - ReadWriteOnce + {{- if .Values.scm.persistence.storageClassName }} + storageClassName: {{ .Values.scm.persistence.storageClassName | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.scm.persistence.size }} + {{- end }} diff --git a/k8s/helm/templates/warp.yaml b/k8s/helm/templates/warp.yaml new file mode 100644 index 0000000..07d0f5d --- /dev/null +++ b/k8s/helm/templates/warp.yaml @@ -0,0 +1,12 @@ +apiVersion: k8s.cloudogu.com/v1 +kind: WarpMenuEntry +metadata: + name: {{ include "scm.name" . }} + labels: + {{- include "scm.labels" . | nindent 4 }} +spec: + category: Development Apps + displayName: + de: SCM-Manager + en: SCM-Manager + path: /{{ include "scm.name" . }} diff --git a/k8s/helm/values.yaml b/k8s/helm/values.yaml new file mode 100644 index 0000000..783fb5f --- /dev/null +++ b/k8s/helm/values.yaml @@ -0,0 +1,130 @@ +global: + imagePullSecrets: + - name: "ces-container-registries" + +networkPolicies: + enabled: true + gateway: + matchLabels: + k8s.cloudogu.com/component.name: k8s-ces-gateway + serviceAccountOperator: + matchLabels: + k8s.cloudogu.com/component.name: service-account-operator + +extraLabels: {} + +scm: + replicas: 1 + + image: + registry: registry.cloudogu.com + repository: official/scm + tag: 3.11.10-1 + imagePullPolicy: IfNotPresent + + podSecurityContext: + fsGroup: 1000 + fsGroupChangePolicy: OnRootMismatch + + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + + resourceLimits: + memory: 500Mi + resourceRequests: + cpu: 126m + memory: 500Mi + + persistence: + size: 2Gi + storageClassName: "" + # Use an existing PersistentVolumeClaim for the scm data volume instead of + # letting the chart create one. When set, no PVC is created and its size/ + # storageClassName above are ignored. + existingClaim: "" + + exposition: + externalPort: 2222 + service: + ports: + web: + protocol: TCP + port: 8080 + targetPort: web + ssh: + protocol: TCP + port: 2222 + targetPort: ssh + sa-manager: + protocol: TCP + port: 8090 + targetPort: sa-manager + + extraVolumeMounts: [] + extraEnv: [] + extraVolumes: [] + extraInitContainers: [] + + tolerations: [] + affinity: {} + nodeSelector: {} + + # Dogu-Configuration + configuration: + meta: + global: + name: global-config + key: config.yaml + normal: + key: normal + sensitive: + key: sensitive + normal: # config.yaml style; slash-keys from dogu.json become nested keys + logging: + root: "WARN" + container_config: + java_max_ram_percentage: "25.0" + java_min_ram_percentage: "50.0" + + # scm-sa-manager sidecar + saManager: + image: + registry: registry.cloudogu.com + repository: k8s/service-account-producer-sidecar + tag: "0.1.3" + imagePullPolicy: IfNotPresent + logLevel: INFO + resourceRequests: + cpu: 50m + memory: 64Mi + resourceLimits: + memory: 64Mi + +# Self-contained gotenberg +gotenberg: + enabled: true + replicas: 1 + image: + registry: docker.io + repository: gotenberg/gotenberg + tag: "8.34.0" + imagePullPolicy: IfNotPresent + resourceRequests: + cpu: 15m + memory: 200Mi + resourceLimits: + memory: 200Mi + extraVolumes: [] + service: + ports: + api: + protocol: TCP + port: 3000 + targetPort: api diff --git a/resources/create-sa.sh b/resources/create-sa.sh index e2f4eda..538bb52 100755 --- a/resources/create-sa.sh +++ b/resources/create-sa.sh @@ -21,7 +21,7 @@ else fi # create random username suffix and password -ID=$(doguctl random -l 6 | tr '[:upper:]' '[:lower:]') +ID="SCM_SA" USER="${SERVICE}_${ID}" PASSWORD=$(doguctl random) diff --git a/resources/dogu_v3/init.sh b/resources/dogu_v3/init.sh new file mode 100644 index 0000000..b32b7b5 --- /dev/null +++ b/resources/dogu_v3/init.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +# DoguV3-only init-container: +# 1. Materialize the /etc/ces/dogu_json/scm/{current,} layout that doguctl expects +# 2. Fix ownership of the persistent volumes for the scm user (uid/gid 1000) + +mkdir -p /var/lib/scm /var/ces/config + +# --- 1. dogu_json layout ------------------------------------------------------ +# doguctl resolves the descriptor from /etc/ces/dogu_json/${HOSTNAME}/. +# As a StatefulSet the pod hostname is the pod name (e.g. scm-0), NOT a fixed "scm" +# (the controller overrides spec.hostname), so the descriptor dir must follow ${HOSTNAME}. +TARGET_DIR="/etc/ces/dogu_json/${HOSTNAME}" +SOURCE_DOGU_JSON="/dogu.json" + +# Take the first "Version" line +DOGU_VERSION="$(awk -F'"' '/"Version"[[:space:]]*:/ {print $4; exit}' "${SOURCE_DOGU_JSON}")" +if [ -z "${DOGU_VERSION}" ]; then + echo "unable to determine dogu version from ${SOURCE_DOGU_JSON}" >&2 + exit 1 +fi + +mkdir -p "${TARGET_DIR}" +printf '%s' "${DOGU_VERSION}" > "${TARGET_DIR}/current" +cp "${SOURCE_DOGU_JSON}" "${TARGET_DIR}/${DOGU_VERSION}" + +echo "prepared dogu.json in ${TARGET_DIR} for to be used by doguctl" + +# --- persistence ownership ------------------------------------------------- +# Must run last (see header comment): fixes up anything doguctl created as root above. +chown -R 1000:1000 /var/lib/scm /var/ces/config + +echo "set ownership for /var/lib/scm and /var/ces/config" diff --git a/resources/dogu_v3/sa-hook-common.sh b/resources/dogu_v3/sa-hook-common.sh new file mode 100755 index 0000000..863321c --- /dev/null +++ b/resources/dogu_v3/sa-hook-common.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Shared by sa-hook-create.sh/sa-hook-remove.sh: translates the named "--key=value" flags that the +# generic service-account-producer-sidecar passes into the bare positional "key=value" parameters +# that create-sa.sh/remove-sa.sh expect + +####################################### +# Reads named "--key=value" flags plus the trailing consumer argument, validates each flag's key +# against an explicit, ordered allowlist, and builds the positional PLAIN_PARAMS array in that +# fixed order - regardless of the order the flags actually arrived in. +# +# Flags of the form "--behavior-=" are ignored. +# +# GLOBALS: +# PLAIN_PARAMS (out) +# CONSUMER (out) +# ARGUMENTS: +# #1 - space-separated, ordered list of known parameter keys, e.g. "fullAccessRepository permissions" +# #2.. - [--key=value...] , as received from the sidecar +####################################### +function readKnownParams() { + local ordered_known_keys="$1" + shift + + local number_of_args=$# + # shellcheck disable=SC2034 # CONSUMER/PLAIN_PARAMS are intentionally global for callers of this function. + CONSUMER="${!number_of_args}" + + declare -A values=() + if [ "${number_of_args}" -gt 1 ]; then + local flag key value + for flag in "${@:1:$((number_of_args - 1))}"; do + key="${flag%%=*}" + key="${key#--}" + value="${flag#*=}" + + if [[ "${key}" == behavior-* ]]; then + continue + fi + + if [[ " ${ordered_known_keys} " != *" ${key} "* ]]; then + echo "unknown parameter: ${key}" >&2 + exit 1 + fi + values["${key}"]="${value}" + done + fi + + PLAIN_PARAMS=() + local known_key + for known_key in ${ordered_known_keys}; do + if [ -n "${values[${known_key}]+set}" ]; then + PLAIN_PARAMS+=("${values[${known_key}]}") + fi + done +} + +function getExistingUserForConsumer() { + local CONSUMER="$1" + ID="SCM_SA" + USERMATCH="${CONSUMER}_${ID}" + + # connection token + API_TOKEN=$(doguctl config --encrypted ${CES_TOKEN_CONFIGURATION_KEY}) + + # read users + for username in $(curl --silent http://localhost:8080/scm/api/v2/users\?fields=_embedded.users.name\&pageSize=999 -H "${CES_TOKEN_HEADER}: ${API_TOKEN}" | jq -r '._embedded.users | .[] | .name') + do + if [[ $username =~ $USERMATCH ]] + then + echo $username + return 0 + fi + done + echo "null" + return 1 +} + diff --git a/resources/dogu_v3/sa-hook-create.sh b/resources/dogu_v3/sa-hook-create.sh new file mode 100755 index 0000000..8a76804 --- /dev/null +++ b/resources/dogu_v3/sa-hook-create.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck disable=SC1091 +source /hooks/sa-hook-common.sh + +# create-sa.sh only understands these two parameters +readKnownParams "permission" "$@" + +# Check if a password-rotation is needed +# +# - no existing account -> create as usual +# - existing account, rotation requested -> remove the old account, then create a new one +# - existing account, no rotation requested -> no-op + +ROTATE="false" +for flag in "$@"; do + if [[ "${flag}" == "--behavior-rotateServiceAccountNow=true" ]]; then + ROTATE="true" + fi +done + +if EXISTING_USER_ID=$(getExistingUserForConsumer "${CONSUMER}"); then + if [[ "${ROTATE}" != "true" ]]; then + echo "service account for ${CONSUMER} already exists (${EXISTING_USER_ID}), no rotation requested - leaving it as is" >&2 + exit 0 + fi + + # Rotating: remove the old account first. + echo "rotating service account for ${CONSUMER} - removing existing account ${EXISTING_USER_ID} first" >&2 + /hooks/remove-sa.sh "irrelevant" "${CONSUMER}" >&2 +fi + +# call create-sa.sh +exec /hooks/create-sa.sh "${PLAIN_PARAMS[@]}" "${CONSUMER}" diff --git a/resources/dogu_v3/sa-hook-exists.sh b/resources/dogu_v3/sa-hook-exists.sh new file mode 100755 index 0000000..f5db5b1 --- /dev/null +++ b/resources/dogu_v3/sa-hook-exists.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +source /hooks/sa-hook-common.sh + +# Checks whether a service account for the given consumer currently exists by using doguctl. +# +# Exit code convention: +# 0 - the service account exists +# 1 - the service account does not exist + +if [ $# -ne 1 ]; then + echo "usage sa-hook-exists.sh " >&2 + exit 2 +fi + +CONSUMER="$1" + +if getExistingUserForConsumer "${CONSUMER}"; then + exit 0 +else + exit 1 +fi diff --git a/resources/dogu_v3/sa-hook-remove.sh b/resources/dogu_v3/sa-hook-remove.sh new file mode 100755 index 0000000..4bf46ce --- /dev/null +++ b/resources/dogu_v3/sa-hook-remove.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck disable=SC1091 +source /hooks/sa-hook-common.sh + +# remove-sa.sh only understands this one optional parameter (deletion of the granted repository). +readKnownParams "permission" "$@" + +# call remove-sa.sh +exec /hooks/remove-sa.sh "irrelevant" "${CONSUMER}" diff --git a/resources/remove-sa.sh b/resources/remove-sa.sh index b1a053e..f1e0a4e 100755 --- a/resources/remove-sa.sh +++ b/resources/remove-sa.sh @@ -12,9 +12,10 @@ SERVICE="$2" # connection token API_TOKEN=$(doguctl config --encrypted ${CES_TOKEN_CONFIGURATION_KEY}) -# read users -USERMATCH=${SERVICE}_[a-zA-Z0-9]{6} +ID="SCM_SA" +USERMATCH="${SERVICE}_${ID}" +# read users for username in $(curl --silent http://localhost:8080/scm/api/v2/users\?fields=_embedded.users.name\&pageSize=999 -H "${CES_TOKEN_HEADER}: ${API_TOKEN}" | jq -r '._embedded.users | .[] | .name') do if [[ $username =~ $USERMATCH ]] diff --git a/resources/startup.sh b/resources/startup.sh index 0c5d52e..f87d2df 100755 --- a/resources/startup.sh +++ b/resources/startup.sh @@ -25,7 +25,7 @@ mkdir -p "${INIT_SCRIPT_LIBRARY_FOLDER}" cp -rf "${MAIN_INIT_SCRIPTS_LIBRARY_FOLDER}"/*.groovy "${INIT_SCRIPT_LIBRARY_FOLDER}/" # merge custom init scripts, if the volume is not empty -if [ "$(ls -A ${CUSTOM_INIT_SCRIPTS_FOLDER}/*.groovy)" ]; then +if ls -A ${CUSTOM_INIT_SCRIPTS_FOLDER}/*.groovy &> /dev/null; then cp "${CUSTOM_INIT_SCRIPTS_FOLDER}/"*.groovy "${INIT_SCRIPT_FOLDER}/" fi diff --git a/resources/var/tmp/scm/init.script.d/040-install-plugins.groovy b/resources/var/tmp/scm/init.script.d/040-install-plugins.groovy index 7494b32..2243d9b 100644 --- a/resources/var/tmp/scm/init.script.d/040-install-plugins.groovy +++ b/resources/var/tmp/scm/init.script.d/040-install-plugins.groovy @@ -92,7 +92,8 @@ if (ecoSystem.isInstalled("cockpit")) { plugins.add("scm-cockpit-legacy-plugin") } -if (ecoSystem.isInstalled("gotenberg")) { +def isEnabled = System.getenv("GOTENBERG_PLUGIN_ENABLED") != "false" +if (isEnabled || ecoSystem.isInstalled("gotenberg")) { plugins.add("scm-gotenberg-plugin") } diff --git a/resources/var/tmp/scm/init.script.d/120-gotenberg-configuration.groovy b/resources/var/tmp/scm/init.script.d/120-gotenberg-configuration.groovy index 9f3a1a6..980caff 100644 --- a/resources/var/tmp/scm/init.script.d/120-gotenberg-configuration.groovy +++ b/resources/var/tmp/scm/init.script.d/120-gotenberg-configuration.groovy @@ -16,14 +16,18 @@ def setGotenbergConfig() { def gotenbergConfiguration = gotenbergConfigurationStore.get() - gotenbergConfiguration.setUrl("http://gotenberg:3000/") + def gotenbergServiceName = System.getenv("GOTENBERG_SERVICE_NAME") ?: "gotenberg" + gotenbergConfiguration.setUrl("http://${gotenbergServiceName}:3000/") gotenbergConfiguration.setEnabled(true) gotenbergConfigurationStore.set(gotenbergConfiguration) } try { - if (ecoSystem.isInstalled("gotenberg")) { + + def isEnabled = System.getenv("GOTENBERG_PLUGIN_ENABLED") != "false" + if (isEnabled || ecoSystem.isInstalled("gotenberg")) { + System.out.println "Gotenberg is installed, configuring gotenberg plugin" setGotenbergConfig() } } catch (ClassNotFoundException e) {