
# Image URL to use all building/pushing image targets
IMG ?= infra-provider:latest
AUDIT_IMG ?= audit:latest
CM_IMG ?= config-manager:latest
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64

GITSHA ?= "no-git-module"
ifneq ("$(wildcard .git)", "")
	GITSHA = $(shell git rev-parse --short=7 HEAD)
endif
GITSHASYM="github.com/tidbcloud/infra-api/pkg/version.Version"
GO_LDFLAGS ?= "-X $(GITSHASYM)=$(GITSHA)"

# 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

# Setting SHELL to bash allows bash commands to be executed by recipes.
# This is a requirement for 'setup-envtest.sh' in the test target.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec

# E2E related environment variables.
E2E_NODES ?= 1

all: build

##@ General

# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk commands is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php

help: ## Display this help.
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\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)

##@ Development

CROSSPLANE_AWS_TAG ?= v0.20.14
CROSSPLANE_GCP_TAG ?= v0.19.2-dev
update-crossplane: ## sync crossplane CRDs and deps
	CROSSPLANE_AWS_TAG=$(CROSSPLANE_AWS_TAG) CROSSPLANE_GCP_TAG=$(CROSSPLANE_GCP_TAG) ./hack/update-crossplane.sh

.PHONY: manifests
manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
	cd api && make manifests

config-manager-manifests: controller-gen ## Generate webhook configs for cluster's revision.
	$(CONTROLLER_GEN) webhook paths="./cmd/config-manager/..." output:webhook:artifacts:config=./cmd/config-manager/config

.PHONY: generate
generate: ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
	cd api && make generate

gardener-gen: controller-gen ## Generate deepcopy for gardener client.
	$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./pkg/gardener/..."

pingcap-gen: controller-gen ## Generate deepcopy for pingcap client
	$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./pkg/pingcap/..."

# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false"
gardener-manifests: controller-gen ## Generate CRDD for gardener
	$(CONTROLLER_GEN)  $(CRD_OPTIONS) paths="./pkg/gardener/core/apis/v1beta1/..." output:crd:artifacts:config=./pkg/gardener/crd
	# https://github.com/kubernetes/kubernetes/issues/80493
	# delete one line make it works to apply the CRD
	sed -i '/description: Standard object metadata\./d' pkg/gardener/crd/core.gardener.cloud_sheet.yaml

crd: manifests kustomize ## Generate final CRD
	$(KUSTOMIZE) build config/crd > config/crd.yaml

webhook: manifests kustomize ## General final webhook deployment
	$(KUSTOMIZE) build config/webhook > config/webhooks.yaml

lint: golangci-lint ## Run golangci-lint linter
	$(GOLANGCI_LINT) run ./...
	cd api && make lint
	cd gitops-sdk && make lint

lint-fix: golangci-lint ## ## Run golangci-lint linter and perform fixes
	$(GOLANGCI_LINT) run --fix ./...
	cd api && make lint-fix

doc:
	cd api && make doc

unit: generate
	mkdir -p coverage
	go test ./controllers/... ./pkg/... ./cmd/audit/... -coverprofile coverage/cover.out -coverpkg ./...
	cd api && make unit
	cd gitops-sdk && make test

