APP = dna-api

BUILD_NUMBER   ?= 1
PROJECT_GROUP  ?= dna
API_PROJECT    ?= api
SQITCH_PROJECT ?= api-db-migrations

BASE_AWS_PROFILE    = gdb-infra-dev
BASE_AWS_ACCOUNT_ID = 483193324480
BASE_AWS_REGION     = us-east-1
BASE_ECR_REGISTRY   = $(BASE_AWS_ACCOUNT_ID).dkr.ecr.$(BASE_AWS_REGION).amazonaws.com

AWS_PROFILE    ?= gdb-whitelist-dev
AWS_ACCOUNT_ID ?= 814622134907
AWS_REGION     ?= us-east-1

ECR_REGISTRY            ?= $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com
API_ECR_REPOSITORY_NAME ?= $(PROJECT_GROUP)/$(API_PROJECT)
API_ECR_REGISTRY_URI    ?= $(ECR_REGISTRY)/$(API_ECR_REPOSITORY_NAME)

SQITCH_ECR_REPOSITORY_NAME ?= $(PROJECT_GROUP)/$(SQITCH_PROJECT)
SQITCH_ECR_REGISTRY_URI    ?= $(ECR_REGISTRY)/$(SQITCH_ECR_REPOSITORY_NAME)

REVISION  ?= $(shell git rev-parse --short=8 HEAD)
BUILDTIME ?= $(shell date -u +"%Y%m%d%H%M%S")
VERSION   ?= build-$(BUILDTIME).$(REVISION)
SEMVER    ?= $(shell cat VERSION )

VENV := ./venv

POSTGRES_IMAGE ?= postgres:13.5
ELASTICSEARCH_IMAGE ?= docker.elastic.co/elasticsearch/elasticsearch:7.10.2

TEST_IMAGE = ${ECR_REGISTRY}/${API_ECR_REPOSITORY_NAME}:${SEMVER}-testing

PYTHON    = $(VENV)/bin/python
PIP       = $(VENV)/bin/pip
PIPENV    = $(VENV)/bin/pipenv
PYTEST    = $(VENV)/bin/pytest
PYLINT    = $(VENV)/bin/pylint
TWINE     = $(VENV)/bin/twine
YAPF      = $(VENV)/bin/yapf
ISORT     = $(VENV)/bin/isort
MYPY      = $(VENV)/bin/mypy
PIP_AUDIT = $(VENV)/bin/pip-audit
FLAKE     = $(VENV)/bin/flake8
BANDIT    = $(VENV)/bin/bandit
BLACK     = $(VENV)/bin/black

.DEFAULT_GOAL := help

## -
## help - this message
.PHONY: help
help: Makefile
	@echo "Application: ${APP}\n"
	@echo "Run command:\n  make <target>\n"
	@grep -E -h '^## .*' $(MAKEFILE_LIST) | sed -n 's/^##//p'  | column -t -s '-' |  sed -e 's/^/ /'

## -
## Local commands:

$(VENV)/bin/activate:
	python -m venv $(VENV)/

##   venv - create local virtual env
venv: $(VENV)/bin/activate
	@echo "Creating virtual environment"
	cp -n .venv.example .venv
	$(PIPENV) install

##   docker-venv - create docker virtual env
docker-venv: $(VENV)/bin/activate
	@echo "Creating virtual environment in docker"
	$(PIP) install -r requirements/base.txt

##   testenv - create local virtual env (tests.txt dependencies)
.PHONY:testenv
testenv: venv
	@echo "Installing test requirements"
	$(PIP) install -r requirements/tests.txt

##   docker-testenv - create local virtual env (tests.txt dependencies)
.PHONY:docker-testenv
docker-testenv: docker-venv
	@echo "Installing test requirements in docker"
	$(PIP) install -r requirements/dev.txt

##   devenv - create local virtual env (dev.txt dependencies)
.PHONY:devenv
devenv: venv
	@echo "Installing dev requirements"
	$(PIPENV) install --dev

##   docker-devenv - create local virtual env (dev.txt dependencies)
.PHONY:docker-devenv
docker-devenv: docker-venv
	@echo "Installing dev requirements in docker"
	$(PIP) install -r requirements/dev.txt

##   test - run tests locally
.PHONY:test
test:
	@echo "Run tests"
	$(PYTEST) -vv --junitxml report.xml --cov-report xml --cov-report term --cov=. --cov-report=html:pytest_coverage --html=pytest_result.html --self-contained-html

