From 4f6f188f87415b5327fa7d7480db6d1eb29d46ad Mon Sep 17 00:00:00 2001 From: tkrop Date: Thu, 4 Jan 2024 15:36:16 +0100 Subject: [PATCH] feat: add separate config (#8) Signed-off-by: tkrop --- config/.codacy.yaml | 8 + config/.gitleaks.toml | 12 + config/.golangci.yaml | 201 ++++++ config/.markdownlint.yaml | 35 + config/Makefile | 21 + config/Makefile.base | 1317 +++++++++++++++++++++++++++++++++++++ config/Makefile.defs | 24 + config/Makefile.vars | 30 + config/revive.toml | 108 +++ 9 files changed, 1756 insertions(+) create mode 100644 config/.codacy.yaml create mode 100644 config/.gitleaks.toml create mode 100644 config/.golangci.yaml create mode 100644 config/.markdownlint.yaml create mode 100644 config/Makefile create mode 100644 config/Makefile.base create mode 100644 config/Makefile.defs create mode 100644 config/Makefile.vars create mode 100644 config/revive.toml diff --git a/config/.codacy.yaml b/config/.codacy.yaml new file mode 100644 index 0000000..38a96a7 --- /dev/null +++ b/config/.codacy.yaml @@ -0,0 +1,8 @@ +engines: + duplication: + minTokenMatch: 50 + exclude_paths: + - "**/*_test.go" + revive: + exclude_paths: + - "**/mock_*_test.go" diff --git a/config/.gitleaks.toml b/config/.gitleaks.toml new file mode 100644 index 0000000..b66bdee --- /dev/null +++ b/config/.gitleaks.toml @@ -0,0 +1,12 @@ +[extend] + useDefault = true + +[allowlist] + description = "Allowlist false positives" + regexTarget = "match" + regexes = [ + # Mark CDP build secret_version as false positive since it does not contain a secret. + '''(?i)(?:secret_version:)(?:['|\"|\s]{0,5})([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s]{0,5}|$)''', + # Mark api-id or api_id or x-api-id as false positive since they are uuids that are used in OAS and stacksets to track apis. + '''(?i)(?:api[_-]id(?:['|\"|\s|:]{0,5}))(?:['|\"|\s]{0,5})([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:['|\"|\n|\r|\s]{0,5}|$)''' + ] diff --git a/config/.golangci.yaml b/config/.golangci.yaml new file mode 100644 index 0000000..970bbcd --- /dev/null +++ b/config/.golangci.yaml @@ -0,0 +1,201 @@ +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + +linters: {} + # Placeholder for dynamically enabled linters. + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 (we recommend 10-20) + max-same-issues: 10 + + # Use default exclusions for common false positives as defined in + # https://golangci-lint.run/usage/false-positives/#default-exclusions + # Default: true (we use false to sync behavior with Codacy) + exclude-use-default: false + + # Defining manually exclusions that make sense. + exclude-rules: + # Exclude go:generate directives from line length checking. + - source: "^//\\s*go:generate\\s" + linters: [ lll, revive ] + # Exclude magic number in time setups and bit shifting. + - source: "[0-9]+ ?\\* ?time\\.|(<<|>>) ?[0-9]+|[0-9]+ ?(<<|>>)" + linters: [ gomnd ] + # Exclude certain standards from being applied in test. + - path: "_test\\.go" + linters: [ bodyclose, contextcheck, dupl, funlen, goconst, gosec, noctx, + goerr113, wrapcheck ] + + # Exclude error return value check because of too many false positives. + - text: 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked' + linters: [ errcheck ] + # Exclude certain revive standards from being applied in tests. + - path: "_test\\.go" + text: "^(max-public-structs|function-length|cognitive-complexity):" + linters: [ revive ] + # Exclude dots in unfinished thoughts. + - source: "(noinspection|TODO)" + linters: [ godot ] + + +# This contains only configs which differ from defaults. For other configs see +# https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 (we allow up to 20) + max-complexity: 20 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 (we recommend 10.0 as baseline) + package-average: 10.0 + + gocognit: + # Minimal code complexity to report. + # Default: 30 (we recommend 10-20) + min-complexity: 20 + + lll: + # Max line length, lines longer will be reported. '\t' is counted as 1 + # character by default, and can be changed with the tab-width option. + # Default: 120 (we recommend 80 but compromise at 100) + line-length: 100 + # Tab width in spaces. + # Default: 1 (go uses 4 for visualization) + tab-width: 4 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] (but some lll does not need explanation) + allow-no-explanation: [ lll, wrapcheck ] + # Enable to require an explanation of nonzero length after each nolint + # directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being + # suppressed. + # Default: false + require-specific: true + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + - shadow # too strict to always work around + + gosec: + # To specify a set of rules to explicitly exclude. + # Available rules: https://github.com/securego/gosec#available-rules + # Default: [] (issues are fixed) + excludes: [ G307 ] + + gocritic: + # Settings passed to gocritic. The settings key is the name of a supported + # gocritic checker. The list of supported checkers can be find in + # https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + tenv: + # The option `all` will run the linter on the whole test files regardless + # of method signatures. Otherwise, only methods that take `*testing.T`, + # `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + revive: + # Enable all available rules. + # Default: false + enable-all-rules: true + # When set to false, ignores files with "GENERATED" header, similar to golint. + # See https://github.com/mgechev/revive#available-rules for details. + # Default: false + ignore-generated-header: true + # Sets the default severity. + # See https://github.com/mgechev/revive#configuration + # Default: warning + severity: error + + rules: + # No need to enforce a file header. + - name: file-header + disabled: true + # Reports on each file in a package. + - name: package-comments + disabled: true + # Reports on comments not matching the name as first word. + - name: exported + disabled: true + # Ident error flow is buggy and throws false alerts. + - name: indent-error-flow + disabled: true + # No need to exclude import shadowing. + - name: import-shadowing + disabled: true + # Restricted alias naming conflicts with '.'-imports. + - name: import-alias-naming + disabled: true + # Exluding '.'-import makes test package separation unnecessary difficult. + - name: dot-imports + disabled: true + # Fails to exclude nolint directives from reporting. + - name: comment-spacings + disabled: true + # Fails to disable writers that actually cannot return errors. + - name: unhandled-error + disabled: true + # Fails to detect and exclude type safe usages of type assertions. + - name: unchecked-type-assertion + disabled: true + # Fails to restrict sufficiently in switches with numeric values. + - name: add-constant + disabled: true + # Rule prevents intentional usage of similar variable names. + - name: flag-parameter + disabled: true + # Rule prevents intentional usage of similar private method names. + - name: confusing-naming + disabled: true + + # Enables a more experienced cyclomatic complexity (we enabled a lot of + # rules to counter-act the complexity trap). + - name: cyclomatic + arguments: [20] + # Enables a more experienced cognitive complexity (we enabled a lot of + # rules to counter-act the complexity trap). + - name: cognitive-complexity + arguments: [20] + # Limit line-length to increase readability. + - name: line-length-limit + arguments: [100] + # We are a bit more relaxed with function length consistent with funlen. + - name: function-length + arguments: [40, 60] + # Limit arguments of functions to the maximum understandable value. + - name: argument-limit + arguments: [6] + # Limit results of functions to the maximum understandable value. + - name: function-result-limit + arguments: [4] + # Raise the limit a bit to allow more complex package models. + - name: max-public-structs + arguments: [8] + # I do not know what I'm doing here... + - name: banned-characters + arguments: ["Ω", "Σ", "σ"] diff --git a/config/.markdownlint.yaml b/config/.markdownlint.yaml new file mode 100644 index 0000000..5769f66 --- /dev/null +++ b/config/.markdownlint.yaml @@ -0,0 +1,35 @@ +# Default state for all rules +default: true + +# MD012/no-multiple-blanks - Multiple consecutive blank lines +MD012: + # Consecutive blank lines + maximum: 3 + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 80 + # Number of characters for headings + heading_line_length: 80 + # Number of characters for code blocks + code_block_line_length: 80 + # Include code blocks + code_blocks: true + # Include tables + tables: true + # Include headings + headings: true + # Include headings + headers: true + # Strict length checking + strict: true + # Stern length checking + stern: false + +# MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines +MD022: + # Blank lines above heading + lines_above: 2 + # Blank lines below heading + lines_below: 1 diff --git a/config/Makefile b/config/Makefile new file mode 100644 index 0000000..9c0b6c5 --- /dev/null +++ b/config/Makefile @@ -0,0 +1,21 @@ +SHELL := /bin/bash + +# Include custom variables to modify behavior. +ifneq ("$(wildcard Makefile.vars)","") + include Makefile.vars +else + $(warning warning: please customize variables in Makefile.vars) +endif + +GOBIN ?= $(shell go env GOPATH)/bin +GOMAKE ?= github.com/tkrop/go-make@v0.0.16 +TARGETS := $(shell command -v go-make >/dev/null || \ + go install $(GOMAKE) && go-make targets) + +# Declare all targets phony to make them available for auto-completion. +.PHONY: $(TARGETS) + +# Delegate all targets to go-make in a single call suppressing other targets. +$(eval $(wordlist 1,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))::;@:) +$(firstword $(MAKECMDGOALS) all):: + $(GOBIN)/go-make $(MAKEFLAGS) $(MAKECMDGOALS); diff --git a/config/Makefile.base b/config/Makefile.base new file mode 100644 index 0000000..91d162c --- /dev/null +++ b/config/Makefile.base @@ -0,0 +1,1317 @@ +# === do not change this Makefile! === +# See MAKEFILE.md for documentation. + +SHELL := /bin/bash + +DIR_RUN := $(CURDIR)/run +DIR_CRED := $(DIR_RUN)/creds +DIR_BUILD := $(CURDIR)/build +DIR_TOOLS := $(DIR_BUILD)/tools +DIR_CONFIG := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +FIND := find . ! -path "./run/*" ! -path "./build/*" +FILTER := sed "s|^./||" | grep -E "^[a-z0-9/_-]*\.go$$" + +# Include custom variables to modify behavior. +ifneq ("$(wildcard Makefile.vars)","") + include Makefile.vars +else + $(warning info: please customize variables in Makefile.vars) +endif + +# Setup sensible defaults for configuration variables. +CODE_QUALITY ?= base +TEST_TIMEOUT ?= 10s + +# Function to search default config files +search-default = $(firstword $(wildcard $(1)) $(wildcard $(DIR_CONFIG)$(1))) + +FILE_MAKE ?= $(call search-default,Makefile) +FILE_VARS ?= $(call search-default,Makefile.vars) +FILE_GOLANGCI ?= $(call search-default,.golangci.yaml) +FILE_MARKDOWN ?= $(call search-default,.markdownlint.yaml) +FILE_GITLEAKS ?= $(call search-default,.gitleaks.toml) +FILE_REVIVE ?= $(call search-default,revive.toml) +FILE_CODACY ?= $(call search-default,.codacy.yaml) + +FILE_CONTAINER ?= Dockerfile +FILE_DELIVERY ?= delivery.yaml +FILE_DELIVERY_REGEX ?= (cdp-runtime\/go-|go-version: \^?)[0-9.]* + +REPOSITORY ?= $(shell git remote get-url origin | \ + sed "s/^https:\/\///; s/^git@//; s/.git$$//; s/:/\//") +GITHOSTNAME ?= $(word 1,$(subst /, ,$(REPOSITORY))) +GITORGNAME ?= $(word 2,$(subst /, ,$(REPOSITORY))) +GITREPONAME ?= $(word 3,$(subst /, ,$(REPOSITORY))) + +TEAM ?= $(shell cat .zappr.yaml | grep "X-Zalando-Team" | \ + sed "s/.*:[[:space:]]*\([a-z-]*\).*/\1/") + + +TOOLS_NPM := $(TOOLS_NPM) \ + markdownlint-cli +TOOLS_GO := $(TOOLS_GO) \ + github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 \ + github.com/zalando/zally/cli/zally \ + golang.org/x/vuln/cmd/govulncheck \ + github.com/uudashr/gocognit/cmd/gocognit \ + github.com/fzipp/gocyclo/cmd/gocyclo \ + github.com/mgechev/revive@v1.2.3 \ + github.com/securego/gosec/v2/cmd/gosec \ + github.com/tsenart/deadcode \ + github.com/tsenart/vegeta \ + honnef.co/go/tools/cmd/staticcheck \ + github.com/zricethezav/gitleaks/v8 \ + github.com/icholy/gomajor \ + github.com/golang/mock/mockgen \ + github.com/tkrop/go-testing/cmd/mock \ + github.com/tkrop/go-make +TOOLS_SH := $(TOOLS_SH) \ + github.com/anchore/syft \ + github.com/anchore/grype + +# function for correct gol-package handling. +go-pkg = $(shell awk -v mode="$(1)" -v filter="$(3)" -v not="$(4)" ' \ + BEGIN { FS = "[/@]"; RS = "[ \n\r]" } { \ + field = NF; \ + if (version = index($$0, "@")) { field-- } \ + if ($$(field) ~ "v[0-9]+") { field-- } \ + if (!filter || \ + (not && ($$(field) !~ filter)) || \ + (!not && ($$(field) ~ filter))) { \ + if (mode == "cmd") { \ + if (!($$(field) in map)) { \ + print $$(field); map[$$(field)]++ \ + } \ + } else if (mode == "install" || mode == "update") { \ + if (!((package = strip(version)) in map)) { \ + if (version) { print $$0; } else { print $$0 "@latest" } \ + map[package]++ \ + } \ + } else if (mode == "strip") { \ + if (!((package = strip(version)) in map)) { \ + print package; map[package]++ \ + } \ + } \ + } \ + } \ + function strip(version) { \ + if (version) { \ + return substr($$0, 1, index($$0, "@") - 1) \ + } else { return $$0 } \ + }' <<<"$(2)") + + +BROWSER ?= xdg-open +VERSION ?= snapshot +ifeq ($(wildcard VERSION),VERSION) + BUILD_VERSION := v$(shell cat VERSION) +else + BUILD_VERSION ?= $(VERSION) +endif + +IMAGE_PUSH ?= pulls +IMAGE_VERSION ?= $(VERSION) + +ifeq ($(words $(subst /, ,$(IMAGE_NAME))),3) + IMAGE_HOST ?= $(word 1,$(subst /, ,$(IMAGE_NAME))) + IMAGE_TEAM ?= $(word 2,$(subst /, ,$(IMAGE_NAME))) + IMAGE_ARTIFACT ?= $(word 3,$(subst /, ,$(IMAGE_NAME))) +else + IMAGE_HOST ?= pierone.stups.zalan.do + IMAGE_TEAM ?= $(TEAM) + IMAGE_ARTIFACT ?= $(GITREPONAME) +endif +IMAGE ?= $(IMAGE_HOST)/$(IMAGE_TEAM)/$(IMAGE_ARTIFACT):$(IMAGE_VERSION) + + +DB_HOST ?= 127.0.0.1 +DB_PORT ?= 5432 +DB_NAME ?= db +DB_USER ?= user +DB_PASSWORD ?= pass +DB_VERSION ?= latest +DB_IMAGE ?= postgres:$(DB_VERSION) + +AWS_SERVICES ?= sqs s3 +AWS_VERSION ?= latest +AWS_IMAGE ?= localstack/localstack:$(AWS_VERSION) + +# Setup codacy integration. +CODACY ?= enabled +ifdef CDP_PULL_REQUEST_NUMBER + CODACY_CONTINUE ?= true +else + CODACY_CONTINUE ?= false +endif +CODACY_PROVIDER ?= ghe +CODACY_USER ?= $(GITORGNAME) +CODACY_PROJECT ?= $(GITREPONAME) +CODACY_API_BASE_URL ?= https://codacy.bus.zalan.do +CODACY_CLIENTS ?= aligncheck deadcode +CODACY_BINARIES ?= gosec staticcheck +CODACY_GOSEC_VERSION ?= 0.4.5 +CODACY_STATICCHECK_VERSION ?= 3.0.12 + +# Function for conversion of variables name. +upper = $(shell echo "$(1)" | tr '[:lower:]' '[:upper:]') + + +# Default target list for all and cdp builds. +TARGETS_ALL ?= init test lint build image +TARGETS_INIT ?= init-hooks init-go $(if $(filter $(CODACY),enabled),init-codacy,) +TARGETS_CLEAN ?= clean-build +TARGETS_UPDATE ?= update-go update-deps update-make +TARGETS_COMMIT ?= test-go test-unit lint-leaks? lint-$(CODE_QUALITY) lint-markdown +TARGETS_TEST ?= test-all $(if $(filter $(CODACY),enabled),test-upload,) +TARGETS_LINT ?= lint-leaks? lint-$(CODE_QUALITY) lint-markdown lint-apis \ + $(if $(filter $(CODACY),enabled),lint-codacy,) + +INIT_MAKE ?= $(notdir $(FILE_MAKE) $(FILE_VARS)) +UPDATE_MAKE ?= $(FILE_MAKE) $(FILE_GOLANGCI) $(FILE_MARKDOWN) \ + $(FILE_GITLEAKS) $(FILE_CODACY) $(FILE_REVIVE) + +# Setup go to use desired and consistent go versions. +GOVERSION := $(shell go version | sed -E "s/.*go([0-9]+\.[0-9]+).*/\1/") + +# Export private repositories not to be downloaded. +export GOPRIVATE := github.bus.zalan.do +export GOBIN ?= $(shell go env GOPATH)/bin + + +# General setup of tokens for run-targets (not to be modified) + +# Often used token setup functions. +run-token-create = \ + ztoken > $(DIR_CRED)/token; echo "Bearer" > $(DIR_CRED)/type +run-token-link = \ + test -n "$(1)" && test -n "$(2)" && test -n "$(3)" && ( \ + test -h "$(DIR_CRED)/$(1)-$(2)" || ln -s type "$(DIR_CRED)/$(1)-$(2)" && \ + test -h "$(DIR_CRED)/$(1)-$(3)" || ln -s token "$(DIR_CRED)/$(1)-$(3)" \ + ) || test -n "$(1)" && test -n "$(2)" && test -z "$(3)" && ( \ + test -h "$(DIR_CRED)/$(1)" || ln -s type "$(DIR_CRED)/$(1)" && \ + test -h "$(DIR_CRED)/$(2)" || ln -s token "$(DIR_CRED)/$(2)" \ + ) || test -n "$(1)" && test -z "$(2)" && test -z "$(3)" && ( \ + test -h "$(DIR_CRED)/$(1)" || ln -s token "$(DIR_CRED)/$(1)" \ + ) || true + +# Stub function for general setup in run-targets. +run-setup = true +# Stub function for common variables in run-targets. +run-vars = +# Stub function for local runtime variables in run-targets. +run-vars-local = +# Stub function for container specific runtime variables in run-targets. +run-vars-image = $(call run-vars-docker) +# Stub function to set up aws localstack run-target. +run-aws-setup = true + +ifneq ("$(wildcard Makefile.defs)","") + include Makefile.defs +else + $(warning info: please define custom functions in Makefile.defs) +endif + + +# Setup default environment variables. +SOURCES := $(shell $(FIND) -name "*.go" ! -name "mock_*_test.go" | $(FILTER)) +MODULES := $(shell echo $(SOURCES) | xargs -r dirname | sort -u) +COMMANDS := $(shell $(FIND) -name "main.go" | xargs -r readlink -f | \ + sed -E "s/^.*\/([^/]*)\/main.go$$/\1/" | sort -u) +COMMANDS_PKG := $(shell $(FIND) -name "main.go" | xargs -r readlink -f | \ + sed -E "s/^(.*\/([^/]*))\/main.go$$/\2=\1/; s|$(CURDIR)|.|") +COMMANDS_REGEX := $(shell echo "^$(COMMANDS)$$" | tr ' ' '|' ) +COMMANDS_GO := $(call go-pkg,cmd,$(TOOLS_GO),$(COMMANDS_REGEX),not) +COMMANDS_SH := $(call go-pkg,cmd,$(TOOLS_SH),$(COMMANDS_REGEX),not) + + +# Setup optimized golang mock setup environment. +MOCK_MATCH_DST := ^(.*)/(.*)://go:generate.*-destination=([^ ]*).*$$ +MOCK_MATCH_SRC := ^(.*)/(.*)://go:generate.*-source=([^ ]*).*$$ +MOCK_TARGETS := $(shell grep "//go:generate[[:space:]]*mockgen" $(SOURCES) | \ + sed -E "s|^(.*)|./\1|; s|$(MOCK_MATCH_DST)|\1/\3=\1/\2|" | sort -u) +MOCK_SOURCES := $(shell grep "//go:generate[[:space:]]*mockgen.*-source=" $(SOURCES) | \ + sed -E "s|^(.*)|./\1|; s|$(MOCK_MATCH_SRC)|\1/\3|" | sort -u | \ + xargs -r readlink -f | sed "s|$(PWD)/||g") +MOCKS := $(shell for TARGET in $(MOCK_TARGETS); \ + do echo "$${TARGET%%=*}"; done | sort -u) + +# Prepare make targets lists with phony execution. +TARGETS_PUBLISH := $(addprefix publish-, all $(COMMANDS)) +TARGETS_TEST_INIT := test-clean init-sources init-hooks +TARGETS_INIT_MAKE := $(addprefix init/,$(INIT_MAKE)) +TARGETS_INIT_CODACY := $(addprefix init-, $(CODACY_BINARIES)) +TARGETS_LINT_CODACY_CLIENTS := $(addprefix lint-, $(CODACY_CLIENTS)) +TARGETS_LINT_CODACY_BINARIES := $(addprefix lint-, $(CODACY_BINARIES)) +TARGETS_IMAGE := $(shell $(FIND) -type f -name "$(FILE_CONTAINER)*" | sed 's|^./||') +TARGETS_IMAGE_BUILD := $(addprefix image-build/, $(TARGETS_IMAGE)) +TARGETS_IMAGE_PUSH := $(addprefix image-push/, $(TARGETS_IMAGE)) +TARGETS_BUILD := $(addprefix build-, $(COMMANDS)) +TARGETS_BUILD_LINUX := $(addprefix $(DIR_BUILD)/linux/, $(COMMANDS)) +TARGETS_INSTALL := $(addprefix install-, $(COMMANDS)) +TARGETS_INSTALL_GO := $(addprefix install-, $(COMMANDS_GO)) +TARGETS_INSTALL_SH := $(addprefix install-, $(COMMANDS_SH)) +TARGETS_INSTALL_NPM := $(addprefix install-, $(TOOLS_NPM:-cli=)) +TARGETS_INSTALL_ALL := $(TARGETS_INSTALL) $(TARGETS_INSTALL_GO) $(TARGETS_INSTALL_NPM) +TARGETS_UNINSTALL := $(addprefix uninstall-, $(COMMANDS)) +TARGETS_UNINSTALL_GO := $(addprefix uninstall-, $(COMMANDS_GO)) +TARGETS_UNINSTALL_SH := $(addprefix uninstall-, $(COMMANDS_SH)) +TARGETS_UNINSTALL_NPM := $(addprefix uninstall-, $(TOOLS_NPM:-cli=)) +TARGETS_UNINSTALL_CODACY := $(addprefix uninstall-codacy-, $(CODACY_BINARIES)) +TARGETS_UNINSTALL_ALL := $(TARGETS_UNINSTALL) $(TARGETS_UNINSTALL_GO) \ + $(TARGETS_UNINSTALL_NPM) $(TARGETS_UNINSTALL_CODACY) +TARGETS_RUN := $(addprefix run-, $(COMMANDS)) +TARGETS_RUN_GO := $(addprefix run-go-, $(COMMANDS)) +TARGETS_RUN_IMAGE := $(addprefix run-image-, $(COMMANDS)) +TARGETS_RUN_CLEAN := $(addprefix run-clean-, $(COMMANDS) db aws) +TARGETS_CLEAN_ALL := clean-init $(TARGETS_CLEAN) clean-run $(TARGETS_UNINSTALL_ALL) +TARGETS_CLEAN_RUN := $(addprefix clean-run-, $(COMMANDS) db aws) +TARGETS_UPDATE_GO := $(addprefix update-,$(COMMANDS_GO)) +TARGETS_UPDATE_MAKE := $(addprefix update/,$(UPDATE_MAKE)) +TARGETS_UPDATE_MAKE? := $(addsuffix ?,$(TARGETS_UPDATE_MAKE)) +TARGETS_UPDATE_ALL := update-go update-deps update-tools update-make +TARGETS_UPDATE_ALL? := udate-go? update-deps? update-make? +TARGETS_UPDATE? := $(filter $(addsuffix ?,$(TARGETS_UPDATE)), \ + $(TARGETS_UPDATE_ALL?) $(TARGETS_UPDATE_MAKE?)) + +# Setup docker or podman command. +IMAGE_CMD ?= $(shell command -v docker || command -v podman) +ifndef IMAGE_CMD + $(error error: docker/podman command not found) +endif + + +# Helper function to resolve match position of word in text. +findany = $(strip $(foreach word,$(1),$(findstring $(word),$(2)))) +pos-recurs = $(if $(findstring $(1),$(2)),$(call pos-recurs,$(1),\ + $(wordlist 2,$(words $(2)),$(2)),x $(3)),$(3)) +pos = $(words $(call pos-recurs,$(1),$(2))) + +# match commands that support arguments ... +CMDWORDS := targets bump release update test- lint run- +CMDMATCH = $(call findany,$(CMDWORDS),$(MAKECMDGOALS))% + +# If any argument contains "run*", "test*", "lint*", "bump" ... +ifneq ($(CMDMATCH),%) + CMD := $(filter $(CMDMATCH),$(MAKECMDGOALS)) + POS = $(call pos,$(CMD),$(MAKECMDGOALS)) + # ... then use the rest as arguments for "run/test-" ... + CMDARGS := $(wordlist $(shell expr $(POS) + 1),\ + $(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + # ...and turn them into do-nothing targets. + $(eval $(CMDARGS)::;@:) + RUNARGS ?= $(CMDARGS) + $(shell if [ -n "$(RUNARGS)" ]; then \ + echo "info: captured arguments [$(RUNARGS)]" >/dev/stderr; \ + fi) +endif + + +## Standard: default targets to test, lint, and build. + +#@ executes the default targets. +all:: $(TARGETS_ALL) +#@ executes the default targets after cleaning up. +all-clean:: clean clean-run all +#@ executes the pre-commit check targets. +commit:: $(TARGETS_COMMIT) +$(DIR_BUILD) $(DIR_RUN) $(DIR_CRED): + @if [ ! -d "$@" ]; then mkdir -p $@; fi; + + +## Support: targets to support processes and users. + +#@ prints this help. +help:: + @cat $(MAKEFILE_LIST) | awk ' \ + BEGIN { \ + printf("\n\033[1mUsage:\033[0m $(MAKE) \033[36m\033[0m\n") \ + } \ + /^## .*/ { \ + if (i = index($$0, ":")) { \ + printf("\n\033[1m%s\033[0m%s\n", \ + substr($$0, 4, i - 4), substr($$0, i)) \ + } else { \ + printf("\n\033[1m%s\033[0m\n", substr($$0, 4)) \ + } next \ + } \ + /^#@ / { \ + if (i = index($$0, ":")) { \ + printf(" \033[36m%-25s\033[0m %s\n", \ + substr($$0, 4, i - 4), substr($$0, i + 2)) \ + } else { line = substr($$0, 4) } next \ + } \ + /^[a-zA-Z_0-9?-]+:/ { \ + if (line) { \ + target = substr($$1, 1, length($$1) - 1); \ + if (i = index(line, " # ")) { \ + target = target " " substr(line, 1, i - 1); \ + line = substr(line, i + 3) \ + } \ + printf(" \033[36m%-25s\033[0m %s\n", target, line); \ + line = "" \ + } \ + }'; + +#@ show the effective makefile. +show:: + @make --no-builtin-rules --no-builtin-variables --print-data-base \ + --question --makefile=$(firstword $(MAKEFILE_LIST)) 2>/dev/null || true; +#@ # list all available targets. +targets:: + @make --no-builtin-rules --no-builtin-variables --print-data-base \ + --question --makefile=$(firstword $(MAKEFILE_LIST)) 2>/dev/null | \ + if [ "$(RUNARGS)" != "raw" ]; then awk -v RS= -F: ' \ + /(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/ { \ + if ($$1 !~ "^[#.]") { print $$1 } \ + }' | sort -d | egrep -v -e '^[^[:alnum:]]'; \ + else cat -; fi || true; + +#@ create a clone of the base repository to update from. +BASE := git@github.com:tkrop/go-make.git +setup/base:: + @$(eval BASEDIR = $(shell mktemp -d)) ( \ + trap "rm -rf $${DIR}" INT TERM EXIT; \ + while pgrep $(MAKE) > /dev/null; do sleep 1; done; \ + rm -rf $${DIR} \ + ) & \ + git clone --no-checkout --depth 2 --single-branch \ + --branch main $(BASE) $(BASEDIR) 2> /dev/null; \ + + +#@ pushes the current branch to the same branch of the origin. +push:: + @git push --set-upstream origin \ + $$(git branch 2> /dev/null | sed -e '/^[^*]/d; s/* \(.*\)/\1/'); +#@ pushes the latest changes to the previous commit of the origin. +fix-commit:: + @git commit --amend --no-edit && git push --force; +#@ pushes the latest changes to the previous commit of the origin without verifying them. +fix-no-verify:: + @git commit --amend --no-edit --no-verify && git push --force; +#@ pushes the latest changes to the previous commit of the origin updating the commit message. +fix-comment:: + @git commit --amend && git push --force; + +#@ # update version and prepare release of the software. +bump:: + @if [ -z "$(RUNARGS)" ]; then ARGS="patch"; else ARGS="$(RUNARGS)"; fi; \ + if [ ! -e VERSION ]; then echo "" >VERSION; fi; \ + if [[ "$${ARGS}" =~ ^(major|minor|patch|[.+-]*)[+-]?$$ ]]; then \ + VERSION="$$(awk -v arg=$${ARGS} '{ \ + op["major"] = 1; op["minor"] = 2; op["patch"] = 3; \ + if ((pos = op[substr(arg,1,5)]) == 0) { pos = length(arg) }; \ + patsplit($$0, v, "[0-9]*", seps); len = length(seps); \ + while (len <= 3) { seps[len-1] = "."; len++ }; \ + if (arg !~ "-$$") { v[pos]++ } else { v[pos]-- }; \ + for (i = 1; i < len; i++) { printf("%01d%s", v[i], seps[i]) } \ + }' VERSION)"; \ + elif ! [[ "$${ARGS}" =~ ^[0-9]+(\.[0-9]+){0,2}(-.*)?$$ ]]; then \ + echo "error: invalid new version [$${ARGS}]"; exit 1; \ + else VERSION="$${ARGS}"; fi; echo $${VERSION} >VERSION; \ + echo "Bumped version to $${VERSION} for auto release!"; \ + + +#@ # release a fixed version of the software as library. +release:: + @if [ -f VERSION ]; then VERSION="$$(cat VERSION)"; fi; \ + if [[ "$(RUNARGS)" =~ ^[0-9]+(\.[0-9]+){0,2}(-.*)?$$ ]]; then \ + VERSION="$(RUNARGS)"; \ + fi; \ + if [ -n "$${VERSION}" -a -z "$$(git tag -l "v$${VERSION}")" ]; then \ + ( git gh-release "v$${VERSION}" ) || ( \ + git config user.name "$$(git log -n 1 --pretty=format:%an)" && \ + git config user.email "$$(git log -n 1 --pretty=format:%ae)" && \ + git tag --message "tag: new version $${VERSION}" "v$${VERSION}" && \ + git push --follow-tags origin "v$${VERSION}" \ + ) && echo "Added release tag v$${VERSION} to repository!"; \ + fi; \ + +publish:: $(TARGETS_PUBLISH) +$(TARGETS_PUBLISH): publish-%: + @if [ "$*" != "all" ]; then \ + if [ -f "cmd/$*/main.go" ]; then CMD="/cmd/$*"; else exit 0; fi; \ + fi; \ + if [ -f VERSION ]; then VERSION="$$(cat VERSION)"; else \ + VERSION="$$(TZ=UTC0 git show --quiet --date='format-local:%Y%m%d%H%M%S' \ + --format="0.0.0-%cd-%H" | cut -c -33)"; \ + fi; \ + TARGET="$(REPOSITORY)$${CMD}@v$${VERSION}"; \ + if [ "$(GITHOSTNAME)" == "github.com" ]; then \ + if [ -z "$${CMD}" ]; then \ + echo "publish: curl $${TARGET}"; \ + curl --silent --show-error --fail --location \ + https://sum.golang.org/lookup/$${TARGET};\ + fi; \ + if [ -f ".$${CMD}/main.go" ]; then \ + echo "publish: go install $${TARGET}"; \ + go install $${TARGET}; \ + fi; \ + fi; + + +#@ install all software created by the project. +install:: $(TARGETS_INSTALL) +#@ install all software created and used by the project. +install-all:: $(TARGETS_INSTALL_ALL) +#@ install-*: install the matched software command or service. + +# install go tools used by the project. +$(TARGETS_INSTALL_GO):: install-%: $(GOBIN)/% +$(addprefix $(GOBIN)/,$(COMMANDS_GO)): $(GOBIN)/%: + go install $(call go-pkg,install,$(TOOLS_GO),^$*$$); +# install go tools providing an install.sh script. +$(TARGETS_INSTALL_SH):: install-%: $(GOBIN)/% +$(addprefix $(GOBIN)/,$(COMMANDS_SH)): $(GOBIN)/%: + @if ! command -v $*; then \ + curl --silent --show-error --fail --location \ + https://raw.githubusercontent.com/anchore/$*/main/install.sh | \ + sh -s -- -b $(GOBIN); \ + fi; +# install npm tools used by the project. +$(TARGETS_INSTALL_NPM):: install-%: $(NVM_BIN)/% +$(addprefix $(NVM_BIN)/,$(TOOLS_NPM:-cli=)): $(NVM_BIN)/%: + @if command -v npm &> /dev/null && ! command -v $*; then \ + echo "npm install --global ^$*$$"; \ + npm install --global $(filter $*-cli,$(TOOLS_NPM)); \ + fi; + +#@ install software command or service created by the project. +$(TARGETS_INSTALL):: install-%: $(GOBIN)/% +$(addprefix $(GOBIN)/,$(COMMANDS)): $(GOBIN)/%: $(DIR_BUILD)/% + cp -f $< $(GOBIN)/$*; + +#@ uninstall all software created by the project. +uninstall:: $(TARGETS_UNINSTALL) +#@ uninstall all software created and used by the projct. +uninstall-all:: $(TARGETS_UNINSTALL_ALL) +#@ uninstall-*: uninstall the matched software command or service. + +# uninstall go tools used by the project. +$(TARGETS_UNINSTALL_GO):: uninstall-%: + @#PACKAGE=$(call go-pkg,strip,$(TOOLS_GO),^$*$$); \ + rm -rf $(wildcard $(GOBIN)/$* $(GOBIN)/$*.config); +# uninstall npm based tools used by the project. +$(TARGETS_UNINSTALL_NPM):: uninstall-%: + @if command -v npm &> /dev/null; then \ + echo "npm uninstall --global $*"; \ + npm uninstall --global $(filter $*-cli,$(TOOLS_NPM)); \ + fi; +# uninstall codacy tools used by the project. +$(TARGETS_UNINSTALL_CODACY):: uninstall-codacy-%: + @VERSION="$(CODACY_$(call upper,$*)_VERSION)"; \ + rm -f "$(GOBIN)/codacy-$*-$${VERSION}"; +# uninstall software command or service created by the project. +$(TARGETS_UNINSTALL):: uninstall-%:; rm -f $(GOBIN)/$*; + + +## Init: targets to initialize tools and (re-)sources. + +#@ initialize the project to prepare it for building. +init:: $(TARGETS_INIT) +#@ initialize the go module and package dependencies. +init-go:: go.sum +go.sum: go.mod + go mod tidy -go=$(GOVERSION); + go mod download; +go.mod: + go mod init "$(REPOSITORY)"; +#@ initialize the pre-commit hook. +init-hooks:: .git/hooks/pre-commit +.git/hooks/pre-commit: + @if [ -n "$$(cat Makefile | grep "^GOMAKE ?=.*")" ]; then \ + echo -ne "#!/bin/sh\n$(MAKE) commit" >$@; chmod 755 $@; \ + fi; \ + +#@ initialize the generated sources. +init-sources:: $(MOCKS) +$(MOCKS):: go.sum $(MOCK_SOURCES) $(GOBIN)/mockgen $(GOBIN)/mock + go generate "$(shell echo $(MOCK_TARGETS) | \ + sed -E "s|.*$@=([^ ]*).*$$|\1|")"; + +#@ initialize the codacy support - if enabled. +init-codacy:: $(TARGETS_INIT_CODACY) +$(TARGETS_INIT_CODACY):: init-%: + @VERSION="$(CODACY_$(call upper,$*)_VERSION)"; \ + FILE="$(GOBIN)/codacy-$*-$${VERSION}"; \ + if [ ! -f $${FILE} ]; then \ + BASE="https://github.com/codacy/codacy-$*/releases/download"; \ + echo curl --silent --location --output $${FILE} \ + $${BASE}/$${VERSION}/codacy-$*-$${VERSION}; \ + curl --silent --location --output $${FILE} \ + $${BASE}/$${VERSION}/codacy-$*-$${VERSION}; \ + chmod 700 $${FILE}; \ + fi; \ + +#@ initialize project by copying config makefile from template. +init-make:: $(TARGETS_INIT_MAKE) +$(TARGETS_INIT_MAKE):: init/%: setup/base + @DIR="$$(pwd)"; FILE="$*"; FILE="$${FILE##$(DIR_CONFIG)}"; \ + cd "$(BASEDIR)"; git show HEAD:config/$${FILE} > $${DIR}/$${FILE} 2>/dev/null; \ + +#@ initialize project by copying Makefile.base from template. +init-make!:: setup/base + @DIR="$$(pwd)"; cd $(BASEDIR); \ + git show HEAD:config/Makefile.base > $${DIR}/Makefile 2>/dev/null; \ + ( echo -e "\n\n# Makefile"; \ + git show HEAD:README.md 2>/dev/null | \ + sed -n '/^## Standard/,/^## Terms/p' | sed '1d;$$d'; \ + git show HEAD:MANUAL.md 2>/dev/null | \ + sed -n '/^## Setup and/,//p'; \ + ) >$${DIR}/MAKEFILE.md; + + +## Test: targets to test the source code (and compiler environment). +TEST_ALL := $(DIR_BUILD)/test-all.cover +TEST_UNIT := $(DIR_BUILD)/test-unit.cover +TEST_BENCH := $(DIR_BUILD)/test-bench.cover + +#@ execute default test set. +test:: $(TARGETS_TEST) +#@ [/] # execute all tests. +test-all:: $(TARGETS_TEST_INIT) $(TEST_ALL) +#@ [/] # execute only unit tests. +test-unit:: $(TARGETS_TEST_INIT) $(TEST_UNIT) +#@ [/] # execute benchmarks. +test-bench:: $(TARGETS_TEST_INIT) $(TEST_BENCH) + +# remove all coverage files of test and benchmarks. +test-clean:: + @if [ -f "$(TEST_ALL)" ]; then rm -vf $(TEST_ALL); fi; \ + if [ -f "$(TEST_UNIT)" ]; then rm -vf $(TEST_UNIT); fi; \ + if [ -f "$(TEST_BENCH)" ]; then rm -vf $(TEST_BENCH); fi; \ + +#@ start the test coverage report. +test-cover:: + @FILE=$$(ls -Art "$(TEST_ALL)" "$(TEST_UNIT)" \ + "$(TEST_BENCH)" 2>/dev/null); \ + go tool cover -html="$${FILE}"; \ + +#@ upload the test coverage report to codacy. +test-upload:: + @FILE=$$(ls -Art "$(TEST_ALL)" "$(TEST_UNIT)" \ + "$(TEST_BENCH)" 2>/dev/null); \ + COMMIT="$$(git log --max-count=1 --pretty=format:"%H")"; \ + SCRIPT="https://coverage.codacy.com/get.sh"; \ + if [ -n "$(CODACY_PROJECT_TOKEN)" ]; then \ + bash <(curl --silent --location $${SCRIPT}) report \ + --project-token $(CODACY_PROJECT_TOKEN) --commit-uuid $${COMMIT} \ + --codacy-api-base-url $(CODACY_API_BASE_URL) --language go \ + --force-coverage-parser go --coverage-reports "$${FILE}"; \ + elif [ -n "$(CODACY_API_TOKEN)" ]; then \ + bash <(curl --silent --location $${SCRIPT}) report \ + --organization-provider $(CODACY_PROVIDER) \ + --username $(CODACY_USER) --project-name $(CODACY_PROJECT) \ + --api-token $(CODACY_API_TOKEN) --commit-uuid $${COMMIT} \ + --codacy-api-base-url $(CODACY_API_BASE_URL) --language go \ + --force-coverage-parser go --coverage-reports "$${FILE}"; \ + fi; \ + +# Function to test versions. +test-go = \ + for VERSION in $${VERSIONS}; do \ + if [ "$(GOVERSION)" != "$${VERSION}" ]; then \ + echo "$${ERROR}: '$(1)' requires $${VERSION}!"; \ + if [[ "$(GOVERSION)" < "$${VERSION}" ]]; then \ + GOCHANGE="upgrade"; CHANGE="downgrade"; \ + else \ + GOCHANGE="downgrade"; CHANGE="upgrade"; \ + fi; \ + echo -e "\t$${GOCHANGE} your local go version to $${VERSION} or "; \ + echo -e "\trun 'make update-go $${VERSION}' to adjust the project!"; \ + exit -1; \ + fi; \ + done; + +#@ test whether project is using the latest go-version. +test-go:: + @ERROR="error: local go version is $(GOVERSION)"; \ + if [ ! -f go.mod ]; then \ + VERSIONS=$$(grep "^go [0-9.]*$$" go.mod | cut -f2 -d' '); \ + $(call test-go,go.mod) \ + fi; \ + if [ -f "$(FILE_DELIVERY)" ]; then \ + VERSIONS="$$(grep -Eo "$(FILE_DELIVERY_REGEX)" \ + "$(FILE_DELIVERY)" | grep -Eo "[0-9.]*" | sort -u)"; \ + $(call test-go,$(FILE_DELIVERY)) \ + fi; \ + +# process test arguments. +testargs = \ + if [ -n "$(RUNARGS)" ]; then ARGS=($(RUNARGS)); \ + if [[ -f "$${ARGS[0]}" && ! -d "$${ARGS[0]}" && \ + "$${ARGS[0]}" == *_test.go ]]; then \ + find $$(dirname $(RUNARGS) | sort -u) \ + -maxdepth 1 -a -name "*.go" -a ! -name "*_test.go" \ + -o -name "common_test.go" -o -name "mock_*_test.go" | \ + sed "s|^|./|"; \ + echo $(addprefix ./,$(RUNARGS)); \ + elif [[ -d "$${ARGS[0]}" && ! -f "$${ARGS[0]}" ]]; then \ + echo $(addprefix ./,$(RUNARGS)); \ + elif [[ ! -f "$${ARGS[0]}" && ! -d "$${ARGS[0]}" ]]; then \ + for ARG in $${ARGS[0]}; do \ + if [ -z "$${PACKAGES}" ]; then PACKAGES="$${ARG%/*}"; \ + else PACKAGES="$${PACKAGES}\n$${ARG%/*}"; fi; \ + if [ -z "$${TEST_CASE}" ]; then TEST_CASES="-run $${ARG\#\#*/}"; \ + else TEST_CASES="$${TEST_CASES} -run $${ARG\#\#*/}"; fi; \ + done; \ + echo -en "$${PACKAGES}" | sort -u | sed "s|^|./|"; \ + echo "$${TEST_CASES}"; \ + else \ + echo "warning: invalid test parameters [$${ARGS[@]}]"; \ + fi; \ + else echo "./..."; fi + +TEST_FLAGS ?= -race -mod=readonly -count=1 +TEST_ARGS ?= $(shell $(testargs)) + +# actuall targets for testing. +$(TEST_ALL):: $(SOURCES) init-sources $(TEST_DEPS) $(DIR_BUILD) + go test $(TEST_FLAGS) -timeout $(TEST_TIMEOUT) \ + -cover -coverprofile $@ $(TEST_ARGS); +$(TEST_UNIT):: $(SOURCES) init-sources $(DIR_BUILD) + go test $(TEST_FLAGS) -timeout $(TEST_TIMEOUT) \ + -cover -coverprofile $@ -short $(TEST_ARGS); +$(TEST_BENCH):: $(SOURCES) init-sources $(DIR_BUILD) + go test $(TEST_FLAGS) -benchtime=8s \ + -cover -coverprofile $@ -short -bench=. $(TEST_ARGS); + +#@ execute a kind of self-test running the main targets. +test-self:: clean-all all-clean update-all? update-all + echo "Self test finished successfully!!!"; + +# Variables and definitions for linting of source code. +COMMA := , +SPACE := $(null) # + +# disabled (deprecated): deadcode golint interfacer ifshort maligned musttag +# nosnakecase rowserrcheck scopelint structcheck varcheck wastedassign +# diabled (distructive): nlreturn ireturn nonamedreturns varnamelen exhaustruct +# exhaustivestruct gochecknoglobals gochecknoinits tagliatelle +# disabled (conflicting): godox gci paralleltest +# not listed (unnecessary): forcetypeassert wsl +# requires unknown setup: depguard + +LINTERS_DISCOURAGED ?= \ + deadcode exhaustivestruct exhaustruct gci gochecknoglobals gochecknoinits \ + godox golint ifshort interfacer ireturn maligned musttag nlreturn \ + nonamedreturns nosnakecase paralleltest rowserrcheck scopelint structcheck \ + tagliatelle varcheck varnamelen wastedassign +LINTERS_MINIMUM ?= \ + asasalint asciicheck bidichk bodyclose dogsled dupl dupword durationcheck \ + errchkjson execinquery exportloopref funlen gocognit goconst gocyclo \ + godot gofmt gofumpt goimports gosimple govet importas ineffassign maintidx \ + makezero misspell nestif nilerr prealloc predeclared promlinter reassign \ + sqlclosecheck staticcheck typecheck unconvert unparam unused \ + usestdlibvars whitespace +LINTERS_BASELINE ?= \ + containedctx contextcheck cyclop decorder errname forbidigo ginkgolinter \ + gocheckcompilerdirectives goheader gomodguard goprintffuncname gosec \ + grouper interfacebloat lll loggercheck nakedret nilnil noctx nolintlint \ + nosprintfhostport +LINTERS_EXPERT ?= \ + errcheck errorlint exhaustive gocritic goerr113 gomnd gomoddirectives \ + revive stylecheck tenv testableexamples testpackage thelper tparallel \ + wrapcheck + +LINT_FILTER := $(LINTERS_CUSTOM) $(LINTERS_DISABLED) +LINT_DISABLED ?= $(subst $(SPACE),$(COMMA),$(strip \ + $(filter-out $(LINT_FILTER),$(LINTERS_DISCOURAGED)) $(LINTERS_DISABLED))) +LINT_MINIMUM ?= $(subst $(SPACE),$(COMMA),$(strip \ + $(filter-out $(LINT_FILTER),$(LINTERS_MINIMUM)) $(LINTERS_CUSTOM))) +LINT_BASELINE ?= $(subst $(SPACE),$(COMMA),$(strip $(LINT_MINIMUM) \ + $(filter-out $(LINT_FILTER),$(LINTERS_BASELINE)))) +LINT_EXPERT ?= $(subst $(SPACE),$(COMMA),$(strip $(LINT_BASELINE) \ + $(filter-out $(LINT_FILTER),$(LINTERS_EXPERT)))) + +ifeq ($(shell ls $(FILE_GOLANGCI) 2>/dev/null), $(FILE_GOLANGCI)) + LINT_CONFIG := --config $(FILE_GOLANGCI) +endif + +LINT_MIN := --enable $(LINT_MINIMUM) --disable $(LINT_DISABLED) +LINT_BASE := --enable $(LINT_BASELINE) --disable $(LINT_DISABLED) +LINT_PLUS := --enable $(LINT_EXPERT) --disable $(LINT_DISABLED) +LINT_MAX := --enable-all --disable $(LINT_DISABLED) +LINT_ALL := --enable $(LINT_EXPERT),$(LINT_DISABLED) --disable-all + +LINT_FLAGS ?= --allow-serial-runners --sort-results --color always +LINT_CMD ?= $(GOBIN)/golangci-lint run $(LINT_CONFIG) $(LINT_FLAGS) +LINT_CMD_CONFIG := make --no-print-directory lint-config +ifeq ($(RUNARGS),linters) + LINT_CMD := $(GOBIN)/golangci-lint linters $(LINT_CONFIG) $(LINT_FLAGS) +else ifeq ($(RUNARGS),config) + LINT_CMD := @ + LINT_MIN := LINT_ENABLED=$(LINT_MINIMUM) \ + LINT_DISABLED=$(LINT_DISABLED) $(LINT_CMD_CONFIG) + LINT_BASE := LINT_ENABLED=$(LINT_BASELINE) \ + LINT_DISABLED=$(LINT_DISABLED) $(LINT_CMD_CONFIG) + LINT_PLUS := LINT_ENABLED=$(LINT_EXPERT) \ + LINT_DISABLED=$(LINT_DISABLED) $(LINT_CMD_CONFIG) + LINT_MAX := LINT_DISABLED=$(LINT_DISABLED) $(LINT_CMD_CONFIG) + LINT_ALL := LINT_ENABLED=$(LINT_EXPERT),$(LINT_DISABLED) $(LINT_CMD_CONFIG) +else ifeq ($(RUNARGS),fix) + LINT_CMD := $(GOBIN)/golangci-lint run $(LINT_CONFIG) $(LINT_FLAGS) --fix +else ifneq ($(RUNARGS),) + LINT_CMD := $(GOBIN)/golangci-lint run $(LINT_CONFIG) $(LINT_FLAGS) + LINT_MIN := --disable-all --enable $(RUNARGS) + LINT_BASE := --disable-all --enable $(RUNARGS) + LINT_PLUS := --disable-all --enable $(RUNARGS) + LINT_MAX := --disable-all --enable $(RUNARGS) + LINT_ALL := --disable-all --enable $(RUNARGS) +endif + + +## Lint: targets to lint source code. + +#@ # execute all linters for the custom code quality level. +lint:: $(TARGETS_LINT) +#@ # execute golangci linters for minimal code quality level (go-files). +lint-min:: init-sources $(GOBIN)/golangci-lint; $(LINT_CMD) $(LINT_MIN) +#@ # execute golangci linters for base code quality level (go-files). +lint-base:: init-sources $(GOBIN)/golangci-lint; $(LINT_CMD) $(LINT_BASE) +#@ # execute golangci linters for plus code quality level (go-files). +lint-plus:: init-sources $(GOBIN)/golangci-lint; $(LINT_CMD) $(LINT_PLUS) +#@ # execute golangci linters for maximal code quality level (go-files). +lint-max:: init-sources $(GOBIN)/golangci-lint; $(LINT_CMD) $(LINT_MAX) +#@ # execute all golangci linters for insane code quality level (go-files). +lint-all:: init-sources $(GOBIN)/golangci-lint; $(LINT_CMD) $(LINT_ALL) + +#@ create a golangci linter config using the custom code quality level. +lint-config:: + @LINT_START=$$(awk '($$0 ~ "^linters:.*"){print NR-1}' $(FILE_GOLANGCI)); \ + LINT_STOP=$$(awk '(start && $$0 ~ "^[^ ]+:$$"){print NR; start=0} \ + ($$0 ~ "^linters:.*"){start=1}' $(FILE_GOLANGCI)); \ + (sed -ne '1,'$${LINT_START}'p' $(FILE_GOLANGCI); \ + echo -e "linters:\n enable:"; \ + for LINTER in "# List of min-set linters (min)" $(LINTERS_MINIMUM) \ + "# List of base-set linters (base)" $(LINTERS_BASELINE) \ + "# List of plus-set linters (plus)" $(LINTERS_EXPERT); do \ + if [ "$${LINTER:0:1}" == "#" ]; then X=""; else X="- "; fi; \ + echo -e " $${X}$${LINTER}"; \ + done; echo -e "\n disable:"; \ + for LINTER in "# List of to-avoid linters (avoid)" $(LINTERS_DISABLED); do \ + if [ "$${LINTER:0:1}" == "#" ]; then X=""; else X="- "; fi; \ + echo -e " $${X}$${LINTER}"; \ + done; \ + echo; sed -ne $${LINT_STOP}',$$p' $(FILE_GOLANGCI)) | \ + awk -v enabled=$${LINT_ENABLED} -v disabled=$${LINT_DISABLED} ' \ + ((start == 2) && ($$1 == "-")) { \ + enabled = enable[$$2]; disabled = disable[$$2]; \ + if (enabled && disabled) { \ + print $$0 " # (conflicting)" \ + } else if (!enabled && disabled) { \ + print gensub("-","# -", 1) " # (disabled)" \ + } else if (!enabled && !disabled && enone) { \ + print gensub("-","# -", 1) " # (missing)" \ + } else { print $$0 } \ + enable[$$2] = 2; next \ + } \ + ((start == 3) && ($$1 == "-")) { \ + enabled = enable[$$2]; disabled = disable[$$2]; \ + if (enabled && disabled) { \ + print gensub("-","# -", 1) " # (conflicting)" \ + } else if (enabled && !disabled) { \ + print gensub("-","# -", 1) " # (enabled)" \ + } else if (!enabled && !disabled && dnone) { \ + print gensub("-","# -", 1) " # (missing)" \ + } else { print $$0 } \ + disable[$$2] = 2; next \ + } \ + ((start != 0) && ($$0 ~ "^[^ ]+:$$")) { start=0 } \ + ((start >= 2) && ($$0 ~ "^ [^ ]+:$$")) { none=1; \ + if (start == 2) { for (key in enable) { \ + if (key && enable[key] == 1) { \ + if (none) { print " # Additional linters" } \ + print " - " key; none=0 \ + } \ + } } else if (start == 3) { for (key in disable) { \ + if (key && disable[key] == 1) { \ + if (none) { print " # Additional linters" } \ + print " - " key; none=0 \ + } \ + } } \ + } \ + ((start != 0) && ($$0 == " disable:")) { start = 3 }\ + ((start != 0) && ($$0 == " enable:")) { start = 2 } \ + ((start == 0) && ($$0 == "linters:")) { start = 1; \ + enone = split(enabled, array, ","); \ + for (i in array){ enable[array[i]] = 1 } \ + dnone = split(disabled, array, ","); \ + for (i in array){ disable[array[i]] = 1 } \ + } { print $$0 }' \ + +#@ execute all codacy linters activated by the custom setup (all files). +lint-codacy:: lint-revive $(TARGETS_LINT_CODACY_CLIENTS) $(TARGETS_LINT_CODACY_BINARIES) +$(TARGETS_LINT_CODACY_CLIENTS):: lint-%: init-sources + @LARGS=("--allow-network" "--skip-uncommitted-files-check"); \ + LARGS+=("--codacy-api-base-url" "$(CODACY_API_BASE_URL)"); \ + if [ -n "$(CODACY_PROJECT_TOKEN)" ]; then \ + LARGS+=("--project-token" "$(CODACY_PROJECT_TOKEN)"); \ + LARGS+=("--upload" "--verbose"); \ + elif [ -n "$(CODACY_API_TOKEN)" ]; then \ + LARGS+=("--provider" "$(CODACY_PROVIDER)"); \ + LARGS+=("--username" "$(CODACY_USER)"); \ + LARGS+=("--project" "$(CODACY_PROJECT)"); \ + LARGS+=("--api-token" "$(CODACY_API_TOKEN)"); \ + LARGS+=("--upload" "--verbose"); \ + fi; \ + echo """$(IMAGE_CMD) run --rm=true --env CODACY_CODE="/code" \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume /tmp:/tmp --volume ".":"/code" \ + codacy/codacy-analysis-cli analyze "$${LARGS[@]}" --tool $*"""; \ + $(IMAGE_CMD) run --rm=true --env CODACY_CODE="/code" \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume /tmp:/tmp --volume "$$(pwd):/code" \ + codacy/codacy-analysis-cli analyze "$${LARGS[@]}" --tool $* || \ + $(CODACY_CONTINUE); \ + +LINT_ARGS_GOSEC_LOCAL := -log /dev/null -exclude G307 ./... +LINT_ARGS_GOSEC_UPLOAD := -log /dev/null -exclude G307 -fmt json ./... +LINT_ARGS_STATICCHECK_LOCAL := -tests ./... +LINT_ARGS_STATICCHECK_UPLOAD := -tests -f json ./... + +$(TARGETS_LINT_CODACY_BINARIES):: lint-%: init-sources init-% $(GOBIN)/% + @COMMIT=$$(git log --max-count=1 --pretty=format:"%H"); \ + VERSION="$(CODACY_$(call upper,$*)_VERSION)"; \ + LARGS=("--silent" "--location" "--request" "POST" \ + "--header" "Content-type: application/json"); \ + if [ -n "$(CODACY_PROJECT_TOKEN)" ]; then \ + BASE="$(CODACY_API_BASE_URL)/2.0/commit/$${COMMIT}"; \ + LARGS+=("-H" "project-token: $(CODACY_PROJECT_TOKEN)"); \ + elif [ -n "$(CODACY_API_TOKEN)" ]; then \ + SPATH="$(CODACY_PROVIDER)/$(CODACY_USER)/$(CODACY_PROJECT)"; \ + BASE="$(CODACY_API_BASE_URL)/2.0/$${SPATH}/commit/$${COMMIT}"; \ + LARGS+=("-H" "api-token: $(CODACY_API_TOKEN)"); \ + fi; \ + if [ -n "$${BASE}" ]; then \ + echo -e """$(GOBIN)/$* $(LINT_ARGS_$(call upper,$*)_UPLOAD) | \ + $(GOBIN)/codacy-$*-$${VERSION} | \ + curl "$${LARGS[@]}" --data @- "$${BASE}/issuesRemoteResults"; \ + curl "$${LARGS[@]}" "$${BASE}/resultsFinal""""; \ + ( $(GOBIN)/$* $(LINT_ARGS_$(call upper,$*)_UPLOAD) | \ + $(GOBIN)/codacy-$*-$${VERSION} | \ + curl "$${LARGS[@]}" --data @- "$${BASE}/issuesRemoteResults"; echo; \ + curl "$${LARGS[@]}" "$${BASE}/resultsFinal"; echo ) || \ + $(CODACY_CONTINUE); \ + else \ + echo $(GOBIN)/$* $(LINT_ARGS_$(call upper,$*)_LOCAL); \ + $(GOBIN)/$* $(LINT_ARGS_$(call upper,$*)_LOCAL) || $(CODACY_CONTINUE); \ + fi; \ + +#@ execute revive linter (go-files). +lint-revive:: init-sources $(GOBIN)/revive + revive -formatter friendly -config=revive.toml $(SOURCES) || $(CODACY_CONTINUE); + +#@ execute markdown linter (md-files). +lint-markdown:: init-sources $(NVM_BIN)/markdownlint + @echo markdownlint --config .markdownlint.yaml .; \ + if command -v markdownlint &> /dev/null; then \ + markdownlint --config .markdownlint.yaml .; \ + else $(IMAGE_CMD) run --tty --volume $$(pwd):/src:ro \ + container-registry.zalando.net/library/node-18-alpine:latest \ + /bin/sh -c "npm install --global markdownlint-cli >/dev/null 2>&1 && \ + cd /src && markdownlint --config .markdownlint.yaml ."; \ + fi; \ + +#@ execute shellcheck to find potential shell script issues (sh-files). +lint-shell:: init-sources + @mapfile -t FILES < <(find -name "*.sh"); ARGS="--color=always"; \ + if [ -n "$(RUNARGS)" ]; then ARGS=(${ARGS} $(RUNARGS)); fi; \ + if [ -n "$${FILES}" ]; then \ + if command -v shellcheck &> /dev/null; then \ + shellcheck "$${ARGS[@]}" "$${FILES[@]}"; \ + else \ + $(IMAGE_CMD) run --rm --volume="$${PWD}:/mnt" \ + koalaman/shellcheck:stable "$${ARGS[@]}" "$${FILES[@]}"; \ + fi; \ + fi; \ + +#@ execute gitleaks to check committed code for leaking secrets (all files). +lint-leaks:: $(GOBIN)/gitleaks + gitleaks detect --no-banner --verbose --source .; +#@ execute gitleaks to check un-committed code changes for leaking secrets (all files). +lint-leaks?:: $(GOBIN)/gitleaks + gitleaks protect --no-banner --verbose --source .; +#@ execute govulncheck to find vulnerabilities in dependencies (go-files). +lint-vuln:: $(GOBIN)/govulncheck + govulncheck -test ./... +#@ # execute go(cyclo|cognit) linter (go-files). +lint-gocyclo lint-gocognit:: lint-%: $(GOBIN)/% + @RUNARGS=($(RUNARGS)); MODS="0"; \ + while [ "$${#RUNARGS[@]}" != "0" ]; do \ + if [ "$${RUNARGS[0]}" == "top" ]; then ARGS+=(-$${RUNARGS[0]}); MODS="1"; \ + elif [ "$${RUNARGS[0]}" == "over" ]; then ARGS+=(-$${RUNARGS[0]}); MODS="1"; \ + elif [ "$${RUNARGS[0]}" == "avg" ]; then ARGS+=(-$${RUNARGS[0]}); MODS="0"; \ + else ARGS+=($${RUNARGS[0]}); MODS=$$((MODS-1)); fi; \ + RUNARGS=("$${RUNARGS[@]:1}"); \ + done; \ + if [[ "$${MODS}" == "0" ]]; then ARGS+=($(MODULES)); fi; \ + $* "$${ARGS[@]}"; + +#@ execute zally api-linter to find API issues (yaml-files). +lint-apis:: $(GOBIN)/zally + @LINTER="https://infrastructure-api-linter.zalandoapis.com"; \ + if ! curl --silent $${LINTER}; then \ + echo "warning: API linter not available;"; exit 0; \ + fi; \ + ARGS=("--linter-service" "$${LINTER}"); \ + if command -v ztoken > /dev/null; then ARGS+=("--token" "$$(ztoken)"); fi; \ + if [ -z "$(filter %.yaml,$(RUNARGS))" ]; then \ + FILES="$$(find zalando-apis -name "*.yaml" 2>/dev/null)"; \ + else FILES="$(filter %.yaml,$(RUNARGS))"; fi; \ + for APISPEC in $${FILES}; do \ + echo "check API: zally \"$${APISPEC}\""; \ + zally "$${ARGS[@]}" lint "$${APISPEC}" || exit 1; \ + done; + + +# Variables for setting up container specific build flags. +BUILD_OS ?= $(shell go env GOOS) +BUILD_ARCH ?= $(shell go env GOARCH) +IMAGE_OS ?= ${shell grep "^FROM [^ ]*$$" $(FILE_CONTAINER) 2>/dev/null | \ + grep -v " as " | sed "s/.*\(alpine\|ubuntu\).*/\1/g"} +ifeq ($(IMAGE_OS),alpine) + BUILD_FLAGS ?= -v -mod=readonly -buildvcs=auto + GOCGO := 0 +else + BUILD_FLAGS ?= -v -mod=readonly -buildvcs=auto -race + GOCGO := 1 +endif + +# Function to select right main package of command. +main-pkg = $(patsubst $(1)=%,%,$(firstword $(filter $(1)=%,$(COMMANDS_PKG)))) + +# Functions and variables to propagate versions to build commands. +LD-PATHS := main $(shell go list ./... | grep "config$$") +ld-flag = $(addprefix -X ,$(addsuffix .$(1),$(LD-PATHS))) +ld-flags = $(call ld-flag,Version=$(BUILD_VERSION)) \ + $(call ld-flag,Revision=$(shell git rev-parse HEAD)) \ + $(call ld-flag,Build=$(shell date --iso-8601=seconds)) \ + $(call ld-flag,Commit=$(shell git log -1 --date=iso8601-strict --format=%cd)) \ + $(call ld-flag,Dirty=$(shell git diff --quiet && echo false || echo true)) \ + $(call ld-flag,Path=$(REPOSITORY)$(patsubst .%,%,$(1))) \ + $(call ld-flag,GitHash=$(shell git rev-parse --short HEAD)) $(LDFLAGS) \ + + +## Build: targets to build native and linux platform executables. + +#@ build default executables (native). +build:: build-native +#@ build native platform executables using system architecture. +build-native:: $(TARGETS_BUILD) +$(TARGETS_BUILD):: build-%: $(DIR_BUILD)/% +$(DIR_BUILD)/%:: init-hooks $(SOURCES) + @mkdir -p "$(dir $@)"; + GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) CGO_ENABLED=1 \ + go build -ldflags="$(call ld-flags,$(call main-pkg,$*))" \ + $(BUILD_FLAGS) -o $@ $(call main-pkg,$*)/main.go; + +#@ build linux platform executables using default (system) architecture. +build-linux:: $(TARGETS_BUILD_LINUX) +$(DIR_BUILD)/linux/%:: $(call main-pkg,%) init-hooks $(SOURCES) + @mkdir -p "$(dir $@)"; + GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) CGO_ENABLED=$(GOCGO) \ + go build -ldflags="$(call ld-flags,$(call main-pkg,$*))" \ + $(BUILD_FLAGS) -o $@ $(call main-pkg,$*)/main.go; + +#@ build container image (alias for image-build). +build-image:: image-build + + +## Image: targets to build and push container images. + +#@ build and push container images - if setup. +image:: $(if $(filter $(IMAGE_PUSH),never),,image-push) +#@ build container images. +image-build:: $(TARGETS_IMAGE_BUILD) + +setup-image-file = \ + FILE="$*"; IMAGE=$(IMAGE); \ + PREFIX="$$(basename "$${FILE%$(FILE_CONTAINER)*}")"; \ + SUFFIX="$$(echo $${FILE\#*$(FILE_CONTAINER)} | sed "s/^[^[:alnum:]]*//")"; \ + INFIX="$${SUFFIX:-$${PREFIX}}"; \ + if [ -n "$${INFIX}" ]; then \ + IMAGE="$${IMAGE/:/-$${INFIX}:}"; \ + fi; + +$(TARGETS_IMAGE_BUILD):: image-build/%: build-linux + @$(call setup-image-file $*) \ + if [ "$(IMAGE_PUSH)" == "never" ]; then \ + echo "We never build images, aborting [$${IMAGE}]."; exit 0; \ + fi; \ + REVISION=$$(git rev-parse HEAD 2>/dev/null); \ + BASE=$$(grep "^FROM[[:space:]]" $${FILE} | tail -n1 | cut -d" " -f2); \ + $(IMAGE_CMD) build --tag $${IMAGE} --file=$${FILE} . \ + --label=org.opencontainers.image.created="$$(date --rfc-3339=seconds)" \ + --label=org.opencontainers.image.vendor=zalando/$(TEAM) \ + --label=org.opencontainers.image.source=$(REPOSITORY) \ + --label=org.opencontainers.image.version=$${IMAGE##*:} \ + --label=org.opencontainers.image.revision=$${REVISION} \ + --label=org.opencontainers.image.base.name=$${BASE} \ + --label=org.opencontainers.image.ref.name=$${IMAGE}; \ + +#@ push contianer images - if setup and allowed. +image-push:: $(TARGETS_IMAGE_PUSH) +$(TARGETS_IMAGE_PUSH):: image-push/%: image-build/% + @$(call setup-image-file $*) \ + if [ "$(IMAGE_PUSH)" == "never" ]; then \ + echo "We never push images, aborting [$${IMAGE}]."; exit 0; \ + elif [ "$(IMAGE_VERSION)" == "snapshot" ]; then \ + echo "We never push snapshot images, aborting [$${IMAGE}]."; exit 0; \ + elif [ -n "$(CDP_PULL_REQUEST_NUMBER)" -a "$(IMAGE_PUSH)" != "pulls" ]; then \ + echo "We never push pull request images, aborting [$${IMAGE}]."; exit 0; \ + fi; \ + $(IMAGE_CMD) push $${IMAGE}; \ + + +## Run: targets for starting services and commands for testing. +HOST := "127.0.0.1" + +#@ start a postgres database instance for testing. +run-db:: $(DIR_RUN) + @if [[ ! " $(TEST_DEPS) $(RUN_DEPS) " =~ " run-db " ]]; then exit 0; fi; \ + echo "info: ensure $(DB_IMAGE) running on $(HOST):$(DB_PORT)"; \ + if [ -n "$$($(IMAGE_CMD) ps | grep "$(DB_IMAGE).*$(HOST):$(DB_PORT)")" ]; then \ + echo "info: port allocated, use existing db container!"; exit 0; \ + fi; \ + $(IMAGE_CMD) start ${IMAGE_ARTIFACT}-db 2>/dev/null || ( \ + $(IMAGE_CMD) run --detach --tty \ + --name ${IMAGE_ARTIFACT}-db \ + --publish $(HOST):$(DB_PORT):5432 \ + --env POSTGRES_USER="$(DB_USER)" \ + --env POSTGRES_PASSWORD="$(DB_PASSWORD)" \ + --env POSTGRES_DB="$(DB_NAME)" $(DB_IMAGE) \ + -c 'shared_preload_libraries=pg_stat_statements' \ + -c 'pg_stat_statements.max=10000' \ + -c 'pg_stat_statements.track=all' \ + $(RUNARGS) 2>&1 & \ + until [ "$$($(IMAGE_CMD) inspect --format {{.State.Running}} \ + $(IMAGE_ARTIFACT)-db 2>/dev/null)" == "true" ]; \ + do echo "waiting for db container" >/dev/stderr; sleep 1; done && \ + until $(IMAGE_CMD) exec $(IMAGE_ARTIFACT)-db \ + pg_isready -h localhost -U $(DB_USER) -d $(DB_NAME); \ + do echo "waiting for db service" >/dev/stderr; sleep 1; done) |\ + tee -a $(DIR_RUN)/$(IMAGE_ARTIFACT)-db; \ + +#@ start an AWS localstack instance for testing. +run-aws:: $(DIR_RUN) + @if [[ ! " $(TEST_DEPS) $(RUN_DEPS) " =~ " run-aws " ]]; then exit 0; fi; \ + echo "info: ensure $(AWS_IMAGE) is running on $(HOST):4566/4571" && \ + if [ -n "$$($(IMAGE_CMD) ps | \ + grep "$(AWS_IMAGE).*$(HOST):4566.*$(HOST):4571")" ]; then \ + echo "info: ports allocated, use existing aws container!"; \ + $(call run-aws-setup) || exit 1 && exit 0 ; \ + fi; \ + $(IMAGE_CMD) start ${IMAGE_ARTIFACT}-aws 2>/dev/null || ( \ + $(IMAGE_CMD) run --detach --tty --name ${IMAGE_ARTIFACT}-aws \ + --publish $(HOST):4566:4566 --publish $(HOST):4571:4571 \ + --env SERVICES="$(AWS_SERVICES)" $(AWS_IMAGE) $(RUNARGS) 2>&1 && \ + until [ "$$($(IMAGE_CMD) inspect --format {{.State.Running}} \ + $(IMAGE_ARTIFACT)-aws 2>/dev/null)" == "true" ]; \ + do echo "waiting for aws container" >/dev/stderr; sleep 1; done && \ + until $(IMAGE_CMD) exec $(IMAGE_ARTIFACT)-aws \ + curl --silent http://$(HOST):4566; \ + do echo "waiting for aws service" >/dev/stderr; sleep 1; done && \ + $(call run-aws-setup) || exit 1) | \ + tee -a $(DIR_RUN)/$(IMAGE_ARTIFACT)-aws.log; \ + +#@ run-*: start the matched command using the native binary. +$(TARGETS_RUN):: run-%: $(DIR_BUILD)/% $(RUN_DEPS) $(DIR_RUN) $(DIR_CRED) + @$(call run-setup); $(call run-vars) $(call run-vars-local) \ + $(DIR_BUILD)/$* $(RUNARGS) 2>&1 | \ + tee -a $(DIR_RUN)/$(IMAGE_ARTIFACT)-$*.log; \ + exit $${PIPESTATUS[0]}; + +#@ run-go-*: start the matched command using go run. +$(TARGETS_RUN_GO):: run-go-%: $(DIR_BUILD)/% $(RUN_DEPS) $(DIR_RUN) $(DIR_CRED) + @$(call run-setup); $(call run-vars) $(call run-vars-local) \ + go run cmd/$*/main.go $(RUNARGS) 2>&1 | \ + tee -a $(DIR_RUN)/$(IMAGE_ARTIFACT)-$*.log; \ + exit $${PIPESTATUS[0]}; + +#@ run-image-*: start the matched command via the container images. +$(TARGETS_RUN_IMAGE):: run-image-%: $(RUN_DEPS) $(DIR_RUN) $(DIR_CRED) + @trap "$(IMAGE_CMD) rm $(IMAGE_ARTIFACT)-$* >/dev/null" EXIT; \ + trap "$(IMAGE_CMD) kill $(IMAGE_ARTIFACT)-$* >/dev/null" INT TERM; \ + if [ -n "$(filter %$*,$(TARGETS_IMAGE))" ]; then \ + IMAGE="$$(echo "$(IMAGE)" | sed -e "s/:/-$*:/")"; \ + else IMAGE="$(IMAGE)" fi; $(call run-setup); \ + $(IMAGE_CMD) run --name $(IMAGE_ARTIFACT)-$* --network=host \ + --volume $(DIR_CRED):/meta/credentials --volume $(DIR_RUN)/temp:/tmp \ + $(call run-vars, --env) $(call run-vars-image, --env) \ + ${IMAGE} /$* $(RUNARGS) 2>&1 | \ + tee -a $(DIR_RUN)/$(IMAGE_ARTIFACT)-$*.log; \ + exit $${PIPESTATUS[0]}; + +#@ clean up all running container images. +run-clean:: $(TARGETS_RUN_CLEAN) +#@ run-clean-*: kill and remove the container image of the matched command. +$(TARGETS_RUN_CLEAN):: run-clean-%: + @echo "check container $(IMAGE_ARTIFACT)-$*"; \ + if [ -n "$$($(IMAGE_CMD) ps | grep "$(IMAGE_ARTIFACT)-$*")" ]; then \ + $(IMAGE_CMD) kill $(IMAGE_ARTIFACT)-$* > /dev/null && \ + echo "killed container $(IMAGE_ARTIFACT)-$*"; \ + fi; \ + if [ -n "$$($(IMAGE_CMD) ps -a | grep "$(IMAGE_ARTIFACT)-$*")" ]; then \ + $(IMAGE_CMD) rm $(IMAGE_ARTIFACT)-$* > /dev/null && \ + echo "removed container $(IMAGE_ARTIFACT)-$*"; \ + fi; \ + + +## Cleanup: targets to clean up sources, tools, and containers. + +#@ clean up resources created during build processes. +clean:: $(TARGETS_CLEAN) +#@ clean up all resources, i.e. also tools installed for the build. +clean-all:: $(TARGETS_CLEAN_ALL) +#@ clean up all resources created by initialization. +clean-init:: clean-hooks +#@ clean up all hooks created by initialization. +clean-hooks::; rm -vrf .git/hooks/pre-commit; +#@ clean up all resources created by building and testing. +clean-build::; rm -vrf $(DIR_BUILD) $(DIR_RUN); + $(FIND) -name "mock_*_test.go" -exec rm -v {} \;; +#@ clean up all package dependencies by removing packages. +clean-deps:: + for PACKAGE in $$(cat go.sum | cut -d ' ' -f 1 | sort -u); do \ + go get $${PACKAGE}@none; \ + done; +#@ clean up all running container images. +clean-run:: $(TARGETS_CLEAN_RUN) +#@ clean-run-*: clean up matched running container image. +$(TARGETS_CLEAN_RUN):: clean-run-%: run-clean-% + + +## Update: targets to update tools, configs, and dependencies. + +# TODO: +# 'update-deps' check how to utilize: 'go list -m -u -mod=mod all' + + +#@ update default or customized update targets. +update:: $(TARGETS_UPDATE) +#@ check default or customized update targets for updates. +update?:: $(TARGETS_UPDATE?) +#@ update all components. +update-all:: $(TARGETS_UPDATE_ALL) +#@ check all components for udpdates. +update-all?:: $(TARGETS_UPDATE_ALL?) + +#@ # update go to latest or given version. +update-go:: + @if ! [[ "$(RUNARGS)" =~ "[0-9.]*" ]]; then \ + ARCH="linux-amd64"; BASE="https://go.dev/dl"; \ + VERSION="$$(curl --silent --header "Accept-Encoding: gzip" \ + "$${BASE}/" | gunzip | grep -o "go[0-9.]*$${ARCH}.tar.gz" | \ + head -n 1 | sed "s/go\(.*\)\.[0-9]*\.$${ARCH}\.tar\.gz/\1/")"; \ + else VERSION="$(RUNARGS)"; fi; \ + echo "info: update golang version to $${VERSION}"; \ + go get go@latest; go mod tidy -go=$${VERSION}; \ + if [ -f $(FILE_DELIVERY) ]; then \ + sed -E -i -e "s/$(FILE_DELIVERY_REGEX)/\1$${VERSION}/" $(FILE_DELIVERY); \ + fi; \ + if [ "$(GOVERSION)" != "$${VERSION}" ]; then \ + echo "warning: current compiler is using $(GOVERSION)" >/dev/stderr; \ + fi; \ + +#@ check whether a new go version exists. +update-go?:: + @ARCH="linux-amd64"; BASE="https://go.dev/dl"; \ + VERSION="$$(curl --silent --header "Accept-Encoding: gzip" \ + "$${BASE}/" | gunzip | grep -o "go[0-9.]*$${ARCH}.tar.gz" | \ + head -n 1 | sed "s/go\(.*\)\.[0-9]*\.$${ARCH}\.tar\.gz/\1/")"; \ + if [ "$(GOVERSION)" != "$${VERSION}" ]; then \ + echo "info: new golang version $${VERSION} available"; \ + echo -e "\trun 'make update-go' to update the project!"; \ + exit -1; \ + fi; \ + +# Functions to create the list of updates (not sure whether go list is contributing). +update-args = \ + if [[ " $(1) " =~ " major " ]]; then ARGS="-cached false -major"; \ + elif [[ " $(1) " =~ " pre " ]]; then ARGS="-cached false -pre"; \ + else ARGS="-cached false"; fi +update-list = \ + ( gomajor list $(1) 2>/dev/null | sed "s/://1; s/\[latest /\[/g"; \ + go list -m -u -mod=mod $(2) | grep "\[.*\]$$" \ + ) | sort -u | sed "s/\]//g; s/\[/=> /" + +#@ [major|pre|minor] # update minor (and major) dependencies to latest versions. +update-deps:: test-go update/go.mod +update/go.mod:: $(GOBIN)/gomajor + @if [ -n "$(RUNARGS)" ]; then $(call update-args,$(RUNARGS)); \ + if [[ " $(RUNARGS) " =~ " minor " ]]; then PACKAGES="all"; fi; \ + readarray -t UPDATES < <($(call update-list,$${ARGS},$${PACKAGES})); \ + for UPDATE in "$${UPDATES[@]}"; do ARGS=($${UPDATE}); \ + SMAJOR="$${ARGS[1]%%.*}"; TMAJOR="$${ARGS[3]%%.*}"; \ + if [ "$${SMAJOR}" != "$${TMAJOR}" ]; then \ + if [[ "$${ARGS[0]}" == *$${SMAJOR}* ]]; then \ + TARGET="$${ARGS[0]/$${SMAJOR}/$${TMAJOR}}"; \ + else TARGET="$${ARGS[0]}/$${TMAJOR}"; fi; \ + echo "update: $${ARGS[0]}@$${ARGS[2]} => $${TARGET}@$${ARGS[3]}"; \ + sed -i -e "s#$${ARGS[0]}#$${TARGET}#g" $(SOURCES); \ + else \ + echo "update: $${ARGS[0]}@$${ARGS[1]} => $${ARGS[3]}"; \ + go get "$${ARGS[0]}@$${ARGS[3]}"; \ + fi; \ + done; \ + else go get -u ./...; fi; \ + ROOT=$$(pwd); \ + for DIR in $(MODULES); do \ + echo "update: $${DIR}"; cd $${ROOT}/$${DIR##./} && \ + go mod tidy -x -v -e -compat=$(GOVERSION) || exit -1; \ + done; \ + +#@ check for major and minor updates to latest versions. +update-deps?:: test-go update/go.mod? +update/go.mod?:: $(GOBIN)/gomajor + @$(call update-args,$(RUNARGS)); cp go.sum go.sum.~save~; \ + $(call update-list,$${ARGS},all); mv go.sum.~save~ go.sum; + +#@ update this build environment to latest version. +update-make:: $(TARGETS_UPDATE_MAKE) +$(TARGETS_UPDATE_MAKE):: update/%: setup/base + @DIR="$$(pwd)"; FILE="$*"; FILE="$${FILE##$(DIR_CONFIG)}"; \ + if [ ! -e "$${DIR}/$${FILE}" ]; then \ + echo "info: $${DIR}/$${FILE} (not configured - update skipped)"; \ + else cd "$(BASEDIR)"; \ + DIFF="$$(diff <(git show HEAD:config/$${FILE} 2>/dev/null) $${DIR}/$${FILE})"; \ + if [ -n "$${DIFF}" ]; then \ + if [ -n "$$(cd $${DIR}; git diff $${FILE})" ]; then \ + echo "info: $${DIR}/$${FILE} (was updated - update blocked)"; \ + else echo "info: $${DIR}/$${FILE} (has changed - update executed)"; \ + git show HEAD:config/$${FILE} > $${DIR}/$${FILE} 2>/dev/null; \ + fi; \ + else \ + echo "info: $${DIR}/$${FILE} (not changed - update skipped)" >/dev/null; \ + fi; \ + fi; \ + +#@ check whether updates for build environment exist. +update-make?:: $(TARGETS_UPDATE_MAKE?) +$(TARGETS_UPDATE_MAKE?):: update/%?: setup/base + @DIR="$$(pwd)"; cd $(BASEDIR); \ + if [ ! -e "$${DIR}/$*" ]; then \ + echo "info: $* is not tracked"; \ + else DIFF="$$(diff <(git show HEAD:$*) $${DIR}/$*)"; \ + if [ -n "$${DIFF}" ]; then \ + if [ -n "$$(cd $${DIR}; git diff $*)" ]; then \ + echo "info: $* is blocked (has been changed)"; \ + else echo "info: $* needs update"; fi; \ + fi; \ + fi; \ + +#@ update all tools required by the project. +update-tools:: $(TARGETS_UPDATE_GO) $(TARGETS_INSTALL_SH) $(TARGETS_INSTALL_NPM) + go mod tidy -compat=${GOVERSION}; +#@ update-*: update the matched software command or service. + +# update go tools used by the project. +$(TARGETS_UPDATE_GO):: update-%: + go install $(call go-pkg,update,$(TOOLS_GO),^$*$$); + +## Custom: custom extension targets. + +# Include custom targets to extend scripts. +ifneq ("$(wildcard Makefile.ext)","") + include Makefile.ext +endif diff --git a/config/Makefile.defs b/config/Makefile.defs new file mode 100644 index 0000000..8ac3ad0 --- /dev/null +++ b/config/Makefile.defs @@ -0,0 +1,24 @@ +# Setup environment for all run-targets (to be modified) +define run-setup + true +endef + +# Define variables for all run-targets (to be modified) +define run-vars + +endef + +# Define variables for local run-targets (to be modified) +define run-vars-local + +endef + +# Define variables for image run-targets (to be modified) +define run-vars-image + +endef + +# Define aws localstack setup (to be modified). +define run-aws-setup + true +endef diff --git a/config/Makefile.vars b/config/Makefile.vars new file mode 100644 index 0000000..8940908 --- /dev/null +++ b/config/Makefile.vars @@ -0,0 +1,30 @@ +# Setup code quality level (default: base). +CODE_QUALITY := base + +# Setup codacy integration (default: enabled [enabled, disabled]). +CODACY := enabled +# Customizing codacy server for open source. +CODACY_API_BASE_URL := https://codacy.bus.zalan.do +# Continue after codacy shows violation (default: false / true [cdp-pipeline]). +CODACY_CONTINUE := true + +# Setup required targets before testing (default: ). +#TEST_DEPS := run-db run-aws +# Setup required targets before running commands (default: ). +#RUN_DEPS := run-db run-aws +# Setup required aws services for testing (default: ). +#AWS_SERVICES := s3 sqs + +# Setup when to push images (default: pulls [never, pulls, merges]) +IMAGE_PUSH ?= never + +# Setup default test timeout (default: 10s). +TEST_TIMEOUT := 10s + +# Setup custom delivery file (default: delivery.yaml). +FILE_DELIVERY := delivery.yaml + +# Custom linters applied to prepare next level (default: ). +LINTERS_CUSTOM := +# Linters swithed off to complete next level (default: ). +LINTERS_DISABLED := diff --git a/config/revive.toml b/config/revive.toml new file mode 100644 index 0000000..7ca89e4 --- /dev/null +++ b/config/revive.toml @@ -0,0 +1,108 @@ +# Translates the 'golangci-lint' settings into an Codacy understandable format. + +# When set to false, ignores files with "GENERATED" header. +ignoreGeneratedHeader = true + +# Sets the default severity to "warning" +severity = "error" + +# Sets the default failure confidence. This means that linting errors +# with less than 0.8 confidence will be ignored. +confidence = 0.8 + +# Sets the error code for failures with severity "error" +errorCode = 1 + +# Sets the error code for failures with severity "warning" +warningCode = 0 + +# Enables all available rules +enableAllRules = true + +# No need to enforce a file header. +[rule.file-header] + disabled = true + +# Reports on each file in a package. +[rule.package-comments] + disabled = true + +# Reports on comments mismatching comments. +[rule.exported] + disabled = true + +# No need to exclude import shadowing. +[rule.import-shadowing] + disabled = true + +# Restricted alias naming conflicts with '.'-imports. +# Not supporte by current Codacy revive v1.3.3 +#[rule.import-alias-naming] + # disabled = true + +# Exluding '.'-import makes test package separation unnecessary difficult. +[rule.dot-imports] + disabled = true + +# Fails to exclude nolint directives from reporting. +# Not supporte by current Codacy revive v1.2.3 +#[rule.comment-spacings] + # disabled = true + +# Revive v1.2.3 is reporting incorrect empty lines. +[rule.empty-lines] + disabled = true + +# Fails to disable writers that actually cannot return errors. +[rule.unhandled-error] + disabled = true + +# Conflicts with strategy to embed assignments in if statements. +[rule.indent-error-flow] + disabled = true + +# Fails to restrict sufficiently in switches with numeric values. +[rule.add-constant] + disabled = true + +# Rule prevents intentional usage of similar variable names. +[rule.flag-parameter] + disabled = true + +# Rule prevents intentional usage of similar private method names. +[rule.confusing-naming] + disabled = true + +# Enables a more experienced cyclomatic complexity (we enabled a log of rules +# to counter-act the complexity trap). +[rule.cyclomatic] + arguments = [20] + +# Enables a more experienced cognitive complexity (we enabled a lot of rules +# to counter-act the complexity trap). +[rule.cognitive-complexity] + arguments = [20] + +# Limit line-length to increase readability. +[rule.line-length-limit] + arguments = [100] + +# We are a bit more relaxed with function length consistent with funlen. +[rule.function-length] + arguments = [40,60] + +# Limit arguments of functions to the maximum understandable value. +[rule.argument-limit] + arguments = [6] + +# Limit results of functions to the maximum understandable value. +[rule.function-result-limit] + arguments = [4] + +# Raise the limit a bit to allow more complex package models. +[rule.max-public-structs] + arguments = [8] + +# I do not know what I'm doing here... +[rule.banned-characters] + arguments = ["Ω", "Σ", "σ"]