1.1. CI/CD Python

1.1.1. Project

git clone https://github.com/sages-pl/src-python.git
ln -s /home/ubuntu/src-python /home/ubuntu/src
cd /home/ubuntu/src

1.1.2. System

sudo apt update
echo 'export IP=$(curl -s ipecho.net/plain)' >> ~/.bashrc
echo 'export PATH=/home/ubuntu/bin:$PATH' >> ~/.bashrc
source "~/.bashrc"

1.1.3. Docker

echo 'export DOCKER_HOST=unix:///run/user/1000/docker.sock' >> ~/.bashrc
source "~/.bashrc"
cat <<EOF | sudo tee "/etc/apparmor.d/home.ubuntu.bin.rootlesskit"
# ref: https://ubuntu.com/blog/ubuntu-23-10-restricted-unprivileged-user-namespaces
abi <abi/4.0>,
include <tunables/global>

/home/ubuntu/bin/rootlesskit flags=(unconfined) {
  userns,

  # Site-specific additions and overrides. See local/README for details.
  include if exists <local/home.ubuntu.bin.rootlesskit>
}
EOF
sudo systemctl restart apparmor.service
sudo apt install -y uidmap
curl https://get.docker.com/rootless |sh -x
sudo apt install -y docker-buildx
systemctl --user enable docker
sudo loginctl enable-linger $(whoami)
docker network create ecosystem

1.1.4. Gitea

cat > ~/bin/run-gitea << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Run docker container"
docker run \\
    --name gitea \\
    --detach \\
    --restart unless-stopped \\
    --network ecosystem \\
    --dns 8.8.8.8 \\
    --publish 3000:3000 \\
    --publish 2222:22 \\
    --env USER_UID=1000 \\
    --env USER_GID=1000 \\
    --env GITEA__server__ROOT_URL=http://$IP:3000/ \\
    --env GITEA__database__DB_TYPE=sqlite3 \\
    --env GITEA__database__PATH=/var/lib/gitea/data/gitea.db \\
    --env GITEA__database__HOST=... \\
    --env GITEA__database__NAME=... \\
    --env GITEA__database__USER=... \\
    --env GITEA__database__PASSWD=... \\
    --volume gitea_data:/var/lib/gitea \\
    --volume gitea_config:/etc/gitea \\
    --volume /etc/timezone:/etc/timezone:ro \\
    --volume /etc/localtime:/etc/localtime:ro \\
    gitea/gitea:latest-rootless

echo "Post-run hooks"
echo "Gitea running on: http://$IP:3000/"

EOF
chmod +x ~/bin/run-gitea
run-gitea

1.1.5. Jenkins

cat > ~/bin/run-jenkins << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Run docker container"
docker run \\
    --name jenkins \\
    --detach \\
    --restart unless-stopped \\
    --network ecosystem \\
    --dns 8.8.8.8 \\
    --publish 8080:8080 \\
    --volume jenkins_data:/var/jenkins_home \\
    --volume /run/user/1000/docker.sock:/var/run/docker.sock \\
    jenkins/jenkins:lts-alpine

docker exec -u root jenkins apk add docker
docker exec -u root jenkins apk add python3 py3-pip
docker exec -u root jenkins mv /usr/lib/python3.12/EXTERNALLY-MANAGED /usr/lib/python3.12/EXTERNALLY-MANAGED.old

chmod o+rw /run/user/1000/docker.sock
sudo ln -s /home/ubuntu/.local/share/docker/volumes/jenkins_data/_data/ /var/jenkins_home

echo "Jenkins running on: http://$IP:8080/"

EOF
chmod +x ~/bin/run-jenkins
run-jenkins

1.1.6. SonarQube

cat > ~/bin/run-sonarqube << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Run docker container"
docker run \\
    --name sonarqube \\
    --detach \\
    --restart unless-stopped \\
    --network ecosystem \\
    --dns 8.8.8.8 \\
    --publish 9000:9000 \\
    --volume sonarqube_data:/opt/sonarqube/data \\
    --volume sonarqube_logs:/opt/sonarqube/logs \\
    --volume sonarqube_extensions:/opt/sonarqube/extensions \\
    sonarqube:community

