#
# Copyright (c) 2023 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
# or implied. See the License for the specific language governing permissions and limitations under
# the License.
#

# Additional flags to pass to the `ginkgo` command.
ginkgo_flags:=

# VERSION defines the project version for the bundle.
# Update this value when you upgrade the version of your project.
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
VERSION ?= 5.0.0

PACKAGE_NAME ?= oran-o2ims

CHANNEL ?= alpha

# BASHATE_VERSION defines the bashate version to download from GitHub releases.
BASHATE_VERSION ?= 2.1.1

# CONTROLLER_GEN_VERSION defines the controller-gen version to download from go modules.
CONTROLLER_GEN_VERSION ?= v0.20.1

# GOLANGCI_LINT_VERSION defines the golangci-lint version to download from GitHub releases.
GOLANGCI_LINT_VERSION ?= v2.12.2

# KUSTOMIZE_VERSION defines the kustomize version to download from go modules.
KUSTOMIZE_VERSION ?= v5@v5.7.1

# MOCK_GEN_VERSION defines the mockgen version to download from go modules.
MOCK_GEN_VERSION ?= v0.3.0

# OPERATOR_SDK_VERSION defines the operator-sdk version to download from GitHub releases.
OPERATOR_SDK_VERSION ?= 1.41.1

# OPM_VERSION defines the opm version to download from GitHub releases.
OPM_VERSION ?= v1.52.0

# SHELLCHECK_VERSION defines the shellcheck version to download from GitHub releases.
SHELLCHECK_VERSION ?= v0.11.0

# YAMLLINT_VERSION defines the yamllint version to download from GitHub releases.
YAMLLINT_VERSION ?= 1.37.1

# YQ_VERSION defines the yq version to download from GitHub releases.
YQ_VERSION ?= v4.45.4

# Konflux catalog configuration
PACKAGE_NAME_KONFLUX = o-cloud-manager
CATALOG_TEMPLATE_KONFLUX_INPUT = .konflux/catalog/catalog-template.in.yaml
CATALOG_TEMPLATE_KONFLUX_OUTPUT = .konflux/catalog/catalog-template.out.yaml
CATALOG_OUTPUT_FORMAT = json
CATALOG_KONFLUX = .konflux/catalog/$(PACKAGE_NAME_KONFLUX)/catalog.$(CATALOG_OUTPUT_FORMAT)

# Konflux bundle image configuration
BUNDLE_NAME_SUFFIX = bundle-5-0
PRODUCTION_BUNDLE_NAME = operator-bundle

# The directory of the current makefile
PROJECT_DIR := $(shell dirname $(abspath $(firstword $(MAKEFILE_LIST))))

# You can use podman or docker as a container engine. Notice that there are some options that might be only valid for one of them.
ENGINE ?= docker

ifeq (${DEBUG}, yes)
	DOCKER_TARGET = debug
	GOBUILD_GCFLAGS = all=-N -l
	KUSTOMIZE_OVERLAY = debug
else
	DOCKER_TARGET = production
	GOBUILD_GCFLAGS = ""
	KUSTOMIZE_OVERLAY = default
endif

# DEFAULT_CHANNEL defines the default channel used in the bundle.
# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
# To re-generate a bundle for any other default channel without changing the default setup, you can:
# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
ifneq ($(origin DEFAULT_CHANNEL), undefined)
BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
endif
BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)

# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
# This variable is used to construct full image tags for bundle and catalog images.
#
# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
# openshift.io/oran-o2ims-bundle:$VERSION and openshift.io/oran-o2ims-catalog:$VERSION.
IMAGE_NAME ?= oran-o2ims-operator
IMAGE_TAG_BASE ?= quay.io/openshift-kni/${IMAGE_NAME}

# MUST_GATHER_IMG defines the image:tag used for the must-gather image.
MUST_GATHER_IMG ?= $(IMAGE_TAG_BASE)-must-gather:$(VERSION)

# BUNDLE_IMG defines the image:tag used for the bundle.
# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:$(VERSION)

# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command.
# --extra-service-accounts is derived from config/rbac/service_accounts_services.yaml
# so that adding a new service SA automatically includes it in the CSV permissions.
# The name prefix must match config/default/kustomization.yaml's namePrefix.
KUSTOMIZE_NAME_PREFIX = $(shell grep '^namePrefix:' config/default/kustomization.yaml | awk '{print $$2}')
EXTRA_SERVICE_ACCOUNTS = $(shell grep '^  name:' config/rbac/service_accounts_services.yaml | \
	sed 's/  name: /$(KUSTOMIZE_NAME_PREFIX)/' | paste -sd, -)
BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) \
	--extra-service-accounts=$(EXTRA_SERVICE_ACCOUNTS)

# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests
# You can enable this value if you would like to use SHA Based Digests
# To enable set flag to true
USE_IMAGE_DIGESTS ?= false
ifeq ($(USE_IMAGE_DIGESTS), true)
        BUNDLE_GEN_FLAGS += --use-image-digests
endif

# Image URL to use all building/pushing image targets
IMG ?= $(IMAGE_TAG_BASE):$(VERSION)

# ENVTEST_K8S_VERSION and ENVTEST_VERSION are derived from go.mod to stay in sync with dependencies.
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')

# E2E_TEST_TIMEOUT is the maximum time the e2e test suite is allowed to run.
E2E_TEST_TIMEOUT ?= 30m

# OCLOUD_MANAGER_NAMESPACE refers to the namespace of the O-Cloud Manager
OCLOUD_MANAGER_NAMESPACE ?= oran-o2ims

# 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

# IMAGE_PULL_POLICY sets the value that is patched into the CSV for the manager container imagePullPolicy.
# If the IMAGE_TAG_BASE is a user repo, the default value is updated to imagePullPolicy=Always.
ifneq (,$(findstring openshift-kni,$(IMAGE_TAG_BASE)))
IMAGE_PULL_POLICY ?= IfNotPresent
else
IMAGE_PULL_POLICY ?= Always
endif

# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker

# Set SKIP_SUBMODULE_SYNC to yes to avoid running the `git submodule update` command in update_deps.sh
export SKIP_SUBMODULE_SYNC ?= no

# 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

# Get the directory of the current makefile
# Trim any trailing slash from the directory path as we will add if when necessary later
PROJECT_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))

## Location to install dependencies to
# If you are setting this externally then you must use an aboslute path
LOCALBIN ?= $(PROJECT_DIR)/bin
$(LOCALBIN):
	mkdir -p $(LOCALBIN)

# This allows all tools in the LOCALBIN folder, ie:opm,yq ... To be used by targets containing scripts
# Prefer binaries in the local bin directory over system binaries.
export PATH := $(abspath $(LOCALBIN)):$(PATH)

# Source directories
SOURCE_DIRS := $(shell find . -maxdepth 1 -type d ! -name "vendor" ! -name "." ! -name ".*")

.PHONY: all
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 command 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

.PHONY: help
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

.PHONY: manifests
manifests: deps-update controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
	$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases

.PHONY: _generate
_generate: deps-update controller-gen
	$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

.PHONY: generate
generate: _generate fmt ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.

##@ Build
.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
	go build -gcflags "${GOBUILD_GCFLAGS}"

# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/

PLATFORM ?= linux/amd64
.PHONY: docker-build
docker-build: manifests generate fmt vet ## Build docker image with the manager.
	$(CONTAINER_TOOL) build -t ${IMG} -f Dockerfile --platform=$(PLATFORM) --target ${DOCKER_TARGET} --build-arg "GOBUILD_GCFLAGS=${GOBUILD_GCFLAGS}" .

.PHONY: docker-push
docker-push: docker-build ## Push docker image with the manager.
	$(CONTAINER_TOOL) push ${IMG}

.PHONY: must-gather-build
must-gather-build: ## Build the must-gather image.
	$(CONTAINER_TOOL) build -t $(MUST_GATHER_IMG) -f Dockerfile.must-gather .

.PHONY: must-gather-push
must-gather-push: must-gather-build ## Push the must-gather image.
	$(CONTAINER_TOOL) push $(MUST_GATHER_IMG)

# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for the manager for cross-platform support
	# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
	sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
	- $(CONTAINER_TOOL) buildx create --name project-v3-builder
	$(CONTAINER_TOOL) buildx use project-v3-builder
	- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
	- $(CONTAINER_TOOL) buildx rm project-v3-builder
	rm Dockerfile.cross

##@ Deployment

ifndef ignore-not-found
  ignore-not-found = false
endif

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

.PHONY: uninstall
uninstall: manifests kustomize kubectl ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
	$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -

.PHONY: deploy
deploy: install manifests kustomize kubectl ## Deploy controller to the K8s cluster specified in ~/.kube/config.
	@$(KUBECTL) create configmap env-config \
		--from-literal=imagePullPolicy=$(IMAGE_PULL_POLICY) \
		--dry-run=client -o yaml > config/manager/env-config.yaml
	cd config/manager \
		&& $(KUSTOMIZE) edit set image controller=${IMG}
	$(KUSTOMIZE) build config/$(KUSTOMIZE_OVERLAY) | $(KUBECTL) apply -f -

.PHONY: undeploy
undeploy: kustomize kubectl ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
	$(KUSTOMIZE) build config/$(KUSTOMIZE_OVERLAY) | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -

##@ Build Dependencies

## oran-binary
BINARY_NAME := oran-o2ims

