Compare commits

...

70 Commits

Author SHA1 Message Date
da60952fe4 refactor(docs): Explain Docker registry use 2023-12-24 03:31:14 +01:00
d6ea3f1853 fix(docker-compose): For a single-service Compose file prefill our example README.md with docker compose --profile 'build' build ...
... instead of docker compose --profile
'build-service' build since the latter won't exist.
2023-12-24 03:18:11 +01:00
adb7bf6795 fix(docker-compose): When we have only one service in Docker Compose set its Dockerfile context dir to just build-context/ instead of build-context/service since the latter doesn't exist 2023-12-24 02:56:03 +01:00
215db1682d refactor(docker-compose): Set default ulimits even when var is set (but set to just an empty string) 2023-12-24 01:30:49 +01:00
36f2eecba1 refactor(compose): Copy Docker images with copy-docker 2023-10-13 02:26:41 +02:00
1f588e90bc refactor(compose): Double-check that VIP is bound on target host 2023-10-13 02:23:57 +02:00
b534a9bccf common-settings.yml is now .yaml 2023-10-13 02:08:12 +02:00
e5e78a0527 feat(compose): Work with a registry 2023-10-13 02:06:56 +02:00
d98de5aff0 refactor(compose): Mention private registry when copying 2023-10-13 01:15:24 +02:00
ffaf43e56f common-settings.yml is now .yaml 2023-10-13 01:13:24 +02:00
20d303e79a fix(compose): Update Dockerfile ref to env example file 2023-10-09 01:33:34 +02:00
117627889f fix(compose): Fix bash var quoting 2023-10-08 18:35:10 +02:00
ab9b1009cb fix(compose): Fix line breaks with multi-component build=yes 2023-10-08 17:54:17 +02:00
6605fe0866 Merge pull request 'fix(compose): Use modern Compose file name (#12)' (#13) from 12-compose-file-name-changes into master
Reviewed-on: #13
2023-10-08 15:42:58 +00:00
782981c6f8 fix(compose): Use modern Compose file name (#12) 2023-10-08 17:41:59 +02:00
aef611f731 Merge pull request '10-compose-add-conventional-commits' (#11) from 10-compose-add-conventional-commits into master
Reviewed-on: #11
2023-10-08 15:29:43 +00:00
c6e93b353d refactor(compose): Adjust spacing in example Environment and Build paragraphs (#10) 2023-10-08 17:28:50 +02:00
e112d0ef8c refactor(compose): Adjust spacing in example Environment paragraph (#10) 2023-10-08 17:28:10 +02:00
387ef3bbbe refactor(compose): Remove trailing newline (#10) 2023-10-08 17:25:50 +02:00
31f67e3aad refactor(compose): Adjust spacing in Environment paragraph (#10) 2023-10-08 17:25:11 +02:00
795585260a refactor(compose): Adjust spacing in Build paragraph (#10) 2023-10-08 17:24:44 +02:00
6c08ba37c6 feat(compose): Update Vault example with Conventional commits paragraph (#10) 2023-10-08 17:23:11 +02:00
e6b8f35906 feat(compose): Update Grafana example with Conventional commits paragraph (#10) 2023-10-08 17:21:46 +02:00
692d115d5c refactor(compose): Adjust line breaks (#10) 2023-10-08 17:18:50 +02:00
0042adba00 feat(compose): Add a README.md section explaining Conventional Commits usage (#10) 2023-10-08 17:16:03 +02:00
96cce1634b docs(docker-compose): More _FIXME_ markers 2023-06-25 00:14:02 +02:00
40ab99bf87 docs(docker-compose): More _FIXME_ markers 2023-06-25 00:13:13 +02:00
c460559b34 docs(docker-compose): Fix image link 2023-06-25 00:11:22 +02:00
9e9ec037b8 docs(docker-compose): Typo 2023-06-25 00:00:43 +02:00
71a2fe1beb docs(docker-compose): In example dir layout focus on important files 2023-06-24 23:57:58 +02:00
0436224728 docs(docker-compose): Add paragraph to copy image to target server 2023-06-24 23:57:02 +02:00
e5dcc062de docs(docker-compose): More _FIXME_ markers 2023-06-24 23:56:43 +02:00
bb6777f389 refactor(docker-compose): No needs for FIXME Markdown italics 2023-06-24 23:37:35 +02:00
467e98b01a docs(docker-compose): Add pull instructions 2023-06-24 23:36:59 +02:00
161967ebac docs(docker-compose): Flesh out individual build processes 2023-06-24 23:30:37 +02:00
3026e30783 docs(docker-compose): Pepper _FIXME_ markers throughout README.md to clean it prior to commit 2023-06-24 23:27:59 +02:00
494228b367 docs(docker-compose): Flesh out individual build processes 2023-06-24 23:22:41 +02:00
024a056d9e docs(docker-compose): Render Docker context creation before build process 2023-06-24 23:12:32 +02:00
0f4b7ac7a5 docs(docker-compose): Add ulimits example to docker-compose.yml 2023-06-24 23:01:58 +02:00
75b31844a0 refactor(docker-compose): By default we want a paragraph about build instructions to be included in README.md 2023-06-24 23:01:29 +02:00
19c38255a2 docs(docker-compose): Set owner for files and dot-files 2023-06-24 23:01:00 +02:00
9fc23aa0b2 docs(docker-compose): No need to ask for a context, we set a dummy context in docs 2023-06-21 00:38:03 +02:00
5c8cf16348 docs(docker-compose): No need to ask for a context, we set a dummy context in docs 2023-06-21 00:35:19 +02:00
5645fba3e1 docs(docker-compose): Add a sane ulimits example 2023-06-21 00:27:37 +02:00
79dc452799 docs(docker-compose): Handle remote deployment via --context 2023-06-21 00:27:23 +02:00
de2b657ec1 docs(docker-compose): Update README.md with example directory structure 2023-06-14 22:11:04 +02:00
9ef85a53e6 refactor(cookiecutter): Typo 2023-06-14 22:07:45 +02:00
1995283c73 docs(docker-compose): Minor consistency changes 2023-06-13 02:11:23 +02:00
d62fc4a8a3 docs(docker-compose): Update Cookiecutter instructions with suppressed prompt 2023-06-13 00:37:37 +02:00
e0d39b389d Merge pull request '7-rewrite-docs-inconsistencies' (#8) from 7-rewrite-docs-inconsistencies into master
Reviewed-on: #8

Fixes #7
2023-06-12 22:35:06 +00:00
4bc93e5546 docs(docker-compose): Update example directory layout (#7) 2023-06-13 00:34:19 +02:00
299c15e30d docs(docker-compose): Update example directory layout (#7) 2023-06-13 00:30:51 +02:00
448401b7b0 docs(docker-compose): Link pip section with a more detailed explanation (#7) 2023-06-13 00:30:18 +02:00
94a802c1e6 docs(docker-compose): Update example directory layout 2023-06-13 00:24:33 +02:00
2ab821e182 docs(docker-compose): Add example README Markdown file 2023-06-12 01:29:22 +02:00
ccfaaac36d Merge pull request 'add-readme-to-docker-compose' (#6) from add-readme-to-docker-compose into master
Reviewed-on: #6
2023-06-11 23:26:51 +00:00
8b09465349 docs(docker-compose): Add example README Markdown file 2023-06-12 01:25:26 +02:00
c871116f9f refactor(docker-compose): Add a subnet to examples 2023-06-11 23:19:49 +02:00
0daecee302 refactor(docker-compose): Slim down Docker bind mount directory structure 2023-06-11 23:19:19 +02:00
7fb2be86ee docs(meta): Better distiguish component_list entry examples 2023-06-11 23:05:23 +02:00
c8e6465ee9 docs(meta): Add dev instructions 2023-06-11 23:03:19 +02:00
1e9e1136f0 docs(meta): H1 headlines all the way 2023-06-11 22:51:51 +02:00
7e2cce9c72 refactor(docker-compose): Let's not commit a pyenv .python-version file 2023-06-11 22:50:42 +02:00
b033946444 feat(docker-compose): Add example for health check between two services 2023-06-11 22:49:45 +02:00
3de179d613 feat(docker-compose): Allow env inside Docker Compose Cookiecutter template 2023-06-11 22:49:04 +02:00
a7a8290f66 feat(docker-compose): Align env example file name with other docs 2023-06-11 22:42:17 +02:00
0c167ec6ab feat(docker-compose): Align env example file name with other docs 2023-06-11 22:41:24 +02:00
63cbe035ad feat(docker-compose): Align env example file name with other docs 2023-06-11 22:17:32 +02:00
8297cf976e docs(build): Update example files 2022-07-05 19:53:40 +02:00
fb26d472f2 docs(build): Update docs with inflect and new layout 2022-07-05 19:49:33 +02:00
36 changed files with 1123 additions and 318 deletions

5
.gitignore vendored
View File

@@ -2,6 +2,9 @@
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# pyenv
.python-version
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
@@ -206,6 +209,8 @@ venv/
ENV/
env.bak/
venv.bak/
!docker-compose/examples/*/env
!docker-compose/{{ cookiecutter.__project_slug }}/env
# Spyder project settings
.spyderproject

207
README.md
View File

@@ -2,24 +2,26 @@
Project directory structure templates for the Python Cookiecutter package.
## What's Cookiecutter?
# What's Cookiecutter?
The Python package [Cookiecutter](https://github.com/cookiecutter/cookiecutter) assists in creating directory structure for whatever purpose you need such as for example a new Python project, an Ansible role or a `docker-compose` project - anything really that benefits from a unifirm reproducible directory structure. If you've ever wanted to put project structure best practices into version control then Cookiecutter's here to help.
Cookiecutter is governed by so-called Cookiecutter templates, most of its magic inside Cookiecutter templates happens via the [Jinja2 template engine](https://palletsprojects.com/p/jinja/). You'll feel right at home if you're familiar with Ansible.
## Repo layout
# Repo layout
Each subdirectory in this repo is a Cookiecutter template, you'll recognize them from their telltale `cookiecutter.json` files. Directories usually also have a readme file explaining more about each individual template.
## Get started
# Get started
Get Cookiecutter like so:
```
pip install cookiecutter
```
Execute a template like so, `docker-compose` as an example:
Unfamiliar with Python and `pip`? Check out [Developing](#developing) further down to get started with a virtual environment.
When all is set execute a template like so, `docker-compose` as an example:
```
cookiecutter https://quico.space/Quico/py-cookiecutter-templates.git --directory 'docker-compose'
```
@@ -31,7 +33,10 @@ This is Cookiecutter prompting for info:
project_slug [dir-name]: grafana
service [grafana]:
component_list [grafana]: grafana,nginx
context [ctx]: cncf
Select build:
1 - no
2 - yes
Choose from 1, 2 [1]:
```
The end result is a directory structure that has everything you need to hit the ground running.
@@ -51,9 +56,191 @@ The end result is a directory structure that has everything you need to hit the
│   ├── Dockerfile
│   └── extras
│   └── .gitkeep
├── common-settings.yml
├── docker-compose.override.yml
├── docker-compose.yml
── env
└── fully.qualified.domain.name.example
├── common-settings.yaml
├── compose.override.yaml
├── compose.yaml
── env
│   └── fqdn_context.env.example
└── README.md
```
# Developing
To change Cookiecutter templates get yourself an environment then make, test and commit your changes. First things first, the environment.
## Environment
Get yourself a Python virtual environment. In this example we're assuming you're running a Linux operating system and you'll be using [pyenv](https://github.com/pyenv/pyenv) to manage virtual environments.
### pyenv
- Install pyenv with what they are calling their automatic installer. Feel free to also read up on pyenv on their [GitHub project page](https://github.com/pyenv/pyenv).
```
curl https://pyenv.run | bash
```
- Following the installer's instruction add at least the following commands to your `~/.bashrc` file
```
export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
```
- You will also likely want to add this line which will make sure pyenv auto-activates venvs when you navigate into certain directories. More on that down at [venv](#venv).
```
eval "$(pyenv virtualenv-init -)"
```
- Also make sure `~/.bashrc` gets loaded for example by including this in a `~/.bash_profile` file
```
[[ -f ~/.bashrc ]] && . ~/.bashrc
```
- Reload `~/.bashrc`
```
source ~/.bashrc
```
### Python
- Update pyenv's package list
```
pyenv update
```
- Pick a Python version you like, for example copy-paste `3.11.4`:
```
pyenv install --list | less
...
3.10.12
3.11.0
3.11-dev
3.11.1
3.11.2
3.11.3
3.11.4
3.12.0b2
3.12-dev
3.13-dev
...
```
- Install Python, wait for compilation to finish on your machine
```
pyenv install 3.11.4
```
### Repo
- Clone this repo
```
git clone https://quico.space/Quico/py-cookiecutter-templates.git ~/py-cookiecutter-templates
```
### venv
- Create a virtual environment where `3.11.4` is the Python version you want to use in this venv and `cookiecutter-3.11.4` is the name of your venv. Adding or dropping the Python version from your venv name comes down to personal preference.
```
pyenv virtualenv 3.11.4 cookiecutter-3.11.4
```
- In your repo path `~/py-cookiecutter-templates` create a `.python-version` file to tell pyenv to always activate your desired venv when inside this dir.
```
cd ~/py-cookiecutter-templates
pyenv local cookiecutter-3.11.4
```
pyenv will immediately prefix your shell's `${PS1}` prompt with the venv name.
```
(cookiecutter-3.11.4) [✔ 23:19 user@machine py-cookiecutter-templates]$
```
It will deactivate the venv and drop its prefix as soon as you navigate out of this dir.
```
(cookiecutter-3.11.4) [✔ 23:19 user@machine py-cookiecutter-templates]$ cd
[✔ 23:19 user@machine ~]$
```
For now though stay in `~/py-cookiecutter-templates`, you're going to want to pip-install [cookiecutter](https://pypi.org/project/cookiecutter).
- Upgrade `pip`
```
pip install --upgrade pip
```
- Install [cookiecutter](https://pypi.org/project/cookiecutter)
```
pip install cookiecutter
```
All done, your environment is set up.
## Change
Make some code changes, for example to the Docker Compose Cookiecutter template. When you're happy run your local Cookiecutter template to see how your changes are rendering.
- Create `/tmp/cookiecutter-docker-compose`
```
mkdir '/tmp/cookiecutter-docker-compose'
```
- Render a Docker Compose directory into your output directory, answer Cookiecutter's prompts:
```
# cookiecutter ~/py-cookiecutter-templates \
--directory docker-compose \
--output-dir /tmp/cookiecutter-docker-compose
project_slug [dir-name]: mydir
service [mydir]: myservice
component_list [myservice]: mycomponent_one,mycomponent_two
Select build:
1 - no
2 - yes
Choose from 1, 2 [1]: 2
```
- Observe that in `/tmp/cookiecutter-docker-compose` you now have your rendered Docker Compose dir:
```
# tree -a .
.
└── mydir
├── build-context
│   ├── mycomponent_one
│   │   ├── docker-data
│   │   │   └── .gitkeep
│   │   ├── Dockerfile
│   │   └── extras
│   │   └── .gitkeep
│   └── mycomponent_two
│   ├── docker-data
│   │   └── .gitkeep
│   ├── Dockerfile
│   └── extras
│   └── .gitkeep
├── common-settings.yaml
├── compose.override.yaml
├── compose.yaml
├── env
│   └── fqdn_context.env.example
└── README.md
```
- For rapid testing you will most likely want to not type prompt answers repeatedly. Give them as command line arguments instead, also specify `--no-input` to suppress prompts:
```
cookiecutter ~/py-cookiecutter-templates \
--no-input \
--directory docker-compose \
--output-dir /tmp/cookiecutter-docker-compose \
project_slug=mydir \
service=myservice \
component_list=mycomponent_one,mycomponent_two \
build=yes
```
## Commit prep
When you're about ready to commit changes into this repo check the following bullet points.
- Did you update the [Cookiecutter template README.md file](docker-compose/README.md), here for example for Docker Compose?
- Change in behavior?
- An updated example directory layout?
- Updated example file content?
- Did you commit a new completely rendered example directory structure into [docker-compose/examples](docker-compose/examples) dir?
- Did you change something that affects existing example directories? If so rerender them.

View File

@@ -12,7 +12,10 @@ Cookiecutter interactively prompts you for the following info, here with example
project_slug [dir-name]: grafana
service [grafana]:
component_list [grafana]: grafana,nginx
context [ctx]: cncf
Select build:
1 - no
2 - yes
Choose from 1, 2 [1]:
```
Done, directory structure and files for your next `docker-compose` project are ready for you to hit the ground running.
@@ -31,7 +34,7 @@ Your four answers translate as follows into rendered files.
│   ├── Dockerfile
...
```
2. The `service` variable by default copies your `project_slug` answer. It's a style decision whether you leave it at that and just hit `Enter`. The service name will come up in rendered `docker-compose.yml` at purely cosmetic locations such as the `networks:` key, `container_name:` and `/opt/docker-data` volume mount presets, here with `ftp` as the service name:
2. The `service` variable by default copies your `project_slug` answer. It's a style decision whether you leave it at that and just hit `Enter`. The service name will come up in rendered `compose.yaml` at purely cosmetic locations such as the `networks:` key, `container_name:` and `/opt/docker-data` volume mount presets, here with `ftp` as the service name:
```
services:
mysql:
@@ -60,20 +63,11 @@ Your four answers translate as follows into rendered files.
...
```
4. The last prompt for a `context` is a generic string to help you distinguish deployments. It can be whatever you want such as for example a team name, here `cncf` which shows up as a preset in an example env file. It defaults to `ctx` just so it can't be empty.
4. Prompt `build` is a yes-no question. Cookiecutter will create a `README.md` file with copy-pastable Docker Compose commands pre-filled. If you answer `yes` to this prompt `README.md` will contain an example paragraph that explains the build process along the lines of:
```
.
└── grafana
...
└── env
└── fully.qualified.domain.name.example <---
```
Which then looks like:
```
CONTEXT=cncf
...
docker compose ... --profile 'build' build
```
Whereas by answering `no` (or just hitting `<Enter>` to accept the default of `no`) no such paragraph will be added to the example Markdown file. Build instructions are only really needed if you need to locally build a derivative image.
Also check out [the Caveats section](#caveats) at the end to learn what this template does not do well.
@@ -98,11 +92,12 @@ Above example of a multi-component (two in this case) `grafana` service will giv
│   ├── Dockerfile
│   └── extras
│   └── .gitkeep
├── common-settings.yml
├── docker-compose.override.yml
├── docker-compose.yml
── env
└── fully.qualified.domain.name.example
├── common-settings.yaml
├── compose.override.yaml
├── compose.yaml
── env
│   └── fqdn_context.env.example
└── README.md
```
Check out file contents over in the [examples/grafana](examples/grafana) subdir.
@@ -118,11 +113,12 @@ With an alternative single-component `hashicorpvault` service the result may loo
│   ├── Dockerfile
│   └── extras
│   └── .gitkeep
├── common-settings.yml
├── docker-compose.override.yml
├── docker-compose.yml
── env
└── fully.qualified.domain.name.example
├── common-settings.yaml
├── compose.override.yaml
├── compose.yaml
── env
│   └── fqdn_context.env.example
└── README.md
```
Check out file contents over in the [examples/hashicorpvault](examples/hashicorpvault) subdir.
@@ -130,9 +126,9 @@ Check out file contents over in the [examples/hashicorpvault](examples/hashicorp
Consider Cookiecutter's project directory and rendered files a starting point. It won't do everything perfectly.
Imagine if you will a service that consists of [Infinispan](https://infinispan.org/) among other things. In Docker Hub's content-addressable image store Infinispan's location is at `infinispan/server` so you obviously want that exact string with a forward slash to show up in your `docker-compose.yml` as the `image:` key's value, same with your `Dockerfile`. The `image:` key's value comes from what you enter in Cookiecutter's `component_list` prompt. Component strings are then used to also pre-fill the `volumes:` key.
Imagine if you will a service that consists of [Infinispan](https://infinispan.org/) among other things. In Docker Hub's content-addressable image store Infinispan's location is at `infinispan/server` so you obviously want that exact string with a forward slash to show up in your `compose.yaml` as the `image:` key's value, same with your `Dockerfile`. The `image:` key's value comes from what you enter in Cookiecutter's `component_list` prompt. Component strings are then used to also pre-fill the `volumes:` key.
This will cause obvious issues (but the `image:` key is kinda correct):
_**This**_ will cause obvious issues (but the `image:` key is kinda correct):
```
services:
infinispan/server:
@@ -140,7 +136,7 @@ services:
container_name: "cacheman-infinispan/server-${CONTEXT}"
```
This won't cause issues (but you'll have to then go in and manually change the `image:` key to use `infinispan/server`):
_**This**_ won't cause issues (but you'll have to then go in and manually change the `image:` key to use `infinispan/server`):
```
services:
infinispan:

View File

@@ -5,6 +5,5 @@
"__service_slug": "{{ cookiecutter.service.lower().replace(' ', '_').replace('-', '_') }}",
"component_list": "{{ cookiecutter.__service_slug }}",
"__component_list_slug": "{{ cookiecutter.component_list.lower().replace(' ', '_').replace('-', '_') }}",
"context": "ctx",
"__context_slug": "{{ cookiecutter.context.lower().replace(' ', '_').replace('-', '_') }}"
"build": ["yes", "no"]
}

View File

@@ -0,0 +1,142 @@
# FIXME
Search and replace all mentions of FIXME with sensible content in this file and in [compose.yaml](compose.yaml).
# Grafana Docker Compose files
Docker Compose files to spin up an instance of Grafana FIXME capitalization FIXME.
# How to run
Add a `COMPOSE_ENV` file and save its location as a shell variable along with the location where this repo lives, here for example `/opt/containers/grafana` plus all other variables. At [env/fqdn_context.env.example](env/fqdn_context.env.example) you'll find an example environment file.
When everything's ready start Grafana with Docker Compose, otherwise head down to [Initial setup](#initial-setup) first.
## Environment
```
export COMPOSE_DIR='/opt/containers/grafana'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT='grafana-'"${COMPOSE_CTX}"
export COMPOSE_FILE="${COMPOSE_DIR}"'/compose.yaml'
export COMPOSE_ENV=<add accordingly>
```
## Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
```
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
```
## Pull
Pull images from Docker Hub verbatim.
```
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' pull
```
## Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local `docker save` and a remote `docker load`. Consider the helper mini-project [quico.space/Quico/copy-docker](https://quico.space/Quico/copy-docker) where [copy-docker.sh](https://quico.space/Quico/copy-docker/src/branch/main/copy-docker.sh) allows the following workflow:
```
source "${COMPOSE_ENV}"
# FIXME Docker Hub image name with or without slash? FIXME
for image in 'grafana:'"${GRAFANA_VERSION}" 'nginx:'"${NGINX_VERSION}"; do
copy-docker "${image}" fully.qualified.domain.name
done
```
## Start
FIXME Does the service use a virtual IP address? FIXME
Make sure your service's virtual IP address is bound on your target host then start containers.
```
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' up --detach
```
# Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
## Datasets
Create ZFS datasets and set permissions as needed.
* Parent dateset
```
zfs create -o mountpoint=/opt/docker-data 'zpool/docker-data'
```
* Container-specific datasets
```
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/grafana/data/db'
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/grafana/data/logs'
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/grafana/config'
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/nginx/data/db'
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/nginx/data/logs'
zfs create -p 'zpool/docker-data/grafana-'"${COMPOSE_CTX}"'/nginx/config'
```
FIXME When changing bind mount locations to real ones remember to also update `volumes:` in [compose.yaml](compose.yaml) FIXME
* Create subdirs
```
mkdir -p '/opt/docker-data/grafana-'"${COMPOSE_CTX}"'/grafana/'{'.ssh','config','data','projects'}
```
* Change ownership
```
chown -R 1000:1000 '/opt/docker-data/grafana-'"${COMPOSE_CTX}"'/grafana/data/'*
```
## Additional files
Place the following files on target server. Use the directory structure at [build-context](build-context) as a guide, specifically at `docker-data`.
FIXME Add details about files that aren't self-explanatory FIXME
```
build-context/
├── grafana
│ ├── docker-data
│ | └── config
│ │ └── grafana.cfg
│ ├── ...
│ └── ...
└── nginx
├── docker-data
| └── config
│ └── nginx.cfg
├── ...
└── ...
```
When done head back up to [How to run](#how-to-run).
# Development
## Conventional commits
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages.
### Commit types
Commit _types_ besides `fix` and `feat` are:
- `refactor`: Keeping functionality while streamlining or otherwise improving function flow
- `docs`: Documentation for project or components
### Commit scopes
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
- `grafana`: A change to how the `grafana` service component works
- `nginx`: A change to how the `nginx` service component works
- `build`: Build-related changes such as `Dockerfile` fixes and features.
- `mount`: Volume or bind mount-related changes.
- `net`: Networking, IP addressing, routing changes
- `meta`: Affects the project's repo layout, file names etc.

View File

@@ -1,6 +1,6 @@
# For the remainder of this Dockerfile EXAMPLE_ARG_FOR_DOCKERFILE will be
# available with a value of 'must_be_available_in_dockerfile', check out the env
# file at 'env/fully.qualified.domain.name.example' for reference.
# file at 'env/fqdn_context.env.example' for reference.
# ARG EXAMPLE_ARG_FOR_DOCKERFILE
# Another env var, this one's needed in the example build step below:

View File

@@ -1,6 +1,6 @@
# For the remainder of this Dockerfile EXAMPLE_ARG_FOR_DOCKERFILE will be
# available with a value of 'must_be_available_in_dockerfile', check out the env
# file at 'env/fully.qualified.domain.name.example' for reference.
# file at 'env/fqdn_context.env.example' for reference.
# ARG EXAMPLE_ARG_FOR_DOCKERFILE
# Another env var, this one's needed in the example build step below:

View File

@@ -1,5 +1,6 @@
services:
grafana-build:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "grafana:${GRAFANA_VERSION}"
profiles: ["build", "build-grafana"]
build:
@@ -9,6 +10,7 @@ services:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"
GRAFANA_VERSION: "${GRAFANA_VERSION}"
nginx-build:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "nginx:${NGINX_VERSION}"
profiles: ["build", "build-nginx"]
build:

View File

@@ -0,0 +1,72 @@
services:
grafana:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "grafana:${GRAFANA_VERSION}"
container_name: "grafana-grafana-${CONTEXT}"
networks:
grafana-default:
profiles: ["full", "grafana"]
depends_on:
nginx:
condition: service_healthy
ulimits:
nproc: ${ULIMIT_NPROC:-65535}
nofile:
soft: ${ULIMIT_NPROC:-65535}
hard: ${ULIMIT_NPROC:-65535}
extends:
file: common-settings.yaml
service: common-settings
ports:
# - "8080:80"
volumes:
# When changing bind mount locations to real ones remember to
# also update "Initial setup" section in README.md.
# - /opt/docker-data/grafana-${CONTEXT}/grafana/data/db:/usr/lib/grafana
# - /opt/docker-data/grafana-${CONTEXT}/grafana/data/logs:/var/log/grafana
# - /opt/docker-data/grafana-${CONTEXT}/grafana/config:/etc/grafana
environment:
# GRAFANA_USER: ${GRAFANA_USER}
# GRAFANA_PASSWORD: ${GRAFANA_PASSWORD}
nginx:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "nginx:${NGINX_VERSION}"
container_name: "grafana-nginx-${CONTEXT}"
networks:
grafana-default:
profiles: ["full", "nginx"]
healthcheck:
test: ["CMD", "fping", "--count=1", "${GRAFANA_VIP}", "--period=500", "--quiet"]
interval: 3s
timeout: 1s
retries: 60
start_period: 2s
ulimits:
nproc: ${ULIMIT_NPROC:-65535}
nofile:
soft: ${ULIMIT_NPROC:-65535}
hard: ${ULIMIT_NPROC:-65535}
extends:
file: common-settings.yaml
service: common-settings
ports:
# - "8080:80"
volumes:
# When changing bind mount locations to real ones remember to
# also update "Initial setup" section in README.md.
# - /opt/docker-data/grafana-${CONTEXT}/nginx/data/db:/usr/lib/nginx
# - /opt/docker-data/grafana-${CONTEXT}/nginx/data/logs:/var/log/nginx
# - /opt/docker-data/grafana-${CONTEXT}/nginx/config:/etc/nginx
environment:
# NGINX_USER: ${NGINX_USER}
# NGINX_PASSWORD: ${NGINX_PASSWORD}
networks:
grafana-default:
name: grafana-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
- subnet: ${SUBNET}

View File

@@ -1,47 +0,0 @@
services:
grafana:
image: "grafana:${GRAFANA_VERSION}"
container_name: "grafana-grafana-${CONTEXT}"
networks:
grafana-default:
profiles: ["full", "grafana"]
extends:
file: common-settings.yml
service: common-settings
ports:
# - "8080:80"
volumes:
# - /opt/docker-data/grafana-grafana-${CONTEXT}/grafana/data/db:/usr/lib/grafana
# - /opt/docker-data/grafana-grafana-${CONTEXT}/grafana/data/logs:/var/log/grafana
# - /opt/docker-data/grafana-grafana-${CONTEXT}/grafana/config:/etc/grafana
environment:
# GRAFANA_USER: ${GRAFANA_USER}
# GRAFANA_PASSWORD: ${GRAFANA_PASSWORD}
nginx:
image: "nginx:${NGINX_VERSION}"
container_name: "grafana-nginx-${CONTEXT}"
networks:
grafana-default:
profiles: ["full", "nginx"]
extends:
file: common-settings.yml
service: common-settings
ports:
# - "8080:80"
volumes:
# - /opt/docker-data/grafana-nginx-${CONTEXT}/nginx/data/db:/usr/lib/nginx
# - /opt/docker-data/grafana-nginx-${CONTEXT}/nginx/data/logs:/var/log/nginx
# - /opt/docker-data/grafana-nginx-${CONTEXT}/nginx/config:/etc/nginx
environment:
# NGINX_USER: ${NGINX_USER}
# NGINX_PASSWORD: ${NGINX_PASSWORD}
networks:
grafana-default:
name: grafana-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
# - subnet: 172.21.184.0/24

View File

@@ -0,0 +1,34 @@
CONTEXT=ux_vilnius
# Set something sensible here and uncomment
# ---
# GRAFANA_VERSION=x.y.z
# NGINX_VERSION=x.y.z
# GRAFANA_VIP=10.1.1.2
# GRAFANA_BUILD_DATE=20230731
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# Subnet to use for this Docker Compose project. Docker defaults to
# container networks in prefix 172.16.0.0/12 which is 1 million addresses in
# the range from 172.16.0.0 to 172.31.255.255. Docker uses 172.17.0.0/16 for
# itself. Use any sensible prefix in 172.16.0.0/12 here except for Docker's
# own 172.17.0.0/16.
# ---
SUBNET=172.30.95.0/24
# See 'compose.override.yaml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -1,30 +0,0 @@
CONTEXT=cncf
# Set something sensible here and uncomment
# ---
# GRAFANA_VERSION=x.y.z
# NGINX_VERSION=x.y.z
# A ${LOCATION} var is usually not needed. It may be helpful when a ${CONTEXT}
# extends over more than one location e.g. to bind-mount location-specific
# config files or certificates into a container.
# ---
# LOCATION=
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# See 'docker-compose.override.yml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -0,0 +1,149 @@
# FIXME
Search and replace all mentions of FIXME with sensible content in this file and in [compose.yaml](compose.yaml).
# Vault Docker Compose files
Docker Compose files to spin up an instance of Vault FIXME capitalization FIXME.
# How to run
Add a `COMPOSE_ENV` file and save its location as a shell variable along with the location where this repo lives, here for example `/opt/containers/hashicorpvault` plus all other variables. At [env/fqdn_context.env.example](env/fqdn_context.env.example) you'll find an example environment file.
When everything's ready start Vault with Docker Compose, otherwise head down to [Initial setup](#initial-setup) first.
## Environment
```
export COMPOSE_DIR='/opt/containers/hashicorpvault'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT='vault-'"${COMPOSE_CTX}"
export COMPOSE_FILE="${COMPOSE_DIR}"'/compose.yaml'
export COMPOSE_OVERRIDE="${COMPOSE_DIR%/}"'/compose.override.yaml'
export COMPOSE_ENV=<add accordingly>
```
## Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
```
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
```
## Build
> Skip to [Pull](#pull) if you already have images in your private registry ready to use. Otherwise read on to build them now.
FIXME We build the `vault` image locally. Our adjustment to the official image is simply adding `/tmp/vault` to it. See [build-context/Dockerfile](build-context/Dockerfile). We use `/tmp/vault` to bind-mount a dedicated ZFS dataset for the application's `tmpdir` location.
```
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${COMPOSE_ENV}" --profile 'build' build
```
## Push
Push to Docker Hub or your private registry. Setting up a private registry is out of scope of this repo. Once you have a registry available you can use it like so:
- On your OS install a Docker credential helper per [github.com/docker/docker-credential-helpers](https://github.com/docker/docker-credential-helpers). This will make sure you won't store credentials hashed (and unencrypted) in `~/.docker/config.json`. On an example Arch Linux machine where D-Bus Secret Service exists this will come via something like the [docker-credential-secretservice-bin](https://aur.archlinux.org/packages/docker-credential-secretservice-bin) Arch User Repository package. Just install and you're done.
- Do a `docker login registry.example.com`, enter username and password, confirm login.
```
source "${COMPOSE_ENV}"
docker push "registry.example.com/project/vault:${VAULT_BUILD_DATE}-${VAULT_VERSION}"
```
## Pull
> Skip this step if you just built images that still exist locally on your build host.
FIXME Rewrite either [Build](#build) or this paragraph for which images are built and which ones pulled, `--profile 'full'` may not make sense.
```
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' pull
```
## Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local `docker save` and a remote `docker load`. Consider the helper mini-project [quico.space/Quico/copy-docker](https://quico.space/Quico/copy-docker) where [copy-docker.sh](https://quico.space/Quico/copy-docker/src/branch/main/copy-docker.sh) allows the following workflow:
```
source "${COMPOSE_ENV}"
# FIXME Docker Hub image name with or without slash? FIXME
copy-docker 'vault:'"${VAULT_VERSION}" fully.qualified.domain.name
```
## Start
```
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" up --detach
```
# Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
## Datasets
Create ZFS datasets and set permissions as needed.
* Parent dateset
```
zfs create -o mountpoint=/opt/docker-data 'zpool/docker-data'
```
* Container-specific datasets
```
zfs create -p 'zpool/docker-data/vault-'"${COMPOSE_CTX}"'/vault/data/db'
zfs create -p 'zpool/docker-data/vault-'"${COMPOSE_CTX}"'/vault/data/logs'
zfs create -p 'zpool/docker-data/vault-'"${COMPOSE_CTX}"'/vault/config'
```
FIXME When changing bind mount locations to real ones remember to also update `volumes:` in [compose.yaml](compose.yaml) FIXME
* Create subdirs
```
mkdir -p '/opt/docker-data/vault-'"${COMPOSE_CTX}"'/vault/'{'.ssh','config','data','projects'}
```
* Change ownership
```
chown -R 1000:1000 '/opt/docker-data/vault-'"${COMPOSE_CTX}"'/vault/data/'*
```
## Additional files
Place the following files on target server. Use the directory structure at [build-context](build-context) as a guide, specifically at `docker-data`.
FIXME Add details about files that aren't self-explanatory FIXME
```
build-context/
├── docker-data
│ └── config
│ └── vault.cfg
├── ...
└── ...
```
When done head back up to [How to run](#how-to-run).
# Development
## Conventional commits
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages.
### Commit types
Commit _types_ besides `fix` and `feat` are:
- `refactor`: Keeping functionality while streamlining or otherwise improving function flow
- `docs`: Documentation for project or components
### Commit scopes
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
- `vault`: A change to how the `vault` service component works
- `build`: Build-related changes such as `Dockerfile` fixes and features.
- `mount`: Volume or bind mount-related changes.
- `net`: Networking, IP addressing, routing changes
- `meta`: Affects the project's repo layout, file names etc.

View File

@@ -1,6 +1,6 @@
# For the remainder of this Dockerfile EXAMPLE_ARG_FOR_DOCKERFILE will be
# available with a value of 'must_be_available_in_dockerfile', check out the env
# file at 'env/fully.qualified.domain.name.example' for reference.
# file at 'env/fqdn_context.env.example' for reference.
# ARG EXAMPLE_ARG_FOR_DOCKERFILE
# Another env var, this one's needed in the example build step below:

View File

@@ -1,9 +1,10 @@
services:
vault-build:
image: "vault:${VAULT_VERSION}"
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "registry.example.com/project/vault:${VAULT_BUILD_DATE}-${VAULT_VERSION}"
profiles: ["build"]
build:
context: "build-context/vault"
context: "build-context"
dockerfile: Dockerfile
args:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"

View File

@@ -0,0 +1,36 @@
services:
vault:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "registry.example.com/project/vault:${VAULT_BUILD_DATE}-${VAULT_VERSION}"
container_name: "vault-${CONTEXT}"
networks:
vault-default:
ulimits:
nproc: ${ULIMIT_NPROC:-65535}
nofile:
soft: ${ULIMIT_NPROC:-65535}
hard: ${ULIMIT_NPROC:-65535}
extends:
file: common-settings.yaml
service: common-settings
ports:
# - "8080:80"
volumes:
# When changing bind mount locations to real ones remember to
# also update "Initial setup" section in README.md.
# - /opt/docker-data/vault-${CONTEXT}/vault/data/db:/usr/lib/vault
# - /opt/docker-data/vault-${CONTEXT}/vault/data/logs:/var/log/vault
# - /opt/docker-data/vault-${CONTEXT}/vault/config:/etc/vault
environment:
# VAULT_USER: ${VAULT_USER}
# VAULT_PASSWORD: ${VAULT_PASSWORD}
networks:
vault-default:
name: vault-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
- subnet: ${SUBNET}

View File

@@ -1,28 +0,0 @@
services:
vault:
image: "vault:${VAULT_VERSION}"
container_name: "vault-${CONTEXT}"
networks:
vault-default:
extends:
file: common-settings.yml
service: common-settings
ports:
# - "8080:80"
volumes:
# - /opt/docker-data/vault-${CONTEXT}/data/db:/usr/lib/vault
# - /opt/docker-data/vault-${CONTEXT}/data/logs:/var/log/vault
# - /opt/docker-data/vault-${CONTEXT}/config:/etc/vault
environment:
# VAULT_USER: ${VAULT_USER}
# VAULT_PASSWORD: ${VAULT_PASSWORD}
networks:
vault-default:
name: vault-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
# - subnet: 172.21.184.0/24

View File

@@ -0,0 +1,33 @@
CONTEXT=ux_vilnius
# Set something sensible here and uncomment
# ---
# VAULT_VERSION=x.y.z
# VAULT_VIP=10.1.1.2
# VAULT_BUILD_DATE=20230731
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# Subnet to use for this Docker Compose project. Docker defaults to
# container networks in prefix 172.16.0.0/12 which is 1 million addresses in
# the range from 172.16.0.0 to 172.31.255.255. Docker uses 172.17.0.0/16 for
# itself. Use any sensible prefix in 172.16.0.0/12 here except for Docker's
# own 172.17.0.0/16.
# ---
SUBNET=172.30.95.0/24
# See 'compose.override.yaml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -1,29 +0,0 @@
CONTEXT=fsf
# Set something sensible here and uncomment
# ---
# VAULT_VERSION=x.y.z
# A ${LOCATION} var is usually not needed. It may be helpful when a ${CONTEXT}
# extends over more than one location e.g. to bind-mount location-specific
# config files or certificates into a container.
# ---
# LOCATION=
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# See 'docker-compose.override.yml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -2,8 +2,7 @@ import sys
service_slug = "{{ cookiecutter.__service_slug }}"
component_list_slug = "{{ cookiecutter.__component_list_slug }}"
context_slug = "{{ cookiecutter.__context_slug }}"
for v in (service_slug, component_list_slug, context_slug):
for v in (service_slug, component_list_slug):
if not v:
print(f"Please answer all prompts with a non-empty string. Aborting and existing 3 ...")
print(f"Please answer all prompts with a non-empty string. Aborting and exiting 3 ...")
sys.exit(3)

View File

@@ -0,0 +1,228 @@
# FIXME
Search and replace all mentions of FIXME with sensible content in this file and in [compose.yaml](compose.yaml).
# {{ cookiecutter.__service_slug.capitalize() }} Docker Compose files
Docker Compose files to spin up an instance of {{ cookiecutter.__service_slug.capitalize() }} FIXME capitalization FIXME.
# How to run
Add a `COMPOSE_ENV` file and save its location as a shell variable along with the location where this repo lives, here for example `/opt/containers/{{ cookiecutter.__project_slug }}` plus all other variables. At [env/fqdn_context.env.example](env/fqdn_context.env.example) you'll find an example environment file.
When everything's ready start {{ cookiecutter.__service_slug.capitalize() }} with Docker Compose, otherwise head down to [Initial setup](#initial-setup) first.
## Environment
```
export COMPOSE_DIR='/opt/containers/{{ cookiecutter.__project_slug }}'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT='{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"
export COMPOSE_FILE="${COMPOSE_DIR}"'/compose.yaml'{% if cookiecutter.build == "yes" %}
export COMPOSE_OVERRIDE="${COMPOSE_DIR%/}"'/compose.override.yaml'{% endif %}
export COMPOSE_ENV=<add accordingly>
```
## Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
```
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
```
{%- if cookiecutter.build == "yes" %}
## Build
{% set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
{%- if loop.first %}
> Skip to [Pull](#pull) if you already have images in your private registry ready to use. Otherwise read on to build them now.
FIXME We build the `{{ cookiecutter.__service_slug }}` image locally. Our adjustment to the official image is simply adding `/tmp/{{ cookiecutter.__service_slug }}` to it. See {% if ',' in cookiecutter.__component_list_slug %}[build-context/{{ cookiecutter.__service_slug }}/Dockerfile](build-context/{{ cookiecutter.__service_slug }}/Dockerfile){%- else %}[build-context/Dockerfile](build-context/Dockerfile){%- endif %}. We use `/tmp/{{ cookiecutter.__service_slug }}` to bind-mount a dedicated ZFS dataset for the application's `tmpdir` location.
```
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${COMPOSE_ENV}" --profile 'build{% if ',' in cookiecutter.__component_list_slug %}-{{ cookiecutter.__service_slug }}{%- endif %}' build
```
{%- endif %}
{% endfor %}
## Push
Push to Docker Hub or your private registry. Setting up a private registry is out of scope of this repo. Once you have a registry available you can use it like so:
- On your OS install a Docker credential helper per [github.com/docker/docker-credential-helpers](https://github.com/docker/docker-credential-helpers). This will make sure you won't store credentials hashed (and unencrypted) in `~/.docker/config.json`. On an example Arch Linux machine where D-Bus Secret Service exists this will come via something like the [docker-credential-secretservice-bin](https://aur.archlinux.org/packages/docker-credential-secretservice-bin) Arch User Repository package. Just install and you're done.
- Do a `docker login registry.example.com`, enter username and password, confirm login.
```
source "${COMPOSE_ENV}"
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- if ',' in cookiecutter.__component_list_slug %}
for image in{% for component in components %} \
'{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}registry.example.com/project/{%- endif -%}{%- endif -%}{{ component }}:'"{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}${% raw %}{{% endraw %}{{ component.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}{%- endif -%}${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"{%- endfor %}; do
docker push 'registry.example.com/project/'"${image}"
done
{%- else %}
docker push "{%- if cookiecutter.build == "yes" -%}registry.example.com/project/{%- endif -%}{{ cookiecutter.__component_list_slug }}:{%- if cookiecutter.build == "yes" -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endif %}
```
{%- endif %}
## Pull
{% if cookiecutter.build == "yes" %}> Skip this step if you just built images that still exist locally on your build host.
FIXME Rewrite either [Build](#build) or this paragraph for which images are built and which ones pulled, `--profile 'full'` may not make sense.{% else %}Pull images from Docker Hub verbatim.{% endif %}
```
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' pull
```
## Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local `docker save` and a remote `docker load`. Consider the helper mini-project [quico.space/Quico/copy-docker](https://quico.space/Quico/copy-docker) where [copy-docker.sh](https://quico.space/Quico/copy-docker/src/branch/main/copy-docker.sh) allows the following workflow:
```
source "${COMPOSE_ENV}"
# FIXME Docker Hub image name with or without slash? FIXME
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- if ',' in cookiecutter.__component_list_slug %}
for image in{% for component in components %} '{{ component }}:'"${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"{%- endfor %}; do
copy-docker "${image}" fully.qualified.domain.name
done
{%- else %}
copy-docker '{{ cookiecutter.__component_list_slug }}:'"${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}" fully.qualified.domain.name
{%- endif %}
```
## Start
{%- if ',' in cookiecutter.__component_list_slug %}
FIXME Does the service use a virtual IP address? FIXME
Make sure your service's virtual IP address is bound on your target host then start containers.
{%- endif %}
```
{%- if ',' in cookiecutter.__component_list_slug %}
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' up --detach
{%- else %}
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" up --detach
{%- endif %}
```
# Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
## Datasets
Create ZFS datasets and set permissions as needed.
* Parent dateset
```
zfs create -o mountpoint=/opt/docker-data 'zpool/docker-data'
```
* Container-specific datasets
```
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/data/db'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/data/logs'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/config'
{%- endfor -%}
{%- else %}
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/db'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/logs'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/config'
{%- endif %}
```
FIXME When changing bind mount locations to real ones remember to also update `volumes:` in [compose.yaml](compose.yaml) FIXME
* Create subdirs
```
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
{%- if loop.first %}
mkdir -p '/opt/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/'{'.ssh','config','data','projects'}
{%- endif %}
{%- endfor %}
```
* Change ownership
```
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
{%- if loop.first %}
chown -R 1000:1000 '/opt/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/'*
{%- endif %}
{%- endfor %}
```
## Additional files
Place the following files on target server. Use the directory structure at [build-context](build-context) as a guide, specifically at `docker-data`.
FIXME Add details about files that aren't self-explanatory FIXME
```
build-context/
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
{%- if not loop.last %}
├── {{ component }}
│ ├── docker-data
│ | └── config
│ │ └── {{ component }}.cfg
│ ├── ...
│ └── ...
{%- else %}
└── {{ component }}
├── docker-data
| └── config
│ └── {{ component }}.cfg
├── ...
└── ...
{%- endif %}
{%- endfor %}
{%- else %}
├── docker-data
│ └── config
│ └── {{ cookiecutter.__service_slug }}.cfg
├── ...
└── ...
{%- endif %}
```
When done head back up to [How to run](#how-to-run).
# Development
## Conventional commits
This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages.
### Commit types
Commit _types_ besides `fix` and `feat` are:
- `refactor`: Keeping functionality while streamlining or otherwise improving function flow
- `docs`: Documentation for project or components
### Commit scopes
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
{%if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
- `{{ component }}`: A change to how the `{{ component }}` service component works
{%- endfor -%}
{%- else %}
- `{{ cookiecutter.__service_slug }}`: A change to how the `{{ cookiecutter.__service_slug }}` service component works
{%- endif %}
- `build`: Build-related changes such as `Dockerfile` fixes and features.
- `mount`: Volume or bind mount-related changes.
- `net`: Networking, IP addressing, routing changes
- `meta`: Affects the project's repo layout, file names etc.

View File

@@ -1,6 +1,6 @@
# For the remainder of this Dockerfile EXAMPLE_ARG_FOR_DOCKERFILE will be
# available with a value of 'must_be_available_in_dockerfile', check out the env
# file at 'env/fully.qualified.domain.name.example' for reference.
# file at 'env/fqdn_context.env.example' for reference.
# ARG EXAMPLE_ARG_FOR_DOCKERFILE
# Another env var, this one's needed in the example build step below:

View File

@@ -0,0 +1,27 @@
services:
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
{{ component }}-build:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}registry.example.com/project/{%- endif -%}{%- endif -%}{{ component }}:{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}${% raw %}{{% endraw %}{{ component.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}{%- endif -%}${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
profiles: ["build", "build-{{ component }}"]
build:
context: "build-context/{{ component }}"
dockerfile: Dockerfile
args:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"
{{ component.upper() }}_VERSION: "${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endfor %}
{%- else %}
{{ cookiecutter.__component_list_slug }}-build:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "{%- if cookiecutter.build == "yes" -%}registry.example.com/project/{%- endif -%}{{ cookiecutter.__component_list_slug }}:{%- if cookiecutter.build == "yes" -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
profiles: ["build"]
build:
context: "build-context"
dockerfile: Dockerfile
args:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"
{{ cookiecutter.__component_list_slug.upper() }}_VERSION: "${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endif %}

View File

@@ -0,0 +1,89 @@
services:
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- set ns = namespace(found=false) -%}
{%- for component in components %}
{%- if loop.first -%}
{%- set ns.first_component = component -%}
{%- elif N is undefined -%}
{%- set ns.second_component = component -%}
{%- set N = 0 -%}
{%- endif -%}
{%- endfor -%}
{%- for component in components %}
{{ component }}:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}registry.example.com/project/{%- endif -%}{%- endif -%}{{ component }}:{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}${% raw %}{{% endraw %}{{ component.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}{%- endif -%}${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
container_name: "{{ cookiecutter.__service_slug }}-{{ component }}-${CONTEXT}"
networks:
{{ cookiecutter.__service_slug }}-default:
profiles: ["full", "{{ component }}"]
{% if loop.first -%}
depends_on:
{{ ns.second_component }}:
condition: service_healthy
{%- else -%}
healthcheck:
test: ["CMD", "fping", "--count=1", "${% raw %}{{% endraw %}{{ ns.first_component.upper() }}_VIP{% raw %}}{% endraw %}", "--period=500", "--quiet"]
interval: 3s
timeout: 1s
retries: 60
start_period: 2s
{%- endif %}
ulimits:
nproc: ${ULIMIT_NPROC:-65535}
nofile:
soft: ${ULIMIT_NPROC:-65535}
hard: ${ULIMIT_NPROC:-65535}
extends:
file: common-settings.yaml
service: common-settings
ports:
# - "8080:80"
volumes:
# When changing bind mount locations to real ones remember to
# also update "Initial setup" section in README.md.
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ component }}/data/db:/usr/lib/{{ component }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ component }}/data/logs:/var/log/{{ component }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ component }}/config:/etc/{{ component }}
environment:
# {{ component.upper() }}_USER: ${% raw %}{{% endraw %}{{ component.upper() }}_USER{% raw %}}{% endraw %}
# {{ component.upper() }}_PASSWORD: ${% raw %}{{% endraw %}{{ component.upper() }}_PASSWORD{% raw %}}{% endraw %}
{%- endfor -%}
{%- else %}
{{ cookiecutter.__component_list_slug }}:
# FIXME image name with or without slash? Docker Hub or private registry? With or without *_BUILD_DATE? FIXME
image: "{%- if cookiecutter.build == "yes" -%}registry.example.com/project/{%- endif -%}{{ cookiecutter.__component_list_slug }}:{%- if cookiecutter.build == "yes" -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
container_name: "{{ cookiecutter.__service_slug }}-${CONTEXT}"
networks:
{{ cookiecutter.__service_slug }}-default:
ulimits:
nproc: ${ULIMIT_NPROC:-65535}
nofile:
soft: ${ULIMIT_NPROC:-65535}
hard: ${ULIMIT_NPROC:-65535}
extends:
file: common-settings.yaml
service: common-settings
ports:
# - "8080:80"
volumes:
# When changing bind mount locations to real ones remember to
# also update "Initial setup" section in README.md.
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ cookiecutter.__service_slug }}/data/db:/usr/lib/{{ cookiecutter.__service_slug }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ cookiecutter.__service_slug }}/data/logs:/var/log/{{ cookiecutter.__service_slug }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/{{ cookiecutter.__service_slug }}/config:/etc/{{ cookiecutter.__service_slug }}
environment:
# {{ cookiecutter.__component_list_slug.upper() }}_USER: ${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_USER{% raw %}}{% endraw %}
# {{ cookiecutter.__component_list_slug.upper() }}_PASSWORD: ${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_PASSWORD{% raw %}}{% endraw %}
{%- endif %}
networks:
{{ cookiecutter.__service_slug }}-default:
name: {{ cookiecutter.__service_slug }}-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
- subnet: ${SUBNET}

View File

@@ -1,25 +0,0 @@
services:
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
{{ component }}-build:
image: "{{ component }}:${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
profiles: ["build", "build-{{ component }}"]
build:
context: "build-context/{{ component }}"
dockerfile: Dockerfile
args:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"
{{ component.upper() }}_VERSION: "${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endfor %}
{%- else %}
{{ cookiecutter.__component_list_slug }}-build:
image: "{{ cookiecutter.__component_list_slug }}:${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
profiles: ["build"]
build:
context: "build-context/{{ cookiecutter.__component_list_slug }}"
dockerfile: Dockerfile
args:
EXAMPLE_ARG_FOR_DOCKERFILE: "${EXAMPLE_ARG_FROM_ENV_FILE}"
{{ cookiecutter.__component_list_slug.upper() }}_VERSION: "${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endif %}

View File

@@ -1,52 +0,0 @@
services:
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
{{ component }}:
image: "{{ component }}:${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"
container_name: "{{ cookiecutter.__service_slug }}-{{ component }}-${CONTEXT}"
networks:
{{ cookiecutter.__service_slug }}-default:
profiles: ["full", "{{ component }}"]
extends:
file: common-settings.yml
service: common-settings
ports:
# - "8080:80"
volumes:
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-{{ component }}-${CONTEXT}/{{ component }}/data/db:/usr/lib/{{ component }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-{{ component }}-${CONTEXT}/{{ component }}/data/logs:/var/log/{{ component }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-{{ component }}-${CONTEXT}/{{ component }}/config:/etc/{{ component }}
environment:
# {{ component.upper() }}_USER: ${% raw %}{{% endraw %}{{ component.upper() }}_USER{% raw %}}{% endraw %}
# {{ component.upper() }}_PASSWORD: ${% raw %}{{% endraw %}{{ component.upper() }}_PASSWORD{% raw %}}{% endraw %}
{%- endfor -%}
{%- else %}
{{ cookiecutter.__component_list_slug }}:
image: "{{ cookiecutter.__component_list_slug }}:${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
container_name: "{{ cookiecutter.__service_slug }}-${CONTEXT}"
networks:
{{ cookiecutter.__service_slug }}-default:
extends:
file: common-settings.yml
service: common-settings
ports:
# - "8080:80"
volumes:
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/data/db:/usr/lib/{{ cookiecutter.__service_slug }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/data/logs:/var/log/{{ cookiecutter.__service_slug }}
# - /opt/docker-data/{{ cookiecutter.__service_slug }}-${CONTEXT}/config:/etc/{{ cookiecutter.__service_slug }}
environment:
# {{ cookiecutter.__component_list_slug.upper() }}_USER: ${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_USER{% raw %}}{% endraw %}
# {{ cookiecutter.__component_list_slug.upper() }}_PASSWORD: ${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_PASSWORD{% raw %}}{% endraw %}
{%- endif %}
networks:
{{ cookiecutter.__service_slug }}-default:
name: {{ cookiecutter.__service_slug }}-${CONTEXT}
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "false"
ipam:
driver: default
config:
# - subnet: 172.21.184.0/24

View File

@@ -0,0 +1,40 @@
CONTEXT=ux_vilnius
# Set something sensible here and uncomment
# ---
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
# {{ component.upper() }}_VERSION=x.y.z
{%- endfor %}
{%- for component in components %}
{%- if loop.first %}
# {{ component.upper() }}_VIP=10.1.1.2
# {{ component.upper() }}_BUILD_DATE=20230731
{%- endif %}
{%- endfor %}
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# Subnet to use for this Docker Compose project. Docker defaults to
# container networks in prefix 172.16.0.0/12 which is 1 million addresses in
# the range from 172.16.0.0 to 172.31.255.255. Docker uses 172.17.0.0/16 for
# itself. Use any sensible prefix in 172.16.0.0/12 here except for Docker's
# own 172.17.0.0/16.
# ---
SUBNET=172.30.95.0/24
# See 'compose.override.yaml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -1,32 +0,0 @@
CONTEXT={{ cookiecutter.__context_slug }}
# Set something sensible here and uncomment
# ---
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{% for component in components %}
# {{ component.upper() }}_VERSION=x.y.z
{%- endfor %}
# A ${LOCATION} var is usually not needed. It may be helpful when a ${CONTEXT}
# extends over more than one location e.g. to bind-mount location-specific
# config files or certificates into a container.
# ---
# LOCATION=
# Feel free to leave defaults. They apply while these vars are commented out
# ---
# RESTARTPOLICY=unless-stopped
# TIMEZONE=Etc/UTC
# See 'docker-compose.override.yml' for how to make a variable available in
# a Dockerfile
# ---
# EXAMPLE_ARG_FROM_ENV_FILE=must_be_available_in_dockerfile

View File

@@ -9,12 +9,16 @@ cookiecutter https://quico.space/Quico/py-cookiecutter-templates.git --directory
Cookiecutter interactively prompts you for the following info, here with example answers:
```
project_slug [project-slug]: dockerhost-firewalld-update
Select rich_logging:
project_slug [project-slug]: update-firewall-source
Select use_rich_logging:
1 - yes
2 - no
Choose from 1, 2 [1]:
Select uses_config_ini:
Select use_config_ini:
1 - yes
2 - no
Choose from 1, 2 [1]:
Select use_inflect:
1 - yes
2 - no
Choose from 1, 2 [1]:
@@ -24,33 +28,38 @@ Done, directory structure and files for your next Python project are ready for y
## Explanation and terminology
Your three answers translate as follows into rendered files.
Your answers translate as follows into rendered files.
1. The `project_slug` is used as a directory name for your Python project where spaces and underscores are replaced-with-dashes. It's also used for a few example variables where `we_use_underscores` instead.
```
.
└── dockerhost-firewalld-update
  ├── dockerhost-firewalld-update.py
  ├── examples
  │   └── config.ini.example
  ├── requirements.in
  └── requirements.txt
└── update-firewall-source
├── examples
│   └── config.ini.example
├── requirements.in
├── requirements.txt
└── update-firewall-source.py
```
2. The `rich_logging` variable adds settings and examples that make ample use of the [Rich package](https://github.com/Textualize/rich/) for beautiful logging. You typically want this so it defaults to `yes`. Just hit `Enter` to confirm. The setting also adds necessary requirements.
3. With `uses_config_ini` you're getting a boat load of functions, presets, variables and examples that integrate a config.ini file via the `configparser` module.
2. The `use_rich_logging` variable adds settings and examples that make ample use of the [Rich package](https://github.com/Textualize/rich/) for beautiful logging. You typically want this so it defaults to `yes`. Just hit `Enter` to confirm. The setting also adds necessary requirements.
3. With `use_config_ini` you're getting a boat load of functions, presets, variables and examples that integrate a config.ini file via the `configparser` module.
4. Lastly with `use_inflect` you're adding the `inflect` module which does grammatically correct text rendering such as plural and singular. It also includes a few examples.
## Result
### Enable Rich and configparser
### Enable Rich, configparser and inflect
Above example of a Python project with Rich and `configparser` enabled will give you a directory structure like this:
```
Above example of a Python project with all of Rich, `configparser` and `inflect` enabled will give you a directory structure like this:
```
.
└── dockerhost-firewalld-update
  ├── dockerhost-firewalld-update.py
  ├── examples
  │   └── config.ini.example
  ├── requirements.in
  └── requirements.txt
```
You can see real-life example file content over at [examples/rich-and-config](examples/rich-and-config). Cookiecutter has generated all necessary dependencies with pinned versions and a `rich-and-config.py` script file to get you started.
└── update-firewall-source
├── examples
│   └── config.ini.example
├── requirements.in
├── requirements.txt
└── update-firewall-source.py
```
You can see real-life example file content over at [examples/update-firewall-source](examples/update-firewall-source). Cookiecutter has generated all necessary dependencies with pinned versions and a `update-firewall-source.py` script file to get you started.

View File

@@ -1,12 +1,12 @@
[DEFAULT]
self_name = rich-and-config
self_name = update-firewall-source
tmp_base_dir = /tmp/%(self_name)s
state_base_dir = /var/lib/%(self_name)s
state_files_dir = %(state_base_dir)s/state
state_file_retention = 50
state_file_name_prefix = state-
state_file_name_suffix = .log
rich_and_config_some_option = "http://localhost:8000/api/query"
update_firewall_source_some_option = "http://localhost:8000/api/query"
another_option = "first"
[this-is-a-section]

View File

@@ -31,7 +31,7 @@ class CONST(object):
# an empty string. An example config.ini file may give a sane config example value here, removing that value
# still results in a valid file.
CFG_KNOWN_DEFAULTS = [
{"key": "self_name", "value": "rich-and-config", "empty_ok": False},
{"key": "self_name", "value": "update-firewall-source", "empty_ok": False},
{"key": "tmp_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/tmp/%(self_name)s"),
"empty_ok": False},
{"key": "state_base_dir", "value": os.path.join(CFG_THIS_FILE_DIRNAME, "data/var/lib/%(self_name)s"),
@@ -40,7 +40,7 @@ class CONST(object):
{"key": "state_file_retention", "value": "50", "is_global": False, "empty_ok": True},
{"key": "state_file_name_prefix", "value": "state-", "is_global": False, "empty_ok": True},
{"key": "state_file_name_suffix", "value": ".log", "is_global": False, "empty_ok": True},
{"key": "rich_and_config_some_option", "value": "http://localhost:8000/api/query", "is_global": True,
{"key": "update_firewall_source_some_option", "value": "http://localhost:8000/api/query", "is_global": True,
"empty_ok": False},
{"key": "another_option", "value": "first", "is_global": True, "empty_ok": True}
]