# Version GIT_HEAD_COMMIT ?= $(shell git rev-parse --short HEAD) VERSION ?= $(or $(shell git describe --abbrev=0 --tags --match "v*" 2>/dev/null),$(GIT_HEAD_COMMIT)) GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) # Defaults REGISTRY ?= ghcr.io REPOSITORY ?= projectcapsule/capsule GIT_TAG_COMMIT ?= $(shell git rev-parse --short $(VERSION)) GIT_MODIFIED_1 ?= $(shell git diff $(GIT_HEAD_COMMIT) $(GIT_TAG_COMMIT) --quiet && echo "" || echo ".dev") GIT_MODIFIED_2 ?= $(shell git diff --quiet && echo "" || echo ".dirty") GIT_MODIFIED ?= $(shell echo "$(GIT_MODIFIED_1)$(GIT_MODIFIED_2)") GIT_REPO ?= $(shell git config --get remote.origin.url) BUILD_DATE ?= $(shell git log -1 --format="%at" | xargs -I{} sh -c 'if [ "$(shell uname)" = "Darwin" ]; then date -r {} +%Y-%m-%dT%H:%M:%S; else date -d @{} +%Y-%m-%dT%H:%M:%S; fi') IMG_BASE ?= $(REPOSITORY) IMG ?= $(IMG_BASE):$(VERSION) CAPSULE_IMG ?= $(REGISTRY)/$(IMG_BASE) CLUSTER_NAME ?= capsule ## Kubernetes Version Support KUBERNETES_SUPPORTED_VERSION ?= "v1.35.0" ## Tool Binaries KUBECTL ?= kubectl HELM ?= helm # Options for 'bundle-build' ifneq ($(origin CHANNELS), undefined) BUNDLE_CHANNELS := --channels=$(CHANNELS) endif ifneq ($(origin DEFAULT_CHANNEL), undefined) BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) endif BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin else GOBIN=$(shell go env GOBIN) endif all: manager # Run tests .PHONY: test test: test-clean generate manifests test-clean @GO111MODULE=on go test -race -v $(shell go list ./... | grep -v "e2e") -coverprofile coverage.out .PHONY: test-clean test-clean: ## Clean tests cache @go clean -testcache # Build manager binary manager: generate golint go build -o bin/manager # Run against the configured Kubernetes cluster in ~/.kube/config run: generate manifests go run . # Generate manifests e.g. CRD, RBAC etc. manifests: generate $(CONTROLLER_GEN) crd paths="./..." output:crd:artifacts:config=charts/capsule/crds # Generate code generate: controller-gen $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." # Generate License Header license-headers: nwa $(NWA) config # Helm SRC_ROOT = $(shell git rev-parse --show-toplevel) helm-controller-version: $(eval VERSION := $(shell grep 'appVersion:' charts/capsule/Chart.yaml | awk '{print "v"$$2}')) $(eval KO_TAGS := $(shell grep 'appVersion:' charts/capsule/Chart.yaml | awk '{print "v"$$2}')) helm-docs: helm-doc $(HELM_DOCS) --chart-search-root ./charts helm-lint: ct @$(CT) lint --config .github/configs/ct.yaml --validate-yaml=false --all --debug helm-schema: helm-plugin-schema cd charts/capsule && $(HELM) schema --use-helm-docs helm-test: HELM_KIND_CONFIG ?= "" helm-test: kind @mkdir -p /tmp/results || true @$(KIND) create cluster --wait=60s --name capsule-charts --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config ./hack/kind-cluster.yaml @make helm-test-exec @$(KIND) delete cluster --name capsule-charts helm-test-exec: ct helm-controller-version ko-build-all $(MAKE) e2e-load-image CLUSTER_NAME=capsule-charts IMAGE=$(CAPSULE_IMG) VERSION=v0.0.0 @$(KUBECTL) create ns capsule-system || true $(MAKE) dev-install-deps $(MAKE) dev-install-grafana-operator-crds @$(CT) install --config $(SRC_ROOT)/.github/configs/ct.yaml --namespace=capsule-system --all --debug # Setup development env dev-build: kind $(KIND) create cluster --wait=60s --name $(CLUSTER_NAME) --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config ./hack/kind-cluster.yaml $(MAKE) dev-install-deps .PHONY: dev-destroy dev-destroy: kind $(KIND) delete cluster --name capsule dev-install-deps: dev-setup-fluxcd dev-setup-cert-manager dev-install-gw-api-crds wait-for-helmreleases API_GW := none API_GW_VERSION := v1.3.0 API_GW_LOOKUP := kubernetes-sigs/gateway-api dev-install-gw-api-crds: @$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/$(API_GW_LOOKUP)/releases/download/$(API_GW_VERSION)/standard-install.yaml GRAFANA := none GRAFANA_VERSION := v5.18.0 GRAFANA_LOOKUP := grafana/grafana-operator dev-install-grafana-operator-crds: @$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/grafana/grafana-operator/releases/download/$(GRAFANA_VERSION)/crds.yaml PROMETHEUS := none PROMETHEUS_VERSION := v0.88.0 PROMETHEUS_LOOKUP := prometheus-operator/prometheus-operator dev-install-prometheus-crds: @$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/prometheus-operator/prometheus-operator/releases/download/$(PROMETHEUS_VERSION)/bundle.yaml # Usage: # LAPTOP_HOST_IP= make dev-setup # For example: # LAPTOP_HOST_IP=192.168.10.101 make dev-setup define TLS_CNF [ req ] default_bits = 4096 distinguished_name = req_distinguished_name req_extensions = req_ext [ req_distinguished_name ] countryName = SG stateOrProvinceName = SG localityName = SG organizationName = CAPSULE commonName = CAPSULE [ req_ext ] subjectAltName = @alt_names [alt_names] IP.1 = $(LAPTOP_HOST_IP) endef export TLS_CNF dev-setup: $(KUBECTL) -n capsule-system scale deployment capsule-controller-manager --replicas=0 || true mkdir -p /tmp/k8s-webhook-server/serving-certs echo "$${TLS_CNF}" > _tls.cnf openssl req -newkey rsa:4096 -days 3650 -nodes -x509 \ -subj "/C=SG/ST=SG/L=SG/O=CAPSULE/CN=CAPSULE" \ -extensions req_ext \ -config _tls.cnf \ -keyout /tmp/k8s-webhook-server/serving-certs/tls.key \ -out /tmp/k8s-webhook-server/serving-certs/tls.crt $(KUBECTL) create secret tls capsule-tls -n capsule-system \ --cert=/tmp/k8s-webhook-server/serving-certs/tls.crt\ --key=/tmp/k8s-webhook-server/serving-certs/tls.key || true rm -f _tls.cnf export WEBHOOK_URL="https://$${LAPTOP_HOST_IP}:9443"; \ export CA_BUNDLE=`openssl base64 -in /tmp/k8s-webhook-server/serving-certs/tls.crt | tr -d '\n'`; \ $(HELM) upgrade \ --dependency-update \ --force-conflicts \ --debug \ --install \ --namespace capsule-system \ --create-namespace \ --set 'crds.install=true' \ --set 'crds.exclusive=true'\ --set 'crds.createConfig=true'\ --set "tls.enableController=false"\ --set "webhooks.exclusive=true"\ --set "webhooks.hooks.nodes.enabled=true"\ --set "webhooks.service.url=$${WEBHOOK_URL}" \ --set "webhooks.service.caBundle=$${CA_BUNDLE}" \ capsule \ ./charts/capsule || true setup-monitoring: dev-setup-fluxcd @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/monitoring | envsubst | kubectl apply -f - @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/monitoring/dashboards | kubectl apply -f - @$(MAKE) wait-for-helmreleases @printf "\n\033[32mAccess Grafana:\033[0m\n\n" @printf " \033[1mkubectl port-forward svc/kube-prometheus-stack-grafana 9090:80 -n monitoring-system\033[0m\n\n" dev-setup-monitoring: setup-monitoring @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/host-proxy | envsubst | kubectl apply -f - dev-setup-argocd: dev-setup-fluxcd @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/argocd | envsubst | kubectl apply -f - @$(MAKE) wait-for-helmreleases @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/argocd/application | envsubst | kubectl apply -f - @printf "\n\033[32mAccess ArgoCD:\033[0m\n\n" @printf " \033[1mkubectl get secret -n argocd argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d\033[0m\n\n" @printf " \033[1mkubectl port-forward svc/argocd-server 9091:80 -n argocd\033[0m\n\n" dev-setup-cert-manager: @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/cert-manager | envsubst | kubectl apply -f - dev-setup-fluxcd: @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/fluxcd | envsubst | kubectl apply -f - # Here to setup the current capsule version # Intended to test updates to new version dev-setup-capsule: dev-setup-fluxcd @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/capsule | envsubst | kubectl apply -f - @$(MAKE) wait-for-helmreleases @$(MAKE) dev-setup-capsule-example dev-setup-capsule-example: dev-setup-fluxcd @$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/capsule/example-setup | envsubst | kubectl apply -f - @$(KUBECTL) create ns wind-test --as joe --as-group projectcapsule.dev || true @$(KUBECTL) create ns wind-prod --as joe --as-group projectcapsule.dev || true @$(KUBECTL) create ns green-test --as bob --as-group projectcapsule.dev || true @$(KUBECTL) create ns green-prod --as bob --as-group projectcapsule.dev || true @$(KUBECTL) create ns solar-test --as alice --as-group projectcapsule.dev || true @$(KUBECTL) create ns solar-prod --as alice --as-group projectcapsule.dev || true @$(KUBECTL) apply -f hack/distro/capsule/example-setup/claims.yaml wait-for-helmreleases: @ echo "Waiting for all HelmReleases to have observedGeneration >= 0..." @while [ "$$($(KUBECTL) get helmrelease -A -o jsonpath='{range .items[?(@.status.observedGeneration<0)]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' | wc -l)" -ne 0 ]; do \ sleep 5; \ done ENTERPRISE_VERSION ?= "0.13.0-rc.2" ENTERPRISE_REGISTRY ?= "oci.peakscale.ch" enterprise-prerelease: mkdir -p ./builds $(MAKE) CAPSULE_IMG=$(ENTERPRISE_REGISTRY)/prereleases/images/capsule VERSION=$(ENTERPRISE_VERSION) ko-publish-capsule $(HELM) package ./charts/capsule --app-version=$(ENTERPRISE_VERSION) --version=$(ENTERPRISE_VERSION) --destination ./builds/ $(HELM) push ./builds/capsule-$(ENTERPRISE_VERSION).tgz oci://$(ENTERPRISE_REGISTRY)/prereleases/charts/ $(MAKE) deploy-enterprise rm -rf ./builds deploy-enterprise: @echo "" @echo "Deploying Capsule Prerelease (Enterprise) $(ENTERPRISE_VERSION)" @echo "" @echo "1) Create image pull secret (Change the credentials with the ones provided to you):" @echo "" @echo "kubectl create secret docker-registry capsule-enterprise -n capsule-system \\" @echo " --docker-username='robot\$$name' \\" @echo " --docker-password='serviceaccount-password' \\" @echo " --docker-server='$(ENTERPRISE_REGISTRY)'" @echo "" @echo "2) Deploy Capsule:" @echo "" @echo "helm upgrade --install capsule \\" @echo " oci://$(ENTERPRISE_REGISTRY)/prereleases/charts/capsule \\" @echo " --namespace capsule-system \\" @echo " --version $(ENTERPRISE_VERSION) \\" @echo " --reuse-values \\" @echo " --set manager.image.registry=$(ENTERPRISE_REGISTRY) \\" @echo " --set manager.image.repository=prereleases/images/capsule \\" @echo " --set manager.image.tag=$(ENTERPRISE_VERSION) \\" @echo " --set manager.image.pullPolicy=Always \\" @echo " --set 'serviceAccount.imagePullSecrets={capsule-enterprise}'" @echo "" #################### # -- Docker #################### KO_PLATFORM ?= linux/$(GOARCH) KOCACHE ?= /tmp/ko-cache KO_REGISTRY := ko.local KO_TAGS ?= "latest" ifdef VERSION KO_TAGS := $(KO_TAGS),$(VERSION) endif LD_FLAGS := "-X main.Version=$(VERSION) \ -X main.GitCommit=$(GIT_HEAD_COMMIT) \ -X main.GitTag=$(VERSION) \ -X main.GitDirty=$(GIT_MODIFIED) \ -X main.BuildTime=$(BUILD_DATE) \ -X main.GitRepo=$(GIT_REPO)" # Docker Image Build # ------------------ .PHONY: ko-build-capsule ko-build-capsule: ko @echo Building Capsule $(KO_TAGS) for $(KO_PLATFORM) >&2 @LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(CAPSULE_IMG) \ $(KO) build ./cmd/ --bare --tags=$(KO_TAGS) --push=false --local --platform=$(KO_PLATFORM) .PHONY: ko-build-all ko-build-all: ko-build-capsule .PHONY: docker-build-capsule-trace docker-build-capsule-trace: ko-build-capsule @docker build \ --no-cache \ --build-arg TARGET_IMAGE=$(CAPSULE_IMG):$(VERSION) \ -t $(CAPSULE_IMG):tracing \ -f Dockerfile.tracing . # Docker Image Publish # ------------------ REGISTRY_PASSWORD ?= dummy REGISTRY_USERNAME ?= dummy .PHONY: ko-login ko-login: ko @$(KO) login $(REGISTRY) --username $(REGISTRY_USERNAME) --password $(REGISTRY_PASSWORD) .PHONY: ko-publish-capsule ko-publish-capsule: ko-login ## Build and publish kyvernopre image (with ko) @LD_FLAGS=$(LD_FLAGS) KOCACHE=$(KOCACHE) KO_DOCKER_REPO=$(CAPSULE_IMG) \ $(KO) build ./cmd/ --bare --tags=$(KO_TAGS) .PHONY: ko-publish-all ko-publish-all: ko-publish-capsule # Sorting imports .PHONY: goimports goimports: goimports -w -l -local "github.com/projectcapsule/capsule" . # Linting code as PR is expecting .PHONY: golint golint: golangci-lint $(GOLANGCI_LINT) run -c .golangci.yaml --verbose .PHONY: golint-fix golint-fix: golangci-lint $(GOLANGCI_LINT) run -c .golangci.yaml --verbose --fix # Running e2e tests in a KinD instance .PHONY: e2e e2e: ginkgo $(MAKE) e2e-build && $(MAKE) e2e-exec && $(MAKE) e2e-destroy e2e-build: kind $(MAKE) dev-build $(MAKE) e2e-install .PHONY: e2e-install e2e-install: helm-controller-version ko-build-all $(MAKE) e2e-load-image CLUSTER_NAME=$(CLUSTER_NAME) IMAGE=$(CAPSULE_IMG) VERSION=$(VERSION) $(HELM) upgrade \ --dependency-update \ --debug \ --install \ --namespace capsule-system \ --create-namespace \ --set 'replicaCount=2'\ --set 'manager.image.pullPolicy=Never' \ --set 'manager.resources=null'\ --set "manager.image.tag=$(VERSION)" \ --set 'manager.livenessProbe.failureThreshold=10' \ --set 'webhooks.hooks.nodes.enabled=true' \ --set "webhooks.exclusive=true"\ --set "manager.options.logLevel=debug"\ capsule \ ./charts/capsule .PHONY: trace-install trace-install: helm upgrade \ --dependency-update \ --debug \ --install \ --namespace capsule-system \ --create-namespace \ --set 'manager.resources=null'\ --set 'manager.livenessProbe.failureThreshold=10' \ --set 'manager.readinessProbe.failureThreshold=10' \ --values charts/capsule/ci/tracing-values.yaml \ capsule \ ./charts/capsule .PHONY: trace-e2e trace-e2e: kind $(MAKE) docker-build-capsule-trace $(KIND) create cluster --wait=60s --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config hack/kind-cluster.yml $(MAKE) e2e-load-image CLUSTER_NAME=capsule-tracing IMAGE=$(CAPSULE_IMG) VERSION=tracing $(MAKE) trace-install $(MAKE) e2e-install-deps $(MAKE) e2e-exec $(KIND) delete cluster --name capsule-tracing .PHONY: trace-unit trace-unit: harpoon $(HARPOON) analyze -e .git/ -e assets/ -e charts/ -e config/ -e docs/ -e e2e/ -e hack/ --directory /tmp/artifacts/ --save $(HARPOON) hunt -D /tmp/results -F harpoon-report.yml --include-cmd-stdout --save .PHONY: seccomp seccomp: $(HARPOON) build --add-syscall-sets=dynamic,docker -D /tmp/results --name capsule-seccomp.json --save .PHONY: e2e-load-image e2e-load-image: kind $(KIND) load docker-image $(IMAGE):$(VERSION) --name $(CLUSTER_NAME) .PHONY: e2e-exec e2e-exec: ginkgo $(GINKGO) -v -tags e2e ./e2e .PHONY: e2e-destroy e2e-destroy: dev-destroy SPELL_CHECKER = npx spellchecker-cli docs-lint: cd docs/content && $(SPELL_CHECKER) -f "*.md" "*/*.md" "!general/crds-apis.md" -d dictionary.txt #################### # -- Helpers #################### pull-upstream: git remote add upstream https://github.com/capsuleproject/capsule.git git fetch --all && git pull upstream ## Location to install dependencies to LOCALBIN ?= $(shell pwd)/bin $(LOCALBIN): mkdir -p $(LOCALBIN) #################### # -- Helm Plugins #################### HELM_SCHEMA_VERSION := "" helm-plugin-schema: @$(HELM) plugin install https://github.com/losisin/helm-values-schema-json.git --version $(HELM_SCHEMA_VERSION) --verify=false || true HELM_DOCS := $(LOCALBIN)/helm-docs HELM_DOCS_VERSION := v1.14.1 HELM_DOCS_LOOKUP := norwoodj/helm-docs helm-doc: @test -s $(HELM_DOCS) || \ $(call go-install-tool,$(HELM_DOCS),github.com/$(HELM_DOCS_LOOKUP)/cmd/helm-docs@$(HELM_DOCS_VERSION)) #################### # -- Tools #################### CONTROLLER_GEN := $(LOCALBIN)/controller-gen CONTROLLER_GEN_VERSION ?= v0.20.0 CONTROLLER_GEN_LOOKUP := kubernetes-sigs/controller-tools controller-gen: @test -s $(CONTROLLER_GEN) && $(CONTROLLER_GEN) --version | grep -q $(CONTROLLER_GEN_VERSION) || \ $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION)) GINKGO := $(LOCALBIN)/ginkgo ginkgo: $(call go-install-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo) CT := $(LOCALBIN)/ct CT_VERSION := v3.14.0 CT_LOOKUP := helm/chart-testing ct: @test -s $(CT) && $(CT) version | grep -q $(CT_VERSION) || \ $(call go-install-tool,$(CT),github.com/$(CT_LOOKUP)/v3/ct@$(CT_VERSION)) KIND := $(LOCALBIN)/kind KIND_VERSION := v0.31.0 KIND_LOOKUP := kubernetes-sigs/kind kind: @test -s $(KIND) && $(KIND) --version | grep -q $(KIND_VERSION) || \ $(call go-install-tool,$(KIND),sigs.k8s.io/kind/cmd/kind@$(KIND_VERSION)) KO := $(LOCALBIN)/ko KO_VERSION := v0.18.1 KO_LOOKUP := google/ko ko: @test -s $(KO) && $(KO) -h | grep -q $(KO_VERSION) || \ $(call go-install-tool,$(KO),github.com/$(KO_LOOKUP)@$(KO_VERSION)) NWA := $(LOCALBIN)/nwa NWA_VERSION := v0.7.7 NWA_LOOKUP := B1NARY-GR0UP/nwa nwa: @test -s $(NWA) && $(NWA) -h | grep -q $(NWA_VERSION) || \ $(call go-install-tool,$(NWA),github.com/$(NWA_LOOKUP)@$(NWA_VERSION)) GOLANGCI_LINT := $(LOCALBIN)/golangci-lint GOLANGCI_LINT_VERSION := v2.8.0 GOLANGCI_LINT_LOOKUP := golangci/golangci-lint golangci-lint: ## Download golangci-lint locally if necessary. @test -s $(GOLANGCI_LINT) && $(GOLANGCI_LINT) -h | grep -q $(GOLANGCI_LINT_VERSION) || \ $(call go-install-tool,$(GOLANGCI_LINT),github.com/$(GOLANGCI_LINT_LOOKUP)/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)) APIDOCS_GEN := $(LOCALBIN)/crdoc APIDOCS_GEN_VERSION := v0.6.4 APIDOCS_GEN_LOOKUP := fybrik/crdoc apidocs-gen: ## Download crdoc locally if necessary. @test -s $(APIDOCS_GEN) && $(APIDOCS_GEN) --version | grep -q $(APIDOCS_GEN_VERSION) || \ $(call go-install-tool,$(APIDOCS_GEN),fybrik.io/crdoc@$(APIDOCS_GEN_VERSION)) HARPOON := $(LOCALBIN)/harpoon HARPOON_VERSION := v0.10.2 HARPOON_LOOKUP := alegrey91/harpoon harpoon: @mkdir $(LOCALBIN) @curl -s https://raw.githubusercontent.com/alegrey91/harpoon/main/install | \ sudo bash -s -- --install-version $(HARPOON_VERSION) --install-dir $(LOCALBIN) # go-install-tool will 'go install' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-install-tool [ -f $(1) ] || { \ set -e ;\ GOBIN=$(LOCALBIN) go install $(2) ;\ } endef