## Tool Binaries
BASHATE ?= $(LOCALBIN)/bashate
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
KUBECTL ?= $(LOCALBIN)/kubectl
KUSTOMIZE ?= $(LOCALBIN)/kustomize
MOCK_GEN ?= $(LOCALBIN)/mockgen
OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk
OPM ?= $(LOCALBIN)/opm
GOVULNCHECK ?= $(LOCALBIN)/govulncheck
SHELLCHECK ?= $(LOCALBIN)/shellcheck
YAMLLINT ?= $(LOCALBIN)/yamllint
YQ ?= $(LOCALBIN)/yq

## Download go tools
.PHONY: kubectl
.PHONY: $(KUBECTL)
kubectl: $(KUBECTL) ## Use envtest to download kubectl
$(KUBECTL): $(LOCALBIN) envtest
	if [ ! -f $(KUBECTL) ] || ! $(KUBECTL) version 2>/dev/null | grep -q "Client Version: v$(ENVTEST_K8S_VERSION)$$"; then \
		KUBEBUILDER_ASSETS=$$($(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path); \
		ln -sf $${KUBEBUILDER_ASSETS}/kubectl $(KUBECTL); \
	fi

.PHONY: kustomize
.PHONY: $(KUSTOMIZE)
kustomize: sync-git-submodules $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
$(KUSTOMIZE): $(LOCALBIN)
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-go-tool \
		TOOL_NAME=kustomize \
		GO_MODULE=sigs.k8s.io/kustomize/kustomize/$(KUSTOMIZE_VERSION) \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN)

.PHONY: controller-gen
.PHONY: $(CONTROLLER_GEN)
controller-gen: sync-git-submodules $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be removed before downloading.
$(CONTROLLER_GEN): $(LOCALBIN)
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-go-tool \
		TOOL_NAME=controller-gen \
		GO_MODULE=sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_GEN_VERSION) \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN)

.PHONY: envtest
.PHONY: $(ENVTEST)
envtest: sync-git-submodules $(ENVTEST) ## Download envtest-setup locally if necessary. If wrong version is installed, it will be removed before downloading.
$(ENVTEST): $(LOCALBIN)
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-go-tool \
		TOOL_NAME=setup-envtest \
		GO_MODULE=sigs.k8s.io/controller-runtime/tools/setup-envtest@$(ENVTEST_VERSION) \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN)

.PHONY: mock-gen
.PHONY: $(MOCK_GEN)
mock-gen: sync-git-submodules $(MOCK_GEN) ## Download mockgen locally if necessary. If wrong version is installed, it will be removed before downloading.
$(MOCK_GEN): $(LOCALBIN)
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-go-tool \
		TOOL_NAME=mockgen \
		GO_MODULE=go.uber.org/mock/mockgen@$(MOCK_GEN_VERSION) \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN)

# Determine sed flags based on the operating system
ifeq ($(shell uname -s),Linux)
SED_FLAGS := -i
else
SED_FLAGS := -i ''
endif

.PHONY: bundle
bundle: operator-sdk manifests kustomize kubectl ## Generate bundle manifests and metadata, then validate generated files.
	$(OPERATOR_SDK) generate kustomize manifests --apis-dir api/ -q
	@$(KUBECTL) create configmap env-config \
		--from-literal=imagePullPolicy=$(IMAGE_PULL_POLICY) \
		--dry-run=client -o yaml > config/manager/env-config.yaml
	cd config/manager \
		&& $(KUSTOMIZE) edit set image controller=$(IMG)
	$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
	@rm bundle/manifests/oran-o2ims-env-config_v1_configmap.yaml ## Clean up the temporary file for bundle validate
	@## Remove standalone RBAC manifests for service SAs — permissions are embedded in the CSV
	@## via --extra-service-accounts so OLM handles namespace substitution correctly.
	@for sa in $(shell grep '^  name:' config/rbac/service_accounts_services.yaml | sed 's/  name: //'); do \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}_rbac.authorization.k8s.io_v1_clusterrole.yaml; \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}_rbac.authorization.k8s.io_v1_clusterrolebinding.yaml; \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}_rbac.authorization.k8s.io_v1_role.yaml; \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}_rbac.authorization.k8s.io_v1_rolebinding.yaml; \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}_v1_serviceaccount.yaml; \
		rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)$${sa}-subject-access-reviewer-binding_rbac.authorization.k8s.io_v1_clusterrolebinding.yaml; \
	done
	@rm -f bundle/manifests/$(KUSTOMIZE_NAME_PREFIX)subject-access-reviewer_rbac.authorization.k8s.io_v1_clusterrole.yaml
	$(OPERATOR_SDK) bundle validate ./bundle
	sed $(SED_FLAGS) -e '/^[[:space:]]*createdAt:/d' bundle/manifests/oran-o2ims.clusterserviceversion.yaml

.PHONY: bundle-build
bundle-build: bundle docker-push ## Build the bundle image.
	$(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) .