.PHONY:checkstyle-exec
checkstyle-exec:
	@echo "Run checkstyle"
	@touch flake8-report.txt
	$(FLAKE)  server/ --max-complexity=15 >> flake8-report.txt || cat flake8-report.txt
	$(FLAKE)  tests/ --max-complexity=10 >> flake8-report.txt || cat flake8-report.txt
	$(FLAKE)  conftest.py --max-complexity=5 >> flake8-report.txt || cat flake8-report.txt
	$(FLAKE)  app.py --max-complexity=5 >> flake8-report.txt || cat flake8-report.txt
	@cat flake8-report.txt
	@echo "Run static-typing check"
	$(MYPY) ./server/

##   checkstyle - run checkstyle locally
.PHONY:checkstyle
checkstyle: devenv checkstyle-exec

##   docker/checkstyle - run checkstyle locally
.PHONY:docker/checkstyle
docker/checkstyle: docker/docker-devenv docker/checkstyle-exec

.PHONY:dependencies-check-exec
dependencies-check-exec:
	@echo "Run dependencies check"
	$(PIP_AUDIT) -r requirements/base.txt

##   dependencies-check - run pip-audit locally
.PHONY:dependencies-check
dependencies-check: devenv dependencies-check-exec

##   docker/dependencies-check - run pip-audit locally
.PHONY:docker/dependencies-check
docker/dependencies-check: docker/docker-devenv docker/dependencies-check-exec

