diff --git a/.github/workflows/_docker-build-deploy.yml b/.github/workflows/_docker-build-deploy.yml index 58e1af9..c65a97d 100644 --- a/.github/workflows/_docker-build-deploy.yml +++ b/.github/workflows/_docker-build-deploy.yml @@ -12,6 +12,13 @@ on: dockerfile: required: true type: string + base-image: + required: false + type: string + user-image: + required: false + type: string + default: appuser publish: required: true type: boolean @@ -36,7 +43,7 @@ jobs: ignore: SC1091,${{ inputs.ignore-linting-rules }} - name: ShellCheck run: | - cd docker/${{ inputs.image-name }} + cd ${{ inputs.docker-context }} [ -d "./bin" ] && shellcheck --external-sources --exclude=SC1091 ./bin/* shellcheck --external-sources --exclude=SC2148 ./Dockerfile - name: Log in to registry @@ -65,9 +72,11 @@ jobs: labels: | runnumber=${{ github.run_id }} build-args: | + BASE_IMAGE=${{ inputs.base-image }} BASE_IMAGE_TAG=${{ env.TAG }} TNA_DOCKER_IMAGE_VERSION=${{ env.TAG }} TNA_DOCKER_IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}/blob/main/${{ inputs.docker-context }}/${{ inputs.dockerfile }} + USER_IMAGE=${{ inputs.user-image }} push: ${{ inputs.publish }} tags: ${{ env.IMAGE_ID }}:${{ env.TAG }} provenance: false @@ -83,9 +92,11 @@ jobs: labels: | runnumber=${{ github.run_id }} build-args: | + BASE_IMAGE=${{ inputs.base-image }} BASE_IMAGE_TAG=latest TNA_DOCKER_IMAGE_VERSION=${{ env.TAG }} TNA_DOCKER_IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}/blob/main/${{ inputs.docker-context }}/${{ inputs.dockerfile }} + USER_IMAGE=${{ inputs.user-image }} push: true tags: ${{ env.IMAGE_ID }}:latest provenance: false diff --git a/.github/workflows/branch-cleanup.yml b/.github/workflows/branch-cleanup.yml index 233f244..f1fdb2d 100644 --- a/.github/workflows/branch-cleanup.yml +++ b/.github/workflows/branch-cleanup.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - image-name: [tna-python, tna-python-django] + image-name: [tna-python, tna-python-root, tna-python-django, tna-python-django-root, tna-python-dev] steps: - name: Prepare image tag run: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 64ab6c3..22f7de3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,17 +22,18 @@ jobs: docker-context: docker/tna-python dockerfile: Dockerfile publish: true + ignore-linting-rules: DL3002,DL3006 python-root: name: Python (root) - needs: python uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-root - docker-context: docker/tna-python-root + docker-context: docker/tna-python + user-image: root dockerfile: Dockerfile publish: true - ignore-linting-rules: DL3002 + ignore-linting-rules: DL3002,DL3006 python-django: name: Python Django @@ -40,17 +41,31 @@ jobs: uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-django + base-image: ghcr.io/nationalarchives/tna-python docker-context: docker/tna-python-django dockerfile: Dockerfile publish: true python-django-root: name: Python Django (root) - needs: python-django + needs: python-root uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-django-root - docker-context: docker/tna-python-django-root + base-image: ghcr.io/nationalarchives/tna-python-root + docker-context: docker/tna-python-django dockerfile: Dockerfile publish: true ignore-linting-rules: DL3002 + + python-dev: + name: Python Dev + needs: python-root + uses: ./.github/workflows/_docker-build-deploy.yml + with: + image-name: tna-python-dev + base-image: ghcr.io/nationalarchives/tna-python-root + docker-context: docker/tna-python-dev + dockerfile: Dockerfile + publish: true + ignore-linting-rules: DL3002,DL3008 diff --git a/.github/workflows/remove-untagged.yml b/.github/workflows/remove-untagged.yml index 4c054f0..b3a1158 100644 --- a/.github/workflows/remove-untagged.yml +++ b/.github/workflows/remove-untagged.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - image-name: [tna-python, tna-python-root, tna-python-django, tna-python-django-root] + image-name: [tna-python, tna-python-root, tna-python-django, tna-python-django-root, tna-python-dev] env: PER_PAGE: 100 steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b875a42..c4e5eb4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,35 +19,50 @@ jobs: docker-context: docker/tna-python dockerfile: Dockerfile publish: false + ignore-linting-rules: DL3002,DL3006 python-root: name: Python (root) - needs: python uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-root - docker-context: docker/tna-python-root + docker-context: docker/tna-python + user-image: root dockerfile: Dockerfile publish: false - ignore-linting-rules: DL3002 + ignore-linting-rules: DL3002,DL3006 - python-django: + python-django: name: Python Django needs: python uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-django + base-image: ghcr.io/nationalarchives/tna-python docker-context: docker/tna-python-django dockerfile: Dockerfile publish: false python-django-root: name: Python Django (root) - needs: python-django + needs: python-root uses: ./.github/workflows/_docker-build-deploy.yml with: image-name: tna-python-django-root - docker-context: docker/tna-python-django-root + base-image: ghcr.io/nationalarchives/tna-python-root + docker-context: docker/tna-python-django dockerfile: Dockerfile publish: false ignore-linting-rules: DL3002 + + python-dev: + name: Python Dev + needs: python-root + uses: ./.github/workflows/_docker-build-deploy.yml + with: + image-name: tna-python-dev + base-image: ghcr.io/nationalarchives/tna-python-root + docker-context: docker/tna-python-dev + dockerfile: Dockerfile + publish: false + ignore-linting-rules: DL3002,DL3008 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5020904..c9f6d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Initial release of `tna-python-flask` Docker image +- Initial release of `tna-python-dev` Docker image ### Changed diff --git a/README.md b/README.md index ee74345..58ebbf5 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,24 @@ The National Archives base Docker images are designed to serve as a starting point for all containerised applications in The National Archives. -## Base Python image +## Base images -- [About tna-python](./docker/tna-python) -- [Example application](./tests/example-python-application) +| Image | Dockerfile | Base image | User | +| ----------------------------------------------- | --------------------------------------------------------------------- | ----------------- | ------ | +| [`tna-python`](docker/tna-python) | [`tna-python/DockerFile`](docker/tna-python/DockerFile) | `python` | `app` | +| `tna-python-root` | [`tna-python/DockerFile`](docker/tna-python/DockerFile) | `python` | `root` | +| [`tna-python-django`](docker/tna-python-django) | [`tna-python-django/DockerFile`](docker/tna-python-django/DockerFile) | `tna-python` | `app` | +| `tna-python-django-root` | [`tna-python-django/DockerFile`](docker/tna-python-django/DockerFile) | `tna-python-root` | `root` | +| [`tna-python-dev`](docker/tna-python-dev) | [`tna-python-dev/DockerFile`](docker/tna-python-dev/DockerFile) | `tna-python-root` | `root` | -### Base Python Django image +### Image inheritance -- [About tna-python-django](./docker/tna-python-django) -- [Example Django application](./tests/example-python-django-application) +```mermaid +graph TD; + debian --> python; + python --> tna-python; + python --> tna-python-root; + tna-python --> tna-python-django; + tna-python-root --> tna-python-django-root; + tna-python-root --> tna-python-dev; +``` diff --git a/docker/tna-python-dev/Dockerfile b/docker/tna-python-dev/Dockerfile new file mode 100644 index 0000000..9b7dc36 --- /dev/null +++ b/docker/tna-python-dev/Dockerfile @@ -0,0 +1,26 @@ +ARG BASE_IMAGE=ghcr.io/nationalarchives/tna-python-root +ARG BASE_IMAGE_TAG=latest + +FROM "$BASE_IMAGE":"$BASE_IMAGE_TAG" + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +RUN apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates curl gnupg; \ + install -m 0755 -d /etc/apt/keyrings; \ + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg; \ + chmod a+r /etc/apt/keyrings/docker.gpg; \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \ + apt-get update; \ + apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; \ + apt-get clean; \ + apt-get autoremove -y --purge; \ + rm -rfv /var/lib/apt/lists/* + +COPY --chown=app bin /home/app/.local/bin/dev +RUN chmod +x -fR /home/app/.local/bin/dev +ENV PATH="/home/app/.local/bin/dev:$PATH" + +COPY --chown=app lib/* /home/app/ + +CMD ["dev"] diff --git a/docker/tna-python-dev/README.md b/docker/tna-python-dev/README.md new file mode 100644 index 0000000..dcc3df4 --- /dev/null +++ b/docker/tna-python-dev/README.md @@ -0,0 +1,32 @@ +# tna-python-dev + +This image extends `tna-python` but adds: + +- `docker` - for managing other containers +- `black`, `flake8` and `isort` - for formatting Python code +- `prettier`, `eslint` and `stylelint` - for formatting JavaScript and CSS + +## Environment variables + +All environment variables defined in [tna-python](../tna-python/README.md). + +## Commands for the Dockerfile + +Run `help` from within the container to see a list of available commands. + +### `format` + +1. Run `isort` +1. Run `black` +1. Run `flake8` +1. Apply prettier to all files in the `/app` directory +1. Run `stylelint` against all SCSS files in the `/app` directory +1. Run `eslint` against all JavaScript files in the `/app` directory + +### `secret-key` + +Generate a string that can be used as the environment variable `SECRET_KEY`: + +- https://docs.python.org/3/library/secrets.html +- https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +- https://flask.palletsprojects.com/en/2.3.x/config/#SECRET_KEY diff --git a/docker/tna-python-dev/bin/dev b/docker/tna-python-dev/bin/dev new file mode 100755 index 0000000..0d82a62 --- /dev/null +++ b/docker/tna-python-dev/bin/dev @@ -0,0 +1,16 @@ +#!/bin/bash + +python -m pip install --quiet black==23.7.0 flake8==6.1.0 isort==5.12.0 + +cd /app || return + +. "$HOME/.nvm/nvm.sh" +if [ -f "/app/.nvmrc" ] +then + nvm install +else + nvm use lts/iron +fi +npm install -g prettier@3.0.2 eslint@8.47.0 stylelint@15.10.3 stylelint-config-standard-scss@10.0.0 stylelint-selector-bem-pattern@3.0.1 + +welcome diff --git a/docker/tna-python-dev/bin/format b/docker/tna-python-dev/bin/format new file mode 100755 index 0000000..644edef --- /dev/null +++ b/docker/tna-python-dev/bin/format @@ -0,0 +1,49 @@ +#!/bin/bash + +cd /app || return + +echo "Running isort..." +isort --settings-file /home/app/.isort.cfg /app --overwrite-in-place +echo + +echo "Running black..." +black -t py38 -t py39 -t py310 -t py311 -t py312 --line-length 80 --verbose /app +echo + +echo "Running flake8..." +flake8 --config=/home/app/.flake8 /app +echo + +. "$HOME/.nvm/nvm.sh" +if [ -f "/app/.nvmrc" ] +then + nvm install +else + nvm use lts/iron +fi + +echo "Running prettier..." +prettier --write /app +echo + +echo "Running stylelint..." +if [ -f "/app/.stylelintrc" ] +then + echo "Using app config" + stylelint --fix "/app/**/*.{css,scss}" +else + echo "Using default config" + stylelint --config /home/app/.stylelintrc --fix "/app/**/*.{css,scss}" +fi +echo + +echo "Running eslint..." +if [ -f "/app/.eslintrc.js" ] +then + echo "Using app config" + eslint --fix "/app" +else + echo "Using default config" + eslint -c /home/app/.eslintrc.js --fix "/app" +fi +echo diff --git a/docker/tna-python-dev/bin/help b/docker/tna-python-dev/bin/help new file mode 100755 index 0000000..5baaff0 --- /dev/null +++ b/docker/tna-python-dev/bin/help @@ -0,0 +1,28 @@ +#!/bin/bash + +echo "==========================================" +echo "TNA Python Dev" +echo "------------------------------------------" +echo "" +echo "help" +echo " Display all commands (this file)" +echo "" +echo "format" +echo " Run isort, black and flake8 against the project Python files and prettier against the JavaScript and CSS" +echo "" +echo "upgrade" +echo " Update the Poetry and Node dependencies" +echo "" +echo "tna-build" +echo " Run the TNA build process" +echo "" +echo "tna-run" +echo " Run the TNA run process" +echo "" +echo "tna-node" +echo " Run a Node command from your package.json" +echo "" +echo "secret-key" +echo " Generate a string that can be used as the environment variable SECRET_KEY" +echo "" +echo "==========================================" diff --git a/docker/tna-python-dev/bin/secret-key b/docker/tna-python-dev/bin/secret-key new file mode 100755 index 0000000..ecb3816 --- /dev/null +++ b/docker/tna-python-dev/bin/secret-key @@ -0,0 +1,3 @@ +#!/bin/bash + +python -c 'import secrets; print(secrets.token_hex())' diff --git a/docker/tna-python-dev/bin/upgrade b/docker/tna-python-dev/bin/upgrade new file mode 100755 index 0000000..3d8b4bd --- /dev/null +++ b/docker/tna-python-dev/bin/upgrade @@ -0,0 +1,14 @@ +#!/bin/bash + +cd /app || return + +poetry update + +. "$HOME/.nvm/nvm.sh" +if [ -f "/app/.nvmrc" ] +then + nvm install +else + nvm use lts/iron +fi +npm update diff --git a/docker/tna-python-dev/bin/welcome b/docker/tna-python-dev/bin/welcome new file mode 100755 index 0000000..14b1de9 --- /dev/null +++ b/docker/tna-python-dev/bin/welcome @@ -0,0 +1,31 @@ +#!/bin/bash + +echo " ,--. ,--. .-------------------------------." +echo " ((O ))--((O )) | |" +echo " ,'_\`--'____\`--'_\`. | Awaiting your command... |" +echo " _: ____________ :_ | Run \"help\" to get started |" +echo " | | ||::::::::::|| | | | |" +echo " | | ||::::::::::|| | | | .-----------------------------'" +echo " |_| |/__________\\| |_| |/" +echo " |________________|" +echo " __..-' \`-..__" +echo " .-| : .----------------. : |-." +echo " ,\\ || | |\\______________/| | || /." +echo " /\`.\\:| | || __ __ __ || | |;/,'\\" +echo " :\`-._\\;.| || '--''--''--' || |,:/_.-':" +echo " | : | || .----------. || | : |" +echo " | | | || '- TNABOT -' || | | |" +echo " | | | || _ _ _ || | | |" +echo " :,--.; | || (_) (_) (_) || | :,--.;" +echo " (\`-'|) | ||______________|| | (|\`-')" +echo " \`--' | |/______________\\| | \`--'" +echo " |____________________|" +echo " \`.________________,'" +echo " (_______)(_______)" +echo " (_______)(_______)" +echo " (_______)(_______)" +echo " (_______)(_______)" +echo " | || |" +echo " '--------''--------'" + +tail -f /dev/null diff --git a/docker/tna-python-dev/lib/.eslintrc.js b/docker/tna-python-dev/lib/.eslintrc.js new file mode 100644 index 0000000..7972d08 --- /dev/null +++ b/docker/tna-python-dev/lib/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: ["eslint:recommended"], + overrides: [ + { + env: { + node: true, + }, + files: [".eslintrc.{js,jsx,mjs,ts,tsx,cjs}"], + parserOptions: { + sourceType: "script", + }, + }, + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + rules: {}, + ignorePatterns: ["/app/node_modules", "/app/*.config.js"] +}; \ No newline at end of file diff --git a/docker/tna-python-dev/lib/.flake8 b/docker/tna-python-dev/lib/.flake8 new file mode 100644 index 0000000..6e00305 --- /dev/null +++ b/docker/tna-python-dev/lib/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 80 +max-complexity = 12 +select = B,C,E,F,W,T4,B9 \ No newline at end of file diff --git a/docker/tna-python-dev/lib/.isort.cfg b/docker/tna-python-dev/lib/.isort.cfg new file mode 100644 index 0000000..6860bdb --- /dev/null +++ b/docker/tna-python-dev/lib/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +profile = black \ No newline at end of file diff --git a/docker/tna-python-dev/lib/.stylelintrc b/docker/tna-python-dev/lib/.stylelintrc new file mode 100644 index 0000000..c2c4e5f --- /dev/null +++ b/docker/tna-python-dev/lib/.stylelintrc @@ -0,0 +1,25 @@ +{ + "extends": [ + "stylelint-config-standard-scss" + ], + "plugins": [ + "stylelint-selector-bem-pattern" + ], + "rules": { + "at-rule-empty-line-before": null, + "block-no-empty": null, + "declaration-empty-line-before": null, + "property-no-vendor-prefix": null, + "value-keyword-case": null, + "scss/dollar-variable-empty-line-before": null, + "scss/double-slash-comment-empty-line-before": null, + "selector-class-pattern": null, + "plugin/selector-bem-pattern": { + "preset": "bem" + } + }, + "ignoreFiles": [ + "/app/node_modules", + "/app/**/*.min.css" + ] +} \ No newline at end of file diff --git a/docker/tna-python-django-root/Dockerfile b/docker/tna-python-django-root/Dockerfile deleted file mode 100644 index 2317bdc..0000000 --- a/docker/tna-python-django-root/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -ARG BASE_IMAGE_TAG=latest -FROM ghcr.io/nationalarchives/tna-python-django:"$BASE_IMAGE_TAG" - -# ========================================== -# Switch to the root user which allows us to -# have full access to the file system -# ========================================== -USER root diff --git a/docker/tna-python-django/Dockerfile b/docker/tna-python-django/Dockerfile index ec06a35..d1952c3 100644 --- a/docker/tna-python-django/Dockerfile +++ b/docker/tna-python-django/Dockerfile @@ -1,5 +1,7 @@ +ARG BASE_IMAGE=ghcr.io/nationalarchives/tna-python ARG BASE_IMAGE_TAG=latest -FROM ghcr.io/nationalarchives/tna-python:"$BASE_IMAGE_TAG" + +FROM "$BASE_IMAGE":"$BASE_IMAGE_TAG" # ========================================== # Move the existing build and run scripts to diff --git a/docker/tna-python-django/README.md b/docker/tna-python-django/README.md index e88c5bd..d1ff3f7 100644 --- a/docker/tna-python-django/README.md +++ b/docker/tna-python-django/README.md @@ -1,6 +1,6 @@ # tna-python-django -This image extends `tna-python` but adds: +This image extends `tna-python` (or `tna-python-root` for `tna-python-django-root`) but adds: - `manage.py` - a generic entrypoint for Django applications @@ -10,9 +10,9 @@ This image assumes you have a version of Django added to you project's `pyprojec All environment variables defined in [tna-python](../tna-python/README.md) as well as: -| Variable | Description | Default | -| ------------------------- | ------------------------------------------- | --------------------------- | -| `DJANGO_SETTINGS_MODULE` | Which Django settings module to load | [None] | +| Variable | Description | Default | +| ------------------------ | ------------------------------------ | ------- | +| `DJANGO_SETTINGS_MODULE` | Which Django settings module to load | [None] | ## Commands for the Dockerfile diff --git a/docker/tna-python-root/Dockerfile b/docker/tna-python-root/Dockerfile deleted file mode 100644 index 9d5fe1b..0000000 --- a/docker/tna-python-root/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -ARG BASE_IMAGE_TAG=latest -FROM ghcr.io/nationalarchives/tna-python:"$BASE_IMAGE_TAG" - -# ========================================== -# Switch to the root user which allows us to -# have full access to the file system -# ========================================== -USER root diff --git a/docker/tna-python/Dockerfile b/docker/tna-python/Dockerfile index 86e9494..6cdbe6c 100644 --- a/docker/tna-python/Dockerfile +++ b/docker/tna-python/Dockerfile @@ -1,4 +1,6 @@ -FROM python:3.12-slim-bookworm +ARG USER_IMAGE=appuser + +FROM python:3.12-slim-bookworm AS root # ========================================== # Label this container image with a semantic @@ -160,6 +162,9 @@ ENV PATH="$POETRY_HOME/bin:$PATH" # ========================================== RUN rm -fR "$POETRY_HOME/venv/lib/python3.11/site-packages/setuptools-65.5.0.dist-info" + + +FROM root AS appuser # ========================================== # Now we have finished installing everything # at a system level, change the current user @@ -169,6 +174,10 @@ RUN rm -fR "$POETRY_HOME/venv/lib/python3.11/site-packages/setuptools-65.5.0.dis # ========================================== USER app + + +FROM "$USER_IMAGE" + # ========================================== # Install the latest LTS version of Node.js, # but keeping within the releases code-named @@ -192,4 +201,4 @@ RUN chmod +x /home/app/.local/bin/tna-build /home/app/.local/bin/tna-node /home/ # run, inspect and debug the container, even # though we have no application running # ========================================== -CMD ["tail", "-f", "/dev/null"] +CMD ["tail", "-f", "/dev/null"] \ No newline at end of file