.PHONY: bundle-push
bundle-push: bundle-build ## Push the bundle image.
	$(CONTAINER_TOOL) push $(BUNDLE_IMG)

.PHONY: bundle-check
bundle-check: bundle
	$(PROJECT_DIR)/hack/check-git-tree.sh

.PHONY: bundle-run
bundle-run: # Install bundle on cluster using operator sdk.
	oc create ns $(OCLOUD_MANAGER_NAMESPACE)
	$(OPERATOR_SDK) --security-context-config restricted -n $(OCLOUD_MANAGER_NAMESPACE) run bundle $(BUNDLE_IMG)

.PHONY: bundle-upgrade
bundle-upgrade: # Upgrade bundle on cluster using operator sdk.
	$(OPERATOR_SDK) run bundle-upgrade $(BUNDLE_IMG)

.PHONY: bundle-clean
bundle-clean: # Uninstall bundle on cluster using operator sdk.
	$(OPERATOR_SDK) cleanup $(PACKAGE_NAME) -n $(OCLOUD_MANAGER_NAMESPACE)
	oc delete ns $(OCLOUD_MANAGER_NAMESPACE)

# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
# These images MUST exist in a registry and be pull-able.
BUNDLE_IMGS ?= $(BUNDLE_IMG)

# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)

# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
ifneq ($(origin CATALOG_BASE_IMG), undefined)
FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
endif

# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
.PHONY: catalog
catalog: opm ## Build a catalog.
	@mkdir -p catalog
	$(PROJECT_DIR)/hack/generate-catalog-index.sh --opm $(OPM) --name $(PACKAGE_NAME) --channel $(CHANNEL) --version $(VERSION)
	$(OPM) render --output=yaml $(BUNDLE_IMG) > catalog/$(PACKAGE_NAME).yaml
	$(OPM) validate catalog

.PHONY: catalog-build
catalog-build: catalog ## Build a catalog image.
	$(CONTAINER_TOOL) build -f catalog.Dockerfile -t $(CATALOG_IMG) .

# Push the catalog image.
.PHONY: catalog-push
catalog-push: ## Push a catalog image.
	$(CONTAINER_TOOL) push $(CATALOG_IMG)

# Deploy from catalog image.
.PHONY: catalog-deploy
catalog-deploy: ## Deploy from catalog image.
	$(PROJECT_DIR)/hack/generate-catalog-deploy.sh \
		--package $(PACKAGE_NAME) \
		--namespace $(OCLOUD_MANAGER_NAMESPACE) \
		--catalog-image $(CATALOG_IMG) \
		--channel $(CHANNEL) \
		--install-mode AllNamespaces \
		| oc create -f -

# Undeploy from catalog image.
.PHONY: catalog-undeploy
catalog-undeploy: ## Undeploy from catalog image.
	$(PROJECT_DIR)/hack/catalog-undeploy.sh --package $(PACKAGE_NAME) --namespace $(OCLOUD_MANAGER_NAMESPACE) --crd-search clcm.openshift.io

##@ Tools and Linting

.PHONY: lint
lint: bashate golangci-lint shellcheck yamllint

.PHONY: tools
tools: opm operator-sdk yq

.PHONY: golangci-lint-download
golangci-lint-download: $(LOCALBIN) sync-git-submodules $(GOLANGCI_LINT) ## If wrong version is installed, it will be removed before downloading.

$(GOLANGCI_LINT): $(LOCALBIN)
	@echo "Downloading golangci-lint..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-go-tool \
		TOOL_NAME=golangci-lint \
		GO_MODULE=github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN)
	@echo "Golangci-lint downloaded successfully."

.PHONY: golangci-lint
.PHONY: $(GOLANGCI_LINT)
golangci-lint: golangci-lint-download ## Run golangci-lint against code.
	@echo "Running golangci-lint on repository go files..."
	$(GOLANGCI_LINT) --version
	$(GOLANGCI_LINT) run -v
	@echo "Golangci-lint linting completed successfully."

.PHONY: bashate-download
bashate-download: sync-git-submodules $(BASHATE) ## Download bashate locally if necessary. If wrong version is installed, it will be removed before downloading.

$(BASHATE): $(LOCALBIN)
	@echo "Downloading bashate..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-bashate \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_BASHATE_VERSION=$(BASHATE_VERSION)
	@echo "Bashate downloaded successfully."

.PHONY: bashate
.PHONY: $(BASHATE)
bashate: bashate-download ## Run bashate against bash files in the repository.
	@echo "Running bashate on repository bash files..."
	find $(PROJECT_DIR) -name '*.sh' \
		-not -path '$(PROJECT_DIR)/vendor/*' \
		-not -path '$(PROJECT_DIR)/*/vendor/*' \
		-not -path '$(PROJECT_DIR)/git/*' \
		-not -path '$(LOCALBIN)/*' \
		-not -path '$(PROJECT_DIR)/testbin/*' \
		-not -path '$(PROJECT_DIR)/telco5g-konflux/*' \
		-print0 \
		| xargs -0 --no-run-if-empty $(BASHATE) -v -e 'E*' -i E006
	@echo "Bashate linting completed successfully."

