SHELL                 := /usr/bin/env bash
DOCKER_USER           ?= quay.io/wire
# kubernetes namespace for running integration tests
NAMESPACE             ?= test-$(USER)
# default docker image tag is your system username, you can override it via environment variable.
DOCKER_TAG            ?= $(USER)
# default helm chart version must be 0.0.42 for local development (because 42 is the answer to the universe and everything)
HELM_SEMVER           ?= 0.0.42
# The list of helm charts needed on internal kubernetes testing environments
CHARTS_INTEGRATION    := wire-server databases-ephemeral rabbitmq fake-aws ingress-nginx-controller nginx-ingress-services wire-ingress fluent-bit kibana k8ssandra-test-cluster wire-server-enterprise
# The list of helm charts to publish on S3
# FUTUREWORK: after we "inline local subcharts",
# (e.g. move charts/brig to charts/wire-server/brig)
# this list could be generated from the folder names under ./charts/ like so:
# CHARTS_RELEASE := $(shell find charts/ -maxdepth 1 -type d | xargs -n 1 basename | grep -v charts)
CHARTS_RELEASE := wire-server redis-ephemeral rabbitmq rabbitmq-external databases-ephemeral	\
fake-aws fake-aws-s3 fake-aws-sqs aws-ingress fluent-bit kibana backoffice		\
calling-test demo-smtp elasticsearch-curator elasticsearch-external				\
elasticsearch-ephemeral minio-external cassandra-external						\
ingress-nginx-controller nginx-ingress-services reaper \
k8ssandra-test-cluster ldap-scim-bridge wire-server-enterprise \
wire-ingress 
KIND_CLUSTER_NAME     := wire-server
HELM_PARALLELISM      ?= 1 # 1 for sequential tests; 6 for all-parallel tests
PSQL_DB               ?= backendA
export PSQL_DB
DEPENDENCY_TRACK_PROJECT_NAME ?= wire-server

package ?= all
EXE_SCHEMA := ./dist/$(package)-schema

# This ensures that focused unit tests written in hspec fail. This is supposed
# to help us avoid merging PRs with focused tests. This will not catch focused
# integration tests as they are run in kubernetes where this Makefile doesn't
# get executed. This is set here as the CI uses this Makefile, this could live
# in several Makefiles we have in this repository, but there is little point of
# doing so.
export HSPEC_OPTIONS ?= --fail-on=focused

default: install

init:
	mkdir -p dist

# Build all Haskell services and executables, run unit tests
.PHONY: install
install: init
	cabal build all
	./hack/bin/cabal-run-all-tests.sh
	./hack/bin/cabal-install-artefacts.sh all

.PHONY: rabbit-clean
rabbit-clean:
	rabbitmqadmin -f pretty_json list queues vhost name \
		| jq -r '.[] | "rabbitmqadmin delete queue name=\(.name) --vhost=\(.vhost)"' \
		| bash
	rabbitmqadmin -f pretty_json list exchanges name vhost \
		| jq -r '.[] |select(.name | startswith("amq") | not) | select (.name != "") | "rabbitmqadmin delete exchange name=\(.name) --vhost=\(.vhost)"' \
		| bash

# Clean
.PHONY: full-clean
full-clean: clean
	make rabbit-clean
	rm -rf ~/.cache/hie-bios
	rm -rf ./dist-newstyle ./.env
	find . -name '*.hie' -type d -exec rm -rf {} \;
	direnv reload
	@echo -e "\n\n*** NOTE: you may want to also 'rm -rf ~/.cabal/store \$$CABAL_DIR/store', not sure.\n"

.PHONY: clean
clean:
ifeq ("$(package)", "all")
	cabal clean
else
	-if ( test -e dist || test -e dist-newstyle ); then  find dist* -type d -name '$(package)-*' -exec rm -rf {}; fi
endif
  # `/dist` and `.ghc.environment` shouldn't be created or used by anybody any more, we're just making sure here.
	-rm -rf dist .ghc.environment
	-rm -f "bill-of-materials.$(HELM_SEMVER).json"
	-rm -rf tmp/sboms

.PHONY: clean-hint
clean-hint:
	@echo -e "\n\n\n>>> PSA: if you get errors that are hard to explain,"
	@echo -e ">>> try 'git submodule update --init --recursive' and 'make full-clean' and run your command again."
	@echo -e ">>> see https://github.com/wireapp/wire-server/blob/develop/docs/src/developer/developer/building.md#linker-errors-while-compiling"
	@echo -e ">>> to never have to remember submodules again, try 'git config --global submodule.recurse true'"
	@echo -e "\n\n\n"

