localstack/localstack

View on GitHub
Makefile

Summary

Maintainability
Test Coverage
IMAGE_NAME ?= localstack/localstack
IMAGE_TAG ?= $(shell cat VERSION)
VENV_BIN ?= python3 -m venv
VENV_DIR ?= .venv
PIP_CMD ?= pip3
TEST_PATH ?= .
TEST_EXEC ?= python -m
PYTEST_LOGLEVEL ?= warning
DISABLE_BOTO_RETRIES ?= 1
MAIN_CONTAINER_NAME ?= localstack-main

MAJOR_VERSION = $(shell echo ${IMAGE_TAG} | cut -d '.' -f1)
MINOR_VERSION = $(shell echo ${IMAGE_TAG} | cut -d '.' -f2)
PATCH_VERSION = $(shell echo ${IMAGE_TAG} | cut -d '.' -f3)

ifeq ($(OS), Windows_NT)
    VENV_ACTIVATE = $(VENV_DIR)/Scripts/activate
else
    VENV_ACTIVATE = $(VENV_DIR)/bin/activate
endif

VENV_RUN = . $(VENV_ACTIVATE)

usage:                    ## Show this help
    @grep -Fh "##" $(MAKEFILE_LIST) | grep -Fv fgrep | sed -e 's/:.*##\s*/##/g' | awk -F'##' '{ printf "%-25s %s\n", $$1, $$2 }'

$(VENV_ACTIVATE): pyproject.toml
    test -d $(VENV_DIR) || $(VENV_BIN) $(VENV_DIR)
    $(VENV_RUN); $(PIP_CMD) install --upgrade pip setuptools wheel plux
    touch $(VENV_ACTIVATE)

venv: $(VENV_ACTIVATE)    ## Create a new (empty) virtual environment

freeze:                   ## Run pip freeze -l in the virtual environment
    @$(VENV_RUN); pip freeze -l

upgrade-pinned-dependencies: venv
    $(VENV_RUN); $(PIP_CMD) install --upgrade pip-tools pre-commit
    $(VENV_RUN); pip-compile --strip-extras --upgrade --strip-extras -o requirements-basic.txt pyproject.toml
    $(VENV_RUN); pip-compile --strip-extras --upgrade --extra runtime -o requirements-runtime.txt pyproject.toml
    $(VENV_RUN); pip-compile --strip-extras --upgrade --extra test -o requirements-test.txt pyproject.toml
    $(VENV_RUN); pip-compile --strip-extras --upgrade --extra dev -o requirements-dev.txt pyproject.toml
    $(VENV_RUN); pip-compile --strip-extras --upgrade --extra typehint -o requirements-typehint.txt pyproject.toml
    $(VENV_RUN); pip-compile --strip-extras --upgrade --extra base-runtime -o requirements-base-runtime.txt pyproject.toml
    $(VENV_RUN); pre-commit autoupdate

install-basic: venv       ## Install basic dependencies for CLI usage into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-basic.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e .

install-runtime: venv     ## Install dependencies for the localstack runtime into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-runtime.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[runtime]"

install-test: venv        ## Install requirements to run tests into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-test.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[test]"

install-dev: venv         ## Install developer requirements into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-dev.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[dev]"

install-dev-types: venv   ## Install developer requirements incl. type hints into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-typehint.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[typehint]"

install-s3: venv     ## Install dependencies for the localstack runtime for s3-only into venv
    $(VENV_RUN); $(PIP_CMD) install -r requirements-base-runtime.txt
    $(VENV_RUN); $(PIP_CMD) install $(PIP_OPTS) -e ".[base-runtime]"

install: install-dev entrypoints  ## Install full dependencies into venv

entrypoints:              ## Run plux to build entry points
    $(VENV_RUN); python -m plux entrypoints
    @# make sure that the entrypoints were correctly created and are non-empty
    @test -s localstack_core.egg-info/entry_points.txt || (echo "Entrypoints were not correctly created! Aborting!" && exit 1)

