From 32f3efe7a8c8ed0a15665cd27aef2434d8035ee8 Mon Sep 17 00:00:00 2001 From: Stefan Reimer Date: Fri, 11 Feb 2022 13:07:07 +0100 Subject: [PATCH] ci: more CI tuning --- .flake8 | 3 +++ .gitignore | 61 ++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 20 +-------------- Dockerfile.test | 28 ++++++++++++++++++++ Makefile | 30 ++++++++++++++-------- app.py | 17 +++++++----- dev-requirements.txt | 1 + 7 files changed, 125 insertions(+), 35 deletions(-) create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 Dockerfile.test diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e5028e9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E501 +exclude = .git,__pycache__,build,dist,report diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adee324 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Vim +*.swp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest* +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +reports/ + +# Translations +*.mo +*.pot + +# dotenv +.env + +# virtualenv +venv/ +ENV/ diff --git a/Dockerfile b/Dockerfile index fd6c3e1..b88c276 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,28 +39,10 @@ COPY app.py /app # Stage 3 - final runtime image -FROM python-alpine as release +FROM python-alpine WORKDIR /app COPY --from=build-image /app /app ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ] CMD [ "app.handler" ] - - -# Tests -FROM release as test - -# Get aws-lambda run time emulator -ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie -RUN chmod 0755 /usr/local/bin/aws-lambda-rie && \ - mkdir -p tests - -# Install pytest -RUN pip install pytest --target /app - -# Add our tests -ADD tests /app/tests - -# Run tests -RUN python -m pytest --capture=tee-sys tests diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 0000000..dab589a --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,28 @@ +ARG REPOSITORY="sns-alert-hub" +ARG TAG="latest" + +FROM ${REPOSITORY}:${TAG} + +# Install additional tools for tests +COPY dev-requirements.txt .flake8 . +RUN export MAKEFLAGS="-j$(nproc)" && \ + pip install -r dev-requirements.txt + +# Unit Tests / Static / Style etc. +COPY tests/ tests/ +RUN flake8 app.py tests && \ + codespell app.py tests + +# Get aws-lambda run time emulator +ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie +RUN chmod 0755 /usr/local/bin/aws-lambda-rie && \ + mkdir -p tests + +# Install pytest +RUN pip install pytest --target /app + +# Add our tests +ADD tests /app/tests + +# Run tests +RUN python -m pytest tests --capture=tee-sys diff --git a/Makefile b/Makefile index e68feca..0cf714e 100644 --- a/Makefile +++ b/Makefile @@ -14,21 +14,31 @@ endif .PHONY: build push scan test -all: build +all: test + +# Ensure we run the tests by removing any previous runs +.PHONY: rm-test-image +rm-test-image: + @test -z "$$(docker image ls -q $(REPOSITORY):$(TAG)-test)" || docker image rm $(REPOSITORY):$(TAG)-test > /dev/null + @test -z "$$(docker image ls -q $(REPOSITORY):$(TAG)-test)" || echo "Error: Removing test image failed" build: - podman build --target release --rm -t $(REPOSITORY):$(TAG) -t $(REPOSITORY):latest . + sed -i -e "s/^__version__ =.*/__version__ = \"$(TAG)\"/" app.py + docker build --rm -t $(REPOSITORY):$(TAG) . -test: - podman build --target test --rm -t $(REPOSITORY):$(TAG) -t $(REPOSITORY):latest . +test: build rm-test-image + docker build --rm -t $(REPOSITORY):$(TAG)-test \ + --build-arg REPOSITORY=$(REPOSITORY) \ + --build-arg TAG=$(TAG) \ + -f Dockerfile.test . -scan: +scan: build trivy $(TRIVY_OPTS) $(REPOSITORY):$(TAG) -push: - aws ecr-public get-login-password --region $(REGION) | podman login --username AWS --password-stdin $(REGISTRY) - podman tag $(REPOSITORY):latest $(REGISTRY)/$(REPOSITORY):$(TAG) $(REGISTRY)/$(REPOSITORY):latest - podman push $(REGISTRY)/$(REPOSITORY):$(TAG) - podman push $(REGISTRY)/$(REPOSITORY):latest +push: scan + aws ecr-public get-login-password --region $(REGION) | docker login --username AWS --password-stdin $(REGISTRY) + docker tag $(REPOSITORY):$(TAG) $(REGISTRY)/$(REPOSITORY):$(TAG) $(REGISTRY)/$(REPOSITORY):latest + docker push $(REGISTRY)/$(REPOSITORY):$(TAG) + docker push $(REGISTRY)/$(REPOSITORY):latest # Delete all untagged images # aws ecr-public batch-delete-image --repository-name $(REPOSITORY) --region $(REGION) --image-ids $$(for image in $$(aws ecr-public describe-images --repository-name $(REPOSITORY) --region $(REGION) --output json | jq -r '.imageDetails[] | select(.imageTags | not ).imageDigest'); do echo -n "imageDigest=$$image "; done) diff --git a/app.py b/app.py index 84767ba..168ea3a 100644 --- a/app.py +++ b/app.py @@ -11,7 +11,7 @@ import apprise __author__ = "Stefan Reimer" __author_email__ = "stefan@zero-downtime.net" -__version__ = "0.7.2" +__version__ = "head" # Global alias lookup cache account_aliases = {} @@ -39,11 +39,13 @@ else: # Ensure slack URLs use ?blocks=yes if "slack.com" in WEBHOOK_URL: - scheme, netloc, path, query_string, fragment = urllib.parse.urlsplit(WEBHOOK_URL) + scheme, netloc, path, query_string, fragment = urllib.parse.urlsplit( + WEBHOOK_URL) query_params = urllib.parse.parse_qs(query_string) query_params["blocks"] = ["yes"] new_query_string = urllib.parse.urlencode(query_params, doseq=True) - WEBHOOK_URL = urllib.parse.urlunsplit((scheme, netloc, path, new_query_string, fragment)) + WEBHOOK_URL = urllib.parse.urlunsplit( + (scheme, netloc, path, new_query_string, fragment)) # Setup apprise asset = apprise.AppriseAsset() @@ -54,7 +56,8 @@ asset.app_url = "https://zero-downtime.net" asset.image_url_mask = ( "https://cdn.zero-downtime.net/assets/zdt/apprise/{TYPE}-{XY}{EXTENSION}" ) -asset.app_id = "{} / {} {}".format("cloudbender", __version__, "zero-downtime.net") +asset.app_id = "{} / {} {}".format("cloudbender", + __version__, "zero-downtime.net") apobj = apprise.Apprise(asset=asset) apobj.add(WEBHOOK_URL) @@ -197,7 +200,8 @@ def handler(event, context): if alert["status"] == "resolved": body = body + "\nDuration: {}".format( humanize.time.precisedelta( - dateutil.parser.parse(alert["startsAt"]) - dateutil.parser.parse(alert["endsAt"]) + dateutil.parser.parse( + alert["startsAt"]) - dateutil.parser.parse(alert["endsAt"]) ) ) else: @@ -228,4 +232,5 @@ def handler(event, context): else: body = sns["Message"] - apobj.notify(body=body, title="Unknown message type", notify_type=apprise.NotifyType.WARNING) + apobj.notify(body=body, title="Unknown message type", + notify_type=apprise.NotifyType.WARNING) diff --git a/dev-requirements.txt b/dev-requirements.txt index 872f4a5..451c559 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,2 +1,3 @@ +pytest flake8 codespell