cabal.project.local:
	cp ./hack/cabal.project.local.template ./cabal.project.local

# Usage: make c package=brig test=1
.PHONY: c
c: treefmt c-fast

.PHONY: c
c-fast: cabal.project.local
	cabal build $(WIRE_CABAL_BUILD_OPTIONS) $(package) || ( make clean-hint; false )
ifeq ($(test), 1)
	./hack/bin/cabal-run-tests.sh $(package) $(testargs)
endif
	./hack/bin/cabal-install-artefacts.sh $(package)

# ci here doesn't refer to continuous integration, but to cabal-run-integration.sh
# Usage: make ci-fast                     - build & run all tests, excluding integration
#        make ci-fast package=all         - build & run all tests, including integration
#        make ci-fast package=brig        - build brig & run "brig-integration"
#        make ci-fast package=integration - build & run "integration"
#
# You can pass environment variables to all the suites, like so
# TASTY_PATTERN=".."  make ci package=brig
#
# If you want to pass arguments to the test-suite call cabal-run-integration.sh directly.
.PHONY: ci-fast
ci-fast: c db-migrate
ifeq ("$(package)", "all")
	./hack/bin/cabal-run-integration.sh all
	./hack/bin/cabal-run-integration.sh integration
endif
	./hack/bin/cabal-run-integration.sh $(package)

# variant of `make ci-fast` that compiles the entire project even if `package` is specified.
.PHONY: ci-safe
ci-safe:
	make c package=all
	make ci-fast

.PHONY: ci
ci:
	@echo -en "\n\n\nplease choose between goals ci-fast and ci-safe.\n\n\n"

# Compile and run services
# Usage: make cr `OR` make cr package=galley
.PHONY: cr
cr: c db-migrate
	./dist/run-services

crm: c db-migrate
	./dist/run-services -m

# Run integration from new test suite
# Usage: make devtest
# Usage: TEST_INCLUDE=test1,test2 make devtest
#
# Now also supports running unit tests for a package in a loop,
# re-loading and -running on file change in the unit tests *and* the
# library.  Just say `make devtest package=wire-subsystems`.  If this
# doesn't work for some package, compare the cabal file with
# wire-subsystems.cabal (eg., name of test suite needs to follow a
# pattern).
#
# There some alternatives, but they are all either too slow or do not
# watch / compile enough modules, or both.  Here is one just running
# make c on a package in a loop for all package changes:
#
# find . -name '*.hs' | entr -s 'make -C ~/src/wire-server c package=wire-subsystems test=1'
.PHONY: devtest
devtest:
ifeq ("$(package)", "all")
	ghcid --command 'cabal repl lib:integration' --test='Testlib.Run.mainI []'
else
	@ghcid --command 'cabal repl $(package):${package}-tests lib:$(package) --enable-multi-repl' --test='Main.main' \
	  || echo -e "\n\n\n*** usage: make devtest package=<package>.\n*** this works for wire-subsystems; for other packages, you may need to edit the cabal file.\n\n"
endif

.PHONY: devtest-package
devtest-package:
	@echo "deprecated: use 'make devtest package=<package>' instead."
	@false

.PHONY: sanitize-pr
sanitize-pr: check-weed treefmt
	make lint-all-shallow
	make cassandra-schema
	make postgres-schema
	@git diff-files --quiet -- || ( echo "There are unstaged changes, please take a look, consider committing them, and try again."; exit 1 )
	@git diff-index --quiet --cached HEAD -- || ( echo "There are staged changes, please take a look, consider committing them, and try again."; exit 1 )
	make list-flaky-tests

.PHONY: check-weed
check-weed:
	./hack/bin/check-weed.sh

list-flaky-tests:
	@echo -e "\n\nif you want to run these, set RUN_FLAKY_TESTS=1\n\n"
	@git grep -Hne '\bflakyTestCase \"'
	@git grep -Hne '[^^]\bflakyTest\b'

# Get a ghci environment running for the given package.
.PHONY: repl
repl: treefmt
	cabal repl $(WIRE_CABAL_BUILD_OPTIONS) $(package)

# Use ghcid to watch a particular package.
# pass target=package:name to specify which target is watched.
.PHONY: ghcid
ghcid:
	ghcid -l=hlint --command "cabal repl $(target)"