.PHONY: operator-sdk
.PHONY: $(OPERATOR_SDK)
operator-sdk: sync-git-submodules $(OPERATOR_SDK) ## Download operator-sdk locally if necessary. If wrong version is installed, it will be removed before downloading.
$(OPERATOR_SDK): $(LOCALBIN)
	@$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-operator-sdk \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_OPERATOR_SDK_VERSION=$(OPERATOR_SDK_VERSION)
	@echo "Operator sdk downloaded successfully."

.PHONY: opm
.PHONY: $(OPM)
opm: sync-git-submodules $(OPM) ## Download opm locally if necessary. If wrong version is installed, it will be removed before downloading.
$(OPM): $(LOCALBIN)
	@$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-opm \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_OPM_VERSION=$(OPM_VERSION)
	$(OPM) version
	@echo "Opm downloaded successfully."

.PHONY: shellcheck-download
shellcheck-download: sync-git-submodules $(SHELLCHECK) ## Download shellcheck locally if necessary. If wrong version is installed, it will be removed before downloading.

$(SHELLCHECK): $(LOCALBIN)
	@echo "Downloading shellcheck..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-shellcheck \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_SHELLCHECK_VERSION=$(SHELLCHECK_VERSION)
	@echo "Shellcheck downloaded successfully."
	$(SHELLCHECK) -V

.PHONY: shellcheck
.PHONY: $(SHELLCHECK)
shellcheck: shellcheck-download ## Run shellcheck against bash scripts in the repository.
	@echo "Running shellcheck on repository bash files..."
	find $(PROJECT_DIR) -name '*.sh' \
		-not -path '$(PROJECT_DIR)/vendor/*' \
		-not -path '$(PROJECT_DIR)/*/vendor/*' \
		-not -path '$(PROJECT_DIR)/git/*' \
		-not -path '$(LOCALBIN)/*' \
		-not -path '$(PROJECT_DIR)/testbin/*' \
		-not -path '$(PROJECT_DIR)/telco5g-konflux/*' \
		-print0 \
		| xargs -0 --no-run-if-empty $(SHELLCHECK) -x
	@echo "Shellcheck linting completed successfully."

.PHONY: yamllint-download
yamllint-download: sync-git-submodules $(YAMLLINT) ## Download yamllint locally if necessary. If wrong version is installed, it will be removed before downloading.

$(YAMLLINT): $(LOCALBIN)
	@echo "Downloading yamllint..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download \
		download-yamllint \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_YAMLLINT_VERSION=$(YAMLLINT_VERSION)
	@echo "Yamllint downloaded successfully."

.PHONY: yamllint
.PHONY: $(YAMLLINT)
yamllint: yamllint-download ## Lint YAML files in the repository
	@echo "Running yamllint on repository YAML files..."
	find $(PROJECT_DIR) -name "*.yaml" -o -name "*.yml" \
		-not -path '$(PROJECT_DIR)/vendor/*' \
		-not -path '$(PROJECT_DIR)/*/vendor/*' \
		-not -path '$(PROJECT_DIR)/git/*' \
		-not -path '$(LOCALBIN)/*' \
		-not -path '$(PROJECT_DIR)/testbin/*' \
		-not -path '$(PROJECT_DIR)/telco5g-konflux/*' \
		-print0 \
		| xargs -0 --no-run-if-empty $(YAMLLINT) --strict -c .yamllint.yaml
	@echo "YAML linting completed successfully."

.PHONY: test-kustomize
test-kustomize: ## Validate that all kustomization.yaml files can build successfully
	@echo "Validating kustomization files..."
	$(PROJECT_DIR)/hack/test-kustomize.sh

.PHONY: yq
.PHONY: $(YQ)
yq: sync-git-submodules $(YQ) ## Download yq locally if necessary. If wrong version is installed, it will be removed before downloading.
$(YQ): $(LOCALBIN)
	@echo "Downloading yq..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/download download-yq \
		DOWNLOAD_INSTALL_DIR=$(LOCALBIN) \
		DOWNLOAD_YQ_VERSION=$(YQ_VERSION)
	$(YQ) --version
	@echo "Yq downloaded successfully."

.PHONY: yq-sort-and-format
yq-sort-and-format: yq ## Sort keys/reformat all yaml files
	@echo "Sorting keys and reformatting YAML files..."
	@find . -name "*.yaml" -o -name "*.yml" | grep -v -E "(telco5g-konflux/|target/|vendor/|$(LOCALBIN)/|\.git/)" | while read file; do \
		echo "Processing $$file..."; \
		$(YQ) -i '.. |= sort_keys(.)' "$$file"; \
	done
	@echo "YAML sorting and formatting completed successfully."