echo "SonarQube running on: http://$IP:9000/"

EOF
chmod +x ~/bin/run-sonarqube
run-sonarqube

1.1.7. SonarScanner

docker pull sonarsource/sonar-scanner-cli

1.1.8. Docker Registry

cat > ~/bin/run-registry << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Run docker container"
docker run \\
    --name registry \\
    --detach \\
    --restart unless-stopped \\
    --network ecosystem \\
    --dns 8.8.8.8 \\
    --publish 5000:5000 \\
    --volume registry_data:/var/lib/registry \\
    registry:latest

echo "Registry running on: http://$IP:5000/"

EOF
chmod +x ~/bin/run-registry
run-registry

1.1.9. Registry UI

cat > ~/registry-ui.yml << EOF

listen_addr: 0.0.0.0:8888
base_path: /

registry_url: http://registry:5000
verify_tls: true

# registry_username: user
# registry_password: pass

# The same one should be configured on Docker registry as Authorization Bearer token.
event_listener_token: token
event_retention_days: 7

event_database_driver: sqlite3
event_database_location: data/registry_events.db
# event_database_driver: mysql
# event_database_location: user:password@tcp(localhost:3306)/docker_events

cache_refresh_interval: 10

# If users can delete tags.
# If set to False, then only admins listed below.
anyone_can_delete: false

# Users allowed to delete tags.
# This should be sent via X-WEBAUTH-USER header from your proxy.
admins: []

# Debug mode. Affects only templates.
debug: true

# How many days to keep tags but also keep the minimal count provided no matter how old.
purge_tags_keep_days: 90
purge_tags_keep_count: 2

EOF
cat > ~/bin/run-registryui << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Run docker container"
docker run \\
    --name registry-ui \\
    --detach \\
    --restart always \\
    --network ecosystem \\
    --publish 8888:8888 \\
    --volume /home/ubuntu/registry-ui.yml:/opt/config.yml:ro \\
    quiq/docker-registry-ui

echo "Registry running on: http://$IP:5000/"
chmod +x ~/bin/run-registry-ui
run-registry-ui

1.1.10. Dependencies

cd /home/ubuntu/src
python3 -m venv .venv
. .venv/bin/activate
echo -n > requirements.txt
echo -n > requirements.lock

1.1.11. Run Files