# Used by CI
.PHONY: lint-all
lint-all: formatc hlint-check-all lint-common

# For use by local devs.
#
# This is not safe for CI because files not changed on the branch may
# have been pushed to develop, or caused by merging develop into the
# branch implicitly on github.
#
# The extra 'hlint-check-pr' has been witnessed to be necessary due to
# some bu in `hlint-inplace-pr`.  Details got lost in history.
.PHONY: lint-all-shallow
lint-all-shallow: lint-common formatf hlint-inplace-pr hlint-check-pr

.PHONY: lint-common
lint-common: check-local-nix-derivations treefmt-check # weeder (does not work on CI yet)

.PHONY: weeder
weeder:
	weeder -N

.PHONY: hlint-check-all
hlint-check-all:
	./tools/hlint.sh -f all -m check

.PHONY: hlint-inplace-all
hlint-inplace-all:
	./tools/hlint.sh -f all -m inplace

.PHONY: hlint-check-pr
hlint-check-pr:
	./tools/hlint.sh -f pr -m check

.PHONY: hlint-inplace-pr
hlint-inplace-pr:
	./tools/hlint.sh -f pr -m inplace

.PHONY: hlint-check
hlint-check:
	./tools/hlint.sh -f changeset -m check

.PHONY: hlint-inplace
hlint-inplace:
	./tools/hlint.sh -f changeset -m inplace

regen-local-nix-derivations:
	./hack/bin/generate-local-nix-packages.sh

check-local-nix-derivations: regen-local-nix-derivations
	git diff --exit-code

# Build everything (Haskell services and nginz)
.PHONY: services
services: init install
	$(MAKE) -C services/nginz

# formats all Haskell files (which don't contain CPP)
.PHONY: format
format:
	./tools/ormolu.sh

# formats all Haskell files changed in this PR, even if local changes are not committed to git
.PHONY: formatf
formatf:
	./tools/ormolu.sh -f pr

# formats all Haskell files even if local changes are not committed to git
.PHONY: formatf-all
formatf-all:
	./tools/ormolu.sh -f all

# checks that all Haskell files are formatted; fail if a `make format` run is needed.
.PHONY: formatc
formatc:
	./tools/ormolu.sh -c

# For any Haskell or Rust file, update or add a license header if necessary.
# Headers should be added according to Ormolu's formatting rules, but please check just in case.
.PHONY: add-license
add-license:
	command -v headroom
	headroom run -a
	@echo ""
	@echo "you might want to run 'make formatf' now to make sure ormolu is happy"

# without redirecting stdin/-out/-err, emacs does something weird that takes 3-5 seconds.
.PHONY: treefmt
treefmt:
	tempdir=$$(mktemp -d); \
	  trap "rm -rf $$tempdir" EXIT; \
	  treefmt -u debug --walk=git </dev/null >$$tempdir/treefmt.tmp 2>&1; \
	  exit_code=$$?; \
	  cat $$tempdir/treefmt.tmp; \
	  exit $$exit_code

.PHONY: treefmt-check
treefmt-check:
	treefmt --fail-on-change -u debug --walk=git

#################################
## docker targets

.PHONY: build-image-%
build-image-%:
	nix build '.#wireServer.imagesNoDocs.$(*)' && \
	./result | docker load | tee /tmp/imageName-$(*) && \
	imageName=$$(grep quay.io /tmp/imageName-$(*) | awk '{print $$3}') && \
	echo 'You can run your image locally using' && \
	echo "  docker run -it --entrypoint bash $$imageName" && \
	echo 'or upload it using' && \
	echo "  docker push $$imageName"

.PHONY: upload-images
upload-images:
	./hack/bin/upload-images.sh imagesNoDocs

.PHONY: upload-images-dev
upload-images-dev:
	./hack/bin/upload-images.sh imagesUnoptimizedNoDocs

HOOGLE_IMAGE_DIR := $(shell mktemp -d -t wire-server-hoogle-image.XXXXXX)

upload-hoogle-image:
	nix -v --show-trace -L build ".#wireServer.hoogleImage" --out-link $(HOOGLE_IMAGE_DIR)/image --fallback
	./hack/bin/upload-image.sh $(HOOGLE_IMAGE_DIR)/image

#################################
## cassandra / postgres management

.PHONY: git-add-cassandra-schema
git-add-cassandra-schema:
	@echo "deprecated.  use 'make cassandra-schema' instead."
	@false