.PHONY:owasp-exec
owasp-exec:
	@echo "Run owasp check"
	$(BANDIT) -f json -o bandit-report.json -x **/venv/*,**/tests/* -r .

##   owasp - run bandit locally
.PHONY:owasp
owasp: devenv owasp-exec

##   docker/owasp - run bandit in docker
.PHONY:docker/owasp
docker/owasp: docker/docker-devenv docker/owasp-exec

.PHONY:static-typing-exec
static-typing-exec:
	@echo "Run static-typing check"
	$(MYPY) ./server/

##   static-typing - run mypy locally
.PHONY:static-typing
static-typing: devenv static-typing-exec

##   docker/static-typing - run mypy in docker
.PHONY:docker/static-typing
docker/static-typing: docker/docker-devenv docker/static-typing-exec

##   static-typing - run pip-audit and bandit locally
.PHONY:security
security: dependencies-check owasp

##   static-typing - run pip-audit and bandit in docker
.PHONY:docker/security
docker/security: docker/dependencies-check docker/owasp
docker/security: docker/owasp

.PHONY:prettify-exec
prettify-exec:
	$(BLACK) . --exclude='/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|venv|_build|buck-out|build|dist|migrations|.pytest_cache)/' --line-length=120 --target-version=py310

##   prettify - run prettify locally
.PHONY:prettify
prettify: devenv prettify-exec
	$(BLACK) . --exclude='/(\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|venv|_build|buck-out|build|dist|migrations|.pytest_cache)/' --line-length=120 --target-version=py310

##   prettify - run prettify in docker
.PHONY:docker/prettify
docker/prettify: docker/docker-devenv docker/prettify-exec

.PHONY:python/clean
python/clean:
	py3clean .

.PHONY:pre-commit
pre-commit: docker/image/build docker/prettify docker/checkstyle docker/security docker/test docker/clean

## -
## Version management

##   version/patch - increment version patch
.PHONY: version/patch
version/patch:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		patch VERSION

##   version/minor - increment version minor
.PHONY: version/minor
version/minor:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		minor VERSION

##   version/major - increment version major
.PHONY: version/major
version/major:
	@bumpversion \
		--config-file .bumpversion.cfg \
		--current-version ${SEMVER} \
		major VERSION

## -
## Docker commands:

##   docker/build-local - build using docker-compose for local testing
.PHONY:docker/build-local
docker/build-local: docker/login/base
	docker-compose build

##   docker/up-local - start dev environment
.PHONY:docker/up-local
docker/up-local: docker/build-local
	docker-compose up -d

##   docker/login/base - login to AWS ECR registry using docker, aws configuration need to be set first
.PHONY:docker/login/base
docker/login/base:
	@aws ecr get-login \
		--profile ${BASE_AWS_PROFILE} \
		--no-include-email \
		--region ${BASE_AWS_REGION} | awk '{print $$6}' | \
		docker login -u AWS --password-stdin https://$(BASE_ECR_REGISTRY) || aws ecr get-login-password \
			--profile ${BASE_AWS_PROFILE} \
			--region ${BASE_AWS_REGION} | \
			docker login --username AWS --password-stdin https://$(BASE_ECR_REGISTRY)

##   docker/login - login to AWS ECR registry using docker, aws configuration need to be set first
.PHONY:docker/login
docker/login:
	@aws ecr get-login \
		--profile ${AWS_PROFILE} \
		--no-include-email \
		--region ${AWS_REGION} | awk '{print $$6}' | \
		docker login -u AWS --password-stdin https://$(ECR_REGISTRY) || aws ecr get-login-password \
			--profile ${AWS_PROFILE} \
			--region ${AWS_REGION} | \
			docker login --username AWS --password-stdin https://$(ECR_REGISTRY)

##   docker/base/image/pull - pull base image from ECR
docker/base/image/pull: docker/login/base
	docker pull $(BASE_ECR_REGISTRY)/python:3.10-slim-buster-runner
	docker pull $(BASE_ECR_REGISTRY)/python:3.10-slim-buster-builder

##   docker/image/build - build lambda package for each environment and put all of them into one docker container
.PHONY: docker/image/build
docker/image/build: docker/login/base
	@echo "Building docker image"
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg REVISION=$(REVISION) \
		--build-arg BUILDTIME=$(BUILDTIME) \
		--target main \
		-t $(API_ECR_REGISTRY_URI):$(VERSION) \
		-t $(API_ECR_REGISTRY_URI):$(REVISION) .
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg REVISION=$(REVISION) \
		--build-arg BUILDTIME=$(BUILDTIME) \
		--target test \
		-t $(TEST_IMAGE) .

##   docker/sqitch/build - build lambda package for each environment and put all of them into one docker container
.PHONY: docker/sqitch/build
docker/sqitch/build: docker/login/base
	@echo "Building docker image"
	docker build \
		--build-arg VERSION=$(VERSION) \
		--build-arg REVISION=$(REVISION) \
		--build-arg BUILDTIME=$(BUILDTIME) \
		-t $(SQITCH_ECR_REGISTRY_URI):$(VERSION) \
		-t $(SQITCH_ECR_REGISTRY_URI):$(REVISION) \
		-f server/db/pgdb_migrations/Dockerfile \
		server/db/pgdb_migrations/

##   docker/image/push - push an image to a registry
.PHONY: docker/image/push
docker/image/push: docker/login
	@echo "Pushing docker image"
	docker push $(API_ECR_REGISTRY_URI):$(VERSION)
	docker push $(API_ECR_REGISTRY_URI):$(REVISION)

##   docker/sqitch/push - push an image to a registry
.PHONY: docker/sqitch/push
docker/sqitch/push: docker/login
	@echo "Pushing docker image"
	docker push $(SQITCH_ECR_REGISTRY_URI):$(VERSION)
	docker push $(SQITCH_ECR_REGISTRY_URI):$(REVISION)

##   docker/test/dependncies - start dependencies
.PHONY: docker/test/dependencies
docker/test/dependencies:
	@echo "Create network if not exist"
	@docker network create test || exit 0
	@echo "Start postgres"
	@docker run -d \
		--env-file .env.sample \
		--mount type=tmpfs,tmpfs-size=512M,destination=/var/lib/postgresql/data \
		--network test \
		--name postgres \
		--rm \
		$(POSTGRES_IMAGE)
	@sleep 10
	@docker logs --tail 10 postgres
	@echo "Start aggregates-postgres"
	@docker run -d \
		--env-file .env.sample \
		--mount type=tmpfs,tmpfs-size=512M,destination=/var/lib/postgresql/data \
		--network test \
		--name postgres-aggregates \
		--rm \
		$(POSTGRES_IMAGE)
	@sleep 10
	@docker logs --tail 10 postgres-aggregates
	@echo "Start elasticsearch"
	@docker run -d \
		-e "discovery.type=single-node" \
		--env-file .env.sample \
		--network test \
		--name elasticsearch \
		--mount type=tmpfs,tmpfs-size=4096M,destination=/var/lib/elasticsearch/data \
		--rm $(ELASTICSEARCH_IMAGE)
	@sleep 10
	@docker logs --tail 10 elasticsearch


##   docker/test/exec - run tests with docker
.PHONY:docker/test/exec
docker/test/exec: docker/image/build docker/sqitch/build
	@echo "Apply migrations"
	@docker run \
		--env-file .env.sample \
		--network test \
		--name sqitch \
		--rm \
		$(SQITCH_ECR_REGISTRY_URI):$(REVISION) -- --verify
	@echo "Run tests"
	@docker run --rm \
		--network test \
		--env-file .env.sample \
		-v $(PWD):/app \
		$(TEST_IMAGE) make VENV=/venv test

##   docker/test - run tests with docker
.PHONY:docker/test
docker/test: docker/test/dependencies docker/test/exec docker/clean

##   docker/clean - mocked command to support existing pipeline in jenkins
.PHONY:docker/clean
docker/clean:
	@echo "Nothing to clean inside docker, stopping containers if running"
	@docker stop postgres || exit 0
	@docker stop postgres-aggregates || exit 0
	@docker stop elasticsearch || exit 0
	@docker network rm test || exit 0
	@docker volume prune -f

##   docker/<local target> - universal docker wrapper for local targets
docker/%:
	docker run --rm \
		--env-file .env.sample \
		-v $(PWD):/app \
		$(TEST_IMAGE) make VENV=/venv $*