dist: entrypoints        ## Build source and built (wheel) distributions of the current version
    $(VENV_RUN); pip install --upgrade twine; python -m build

publish: clean-dist dist  ## Publish the library to the central PyPi repository
    # make sure the dist archive contains a non-empty entry_points.txt file before uploading
    tar --wildcards --to-stdout -xf dist/localstack?core*.tar.gz "localstack?core*/localstack_core.egg-info/entry_points.txt" | grep . > /dev/null 2>&1 || (echo "Refusing upload, localstack-core dist does not contain entrypoints." && exit 1)
    $(VENV_RUN); twine upload dist/*

coveralls:                   ## Publish coveralls metrics
    $(VENV_RUN); coveralls

start:                       ## Manually start the local infrastructure for testing
    ($(VENV_RUN); exec bin/localstack start --host)

TAGS ?= $(IMAGE_NAME)
docker-save-image:           ## Export the built Docker image
    docker save -o target/localstack-docker-image-$(PLATFORM).tar $(TAGS)

# By default we export the community image
TAG ?= $(IMAGE_NAME)
# By default we load the result to the docker daemon
DOCKER_BUILD_FLAGS ?= "--load"
DOCKERFILE ?= "./Dockerfile"
docker-build:               ## Build Docker image
    # start build
    # --add-host: Fix for Centos host OS
    # --build-arg BUILDKIT_INLINE_CACHE=1: Instruct buildkit to inline the caching information into the image
    # --cache-from: Use the inlined caching information when building the image
    DOCKER_BUILDKIT=1 docker buildx build --pull --progress=plain \
        --cache-from $(TAG) --build-arg BUILDKIT_INLINE_CACHE=1 \
        --build-arg LOCALSTACK_PRE_RELEASE=$(shell cat VERSION | grep -v '.dev' >> /dev/null && echo "0" || echo "1") \
        --build-arg LOCALSTACK_BUILD_GIT_HASH=$(shell git rev-parse --short HEAD) \
        --build-arg=LOCALSTACK_BUILD_DATE=$(shell date -u +"%Y-%m-%d") \
        --build-arg=LOCALSTACK_BUILD_VERSION=$(IMAGE_TAG) \
        --add-host="localhost.localdomain:127.0.0.1" \
        -t $(TAG) $(DOCKER_BUILD_FLAGS) . -f $(DOCKERFILE)

docker-build-multiarch:   ## Build the Multi-Arch Full Docker Image
    # Make sure to prepare your environment for cross-platform docker builds! (see doc/developer_guides/README.md)
    # Multi-Platform builds cannot be loaded to the docker daemon from buildx, so we can't add "--load".
    make DOCKER_BUILD_FLAGS="--platform linux/amd64,linux/arm64" docker-build

SOURCE_IMAGE_NAME ?= $(IMAGE_NAME)
TARGET_IMAGE_NAME ?= $(IMAGE_NAME)
docker-push-master:       ## Push a single platform-specific Docker image to registry IF we are currently on the master branch
    (CURRENT_BRANCH=`(git rev-parse --abbrev-ref HEAD | grep '^master$$' || ((git branch -a | grep 'HEAD detached at [0-9a-zA-Z]*)') && git branch -a)) | grep '^[* ]*master$$' | sed 's/[* ]//g' || true`; \
        test "$$CURRENT_BRANCH" != 'master' && echo "Not on master branch.") || \
    ((test "$$DOCKER_USERNAME" = '' || test "$$DOCKER_PASSWORD" = '' ) && \
        echo "Skipping docker push as no credentials are provided.") || \
    (REMOTE_ORIGIN="`git remote -v | grep '/localstack' | grep origin | grep push | awk '{print $$2}'`"; \
        test "$$REMOTE_ORIGIN" != 'https://github.com/localstack/localstack.git' && \
        test "$$REMOTE_ORIGIN" != 'git@github.com:localstack/localstack.git' && \
        echo "This is a fork and not the main repo.") || \
    ( \
        docker info | grep Username || docker login -u $$DOCKER_USERNAME -p $$DOCKER_PASSWORD; \
            docker tag $(SOURCE_IMAGE_NAME):latest $(TARGET_IMAGE_NAME):latest-$(PLATFORM) && \
        ((! (git diff HEAD^ VERSION | tail -n 1 | grep -v '.dev') && \
            echo "Only pushing tag 'latest' as version has not changed.") || \
            (docker tag $(TARGET_IMAGE_NAME):latest-$(PLATFORM) $(TARGET_IMAGE_NAME):stable-$(PLATFORM) && \
                docker tag $(TARGET_IMAGE_NAME):latest-$(PLATFORM) $(TARGET_IMAGE_NAME):$(IMAGE_TAG)-$(PLATFORM) && \
                docker tag $(TARGET_IMAGE_NAME):latest-$(PLATFORM) $(TARGET_IMAGE_NAME):$(MAJOR_VERSION)-$(PLATFORM) && \
                docker tag $(TARGET_IMAGE_NAME):latest-$(PLATFORM) $(TARGET_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION)-$(PLATFORM) && \
                docker tag $(TARGET_IMAGE_NAME):latest-$(PLATFORM) $(TARGET_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION)-$(PLATFORM) && \
                docker push $(TARGET_IMAGE_NAME):stable-$(PLATFORM) && \
                docker push $(TARGET_IMAGE_NAME):$(IMAGE_TAG)-$(PLATFORM) && \
                docker push $(TARGET_IMAGE_NAME):$(MAJOR_VERSION)-$(PLATFORM) && \
                docker push $(TARGET_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION)-$(PLATFORM) && \
                docker push $(TARGET_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION)-$(PLATFORM) \
                )) && \
                  docker push $(TARGET_IMAGE_NAME):latest-$(PLATFORM) \
    )

MANIFEST_IMAGE_NAME ?= $(IMAGE_NAME)
docker-create-push-manifests:    ## Create and push manifests for a docker image (default: community)
    (CURRENT_BRANCH=`(git rev-parse --abbrev-ref HEAD | grep '^master$$' || ((git branch -a | grep 'HEAD detached at [0-9a-zA-Z]*)') && git branch -a)) | grep '^[* ]*master$$' | sed 's/[* ]//g' || true`; \
        test "$$CURRENT_BRANCH" != 'master' && echo "Not on master branch.") || \
    ((test "$$DOCKER_USERNAME" = '' || test "$$DOCKER_PASSWORD" = '' ) && \
        echo "Skipping docker manifest push as no credentials are provided.") || \
    (REMOTE_ORIGIN="`git remote -v | grep '/localstack' | grep origin | grep push | awk '{print $$2}'`"; \
        test "$$REMOTE_ORIGIN" != 'https://github.com/localstack/localstack.git' && \
        test "$$REMOTE_ORIGIN" != 'git@github.com:localstack/localstack.git' && \
        echo "This is a fork and not the main repo.") || \
    ( \
        docker info | grep Username || docker login -u $$DOCKER_USERNAME -p $$DOCKER_PASSWORD; \
            docker manifest create $(MANIFEST_IMAGE_NAME):latest --amend $(MANIFEST_IMAGE_NAME):latest-amd64 --amend $(MANIFEST_IMAGE_NAME):latest-arm64 && \
        ((! (git diff HEAD^ VERSION | tail -n 1 | grep -v '.dev') && \
                echo "Only pushing tag 'latest' as version has not changed.") || \
            (docker manifest create $(MANIFEST_IMAGE_NAME):$(IMAGE_TAG) \
            --amend $(MANIFEST_IMAGE_NAME):$(IMAGE_TAG)-amd64 \
            --amend $(MANIFEST_IMAGE_NAME):$(IMAGE_TAG)-arm64 && \
            docker manifest create $(MANIFEST_IMAGE_NAME):stable \
            --amend $(MANIFEST_IMAGE_NAME):stable-amd64 \
            --amend $(MANIFEST_IMAGE_NAME):stable-arm64 && \
            docker manifest create $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION) \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION)-amd64 \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION)-arm64 && \
            docker manifest create $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION) \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION)-amd64 \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION)-arm64 && \
            docker manifest create $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION) \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION)-amd64 \
            --amend $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION)-arm64 && \
                docker manifest push $(MANIFEST_IMAGE_NAME):stable && \
                docker manifest push $(MANIFEST_IMAGE_NAME):$(IMAGE_TAG) && \
                docker manifest push $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION) && \
                docker manifest push $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION) && \
                docker manifest push $(MANIFEST_IMAGE_NAME):$(MAJOR_VERSION).$(MINOR_VERSION).$(PATCH_VERSION))) && \
        docker manifest push $(MANIFEST_IMAGE_NAME):latest \
    )

docker-run-tests:          ## Initializes the test environment and runs the tests in a docker container
    docker run -e LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC=1 --entrypoint= -v `pwd`/requirements-test.txt:/opt/code/localstack/requirements-test.txt -v `pwd`/tests/:/opt/code/localstack/tests/ -v `pwd`/target/:/opt/code/localstack/target/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/localstack:/var/lib/localstack \
        $(IMAGE_NAME) \
        bash -c "make install-test && DEBUG=$(DEBUG) PYTEST_LOGLEVEL=$(PYTEST_LOGLEVEL) PYTEST_ARGS='$(PYTEST_ARGS)' COVERAGE_FILE='$(COVERAGE_FILE)' TEST_PATH='$(TEST_PATH)' LAMBDA_IGNORE_ARCHITECTURE=1 LAMBDA_INIT_POST_INVOKE_WAIT_MS=50 TINYBIRD_PYTEST_ARGS='$(TINYBIRD_PYTEST_ARGS)' TINYBIRD_DATASOURCE='$(TINYBIRD_DATASOURCE)' TINYBIRD_TOKEN='$(TINYBIRD_TOKEN)' TINYBIRD_URL='$(TINYBIRD_URL)' CI_COMMIT_BRANCH='$(CI_COMMIT_BRANCH)' CI_COMMIT_SHA='$(CI_COMMIT_SHA)' CI_JOB_URL='$(CI_JOB_URL)' CI_JOB_NAME='$(CI_JOB_NAME)' CI_JOB_ID='$(CI_JOB_ID)' CI='$(CI)' TEST_AWS_REGION_NAME='${TEST_AWS_REGION_NAME}' TEST_AWS_ACCESS_KEY_ID='${TEST_AWS_ACCESS_KEY_ID}' TEST_AWS_ACCOUNT_ID='${TEST_AWS_ACCOUNT_ID}' make test-coverage"

docker-run-tests-s3-only:          ## Initializes the test environment and runs the tests in a docker container for the S3 only image
    # TODO: We need node as it's a dependency of the InfraProvisioner at import time, remove when we do not need it anymore
    # g++ is a workaround to fix the JPype1 compile error on ARM Linux "gcc: fatal error: cannot execute ‘cc1plus’" because the test dependencies include the runtime dependencies.
    docker run -e LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC=1 --entrypoint= -v `pwd`/requirements-test.txt:/opt/code/localstack/requirements-test.txt -v `pwd`/tests/:/opt/code/localstack/tests/ -v `pwd`/target/:/opt/code/localstack/target/ -v /var/run/docker.sock:/var/run/docker.sock -v /tmp/localstack:/var/lib/localstack \
        $(IMAGE_NAME) \
        bash -c "apt-get update && apt-get install -y g++ && make install-test && apt-get install -y --no-install-recommends gnupg && mkdir -p /etc/apt/keyrings && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo \"deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main\" > /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install -y --no-install-recommends nodejs && DEBUG=$(DEBUG) PYTEST_LOGLEVEL=$(PYTEST_LOGLEVEL) PYTEST_ARGS='$(PYTEST_ARGS)' TEST_PATH='$(TEST_PATH)' TINYBIRD_PYTEST_ARGS='$(TINYBIRD_PYTEST_ARGS)' TINYBIRD_DATASOURCE='$(TINYBIRD_DATASOURCE)' TINYBIRD_TOKEN='$(TINYBIRD_TOKEN)' TINYBIRD_URL='$(TINYBIRD_URL)' CI_COMMIT_BRANCH='$(CI_COMMIT_BRANCH)' CI_COMMIT_SHA='$(CI_COMMIT_SHA)' CI_JOB_URL='$(CI_JOB_URL)' CI_JOB_NAME='$(CI_JOB_NAME)' CI_JOB_ID='$(CI_JOB_ID)' CI='$(CI)' make test"


docker-cp-coverage:
    @echo 'Extracting .coverage file from Docker image'; \
        id=$$(docker create localstack/localstack); \
        docker cp $$id:/opt/code/localstack/.coverage .coverage; \
        docker rm -v $$id

test:                        ## Run automated tests
    ($(VENV_RUN); $(TEST_EXEC) pytest --durations=10 --log-cli-level=$(PYTEST_LOGLEVEL) $(PYTEST_ARGS) $(TEST_PATH))

test-coverage: LOCALSTACK_INTERNAL_TEST_COLLECT_METRIC = 1
test-coverage: TEST_EXEC = python -m coverage run $(COVERAGE_ARGS) -m
test-coverage: test      ## Run automated tests and create coverage report

lint:                        ## Run code linter to check code style, check if formatter would make changes and check if dependency pins need to be updated
    ($(VENV_RUN); python -m ruff check --output-format=full . && python -m ruff format --check .)
    $(VENV_RUN); pre-commit run check-pinned-deps-for-needed-upgrade --files pyproject.toml # run pre-commit hook manually here to ensure that this check runs in CI as well


lint-modified:               ## Run code linter to check code style, check if formatter would make changes on modified files, and check if dependency pins need to be updated because of modified files
    ($(VENV_RUN); python -m ruff check --output-format=full `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs` && python -m ruff format --check `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`)
    $(VENV_RUN); pre-commit run check-pinned-deps-for-needed-upgrade --files $(git diff master --name-only) # run pre-commit hook manually here to ensure that this check runs in CI as well

check-aws-markers:               ## Lightweight check to ensure all AWS tests have proper compatibilty markers set
    ($(VENV_RUN); python -m pytest --co tests/aws/)

format:                      ## Run ruff to format the whole codebase
    ($(VENV_RUN); python -m ruff format .; python -m ruff check --output-format=full --fix .)

format-modified:          ## Run ruff to format only modified code
    ($(VENV_RUN); python -m ruff format `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`; python -m ruff check --output-format=full --fix `git diff --diff-filter=d --name-only HEAD | grep '\.py$$' | xargs`)

init-precommit:              ## install te pre-commit hook into your local git repository
    ($(VENV_RUN); pre-commit install)

clean:                       ## Clean up (npm dependencies, downloaded infrastructure code, compiled Java classes)
    rm -rf .filesystem
    rm -rf build/
    rm -rf dist/
    rm -rf *.egg-info
    rm -rf $(VENV_DIR)

clean-dist:                  ## Clean up python distribution directories
    rm -rf dist/ build/
    rm -rf *.egg-info

.PHONY: usage freeze install-basic install-runtime install-test install-dev install entrypoints dist publish coveralls start docker-save-image docker-build docker-build-multiarch docker-push-master docker-create-push-manifests docker-run-tests docker-cp-coverage test test-coverage lint lint-modified format format-modified init-precommit clean clean-dist upgrade-pinned-dependencies