.PHONY: cassandra-schema
cassandra-schema: db-migrate cassandra-schema-impl

.PHONY: cassandra-schema-impl
cassandra-schema-impl:
	./hack/bin/cassandra_dump_schema > ./cassandra-schema.cql

.PHONY: postgres-schema
postgres-schema: postgres-reset postgres-schema-impl

.PHONY: postgres-schema-impl
postgres-schema-impl:
	./hack/bin/postgres_dump_schema > ./postgres-schema.sql

.PHONY: cqlsh
cqlsh:
	$(eval CASSANDRA_CONTAINER := $(shell docker ps | grep 'cassandra' | perl -ne '/^(\S+)\s/ && print $$1'))
	@echo "make sure you have ./deploy/dockerephemeral/run.sh running in another window!"
	docker exec -it $(CASSANDRA_CONTAINER) cqlsh

.PHONY: psql
psql:
	@grep -q wire-server:wire-server ~/.pgpass || \
	  echo "consider running 'echo localhost:5432:$(PSQL_DB):wire-server:posty-the-gres > ~/.pgpass ; chmod 600 ~/.pgpass '"
	psql -h localhost -p 5432 $(PSQL_DB) -U wire-server -w || \
	  echo 'if the database is missing, consider running "make postgres-reset", or setting $$PSQL_DB to the correct table space.'

.PHONY: db-reset-package
db-reset-package:
	@echo "Deprecated! Please use 'db-reset' instead"
	$(MAKE) db-reset package=$(package)

.PHONY: db-migrate-package
db-migrate-package:
	@echo "Deprecated! Please use 'db-migrate' instead"
	$(MAKE) db-migrate package=$(package)

# Reset all keyspaces and reset the ES index
.PHONY: db-reset
db-reset: c postgres-reset cassandra-reset es-reset rabbitmq-reset

.PHONY: cassandra-reset
cassandra-reset: c
	./dist/brig-schema --keyspace brig_test --replication-factor 1 --reset
	./dist/galley-schema --keyspace galley_test --replication-factor 1 --reset
	./dist/gundeck-schema --keyspace gundeck_test --replication-factor 1 --reset
	./dist/spar-schema --keyspace spar_test --replication-factor 1 --reset

	./dist/brig-schema --keyspace brig_test2 --replication-factor 1 --reset
	./dist/galley-schema --keyspace galley_test2 --replication-factor 1 --reset
	./dist/gundeck-schema --keyspace gundeck_test2 --replication-factor 1 --reset
	./dist/spar-schema --keyspace spar_test2 --replication-factor 1 --reset
	./integration/scripts/integration-dynamic-backends-db-schemas.sh --replication-factor 1 --reset

.PHONY: postgres-reset
postgres-reset: c
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --reset --dbname backendA
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --reset --dbname backendB
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --reset --dbname dyn-1
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --reset --dbname dyn-2
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --reset --dbname dyn-3

.PHONY: postgres-migrate
postgres-migrate: c
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --dbname backendA
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --dbname backendB
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --dbname dyn-1
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --dbname dyn-2
	./dist/brig -c ./services/brig/brig.integration.yaml migrate-postgres --dbname dyn-3

.PHONY: es-reset
es-reset: c
	./dist/brig-index reset \
		--elasticsearch-index-prefix directory \
		--elasticsearch-server https://localhost:9200 \
		--elasticsearch-ca-cert ./libs/wire-subsystems/test/resources/elasticsearch-ca.pem \
		--elasticsearch-credentials ./libs/wire-subsystems/test/resources/elasticsearch-credentials.yaml > /dev/null
	./dist/brig-index reset \
		--elasticsearch-index-prefix directory2 \
		--elasticsearch-server https://localhost:9200 \
		--elasticsearch-ca-cert ./libs/wire-subsystems/test/resources/elasticsearch-ca.pem \
		--elasticsearch-credentials ./libs/wire-subsystems/test/resources/elasticsearch-credentials.yaml > /dev/null
	./integration/scripts/integration-dynamic-backends-brig-index.sh \
		--elasticsearch-server https://localhost:9200 \
		--elasticsearch-ca-cert ./libs/wire-subsystems/test/resources/elasticsearch-ca.pem \
		--elasticsearch-credentials ./libs/wire-subsystems/test/resources/elasticsearch-credentials.yaml > /dev/null
	@echo -e "\n'brig-index reset' only deletes the index and regenerates the mapping, but doesn't generate or populate a new index, so you need to call 'make es-reindex explicitly now!\n"