# GOVULNCHECK_VERSION is parsed from the GitHub Actions workflow to maintain a single source of truth.
GOVULNCHECK_VERSION := $(shell grep 'GOVULNCHECK_VERSION:' $(PROJECT_DIR)/.github/workflows/govulncheck.yml | head -1 | awk '{print $$2}')

.PHONY: govulncheck-download
govulncheck-download: $(GOVULNCHECK) ## Download govulncheck locally if necessary.

$(GOVULNCHECK): $(LOCALBIN)
	@echo "Downloading govulncheck $(GOVULNCHECK_VERSION)..."
	GOBIN=$(LOCALBIN) go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION)
	@echo "Govulncheck downloaded successfully."

.PHONY: govulncheck
govulncheck: govulncheck-download ## Run govulncheck vulnerability scanner against code.
	$(GOVULNCHECK) ./...

##@ Binary
.PHONY: binary
binary: $(LOCALBIN)
	go build -o $(LOCALBIN)/$(BINARY_NAME) -mod=vendor

.PHONY: crd-watcher
crd-watcher: $(LOCALBIN) ## Build the CRD watcher binary.
	go build -o $(LOCALBIN)/crd-watcher -mod=vendor ./dev-tools/crd-watcher

.PHONY: generate
go-generate:
	go generate ./...
	@for file in *.gen*; do \
		if ! git diff --exit-code -- $$file; then \
			echo "Error: $$file is stale. Please commit the updated file."; \
			exit 1; \
		fi \
	done
	@echo "All generated files are up-to-date."

COVERAGE_DIR := $(PROJECT_DIR)/coverage
COVERAGE_UNIT := $(COVERAGE_DIR)/unit.out
COVERAGE_ENVTEST := $(COVERAGE_DIR)/envtest.out
COVERAGE_MERGED := $(COVERAGE_DIR)/merged.out

.PHONY: test tests
test tests:
	@echo "Run ginkgo excluding envtest tests"
	@mkdir -p $(COVERAGE_DIR)
	ginkgo run -r --label-filter="!envtest" --coverprofile=unit.out --output-dir=$(COVERAGE_DIR) ./internal ./api $(ginkgo_flags)

.PHONY: test-e2e
test-e2e: envtest kubectl
ifeq ($(shell uname -s),Linux)
	@chmod -R u+w $(LOCALBIN)
endif
	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -i --bin-dir $(LOCALBIN) -p path)" go test ./test/e2e/ -v -timeout $(E2E_TEST_TIMEOUT) ginkgo.v

.PHONY: test-envtest
test-envtest: envtest
ifeq ($(shell uname -s),Linux)
	@chmod -R u+w $(LOCALBIN)
endif
	@echo "Run ginkgo envtest tests"
	@mkdir -p $(COVERAGE_DIR)
	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" ginkgo run -r --label-filter="envtest" --coverprofile=envtest.out --output-dir=$(COVERAGE_DIR) ./internal $(ginkgo_flags)

.PHONY: test-crd-watcher
test-crd-watcher:
	@echo "Run crd-watcher unit tests"
	cd dev-tools/crd-watcher && go test -v $(ginkgo_flags)

.PHONY: e2e-outline
e2e-outline: ## Generate e2e test inventory document from Ginkgo outlines
	hack/generate-e2e-outline.sh

.PHONY: test-coverage-check
test-coverage-check: ## Check code coverage against per-package thresholds
	@echo "Merging coverage profiles"
	@mkdir -p $(COVERAGE_DIR)
	@# Merge unit and envtest profiles if both exist; use whichever is available
	@if [ -f $(COVERAGE_UNIT) ] && [ -f $(COVERAGE_ENVTEST) ]; then \
		head -1 $(COVERAGE_UNIT) > $(COVERAGE_MERGED); \
		tail -n +2 $(COVERAGE_UNIT) >> $(COVERAGE_MERGED); \
		tail -n +2 $(COVERAGE_ENVTEST) >> $(COVERAGE_MERGED); \
	elif [ -f $(COVERAGE_UNIT) ]; then \
		cp $(COVERAGE_UNIT) $(COVERAGE_MERGED); \
	elif [ -f $(COVERAGE_ENVTEST) ]; then \
		cp $(COVERAGE_ENVTEST) $(COVERAGE_MERGED); \
	else \
		echo "ERROR: No coverage profiles found. Run 'make test' and/or 'make test-envtest' first."; \
		exit 1; \
	fi
	@echo "Checking coverage thresholds"
	@$(PROJECT_DIR)/hack/check-coverage.sh $(COVERAGE_MERGED)

.PHONY: fmt
fmt:
	@echo "Run fmt"
	gofmt -s -l -w main.go $(SOURCE_DIRS)

