From 3a347b49d19a649677c4bb147ce5f26dedebf01e Mon Sep 17 00:00:00 2001 From: adrianc Date: Sun, 1 Sep 2024 16:19:33 +0300 Subject: [PATCH] Helm chart release automation push helm chart as oci image to ghcr on release (tag) Signed-off-by: adrianc --- .github/workflows/chart-push-release.yaml | 28 +++ Makefile | 261 ++++++++++++---------- hack/release/chart-push.sh | 34 +++ hack/release/chart-update.sh | 36 +++ 4 files changed, 236 insertions(+), 123 deletions(-) create mode 100644 .github/workflows/chart-push-release.yaml create mode 100755 hack/release/chart-push.sh create mode 100755 hack/release/chart-update.sh diff --git a/.github/workflows/chart-push-release.yaml b/.github/workflows/chart-push-release.yaml new file mode 100644 index 0000000..604109b --- /dev/null +++ b/.github/workflows/chart-push-release.yaml @@ -0,0 +1,28 @@ +name: "Push helm chart on release" + +env: + IMAGE_NAME: ghcr.io/${{ github.repository }} + +on: + push: + tags: + - v* +jobs: + package-and-push-helm-chart: + runs-on: ubuntu-22.04 + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: update chart + env: + GITHUB_TAG: ${{ github.ref_name }} + GITHUB_REPO_OWNER: ${{ github.repository_owner }} + run: make chart-prepare-release + + - name: push chart + env: + GITHUB_TAG: ${{ github.ref_name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPO_OWNER: ${{ github.repository_owner }} + run: make chart-push-release diff --git a/Makefile b/Makefile index 678d1ed..95b0751 100644 --- a/Makefile +++ b/Makefile @@ -91,8 +91,135 @@ COVER_MODE = atomic COVER_PROFILE = cover.out LCOV_PATH = lcov.info -.PHONY: all -all: build +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Location for build binaries +BUILDDIR ?= $(shell pwd)/build +$(BUILDDIR): + mkdir -p $(BUILDDIR) + +##@ Binary Dependencies download +MOCKERY ?= $(LOCALBIN)/mockery +MOCKERY_VERSION ?= v2.44.2 +.PHONY: mockery +mockery: $(MOCKERY) ## Download mockery locally if necessary. +$(MOCKERY): | $(LOCALBIN) + GOBIN=$(LOCALBIN) go install github.com/vektra/mockery/v2@$(MOCKERY_VERSION) + +.PHONY: kustomize +KUSTOMIZE ?= $(LOCALBIN)/kustomize +KUSTOMIZE_VERSION ?= v5.4.2 +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +$(KUSTOMIZE): $(LOCALBIN) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION) + +.PHONY: controller-gen +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +CONTROLLER_TOOLS_VERSION ?= v0.15.0 +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ + GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.PHONY: envtest +ENVTEST ?= $(LOCALBIN)/setup-envtest +ENVTEST_VERSION ?= latest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(ENVTEST_VERSION) + +.PHONY: operator-sdk +OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk +operator-sdk: ## Download operator-sdk locally if necessary. +ifeq (,$(wildcard $(OPERATOR_SDK))) +ifeq (, $(shell which operator-sdk 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPERATOR_SDK)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ + chmod +x $(OPERATOR_SDK) ;\ + } +else +OPERATOR_SDK = $(shell which operator-sdk) +endif +endif + +.PHONY: opm +OPM = $(LOCALBIN)/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +SKAFFOLD_VER := v2.12.0 +SKAFFOLD := $(abspath $(LOCALBIN)/skaffold-$(SKAFFOLD_VER)) +.PHONY: skaffold +skaffold: $(SKAFFOLD) ## Download skaffold locally if necessary. +$(SKAFFOLD): | $(LOCALBIN) + @{ \ + set -e;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -fsSL https://storage.googleapis.com/skaffold/releases/$(SKAFFOLD_VER)/skaffold-$${OS}-$${ARCH} -o $(SKAFFOLD); \ + chmod +x $(SKAFFOLD);\ + } + +# minikube is used to set-up a local kubernetes cluster for dev work. +MINIKUBE_VER := v1.33.1 +MINIKUBE := $(abspath $(LOCALBIN)/minikube-$(MINIKUBE_VER)) +.PHONY: minikube +minikube: $(MINIKUBE) ## Download minikube locally if necessary. +$(MINIKUBE): | $(LOCALBIN) + @{ \ + set -e;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -fsSL https://storage.googleapis.com/minikube/releases/$(MINIKUBE_VER)/minikube-$${OS}-$${ARCH} -o $(MINIKUBE); \ + chmod +x $(MINIKUBE);\ + } + +HELM := $(abspath $(LOCALBIN)/helm) +.PHONY: helm +helm: $(HELM) ## Download helm (last release) locally if necessary. +$(HELM): | $(LOCALBIN) + @{ \ + curl -fsSL -o $(LOCALBIN)/get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \ + chmod 700 $(LOCALBIN)/get_helm.sh && \ + HELM_INSTALL_DIR=$(LOCALBIN) USE_SUDO=false $(LOCALBIN)/get_helm.sh && \ + rm -f $(LOCALBIN)/get_helm.sh; \ + } + +YQ := $(abspath $(LOCALBIN)/yq) +YQ_VERSION=v4.44.1 +.PHONY: yq +yq: $(YQ) ## Download yq locally if necessary. +$(YQ): | $(LOCALBIN) + @curl -fsSL -o $(YQ) https://github.com/mikefarah/yq/releases/download/$(YQ_VERSION)/yq_linux_amd64 && chmod +x $(YQ) + +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +GOLANGCI_LINT_VERSION ?= v1.59.1 +.PHONY: golangci-lint ## Download golangci-lint locally if necessary. +golangci-lint: + @[ -f $(GOLANGCI_LINT) ] || { \ + set -e ;\ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ + } ##@ General @@ -111,6 +238,9 @@ all: build help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +.PHONY: all +all: build + .PHONY: clean clean: ## clean files rm -rf $(LOCALBIN) @@ -138,15 +268,6 @@ unit-test: envtest ## Run unit tests. .PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up. test-e2e: go test ./test/e2e/ -v -ginkgo.v - -GOLANGCI_LINT = $(LOCALBIN)/golangci-lint -GOLANGCI_LINT_VERSION ?= v1.59.1 -golangci-lint: - @[ -f $(GOLANGCI_LINT) ] || { \ - set -e ;\ - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ - } - .PHONY: lint lint: golangci-lint ## Run golangci-lint linter & yamllint @@ -225,16 +346,6 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi ##@ Build Dependencies -## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin -$(LOCALBIN): - mkdir -p $(LOCALBIN) - -## Location for build binaries -BUILDDIR ?= $(shell pwd)/build -$(BUILDDIR): - mkdir -p $(BUILDDIR) - .PHONY: bundle bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. $(OPERATOR_SDK) generate kustomize manifests -q @@ -274,110 +385,14 @@ catalog-build: opm ## Build a catalog image. catalog-push: ## Push a catalog image. $(MAKE) docker-push IMG=$(CATALOG_IMG) -##@ Binary Dependencies download - -MOCKERY ?= $(LOCALBIN)/mockery -MOCKERY_VERSION ?= v2.44.2 -.PHONY: mockery -mockery: $(MOCKERY) ## Download mockery locally if necessary. -$(MOCKERY): | $(LOCALBIN) - GOBIN=$(LOCALBIN) go install github.com/vektra/mockery/v2@$(MOCKERY_VERSION) - -.PHONY: kustomize -KUSTOMIZE ?= $(LOCALBIN)/kustomize -KUSTOMIZE_VERSION ?= v5.4.2 -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. -$(KUSTOMIZE): $(LOCALBIN) - @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ - echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ - rm -rf $(LOCALBIN)/kustomize; \ - fi - test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION) - -.PHONY: controller-gen -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen -CONTROLLER_TOOLS_VERSION ?= v0.15.0 -controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. -$(CONTROLLER_GEN): $(LOCALBIN) - test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ - GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) +.PHONY: chart-prepare-release +chart-prepare-release: | $(YQ) ## prepare helm chart for release + @GITHUB_TAG=$(GITHUB_TAG) GITHUB_REPO_OWNER=$(GITHUB_REPO_OWNER) hack/release/chart-update.sh -.PHONY: envtest -ENVTEST ?= $(LOCALBIN)/setup-envtest -ENVTEST_VERSION ?= latest -envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. -$(ENVTEST): $(LOCALBIN) - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(ENVTEST_VERSION) -.PHONY: operator-sdk -OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk -operator-sdk: ## Download operator-sdk locally if necessary. -ifeq (,$(wildcard $(OPERATOR_SDK))) -ifeq (, $(shell which operator-sdk 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPERATOR_SDK)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ - chmod +x $(OPERATOR_SDK) ;\ - } -else -OPERATOR_SDK = $(shell which operator-sdk) -endif -endif - -.PHONY: opm -OPM = $(LOCALBIN)/opm -opm: ## Download opm locally if necessary. -ifeq (,$(wildcard $(OPM))) -ifeq (,$(shell which opm 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPM)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\ - chmod +x $(OPM) ;\ - } -else -OPM = $(shell which opm) -endif -endif - -SKAFFOLD_VER := v2.12.0 -SKAFFOLD := $(abspath $(LOCALBIN)/skaffold-$(SKAFFOLD_VER)) -.PHONY: skaffold -skaffold: $(SKAFFOLD) ## Download skaffold locally if necessary. -$(SKAFFOLD): | $(LOCALBIN) - @{ \ - set -e;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -fsSL https://storage.googleapis.com/skaffold/releases/$(SKAFFOLD_VER)/skaffold-$${OS}-$${ARCH} -o $(SKAFFOLD); \ - chmod +x $(SKAFFOLD);\ - } - -# minikube is used to set-up a local kubernetes cluster for dev work. -MINIKUBE_VER := v1.33.1 -MINIKUBE := $(abspath $(LOCALBIN)/minikube-$(MINIKUBE_VER)) -.PHONY: minikube -minikube: $(MINIKUBE) ## Download minikube locally if necessary. -$(MINIKUBE): | $(LOCALBIN) - @{ \ - set -e;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -fsSL https://storage.googleapis.com/minikube/releases/$(MINIKUBE_VER)/minikube-$${OS}-$${ARCH} -o $(MINIKUBE); \ - chmod +x $(MINIKUBE);\ - } - -HELM := $(abspath $(LOCALBIN)/helm) -.PHONY: helm -helm: $(HELM) ## Download helm (last release) locally if necessary. -$(HELM): | $(LOCALBIN) - @{ \ - curl -fsSL -o $(LOCALBIN)/get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 && \ - chmod 700 $(LOCALBIN)/get_helm.sh && \ - HELM_INSTALL_DIR=$(LOCALBIN) USE_SUDO=false $(LOCALBIN)/get_helm.sh && \ - rm -f $(LOCALBIN)/get_helm.sh; \ - } +.PHONY: chart-push-release +chart-push-release: | $(HELM) ## push release helm chart + @GITHUB_TAG=$(GITHUB_TAG) GITHUB_TOKEN=$(GITHUB_TOKEN) GITHUB_REPO_OWNER=$(GITHUB_REPO_OWNER) hack/release/chart-push.sh ##@ Dev diff --git a/hack/release/chart-push.sh b/hack/release/chart-push.sh new file mode 100755 index 0000000..9eb3105 --- /dev/null +++ b/hack/release/chart-push.sh @@ -0,0 +1,34 @@ +#!/bin/bash +set -ex + +# github repo owner: e.g mellanox +GITHUB_REPO_OWNER=${GITHUB_REPO_OWNER:-} +# github api token with package:write permissions +GITHUB_TOKEN=${GITHUB_TOKEN:-} +# github tag e.g v1.2.3 +GITHUB_TAG=${GITHUB_TAG:-} + +BASE=${PWD} +HELM_CMD="${BASE}/bin/helm" +HELM_CHART=${BASE}/deployment/maintenance-operator-chart +HELM_CHART_VERSION=${GITHUB_TAG#"v"} +HELM_CHART_TARBALL="maintenance-operator-chart-${HELM_CHART_VERSION}.tgz" + +if [ -z "$GITHUB_REPO_OWNER" ]; then + echo "ERROR: GITHUB_REPO_OWNER must be provided as env var" + exit 1 +fi + +if [ -z "$GITHUB_TOKEN" ]; then + echo "ERROR: GITHUB_TOKEN must be provided as env var" + exit 1 +fi + +if [ -z "$GITHUB_TAG" ]; then + echo "ERROR: GITHUB_TAG must be provided as env var" + exit 1 +fi + +$HELM_CMD package ${HELM_CHART} +$HELM_CMD registry login ghcr.io -u ${GITHUB_REPO_OWNER} -p ${GITHUB_TOKEN} +$HELM_CMD push ${HELM_CHART_TARBALL} oci://ghcr.io/${GITHUB_REPO_OWNER} diff --git a/hack/release/chart-update.sh b/hack/release/chart-update.sh new file mode 100755 index 0000000..a1dad8d --- /dev/null +++ b/hack/release/chart-update.sh @@ -0,0 +1,36 @@ +#!/bin/bash +set -ex + +# github tag e.g v1.2.3 +GITHUB_TAG=${GITHUB_TAG:-} +# github repo owner e.g mellanox +GITHUB_REPO_OWNER=${GITHUB_REPO_OWNER:-} + +BASE=${PWD} +YQ_CMD="${BASE}/bin/yq" +HELM_VALUES=${BASE}/deployment/maintenance-operator-chart/values.yaml +HELM_CHART=${BASE}/deployment/maintenance-operator-chart/Chart.yaml + + +if [ -z "$GITHUB_TAG" ]; then + echo "ERROR: GITHUB_TAG must be provided as env var" + exit 1 +fi + +if [ -z "$GITHUB_REPO_OWNER" ]; then + echo "ERROR: GITHUB_REPO_OWNER must be provided as env var" + exit 1 +fi + +# tag provided via env var +OPERATOR_TAG=${GITHUB_TAG} + +# patch values.yaml in-place + +# maintenance-operator image: +OPERATOR_REPO=${GITHUB_REPO_OWNER} # this is used to allow to release maintenance-operator from forks +$YQ_CMD -i ".operator.image.repository = \"ghcr.io/${OPERATOR_REPO}/maintenance-operator\"" ${HELM_VALUES} + +# patch Chart.yaml in-place +$YQ_CMD -i ".version = \"${OPERATOR_TAG#"v"}\"" ${HELM_CHART} +$YQ_CMD -i ".appVersion = \"${OPERATOR_TAG}\"" ${HELM_CHART}