ENVTEST_ASSETS_DIR=$(shell pwd)/testbin
export PATH := PATH  := $(PATH):$(PWD)/testbin/bin
integration: manifests generate ginkgo ## Run tests.
	mkdir -p coverage
	mkdir -p test/integration/coverage
	mkdir -p ${ENVTEST_ASSETS_DIR}
	mkdir -p ${ENVTEST_ASSETS_DIR}/manifests/
	cp -r ./manifests/* ${ENVTEST_ASSETS_DIR}/manifests/
	./hack/grab-manifest.sh ./testbin
	test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl --show-error --fail -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.2/hack/setup-envtest.sh
	source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh && \
	fetch_envtest_tools $(ENVTEST_ASSETS_DIR) && \
	setup_envtest_env $(ENVTEST_ASSETS_DIR) && \
	$(GINKGO) -output-dir=./coverage/ -focus=$(or $(E2E_FOCUS), $(focus)) --slow-spec-threshold=300s -cover -coverpkg github.com/tidbcloud/infra-api/... -coverprofile=cover.it.out ./test/integration/...
	cd api && make test

test: unit integration

GENHTML=genhtml  # part of lcov
coverage: test gcov2lcov
	$(GCOV2LCOV) -infile=coverage/cover.out -outfile=coverage/cover.out.lcov
	$(GCOV2LCOV) -infile=coverage/cover.it.out -outfile=coverage/cover.it.out.lcov
	mkdir -p cover_html
	$(GENHTML) -o cover_html/all coverage/cover.out.lcov coverage/cover.it.out.lcov
	$(GENHTML) -o cover_html/ut_only coverage/cover.out.lcov

coverage-after-test: gcov2lcov
	$(GCOV2LCOV) -infile=coverage/cover.out -outfile=coverage/cover.out.lcov
	$(GCOV2LCOV) -infile=coverage/cover.it.out -outfile=coverage/cover.it.out.lcov
	mkdir -p cover_html
	$(GENHTML) -o coverage/cover_html/all coverage/cover.out.lcov coverage/cover.it.out.lcov
	$(GENHTML) -o coverage/cover_html/ut_only coverage/cover.out.lcov

# Make a PR ready for review.
reviewable: manifests generate crd webhook generate-mockgen lint test doc
	@go mod tidy

# Ensure a branch is reviewable and up to date
verify: reviewable
	@echo "checking that branch is clean"
	@test -z "$$(git status --porcelain)" || (echo -e "unclean working tree, did you forget to run 'make reviewable'?\ngit status:\n$$(git status --porcelain)" && exit 1)
	@echo "branch is clean"

check-generated-file-only: manifests generate crd webhook generate-mockgen doc
	@go mod tidy
	@echo "checking that branch is clean"
	@test -z "$$(git status --porcelain)" || (echo -e "unclean working tree, did you forget to run 'make reviewable'?\ngit status:\n$$(git status --porcelain)" && exit 1)
	@echo "branch is clean"

##@ QA
e2e: ginkgo
	$(GINKGO) -stream --slow-spec-threshold=3000s --nodes=$(E2E_NODES) --focus=$(E2E_FOCUS) --skip=$(E2E_SKIP) --junit-report --keep-separate-reports ./test/e2e/...


e2ev2: ginkgo
	$(GINKGO) --timeout=7200s --slow-spec-threshold=6000s --nodes=$(E2E_NODES) --focus=$(E2E_FOCUS) --skip=$(E2E_SKIP) -v ./test/e2ev2/...

##@ Build

build: generate ## Build manager binary.
	go build -o bin/manager -ldflags $(GO_LDFLAGS) cmd/infra-provider/main.go

run: manifests generate lint ## Run a controller from your host.
	go run ./cmd/infra-provider/main.go

.PHONY: docker-build
docker-build: ## Build the manager docker image.
	go mod vendor
	docker build --build-arg GO_LDFLAGS=$(GO_LDFLAGS) --network host -t ${IMG} .

docker-push: ## Push the manager docker image.
	docker push ${IMG}

docker-build-audit: ## Build the audit docker image.
	go mod vendor
	docker build --file audit.Dockerfile --network host -t ${AUDIT_IMG} .

docker-push-audit: ## Push the audit docker image.
	docker push ${AUDIT_IMG}

docker-mutil-arch-audit: ## Build and push the multi-arch version audit docker image.
	go mod vendor
	docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host'
	docker buildx build --push --platform $(DOCKER_PLATFORMS) --file audit.Dockerfile --network host -t ${AUDIT_IMG} .

docker-build-config-manager: ## Build the config-manager docker image.
	go mod vendor
	docker build --file config-manager.Dockerfile --network host -t ${CM_IMG} .

docker-push-config-manager: ## Push the config-manager docker image.
	docker push ${CM_IMG}

docker-mutil-arch-config-manager: ## Build and push the multi-arch version config-manager docker image.
	go mod vendor
	docker buildx create --use --buildkitd-flags '--allow-insecure-entitlement network.host'
	docker buildx build --push --platform $(DOCKER_PLATFORMS) --file config-manager.Dockerfile --network host -t ${CM_IMG} .

##@ Deployment

install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
	$(KUSTOMIZE) build config/crd | kubectl apply -f -

uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
	$(KUSTOMIZE) build config/crd | kubectl delete -f -

deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
	cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
	$(KUSTOMIZE) build config/default | kubectl apply -f -

undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
	$(KUSTOMIZE) build config/default | kubectl delete -f -

KUSTOMIZE = $(shell pwd)/bin/kustomize
kustomize: ## Download kustomize locally if necessary.
	$(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@v4.5.7)

CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
controller-gen: ## Download controller-gen locally if necessary.
	$(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1)

GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
golangci-lint: ## Download golangci-lint locally if necessary
	$(call go-get-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@v1.46.2)

MOCKGEN = $(shell pwd)/bin/mockgen
mockgen: ## Download mockgen locally if necessary
	$(call go-get-tool,$(MOCKGEN),github.com/golang/mock/mockgen@v1.6.0)

GINKGO = $(shell pwd)/bin/ginkgo
ginkgo:
	$(call go-get-tool,$(GINKGO),github.com/onsi/ginkgo/v2/ginkgo@v2.1.4)

GCOV2LCOV = $(shell pwd)/bin/gcov2lcov
gcov2lcov:
	$(call go-get-tool, $(GCOV2LCOV),github.com/jandelgado/gcov2lcov@v1.0.5)

.PHONY: mockgen
generate-mockgen: mockgen ## General gomock(https://github.com/golang/mock) files
	$(MOCKGEN) -source=./pkg/providers/aws/kms.go -package fake > ./pkg/providers/aws/fake/kms.go
	$(MOCKGEN) -source=./pkg/providers/aws/iam.go -package fake > ./pkg/providers/aws/fake/iam.go
	$(MOCKGEN) -source=./pkg/providers/aws/route53.go -package fake > ./pkg/providers/aws/fake/route53.go
	$(MOCKGEN) -source=./pkg/providers/aws/ec2.go -package fake > ./pkg/providers/aws/fake/ec2.go
	$(MOCKGEN) -source=./pkg/providers/aws/elb.go -package fake > ./pkg/providers/aws/fake/elb.go
	$(MOCKGEN) -source=./pkg/providers/aws/s3.go -package fake > ./pkg/providers/aws/fake/s3.go
	$(MOCKGEN) -source=./pkg/providers/aws/sts.go -package fake > ./pkg/providers/aws/fake/sts.go
	$(MOCKGEN) -source=./pkg/providers/aws/resourcegroupstaggingapi.go -package fake > ./pkg/providers/aws/fake/resourcegroupstaggingapi.go
	$(MOCKGEN) -source=./pkg/gardener/gardener.go -package fake > ./pkg/gardener/fake/gardener.go
	$(MOCKGEN) -source=./pkg/providers/gcp/gcp.go -package fake > ./pkg/providers/gcp/fake/gcp.go

	$(MOCKGEN) -source=./pkg/audit/server/handler.go -package server > ./pkg/audit/server/gomock_handler.go

	$(MOCKGEN) -source=./pkg/o11y/api.go -package fake > ./pkg/o11y/fake/api_impl.go

# go-get-tool will 'go get' any package $2 and install it to $1.
# go get is deprecated for installing executables since 1.17, https://go.dev/doc/go-get-install-deprecation
# (FIXME): try downgrade your go binary to install this.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-get-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\
rm -rf $$TMP_DIR ;\
}
endef
