SHELL := /bin/bash

GIT_VERSION         ?= v0.1.0
GIT_COMMIT          ?= $(shell git rev-parse HEAD)
BUILD_DATE          ?= $(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
GIT_MODULE          ?= opendev.org/airship/airshipctl/pkg/version

LDFLAGS             += -X ${GIT_MODULE}.gitVersion=${GIT_VERSION}
LDFLAGS             += -X ${GIT_MODULE}.gitCommit=${GIT_COMMIT}
LDFLAGS             += -X ${GIT_MODULE}.buildDate=${BUILD_DATE}

GO_FLAGS            := -ldflags '-extldflags "-static"' -tags=netgo -trimpath
GO_FLAGS            += -ldflags '$(LDFLAGS)'
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN 2> /dev/null))
GOBIN = $(shell go env GOPATH 2> /dev/null)/bin
else
GOBIN = $(shell go env GOBIN 2> /dev/null)
endif

# Produce CRDs that work back to Kubernetes 1.21.2
CRD_OPTIONS ?= crd:crdVersions=v1
TOOLBINDIR          := tools/bin

# linting
LINTER              := $(TOOLBINDIR)/golangci-lint
LINTER_CONFIG       := .golangci.yaml

# docker
DOCKER_MAKE_TARGET  := build
DOCKER_CMD_FLAGS    :=

# docker image options
DOCKER_REGISTRY     ?= quay.io
DOCKER_FORCE_CLEAN  ?= true
DOCKER_IMAGE_NAME   ?= airshipctl
DOCKER_IMAGE_PREFIX ?= airshipit
DOCKER_IMAGE_TAG    ?= latest
DOCKER_IMAGE        ?= $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_PREFIX)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
DOCKER_TARGET_STAGE ?= release
PUBLISH             ?= false
# use this variable for image labels added in internal build process
COMMIT              ?= $(shell git rev-parse HEAD)
LABEL               ?= org.airshipit.build=community
LABEL               += --label "org.opencontainers.image.revision=$(COMMIT)"
LABEL               += --label "org.opencontainers.image.created=$(shell date --rfc-3339=seconds --utc)"
LABEL               += --label "org.opencontainers.image.title=$(DOCKER_IMAGE_NAME)"

# go options
PKG                 ?= ./...
TESTS               ?= .
TEST_FLAGS          ?=
COVER_FLAGS         ?=
COVER_PROFILE       ?= cover.out
COVER_EXCLUDE       ?= (zz_generated|errors)

# proxy options
PROXY               ?= http://proxy.foo.com:8000
NO_PROXY            ?= localhost,127.0.0.1,.svc.cluster.local
USE_PROXY           ?= false

# docker build flags
DOCKER_CMD_FLAGS    += --network=host
DOCKER_CMD_FLAGS    += --force-rm=$(DOCKER_FORCE_CLEAN)
ifeq ($(USE_PROXY), true)
DOCKER_CMD_FLAGS    += --build-arg http_proxy=$(PROXY)
DOCKER_CMD_FLAGS    += --build-arg https_proxy=$(PROXY)
DOCKER_CMD_FLAGS    += --build-arg HTTP_PROXY=$(PROXY)
DOCKER_CMD_FLAGS    += --build-arg HTTPS_PROXY=$(PROXY)
DOCKER_CMD_FLAGS    += --build-arg no_proxy=$(NO_PROXY)
DOCKER_CMD_FLAGS    += --build-arg NO_PROXY=$(NO_PROXY)
endif
ifneq ($(strip $(GOPROXY)),)
DOCKER_CMD_FLAGS    += --build-arg GOPROXY=$(strip $(GOPROXY))
endif

# Godoc server options
GD_PORT             ?= 8080

# Documentation location
DOCS_DIR            ?= docs

# document validation options
UNAME               != uname
export KIND_URL     ?= https://kind.sigs.k8s.io/dl/v0.8.1/kind-$(UNAME)-amd64
KUBECTL_VERSION     ?= v1.21.2
export KUBECTL_URL  ?= https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl

.PHONY: depend
depend:
	@go mod download

.PHONY: build

.PHONY: install
install: depend
install:
	@CGO_ENABLED=0 go install .