mkdir -p run/
echo 'echo Not Implemented' > run/all
echo 'echo Not Implemented' > run/about
echo 'echo Not Implemented' > run/build-envvars
echo 'echo Not Implemented' > run/build-dependencies
echo 'echo Not Implemented' > run/build-compile
echo 'echo Not Implemented' > run/test-all
echo 'echo Not Implemented' > run/test-codestyle
echo 'echo Not Implemented' > run/test-coverage
echo 'echo Not Implemented' > run/test-documentation
echo 'echo Not Implemented' > run/test-formatter
echo 'echo Not Implemented' > run/test-functional
echo 'echo Not Implemented' > run/test-integration
echo 'echo Not Implemented' > run/test-lint
echo 'echo Not Implemented' > run/test-load
echo 'echo Not Implemented' > run/test-mutation
echo 'echo Not Implemented' > run/test-regression
echo 'echo Not Implemented' > run/test-security
echo 'echo Not Implemented' > run/test-smoke
echo 'echo Not Implemented' > run/test-static
echo 'echo Not Implemented' > run/test-typing
echo 'echo Not Implemented' > run/test-ui
echo 'echo Not Implemented' > run/test-unit
echo 'echo Not Implemented' > run/report
echo 'echo Not Implemented' > run/image-compile
echo 'echo Not Implemented' > run/image-build
echo 'echo Not Implemented' > run/image-push
echo 'echo Not Implemented' > run/image-remove
echo 'echo Not Implemented' > run/deploy-dev
echo 'echo Not Implemented' > run/deploy-test
echo 'echo Not Implemented' > run/deploy-preprod
echo 'echo Not Implemented' > run/deploy-prod
chmod +x run/*

1.1.12. Dockerfile

cat > Dockerfile.singlestage << EOF

FROM python:3.12-alpine
WORKDIR /data
ENV PYTHONPATH=src
COPY requirements.lock /data/requirements.lock
RUN pip install --upgrade --no-cache-dir -r /data/requirements.lock
COPY src /data/src
COPY test /data/test
COPY run /data/run
CMD python3 /data/src/__main__.py

EOF
cat > Dockerfile.multistage << EOF

## Set build environment
FROM python:3.12-alpine AS build
ENV PYTHONPATH=src
WORKDIR /data

## Copy files
COPY requirements.lock /data/requirements.lock
COPY src /data/src
COPY test /data/test
COPY run /data/run

## Setup Env
RUN run/build-debug
RUN run/build-envvars
RUN run/build-dependencies
RUN run/build-compile

## Run tests
RUN run/test-codestyle
RUN run/test-coverage
RUN run/test-documentation
RUN run/test-formatter
RUN run/test-functional
RUN run/test-integration
RUN run/test-lint
RUN run/test-load
RUN run/test-mutation
RUN run/test-regression
RUN run/test-security
RUN run/test-smoke
RUN run/test-static
RUN run/test-typing
RUN run/test-ui
RUN run/test-unit

## Create executable
RUN run/image-compile

## Prepare production ready Image
FROM python:3.12-alpine
COPY --from=build /data/myapp.pyz /myapp.pyz
CMD python3 /myapp.pyz

EOF
ln -s Dockerfile.multistage Dockerfile

1.1.13. Sonar Properties

export SONARQUBE_TOKEN=...
cat > sonar-project.properties << EOF

## Sonar Server
sonar.host.url=http://sonarqube:9000/
sonar.token=$SONARQUBE_TOKEN

## Software Configuration Management
sonar.scm.enabled=true
sonar.scm.provider=git

## SonarScanner Config
sonar.sourceEncoding=UTF-8
sonar.verbose=false
sonar.log.level=INFO
sonar.showProfiling=false
sonar.projectBaseDir=/usr/src/
sonar.working.directory=/tmp/

## Quality Gates
sonar.qualitygate.wait=true
sonar.qualitygate.timeout=300

## Python Project
sonar.projectKey=mypythonproject
sonar.projectName=MyPythonProject
sonar.projectVersion=1.0.0

## Python Config
sonar.language=py
sonar.python.version=3.12
sonar.sources=src
sonar.tests=test
sonar.inclusions=**/*.py
sonar.exclusions=**/migrations/**,**/*.pyc,**/__pycache__/**
sonar.python.file.suffixes=py
sonar.ipynb.file.suffixes=ipynb

## Python Tools
sonar.python.xunit.skipDetails=false
sonar.python.xunit.reportPath=.tmp/xunit.xml
sonar.python.coverage.reportPaths=.tmp/coverage.xml,.tmp/cobertura.xml
sonar.python.bandit.reportPaths=.tmp/bandit.json
sonar.python.mypy.reportPaths=.tmp/index.xml
sonar.python.pylint.reportPaths=.tmp/pylint.txt
sonar.python.flake8.reportPaths=.tmp/flake8.txt
sonar.python.ruff.reportPaths=.tmp/ruff.xml

## Documentation
# https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/analysis-parameters/

EOF

1.1.14. Jenkinsfile

cat > Jenkinsfile << EOF

pipeline {
  triggers {pollSCM('* * * * *')}
  agent any

  stages {
    stage('About')           {steps{ sh 'run/about' }}

    stage('Build') {stages {
      stage('Envvars')       {steps{ sh 'run/build-envvars' }}
      stage('Dependencies')  {steps{ sh 'run/build-dependencies' }}
      stage('Compile')       {steps{ sh 'run/build-compile' }}
    }}

    stage('Test') {parallel {
      stage('Codestyle')     {steps{ sh 'run/test-codestyle' }}
      stage('Coverage')      {steps{ sh 'run/test-coverage' }}
      stage('Documentation') {steps{ sh 'run/test-documentation' }}
      stage('Formatter')     {steps{ sh 'run/test-formatter' }}
      stage('Functional')    {steps{ sh 'run/test-functional' }}
      stage('Integration')   {steps{ sh 'run/test-integration' }}
      stage('Lint')          {steps{ sh 'run/test-lint' }}
      stage('Load')          {steps{ sh 'run/test-load' }}
      stage('Mutation')      {steps{ sh 'run/test-mutation' }}
      stage('Regression')    {steps{ sh 'run/test-regression' }}
      stage('Security')      {steps{ sh 'run/test-security' }}
      stage('Smoke')         {steps{ sh 'run/test-smoke' }}
      stage('Static')        {steps{ sh 'run/test-static' }}
      stage('Typing')        {steps{ sh 'run/test-typing' }}
      stage('UI')            {steps{ sh 'run/test-ui' }}
      stage('Unit')          {steps{ sh 'run/test-unit' }}
    }}

    stage('Report')            { steps { sh 'run/report' }}

    stage('Image') {stages {
      stage('Build')         {steps{ sh 'run/image-build' }}
      stage('Push')          {steps{ sh 'run/image-push' }}
      stage('Remove')        {steps{ sh 'run/image-remove' }}
    }}

    stage('Deploy') {stages {
      stage('Dev')           {steps{ sh 'run/deploy-dev' }}
      stage('Test')          {steps{ sh 'run/deploy-test' }}
      stage('Preprod')       {steps{ sh 'run/deploy-preprod' }}
      stage('Prod')          {steps{ sh 'run/deploy-prod' }}
    }}
  }
}

EOF

1.1.15. Pyproject TOML

cat > pyproject.toml << EOF

[project]
name = "myproject"
version = "1.0.0"
requires-python = ">=3.12"
readme = "README.md"
keywords = ["ares", "mars", "nasa", "human-spaceflight"]
authors = [{name = "John Doe", email = "jdoe@example.com"}]
license = {file = "LICENSE"}
classifiers = [
    "Development Status :: 3 - Alpha",
    "Environment :: Console",
    "Intended Audience :: System Administrators",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
    "Operating System :: OS Independent",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Libraries :: Application Frameworks",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Topic :: Software Development :: Build Tools",
    "Topic :: Software Development :: Testing",
    "Topic :: Software Development :: Version Control",
    "Topic :: Software Development :: Quality Assurance",
    "Topic :: System :: Software Distribution",
]

[project.urls]
homepage = "https://github.com/myusername/myproject"
documentation = "https://github.com/myusername/myproject"
repository = "https://github.com/myusername/myproject.git"
changelog = "https://github.com/myusername/myproject/releases"
bugtracker = "https://github.com/myusername/myproject/issues"

[tool.ruff]
target-version = "py312"
line-length = 88
indent-width = 4

exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".git-rewrite",
    ".hg",
    ".mypy_cache",
    ".nox",
    ".pants.d",
    ".pytype",
    ".ruff_cache",
    ".svn",
    ".tox",
    ".venv",
    "__pypackages__",
    "_build",
    "buck-out",
    "build",
    "dist",
    "node_modules",
    "venv",
]

[tool.ruff.lint]
# Enable Pyflakes ("F") and a subset of the pycodestyle ("E")  codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings ("W") or
# McCabe complexity ("C901") by default.
select = ["E4", "E7", "E9", "F"]
ignore = []

# Allow fix for all enabled rules (when "--fix") is provided.
fixable = ["ALL"]

[tool.ruff.format]
quote-style = "single"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

EOF

1.1.16. Run All

cat > run/all << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

run/about
run/build-envvars
run/build-dependencies
run/build-compile
run/test-all
run/test-codestyle
run/test-coverage
run/test-documentation
run/test-formatter
run/test-functional
run/test-integration
run/test-lint
run/test-load
run/test-mutation
run/test-regression
run/test-security
run/test-smoke
run/test-static
run/test-typing
run/test-ui
run/test-unit
run/report
run/image-compile
run/image-build
run/image-push
run/image-remove
run/deploy-dev
run/deploy-test
run/deploy-preprod
run/deploy-prod

EOF

1.1.17. Test All

cat > run/test-all << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

run/test-codestyle
run/test-coverage
run/test-documentation
run/test-formatter
run/test-functional
run/test-integration
run/test-lint
run/test-load
run/test-mutation
run/test-regression
run/test-security
run/test-smoke
run/test-static
run/test-typing
run/test-ui
run/test-unit

EOF

1.1.18. About

cat > run/about << EOF
#!/bin/sh

echo ""
echo "OS configuration:"
echo "Hostname: \$(hostname)"
echo "PWD: \$(pwd)"
echo "Whoami: \$(whoami)"
echo "ID: \$(id)"
echo "PATH: \$(echo \$PATH)"

echo ""
echo "Python configuration: "
echo "Executable: \$(which python3)"
echo "Version: \$(python3 --version)"

echo ""
echo "Debugging:"
echo "For Debug uncomment line with sleep:"
# sleep 3600

echo ""
echo "While build is on hold, execute:"
echo docker exec -it -u \$(whoami) --workdir "\$(pwd)" \$(hostname) sh

EOF

1.1.19. Build Envvars

cat > run/build-envvars << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src
export PYTHONWARNINGS=always
export PYTHONDEBUG=1
export PYTHONASYNCIODEBUG=1
export PYTHONDEVMODE=1
export PYTHONMALLOC=debug

EOF

1.1.20. Build Dependencies

cat > run/build-dependencies << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Upgrade pip"
python3 -m pip install --upgrade --no-cache-dir pip

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir -r requirements.lock

EOF

1.1.21. Test Unit

cat > run/test-unit << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Run analysis"
python3 -m unittest discover -v test

EOF

1.1.22. Test Integration

cat > run/test-integration << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Run analysis"
python3 -m doctest -v test/*.py

EOF

1.1.23. Test Security

cat > run/test-security << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir bandit

echo "Fail build if high severity bugs are discovered"
python3 -m bandit --exclude test --skip B311 --recursive src --silent --severity-level high

echo ""
echo "Pass build if not high severity bugs are discovered"
echo "Report will be uploaded to SonarQube"
python3 -m bandit --exclude test --skip B311 --recursive src --format json --output=.tmp/bandit.json --exit-zero

echo ""
echo "Show the results"
cat .tmp/bandit.json

EOF

1.1.24. Test Coverage

cat > run/test-coverage << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir coverage

echo "Run coverage analysis"
python3 -m coverage run src

echo "Gather reports"
python3 -m coverage report
python3 -m coverage xml -o .tmp/coverage.xml

echo ""
echo "Show the results"
cat .tmp/coverage.xml

EOF

1.1.25. Test Codestyle

cat > run/test-codestyle << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir flake8
python3 -m pip install --upgrade --no-cache-dir pycodestyle

echo "Run tests"
python3 -m flake8 --doctest --output-file=.tmp/flake8.txt src
python3 -m pycodestyle -v src/

echo ""
echo "Show the results"
cat .tmp/flake8.txt

EOF

1.1.26. Test Documentation

cat > run/test-documentation << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir pydocstyle tomli

echo "Run analysis"
python3 -m pydocstyle src/ || true

EOF

1.1.27. Test Lint

cat > run/test-lint << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir pylint

echo "Run verification for most common problems"
python3 -m pylint --exit-zero --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" --output=.tmp/pylint.txt --disable=C0114,C0115,C0116,E0401,C0103 src

echo ""
echo "Show the results"
cat .tmp/pylint.txt

EOF

1.1.28. Test Static

cat > run/test-static << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir pylama[all] setuptools

echo "Run verification for most common problems"
python3 -m pylama src --linters 'eradicate,mccabe,mypy,pycodestyle,pydocstyle,pyflakes,pylint,isort' --ignore C100,D101,D102,D107,C113,C0114,C115,C0116,D105,C0115,D100,D103,D106,D104 --skip .venv --format pylint --report .tmp/pylama.txt || true

echo ""
echo "Show the results"
cat .tmp/pylama.txt

EOF

1.1.29. Test Formatter

cat > run/test-formatter << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir ruff

echo "Run verification for most common problems"
python3 -m ruff check --exit-zero --output-format=junit --output-file=.tmp/ruff.xml src/

echo ""
echo "Show the results"
cat .tmp/ruff.xml

EOF

1.1.30. Test Formatter

cat > run/test-typing << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir mypy lxml

echo "Run analysis"
python3 -m mypy --ignore-missing-imports --cobertura-xml-report=.tmp src || true
python3 -m mypy --ignore-missing-imports --xml-report=.tmp src || true

echo ""
echo "Show the results"
cat .tmp/cobertura.xml
cat .tmp/index.xml

EOF

1.1.31. Test Mutation

cat > run/test-mutation << EOF
#!/bin/sh
echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Set environment variable"
export PYTHONPATH=src

echo "Create output directory"
mkdir -p .tmp

echo "Clear cache from previous analysis"
rm -fr .mutmut-cache

echo "Install dependencies"
python3 -m pip install --upgrade --no-cache-dir mutmut

echo "Run analysis"
python3 -m mutmut run --simple-output --paths-to-mutate src --tests-dir test || true

echo "Create reports"
python3 -m mutmut results
python3 -m mutmut junitxml --suspicious-policy=ignore --untested-policy=ignore > .tmp/xunit.xml

echo ""
echo "Show the results"
cat .tmp/xunit.xml

EOF

1.1.32. Test Mutation

cat > run/report << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Fix path for SonarScanner"
sed -r 's|<source>.+?</source>|<source>/usr/src</source>|' -i .tmp/coverage.xml
sed -r 's|<source>.+?</source>|<source>/usr/src</source>|' -i .tmp/cobertura.xml

echo "Run analysis"
docker run --rm --net=ecosystem -v \$(pwd):/usr/src:ro sonarsource/sonar-scanner-cli

EOF

1.1.33. Image Compile

cat > run/image-compile << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Install all dependencies inside src folder"
python3 -m pip install --upgrade --no-cache-dir -r requirements.lock --target src

echo "Remove not needed .dist-info files"
rm -fr src/*.dist-info

echo "Compile all .py files into .pyc"
python3 -m compileall -f src

# echo "Remove all .py files (it should work perfectly fine only with .pyc files"
# find src -name '*.py' -not -name '__main__.py' -not -name '__init__.py' -delete  # not working for now

echo "Collect all files (and dependencies) into .pyz file (uncompressed zip archive)"
python3 -m zipapp --python="/usr/bin/env python3" --output=myapp.pyz src

EOF

1.1.34. Image Build

cat > run/image-build << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Build Docker image based on the current repository state (git commit)"
docker build --pull . -t localhost:5000/myapp:\$(git log -1 --format='%h')

EOF

1.1.35. Image Push

cat > run/image-push << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Upload Docker image to the binary repisitory (Registry)"
docker push localhost:5000/myapp:\$(git log -1 --format='%h')

EOF

1.1.36. Image Remove

cat > run/image-remove << EOF
#!/bin/sh

echo "Set flag to print trace of commands"
set -x

echo "Set flag to exit immediately if a command exits with a non-zero status"
set -e

echo "Remove temporary build image"
docker rmi localhost:5000/myapp:\$(git log -1 --format='%h')

EOF