.PHONY: es-reindex
es-reindex: c
	./dist/brig-index reindex \
		--pg-pool-size 10 \
		--pg-pool-acquisition-timeout 10s \
		--pg-pool-aging-timeout 1d \
		--pg-pool-idleness-timeout 1h \
		--pg-settings '{"host":"127.0.0.1","port":"5432","user":"wire-server","dbname":"backendA"}' \
		--pg-password-file ./libs/wire-subsystems/test/resources/postgres-credentials.yaml \
		--elasticsearch-server https://localhost:9200 \
		--elasticsearch-ca-cert ./libs/wire-subsystems/test/resources/elasticsearch-ca.pem \
		--elasticsearch-credentials ./libs/wire-subsystems/test/resources/elasticsearch-credentials.yaml > /dev/null

.PHONY: rabbitmq-reset
rabbitmq-reset: rabbit-clean

# Migrate all keyspaces and reset the ES index
# Does not migrate postgres as brig does that on startup.
.PHONY: db-migrate
db-migrate: c postgres-migrate
	./dist/brig-schema --keyspace brig_test --replication-factor 1 > /dev/null
	./dist/galley-schema --keyspace galley_test --replication-factor 1 > /dev/null
	./dist/gundeck-schema --keyspace gundeck_test --replication-factor 1 > /dev/null
	./dist/spar-schema --keyspace spar_test --replication-factor 1 > /dev/null
	./dist/brig-schema --keyspace brig_test2 --replication-factor 1 > /dev/null
	./dist/galley-schema --keyspace galley_test2 --replication-factor 1 > /dev/null
	./dist/gundeck-schema --keyspace gundeck_test2 --replication-factor 1 > /dev/null
	./dist/spar-schema --keyspace spar_test2 --replication-factor 1 > /dev/null
	./integration/scripts/integration-dynamic-backends-db-schemas.sh --replication-factor 1 > /dev/null
	make es-reset

#################################
## dependencies

libzauth:
	$(MAKE) -C libs/libzauth install

#####################################
# Today we pretend to be CI and run integration tests on kubernetes
# (see also docs/developer/processes.md)
#
# NOTE: This uses local helm charts from .local/charts (which it builds before running this)
#
# NOTE/WARNING: By default, it uses local docker image tags,
# which will not work at this time on your remote kubernetes cluster. [FUTUREWORK: local kubernetes cluster]
#
# If you wish to use docker images that are uploaded to quay.io, you must set DOCKER_TAG
#
#   DOCKER_TAG=<desired-wire-server-docker-tag> make kube-integration
#
# and if you don't know what a good DOCKER_TAG might be, you can run
#
#   make latest-tag
#
# This task requires: [FUTUREWORK: add tooling setup to wire-server]
#   - helm (version 3.1.1)
#   - kubectl
#   - a valid kubectl context configured (i.e. access to a kubernetes cluster)
.PHONY: kube-integration
kube-integration:  kube-integration-setup kube-integration-test

.PHONY: kube-integration-setup
kube-integration-setup: charts-integration helm-oci-login
	export NAMESPACE=$(NAMESPACE); export HELM_PARALLELISM=$(HELM_PARALLELISM); ./hack/bin/integration-setup-federation.sh

.PHONY: kube-integration-test
kube-integration-test:
	export NAMESPACE=$(NAMESPACE); \
	export HELM_PARALLELISM=$(HELM_PARALLELISM); \
	export VERSION=${DOCKER_TAG}; \
	export UPLOAD_LOGS=${UPLOAD_LOGS}; \
	./hack/bin/integration-test.sh

.PHONY: kube-integration-teardown
kube-integration-teardown:
	export NAMESPACE=$(NAMESPACE); export HELM_PARALLELISM=$(HELM_PARALLELISM); ./hack/bin/integration-teardown-federation.sh

.PHONY: helm-oci-login
helm-oci-login:
	./hack/bin/helm-oci-login.sh

.PHONY: kube-restart-%
kube-restart-%:
	kubectl delete pod -n $(NAMESPACE) -l app=$(*)
	kubectl delete pod -n $(NAMESPACE)-fed2 -l app=$(*)

.PHONY: latest-tag
latest-tag:
	./hack/bin/find-latest-docker-tag.sh