# Core of build logic
BIN_DIR      := bin
BIN_SRC_DIR  := krm-functions
BINS         := airshipctl $(subst $(BIN_SRC_DIR)/,,$(wildcard $(BIN_SRC_DIR)/*))
IMGS         := $(BINS)

# This section sets the settings for different subcomponents

# airshipctl is a special case - we need to override it manually:
# its makefile target for image is 'docker-image' - others have
# docker-image-<name of component> targets
airshipctl_IMG_TGT_NAME:=docker-image
# its main.go is in the root of repo - others have main.go in
# $(BIN_SRC_DIR)/<name of component>/main.go
airshipctl_FROM_PATH:=.
# and its Dockerfile is also in the root of repo - others have Dockerfile in
# $(BIN_SRC_DIR)/<name of component>/Dockerfile
docker-image_DOCKERFILE:=Dockerfile

# kubeval-validator, toolbox and toolbox-virsh don't depend on
# airshipctl repo. Their Dockerfiles don't
# need to be called from the root of the repo.
applier_IS_INDEPENDED:=true
kubeval-validator_IS_INDEPENDED:=true
clusterctl_IS_INDEPENDED:=true
clusterctl-v0.3_IS_INDEPENDED:=true
toolbox-virsh_IS_INDEPENDED:=true
# in addition toolbox-virsh docker image needs toolbox docker image to be built first
docker-image-clusterctl-v0.3_DEPENDENCY:=docker-image-clusterctl
docker-image-toolbox-virsh_DEPENDENCY:=docker-image-toolbox

# The template that generates targets for creating binaries per component:
# Targets will be generated only for components that depend on airshipctl repo (part of that go module)
# Note: expressions with ?= won't be executed if the values of that variable was already set to it.
# Using that syntax it's possible to build values overrides for components.
# Note 2: $$ is needed to instruct make-engine that variable should be used after template rendering.
# When template is rendered all $ will be rendered in the template and $$ will be converted to $, e.g.
# if we call map_binary_defaults_tmpl for airshipctl $1 will be converted to 'airshipctl' and we'll get
# ifneq ($(airshipctl_IS_INDEPENDED),true)
# airshipctl_FROM_PATH?=$(BIN_SRC_DIR)/airshipctl/main.go
# ...
# since we defining airshipctl_FROM_PATH above, and ?= is used in the 2nd line
# airshipctl_FROM_PATH will stay the same as it was defined above.
define map_binary_defaults_tmpl
ifneq ($$($1_IS_INDEPENDED),true)
$1_FROM_PATH?=$$(BIN_SRC_DIR)/$1/main.go

$$(warning Adding dynamic target $$(BIN_DIR)/$1)
$$(BIN_DIR)/$1: $$($1_FROM_PATH) depend
	@CGO_ENABLED=0 go build -o $$@ $$(GO_FLAGS) $$<

$$(warning Adding dynamic target $1)
.PHONY: $1
$1: $$(BIN_DIR)/$1

build: $1
endif
endef
map_binary_defaults = $(eval $(call map_binary_defaults_tmpl,$1))
# Go through all components and generate binary targets for each of them
$(foreach bin,$(BINS),$(call map_binary_defaults,$(bin)))

.PHONY: images
.PHONY: images-publish

# The template that generates targets for creating images per components
# There is a special logic to handle per-components overrides
# 2 targets will be generated per component: docker-image-<component name> (possible to override)
# and docker-image-<component name>-publish
define map_image_defaults_tmpl
$1_IMG_TGT_NAME?=docker-image-$1

$$($1_IMG_TGT_NAME)_IMG_TITLE?=$1
$$($1_IMG_TGT_NAME)_IMG_TAG?=$$(DOCKER_IMAGE_TAG)
$$($1_IMG_TGT_NAME)_DOCKERTGT?=$$(DOCKER_TARGET_STAGE)
$$($1_IMG_TGT_NAME)_DOCKERFILE?=$$(BIN_SRC_DIR)/$1/Dockerfile
$$($1_IMG_TGT_NAME)_MAKETGT?=$$(BIN_DIR)/$1

ifeq ($$($1_IS_INDEPENDED),true)
$$($1_IMG_TGT_NAME)_DOCKERROOT?=$$(BIN_SRC_DIR)/$1
else
$$($1_IMG_TGT_NAME)_DOCKERROOT?=.
endif

ifneq ($1,airshipctl)
ifneq ($$(origin DOCKER_BASE_PLUGINS_GO_IMAGE), undefined)
$$($1_IMG_TGT_NAME)_BASE_GO_IMAGE?=$$(DOCKER_BASE_PLUGINS_GO_IMAGE)
endif
endif
$$($1_IMG_TGT_NAME)_BASE_GO_IMAGE?=$$(DOCKER_BASE_GO_IMAGE)
ifneq ($$(strip $$($$($1_IMG_TGT_NAME)_BASE_GO_IMAGE)),)
$$($1_IMG_TGT_NAME)_BUILD_ARG  += GO_IMAGE=$$($$($1_IMG_TGT_NAME)_BASE_GO_IMAGE)
endif

ifneq ($1,airshipctl)
ifneq ($$(origin DOCKER_BASE_PLUGINS_BUILD_IMAGE), undefined)
$$($1_IMG_TGT_NAME)_BASE_BUILD_IMAGE?=$$(DOCKER_BASE_PLUGINS_BUILD_IMAGE)
endif
endif
$$($1_IMG_TGT_NAME)_BASE_BUILD_IMAGE?=$$(DOCKER_BASE_BUILD_IMAGE)
ifneq ($$(strip $$($$($1_IMG_TGT_NAME)_BASE_BUILD_IMAGE)),)
$$($1_IMG_TGT_NAME)_BUILD_ARG  += BUILD_IMAGE=$$($$($1_IMG_TGT_NAME)_BASE_BUILD_IMAGE)
endif

ifneq ($1,airshipctl)
ifneq ($$(origin DOCKER_BASE_PLUGINS_RELEASE_IMAGE), undefined)
$$($1_IMG_TGT_NAME)_BASE_RELEASE_IMAGE?=$$(DOCKER_BASE_PLUGINS_RELEASE_IMAGE)
endif
endif
$$($1_IMG_TGT_NAME)_BASE_RELEASE_IMAGE?=$$(DOCKER_BASE_RELEASE_IMAGE)
ifneq ($$(strip $$($$($1_IMG_TGT_NAME)_BASE_RELEASE_IMAGE)),)
$$($1_IMG_TGT_NAME)_BUILD_ARG  += RELEASE_IMAGE=$$($$($1_IMG_TGT_NAME)_BASE_RELEASE_IMAGE)
endif

ifeq ($1,clusterctl-v0.3)
$$($1_IMG_TGT_NAME)_IMG_TAG=v0.3
$$($1_IMG_TGT_NAME)_IMG_TITLE=clusterctl
endif

$$(warning Adding dynamic target $$($1_IMG_TGT_NAME))
.PHONY: $$($1_IMG_TGT_NAME)
$$($1_IMG_TGT_NAME): $$($$($1_IMG_TGT_NAME)_DEPENDENCY)
	docker build $$($$($1_IMG_TGT_NAME)_DOCKERROOT) $$(DOCKER_CMD_FLAGS)\
		--file $$($$($1_IMG_TGT_NAME)_DOCKERFILE) \
		--label $$(LABEL) \
		--label "org.opencontainers.image.revision=$$(COMMIT)" \
		--label "org.opencontainers.image.created=$$(shell date --rfc-3339=seconds --utc)" \
		--label "org.opencontainers.image.title=$$($$($1_IMG_TGT_NAME)_IMG_TITLE)" \
		--target $$($$($1_IMG_TGT_NAME)_DOCKERTGT) \
		$$(addprefix --build-arg ,$$($$($1_IMG_TGT_NAME)_BUILD_ARG)) \
		--build-arg MAKE_TARGET=$$($$($1_IMG_TGT_NAME)_MAKETGT) \
		--tag $$(DOCKER_REGISTRY)/$$(DOCKER_IMAGE_PREFIX)/$$($$($1_IMG_TGT_NAME)_IMG_TITLE):$$($$($1_IMG_TGT_NAME)_IMG_TAG) \
		$$(foreach tag,$$(DOCKER_IMAGE_EXTRA_TAGS),--tag $$(DOCKER_REGISTRY)/$$(DOCKER_IMAGE_PREFIX)/$1:$$(tag) )
ifeq ($$(PUBLISH), true)
	@docker push $$(DOCKER_REGISTRY)/$$(DOCKER_IMAGE_PREFIX)/$$($$($1_IMG_TGT_NAME)_IMG_TITLE):$$($$($1_IMG_TGT_NAME)_IMG_TAG)
endif

images: $$($1_IMG_TGT_NAME)

$$(warning Adding dynamic target $$($1_IMG_TGT_NAME)-publish)
.PHONY: $$($1_IMG_TGT_NAME)-publish
$$($1_IMG_TGT_NAME)-publish: $$($1_IMG_TGT_NAME)
	@docker push $$(DOCKER_REGISTRY)/$$(DOCKER_IMAGE_PREFIX)/$1:$$(DOCKER_IMAGE_TAG)

images-publish: $$($1_IMG_TGT_NAME)-publish
endef
map_image_defaults = $(eval $(call map_image_defaults_tmpl,$1))
# go through components and render the template
$(foreach img,$(IMGS),$(call map_image_defaults,$(img)))

.PHONY: test
test: lint
test: cover
test: check-copyright

.PHONY: unit-tests
unit-tests: TESTFLAGS += -race -v
unit-tests:
	@echo "Performing unit test step..."
	@go test -run $(TESTS) $(PKG) $(TESTFLAGS) $(COVER_FLAGS)
	@echo "All unit tests passed"

.PHONY: cover
cover: COVER_FLAGS = -covermode=atomic -coverprofile=fullcover.out
cover: unit-tests
	@grep -vE "$(COVER_EXCLUDE)" fullcover.out > $(COVER_PROFILE)
	@./tools/coverage_check $(COVER_PROFILE)

.PHONY: fmt
fmt: lint

.PHONY: lint
lint: tidy
lint: $(LINTER)
	@echo "Performing linting step..."
	@./tools/whitespace_linter
	@./$(LINTER) run --config $(LINTER_CONFIG)
	@echo "Linting completed successfully"

.PHONY: tidy
tidy:
	@echo "Checking that go.mod is up to date..."
	@./tools/gomod_check
	@echo "go.mod is up to date"

.PHONY: golint
golint:
	@./tools/golint

.PHONY: print-docker-image-tag
print-docker-image-tag:
	@echo "$(DOCKER_IMAGE)"

.PHONY: docker-image-test-suite
docker-image-test-suite: docker-image_MAKETGT = "cover update-golden generate check-git-diff"
docker-image-test-suite: docker-image_DOCKERTGT = builder
docker-image-test-suite: docker-image

.PHONY: docker-image-unit-tests
docker-image-unit-tests: docker-image_MAKETGT = cover
docker-image-unit-tests: docker-image_DOCKERTGT = builder
docker-image-unit-tests: docker-image

.PHONY: docker-image-lint
docker-image-lint: docker-image_MAKETGT = "lint check-copyright"
docker-image-lint: docker-image_DOCKERTGT = builder
docker-image-lint: docker-image

.PHONY: docker-image-golint
docker-image-golint: docker-image_MAKETGT = golint
docker-image-golint: docker-image_DOCKERTGT = builder
docker-image-golint: docker-image

.PHONY: docker-image-check-manifests
docker-image-check-manifests: docker-image_MAKETGT = "generate manifests check-git-diff"
docker-image-check-manifests: docker-image_DOCKERTGT = builder
docker-image-check-manifests: docker-image

.PHONY: clean
clean:
	@rm -fr $(BIN_DIR)
	@rm -fr $(COVER_PROFILE)

.PHONY: docs
docs:
	tox

.PHONY: godoc
godoc:
	@go install golang.org/x/tools/cmd/godoc
	@echo "Follow this link to package documentation: http://localhost:${GD_PORT}/pkg/opendev.org/airship/airshipctl/"
	@godoc -http=":${GD_PORT}"

.PHONY: cli-docs
cli-docs:
	@echo "Generating CLI documentation..."
	@go run $(DOCS_DIR)/tools/generate_cli_docs.go
	@echo "CLI documentation generated"

.PHONY: releasenotes
releasenotes:
	@echo "TODO"

$(TOOLBINDIR):
	mkdir -p $(TOOLBINDIR)

$(LINTER): $(TOOLBINDIR)
	./tools/install_linter

.PHONY: update-golden
update-golden: delete-golden
update-golden: TESTFLAGS += -update
update-golden: PKG = opendev.org/airship/airshipctl/cmd/...
update-golden: unit-tests
update-golden: cli-docs

# The delete-golden target is a utility for update-golden
.PHONY: delete-golden
delete-golden:
	@find . -type f -name "*.golden" -delete

# Used by gates after unit-tests and update-golden targets to ensure no files are deleted.
.PHONY: check-git-diff
check-git-diff:
	@./tools/git_diff_check

# add-copyright is a utility to add copyright header to missing files
.PHONY: add-copyright
add-copyright:
	@./tools/add_license.sh

# check-copyright is a utility to check if copyright header is present on all files
.PHONY: check-copyright
check-copyright:
	@./tools/check_copyright

# Validate YAMLs for all sites
.PHONY: validate-docs
validate-docs:
	@./tools/validate_docs

# Validate all URL references in documentation work
.PHONY: dead-link-linter
dead-link-linter:
	@./tools/dead-link-linter

# Generate code
generate: controller-gen
	$(CONTROLLER_GEN) object:headerFile="tools/license_go.txt" paths="./..."

# find or download controller-gen
# download controller-gen if necessary
controller-gen:
ifeq (, $(shell which controller-gen))
	@{ \
	set -e ;\
	CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
	cd $$CONTROLLER_GEN_TMP_DIR ;\
	go mod init tmp ;\
	go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.2 ;\
	rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
	}
CONTROLLER_GEN=$(GOBIN)/controller-gen
else
CONTROLLER_GEN=$(shell which controller-gen)
endif

# Generate manifests e.g. CRD, RBAC etc.
manifests: controller-gen
	find manifests/function/airshipctl-schemas/ -type f -not -name 'kustomization.yaml' -delete && $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=manifests/function/airshipctl-schemas