.PHONY: vet
vet: ## Run go vet against code.
	go vet ./...

.PHONY: deps-update
deps-update: mock-gen golangci-lint-download
	@echo "Update dependencies"
	$(PROJECT_DIR)/hack/update_deps.sh
	$(PROJECT_DIR)/hack/install_test_deps.sh

# NOTE: `bundle-check` should be the last job in the list for `ci-job`
# test-e2e is run separately as it takes ~20 minutes and does not contribute to coverage
.PHONY: ci-job
ci-job: deps-update go-generate generate fmt vet lint test test-envtest test-crd-watcher test-coverage-check e2e-outline bundle-check

.PHONY: clean
clean:
	-rm $(LOCALBIN)/$(BINARY_NAME)

SCORECARD_VERBOSE ?= false
SCORECARD_STRICT ?= true

.PHONY: scorecard-test
scorecard-test: operator-sdk
	@test -n "$(KUBECONFIG)" || (echo "The environment variable KUBECONFIG must not empty" && false)
	oc create ns $(OCLOUD_MANAGER_NAMESPACE) --dry-run=client -o yaml | oc apply -f -
ifeq ($(SCORECARD_VERBOSE),true)
	$(OPERATOR_SDK) scorecard bundle -o text --kubeconfig "$(KUBECONFIG)" -n $(OCLOUD_MANAGER_NAMESPACE) --pod-security=restricted --wait-time=120s
else
	@$(OPERATOR_SDK) scorecard bundle -o json --kubeconfig "$(KUBECONFIG)" -n $(OCLOUD_MANAGER_NAMESPACE) --pod-security=restricted --wait-time=120s | \
		python3 $(PROJECT_DIR)/hack/scorecard-summary.py $(if $(filter true,$(SCORECARD_STRICT)),--strict)
endif

# markdownlint rules, following: https://github.com/openshift/enhancements/blob/master/Makefile
.PHONY: markdownlint-image
markdownlint-image:  ## Build local container markdownlint-image
	$(CONTAINER_TOOL) image build -f $(PROJECT_DIR)/hack/Dockerfile.markdownlint --tag $(IMAGE_NAME)-markdownlint:latest $(PROJECT_DIR)/hack

.PHONY: markdownlint-image-clean
markdownlint-image-clean:  ## Remove locally cached markdownlint-image
	$(CONTAINER_TOOL) image rm $(IMAGE_NAME)-markdownlint:latest

markdownlint: markdownlint-image  ## run the markdown linter
	$(CONTAINER_TOOL) run \
		--rm=true \
		--env RUN_LOCAL=true \
		--env VALIDATE_MARKDOWN=true \
		--env PULL_BASE_SHA=$(PULL_BASE_SHA) \
		-v $$(pwd):/workdir:Z \
		$(IMAGE_NAME)-markdownlint:latest

##@ O-RAN Postgres DB

.PHONY: connect-postgres
connect-postgres: ##Connect to O-RAN postgres
	oc wait --for=condition=Ready pod -l app=postgres-server -n $(OCLOUD_MANAGER_NAMESPACE) --timeout=30s
	@echo "Starting port-forward in background on port 5432:5432 to postgres-server in namespace $(OCLOUD_MANAGER_NAMESPACE)"
	nohup oc port-forward --address localhost svc/postgres-server 5432:5432 -n $(OCLOUD_MANAGER_NAMESPACE) > pgproxy.log 2>&1 &

.PHONY: connect-cluster-server
connect-cluster-server: ##Connect to resource server svc
	@echo "Starting port-forward in background on port 8001:8000 to cluster server svc in namespace $(OCLOUD_MANAGER_NAMESPACE)"
	nohup oc port-forward --address localhost svc/cluster-server 8001:8000 -n $(OCLOUD_MANAGER_NAMESPACE) > pgproxy_resource.log 2>&1 &

##@ Konflux

.PHONY: sync-git-submodules
sync-git-submodules:
	@echo "Checking git submodules"
	@if [ "$(SKIP_SUBMODULE_SYNC)" != "yes" ]; then \
		echo "Syncing git submodules"; \
		git submodule sync --recursive; \
		git submodule update --init --recursive; \
	else \
		echo "Skipping submodule sync"; \
	fi

.PHONY: konflux-validate-catalog-template-bundle
konflux-validate-catalog-template-bundle: sync-git-submodules yq operator-sdk ## validate the last bundle entry on the catalog template file
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/catalog konflux-validate-catalog-template-bundle \
		CATALOG_TEMPLATE_KONFLUX_INPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_INPUT) \
		CATALOG_TEMPLATE_KONFLUX_OUTPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_OUTPUT) \
		YQ=$(YQ) \
		OPERATOR_SDK=$(OPERATOR_SDK) \
		ENGINE=$(ENGINE)