.PHONY: release-chart-%
release-chart-%:
	@if [ "${HELM_SEMVER}" = "0.0.42" ]; then \
	      echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<something>"; \
	    exit 1; \
	fi
	@if [ "${DOCKER_TAG}" = "${USER}" ]; then \
	      echo "Environment variable DOCKER_TAG not set to non-default value. Re-run with DOCKER_TAG=<something>"; \
	    exit 1; \
	fi
	make chart-$(*)

.PHONY: guard-tag
guard-tag:
	@if [ "${DOCKER_TAG}" = "${USER}" ]; then \
	      echo "Environment variable DOCKER_TAG not set to non-default value. Re-run with DOCKER_TAG=<something>. Try using 'make latest-tag' for latest develop docker image tag";\
	    exit 1; \
	fi

# Rationale for copying charts to a gitignored folder before modifying helm versions and docker image tags:
#
# * we want to keep git history clean and not clutter it with version bump commits
#   * synchronizing version bumps with multiple PRs, releases to master and merges back to develop is hard to do in git
#   * we don't want to spend time modifying version tags manually all the time
# * we want version pinning for helm charts and docker images for reproducible results during deployments
#   * CI will keep track of versioning and upload charts to an S3 mirror
#   * if you need to do this locally, also use this make target and set desired versions accordingly.
.PHONY: chart-%
chart-%:
	./hack/bin/copy-charts.sh $(*)
	./hack/bin/set-wire-server-image-version.sh $(DOCKER_TAG)
	./hack/bin/set-helm-chart-version.sh "$*" $(HELM_SEMVER)

# Usecase for this make target:
#  * for local integration testing of wire-server inside kubernetes
.PHONY: charts-integration
charts-integration: $(foreach chartName,$(CHARTS_INTEGRATION),chart-$(chartName))

.PHONY: charts-serve
charts-serve: charts-integration
	./hack/bin/serve-charts.sh $(CHARTS_INTEGRATION)

.PHONY: charts-serve-all
charts-serve-all: $(foreach chartName,$(CHARTS_RELEASE),chart-$(chartName))
	./hack/bin/serve-charts.sh $(CHARTS_RELEASE)

# Usecase for this make target:
# 1. for releases of helm charts
# 2. for testing helm charts more generally
.PHONY: charts-release
charts-release: $(foreach chartName,$(CHARTS_RELEASE),release-chart-$(chartName))

# Prepare .local/charts to be read by `helmfile`
.PHONY: .local/charts
.local/charts: charts-release
	./hack/bin/prepare-local-charts.sh $(CHARTS_RELEASE)

.PHONY: clean-charts
clean-charts:
	rm -rf .local/charts

##########################################
# Helm chart releasing (mirroring to S3)
# Only CI should run these targets ideally

# Usecases for this make target:
# To release one single helm chart to S3 mirror
# (assummption: CI sets DOCKER_TAG and HELM_SEMVER)
.PHONY: upload-chart-%
upload-chart-%: release-chart-%
	./hack/bin/upload-helm-charts-s3.sh -r $(HELM_REPO) -d .local/charts/$(*)

# Usecases for this make target:
# To upload all helm charts in the CHARTS_RELEASE list (see top of the time)
# (assummption: CI sets DOCKER_TAG and HELM_SEMVER)
.PHONY: upload-charts
upload-charts: charts-release
	./hack/bin/upload-helm-charts-s3.sh -r $(HELM_REPO)

.PHONY: echo-release-charts
echo-release-charts:
	@echo ${CHARTS_RELEASE}

.PHONY: kind-cluster
kind-cluster:
	kind create cluster --name $(KIND_CLUSTER_NAME)

.PHONY: kind-delete
kind-delete:
	rm -f $(CURDIR)/.local/kind-kubeconfig
	kind delete cluster --name $(KIND_CLUSTER_NAME)

.PHONY: kind-reset
kind-reset: kind-delete kind-cluster

.PHONY: kind-upload-images
kind-upload-images:
	DOCKER_TAG=$(DOCKER_TAG) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) ./hack/bin/kind-upload-images.sh

.PHONY: kind-upload-image
kind-upload-image-%:
	DOCKER_TAG=$(DOCKER_TAG) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) ./hack/bin/kind-upload-image.sh wireServer.imagesUnoptimizedNoDocs.$(*)

.local/kind-kubeconfig:
	mkdir -p $(CURDIR)/.local
	kind get kubeconfig --name $(KIND_CLUSTER_NAME) > $(CURDIR)/.local/kind-kubeconfig
	chmod 0600 $(CURDIR)/.local/kind-kubeconfig

# This guard is a fail-early way to save needing to debug nginz container not
# starting up in the second namespace of the kind cluster in some cases. Error
# message was:
#     nginx PID: 8
#     Couldn't initialize inotify: No file descriptors available
#     Try increasing the value of /proc/sys/fs/inotify/max_user_instances
#     inotifywait failed, killing nginx
.PHONY: guard-inotify
guard-inotify:
	@if [[ $$(cat /proc/sys/fs/inotify/max_user_instances) -lt 200 ]]; then \
		echo "Your /proc/sys/fs/inotify/max_user_instances value is most likely too low to run two full environments of wire-server in kind/kubernetes"; \
		echo "You can run: "; \
		echo ""; \
		echo "  echo \"1000\" | sudo tee /proc/sys/fs/inotify/max_user_instances"; \
		echo ""; \
		echo "(or, to make that change permanent across reboots, you can run: )"; \
		echo ""; \
		echo "  echo 'fs.inotify.max_user_instances = 1000' | sudo tee /etc/sysctl.d/99-whatever.conf;"; \
		echo "  sudo sysctl -p --system"; \
		echo ""; \
		exit 1; \
	fi

.PHONY: kind-integration-setup
kind-integration-setup: guard-inotify .local/kind-kubeconfig
	KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig helmfile sync -f $(CURDIR)/hack/helmfile-federation-v0.yaml.gotmpl
	HELMFILE_ENV="kind" KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig make kube-integration-setup

.PHONY: kind-integration-test
kind-integration-test: .local/kind-kubeconfig
	HELMFILE_ENV="kind" KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig make kube-integration-test

kind-integration-e2e: .local/kind-kubeconfig
	cd services/brig && KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig ./federation-tests.sh $(NAMESPACE)

kind-restart-all: .local/kind-kubeconfig
	export KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig && \
	kubectl delete pod -n $(NAMESPACE) -l release=$(NAMESPACE)-wire-server && \
	kubectl delete pod -n $(NAMESPACE)-fed2 -l release=$(NAMESPACE)-wire-server-2

kind-restart-nginx-ingress: .local/kind-kubeconfig
	export KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig && \
	kubectl delete pod -n $(NAMESPACE) -l app=nginx-ingress && \
	kubectl delete pod -n $(NAMESPACE)-fed2 -l app=nginx-ingress

kind-restart-%: .local/kind-kubeconfig
	export KUBECONFIG=$(CURDIR)/.local/kind-kubeconfig && \
	kubectl delete pod -n $(NAMESPACE) -l app=$(*) && \
	kubectl delete pod -n $(NAMESPACE)-fed2 -l app=$(*)

# This target can be used to template a helm chart with values filled in from
# hack/helm_vars (what CI uses) as overrides, if available. This allows debugging helm
# templating issues without actually installing anything, and without needing
# access to a kubernetes cluster. e.g.:
#   make helm-template-wire-server
helm-template-%: clean-charts charts-integration
	./hack/bin/helm-template.sh $(*)

# Render the wire-server manifest from an explicit values file.
# Usage:
#   make render-manifest VALUES_FILE=/tmp/values.yaml
#   make render-manifest VALUES_FILE=/tmp/values.yaml OUTPUT_FILE=/tmp/rendered.yaml
# (you can get the live values e.g. like this: helm get values wire-server -n wire -a)
render-manifest: clean-charts charts-integration
	./hack/bin/render-manifest.sh "$(VALUES_FILE)"

# Render wire-server from live values and compare it with the live manifest.
# Usage:
#   helm get values wire-server -n wire -a > /tmp/staging/live-values.yaml
#   helm get manifest wire-server -n wire > /tmp/staging/live-manifest.yaml
#   make diff-live-manifest LIVE_VALUES_FILE=/tmp/staging/live-values.yaml LIVE_MANIFEST_FILE=/tmp/staging/live-manifest.yaml
diff-live-manifest: clean-charts charts-integration
	OUTPUT_FILE="/tmp/wire-server.yaml" ./hack/bin/render-manifest.sh "$(LIVE_VALUES_FILE)"; \
	DIFF_OUTPUT_FILE="$(DIFF_OUTPUT_FILE)" ./hack/bin/diff-wire-server-manifests.sh "$(LIVE_MANIFEST_FILE)" /tmp/wire-server.yaml