.PHONY: konflux-validate-catalog
konflux-validate-catalog: sync-git-submodules opm ## validate the current catalog file
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/catalog konflux-validate-catalog \
		CATALOG_KONFLUX=$(PROJECT_DIR)/$(CATALOG_KONFLUX) \
		OPM=$(OPM)

.PHONY: konflux-generate-catalog
konflux-generate-catalog: sync-git-submodules yq opm ## generate a quay.io catalog
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/catalog konflux-generate-catalog \
		CATALOG_TEMPLATE_KONFLUX_INPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_INPUT) \
		CATALOG_TEMPLATE_KONFLUX_OUTPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_OUTPUT) \
		CATALOG_KONFLUX=$(PROJECT_DIR)/$(CATALOG_KONFLUX) \
		CATALOG_OUTPUT_FORMAT=$(CATALOG_OUTPUT_FORMAT) \
		PACKAGE_NAME_KONFLUX=$(PACKAGE_NAME_KONFLUX) \
		BUNDLE_BUILDS_FILE=$(PROJECT_DIR)/.konflux/catalog/bundle.builds.in.yaml \
		OPM=$(OPM) \
		YQ=$(YQ)
	$(MAKE) konflux-validate-catalog

.PHONY: konflux-generate-catalog-production
konflux-generate-catalog-production: sync-git-submodules yq opm ## generate a registry.redhat.io catalog
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/catalog konflux-generate-catalog-production \
		CATALOG_TEMPLATE_KONFLUX_INPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_INPUT) \
		CATALOG_TEMPLATE_KONFLUX_OUTPUT=$(PROJECT_DIR)/$(CATALOG_TEMPLATE_KONFLUX_OUTPUT) \
		CATALOG_KONFLUX=$(PROJECT_DIR)/$(CATALOG_KONFLUX) \
		CATALOG_OUTPUT_FORMAT=$(CATALOG_OUTPUT_FORMAT) \
		PACKAGE_NAME_KONFLUX=$(PACKAGE_NAME_KONFLUX) \
		BUNDLE_NAME_SUFFIX=$(BUNDLE_NAME_SUFFIX) \
		PRODUCTION_BUNDLE_NAME=$(PRODUCTION_BUNDLE_NAME) \
		BUNDLE_BUILDS_FILE=$(PROJECT_DIR)/.konflux/catalog/bundle.builds.in.yaml \
		OPM=$(OPM) \
		YQ=$(YQ)
	$(MAKE) konflux-validate-catalog

.PHONY: konflux-filter-unused-redhat-repos
konflux-filter-unused-redhat-repos: sync-git-submodules ## Filter unused repositories from redhat.repo files in runtime lock folder
	@echo "Filtering unused repositories from runtime lock folder..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/rpm-lock filter-unused-repos REPO_FILE=$(PROJECT_DIR)/.konflux/lock-runtime/redhat.repo
	@echo "Filtering completed for runtime lock folder."

.PHONY: konflux-update-tekton-task-refs
konflux-update-tekton-task-refs: sync-git-submodules ## Update task references in Tekton pipeline files
	@echo "Updating task references in Tekton pipeline files..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/tekton update-task-refs \
		PIPELINE_FILES="$$(find $(PROJECT_DIR)/.tekton -type f \( -name '*.yaml' -o -name '*.yml' \) -print0 | xargs -0 -r printf '%s ')"
	@echo "Task references updated successfully."

.PHONY: konflux-compare-catalog
konflux-compare-catalog: sync-git-submodules ## Compare generated catalog with upstream FBC image
	@echo "Comparing generated catalog with upstream FBC image..."
	$(MAKE) -C $(PROJECT_DIR)/telco5g-konflux/scripts/catalog konflux-compare-catalog \
		CATALOG_KONFLUX=$(PROJECT_DIR)/$(CATALOG_KONFLUX) \
		PACKAGE_NAME_KONFLUX=$(PACKAGE_NAME_KONFLUX) \
		UPSTREAM_FBC_IMAGE=quay.io/redhat-user-workloads/telco-5g-tenant/$(PACKAGE_NAME_KONFLUX)-fbc-5-0:latest

.PHONY: konflux-all
konflux-catalog-all: konflux-validate-catalog-template-bundle konflux-generate-catalog-production  konflux-compare-catalog ## Run all konflux catalog logic
	@echo "All Konflux targets completed successfully."

##@ API Documentation

SWAGGER_UI_PORT ?= 9090

.PHONY: swagger-ui-start
swagger-ui-start: ## Start Swagger UI to browse all OpenAPI documentation interactively
	@ENGINE=$(ENGINE) SWAGGER_UI_PORT=$(SWAGGER_UI_PORT) ./hack/swagger-ui.sh start

.PHONY: swagger-ui-stop
swagger-ui-stop: ## Stop the Swagger UI container
	@ENGINE=$(ENGINE) ./hack/swagger-ui.sh stop