render-ci-manifest: clean-charts charts-integration
	VALUES_FILE="$${VALUES_FILE:-$$(mktemp).yaml}"; export VALUES_FILE; \
	./hack/bin/helm-render-ci-values.sh && \
	./hack/bin/render-manifest.sh "$$VALUES_FILE"

# SBOM creation and uploading (Helm charts, Helmfile, docker-compose)
#
# For non-Nix environments (Kubernetes, docker-compose) and Helm charts we can
# use the usual tools and do not need tom-bombadil.
#
# There is a Nix `devShell` which provides an environment for these targets, `sbom`.
# E.g. to run the `sboms` target:
# `nix develop .\#sbom --command make sboms HELM_SEMVER=... DOCKER_TAG=...`
#
# Why don't we simply add this `nix develop` call to the Makefile targets?
# Targets should be independently executable and creating a Nix env in a Nix
# env doesn't play well.

# Generate all SBOMs (Helm + Docker Compose + Helmfile + Nix Docker Images + Nix DevShell)
.PHONY: sboms
sboms: sboms-helm sboms-docker-compose sboms-helmfile sboms-nix-docker-images sboms-nix-devshell

# Generate SBOMs for Helm charts
.PHONY: sboms-helm
sboms-helm: .local/charts
	@if [ "$(HELM_SEMVER)" = "0.0.42" ]; then \
		echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<version>"; \
		exit 1; \
	fi
	./hack/bin/create-helm-sboms.sh tmp/sboms/helm $(HELM_SEMVER)

# Generate SBOMs for Docker Compose
.PHONY: sboms-docker-compose
sboms-docker-compose:
	./hack/bin/create-docker-compose-sboms.sh tmp/sboms/docker-compose

# Generate SBOMs for Helmfile
.PHONY: sboms-helmfile
sboms-helmfile: .local/charts
	@if [ "$(HELM_SEMVER)" = "0.0.42" ]; then \
		echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<version>"; \
		exit 1; \
	fi
	./hack/bin/create-helmfile-sboms.sh tmp/sboms/helmfile $(HELM_SEMVER)

# Generate SBOMs for Nix-built Docker images using sbomnix
# This generates SBOMs from the Nix store paths of executables that go into Docker images
.PHONY: sboms-nix-docker-images
sboms-nix-docker-images:
	@if [ "$(HELM_SEMVER)" = "0.0.42" ]; then \
		echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<version>"; \
		exit 1; \
	fi
	./hack/bin/create-nix-docker-image-sboms.sh tmp/sboms/nix-docker-images $(HELM_SEMVER) imagesUnoptimizedNoDocs

# Generate SBOMs for Nix devShells using sbomnix
# This generates SBOMs from the Nix store paths of packages in the development environments
.PHONY: sboms-nix-devshell
sboms-nix-devshell:
	@if [ "$(HELM_SEMVER)" = "0.0.42" ]; then \
		echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<version>"; \
		exit 1; \
	fi
	./hack/bin/create-nix-devshell-sbom.sh tmp/sboms/nix-devshell $(HELM_SEMVER)

# Validate all SBOM files using cyclonedx
.PHONY: validate-sboms
validate-sboms:
	@echo "Validating SBOM files..."
	@find tmp/sboms -name '*.json' -type f -not -path '*/.oci-cache/*' | while read sbom; do \
		echo "Validating: $$sbom"; \
		cyclonedx validate --input-file "$$sbom" --fail-on-errors; \
	done
	@echo "All SBOMs validated successfully"

# Upload all SBOMs to Dependency Track
# Requires DEPENDENCY_TRACK_API_KEY environment variable
.PHONY: upload-sboms
upload-sboms:
	@if [ "$(HELM_SEMVER)" = "0.0.42" ]; then \
		echo "Environment variable HELM_SEMVER not set to non-default value. Re-run with HELM_SEMVER=<version>"; \
		exit 1; \
	fi
	./hack/bin/upload-all-sboms.sh $(DEPENDENCY_TRACK_PROJECT_NAME) "$(HELM_SEMVER)"

.PHONY: openapi-validate
openapi-validate:
	@echo -e "Make sure you are running the backend in another terminal (make cr)\n"
	vacuum lint -a -d -e <(curl http://localhost:8082/v7/api/swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/cannon-swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/cargohold-swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/spar-swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/gundeck-swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/brig-swagger.json)
	vacuum lint -a -d -e <(curl http://localhost:8082/api-internal/swagger-ui/galley-swagger.json)
