├── .github └── workflows │ └── release.yml ├── README.md ├── defaults └── main.yml ├── meta └── main.yml └── tasks └── main.yml /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | GALAXY_USERNAME: IronicBadger 10 | 11 | jobs: 12 | 13 | release: 14 | name: Release 15 | runs-on: ubuntu-latest 16 | steps: 17 | 18 | - name: Set up Python 3. 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: '3.x' 22 | 23 | - name: Install Ansible. 24 | run: pip3 install ansible-core 25 | 26 | # Galaxy uses CamelCase username but GitHub is all lowercase 27 | # This concatenates the versions together to work with 28 | # And triggers an import on Galaxy 29 | - name: Trigger a new import on Galaxy. 30 | run: >- 31 | ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} 32 | $(echo ${{ env.GALAXY_USERNAME }}) $(echo ${{ github.repository }} | cut -d/ -f2) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ansible-role-docker-compose-generator 2 | 3 | This role is designed to ingest directories of `compose.yaml` files and output a sanitised version to a remote host using Ansible. 4 | 5 | > ⚠️ **Warning:** v1 of this role used a completely different data structure. See [v1 vs v2 of this role](#v1-of-this-role-vs-v2) 6 | 7 | I wrote the following [blog post](https://blog.ktz.me/docker-compose-generator-v2-release/) on the v2 release for more info. 8 | 9 | ## Usage 10 | 11 | Import this role into your Ansible setup either as a [git submodule](https://blog.ktz.me/git-submodules-for-fun-and-profit-with-ansible/) or via Ansible Galaxy. 12 | 13 | In the root of your git repo create a `services` directory and populate it as follows: 14 | 15 | ``` 16 | . 17 | └── services 18 | ├── ansible-hostname1 19 | │ ├── librespeed 20 | │ │ └── compose.yaml 21 | │ ├── jellyfin 22 | │ │ └── compose.yaml 23 | └── ansible-hostname2 24 | └── uptime-kuma 25 | └── compose.yaml 26 | ``` 27 | 28 | Each `compose.yaml` file is a standard format file, for example librespeed looks like this: 29 | 30 | ``` 31 | services: 32 | librespeed: 33 | image: lscr.io/linuxserver/librespeed 34 | container_name: librespeed 35 | ports: 36 | - 8008:80 37 | environment: 38 | - "TZ={{ host_timezone }}" 39 | - "PASSWORD={{ testpass }}" 40 | restart: unless-stopped 41 | ``` 42 | 43 | Notice that variable interpolation is supported. The source of these variables can be either an encryped secrets file via Ansible vault (read more about that [here](https://blog.ktz.me/secret-management-with-docker-compose-and-ansible/) - or see [ironicbadger/infra](https://github.com/ironicbadger/infra) for an implemented example), an `env` file you manually place alongside the `compose.yaml` on the remote host (see [docker compose variable interpolation](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#interpolation-syntax)), or any other standard Ansible variable source. 44 | 45 | Multiple services per compose file are also supported. Useful to run a database alongside an app, for example. 46 | 47 | By default, if a `compose.yaml` file is found it will be included in the automation, and placed into the output `compose.yaml` on the remote host. This file is placed under the `docker_compose_generator_output_path` which is the home folder of the ssh user. The role also supports disabling specific compose files by matching the name of the file against a `host_var` or `group_var` file with the following variable: 48 | 49 | ``` 50 | disabled_compose_files: 51 | - jellyfin 52 | ``` 53 | 54 | ## Custom hostnames 55 | 56 | By default, the role is looking for a directory structure under `services/` which matches your Ansible hostname. If your hostname doesn't match the name of this directory for some reason (maybe it's an IP address, rather than a hostname), you can override the name with the variable: 57 | 58 | ``` 59 | docker_compose_hostname: my-custom-hostname 60 | ``` 61 | 62 | ## Override services directory location 63 | 64 | By default, the role is looking for services by determining where the `playbook_dir` is and appending `services/`. 65 | If your playbooks are for example inside a dedicated playbooks directory you can overwrite the services location by setting `services_directory` either in a task var, group_vars or host_vars. 66 | 67 | ## v1 of this role vs v2 68 | 69 | v1 of this role used a large custom data structure and an ever more complex jinja2 based templating approach. The custom nature of this approach added friction when adding new services and made it difficult to copy/paste from upstream repositories to try things out quickly. 70 | 71 | v2 supports using standalone, native compose files. This makes it much more straightforward to try out new software without needing to 'convert' it to work with the v1 custom data structures. 72 | 73 | If you find any edge cases I've missed for v2, please open an issue or PR. I'd be happy to review. 74 | 75 | Special thanks goes to [u/fuzzymistborn](https://github.com/fuzzymistborn) for the spark for the idea to make this change. As ever, you can find a full working example of my usage of this role over at [ironicbadger/infra](https://github.com/ironicbadger/infra). 76 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | docker_compose_generator_output_path: "~" 3 | docker_compose_generator_uid: "1000" 4 | docker_compose_generator_gid: "1000" 5 | services_directory: "{{ playbook_dir }}/services/" 6 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: docker_compose_generator 4 | author: Alex Kretzschmar 5 | description: Create a docker-compose.yml file 6 | issue_tracker_url: https://github.com/ironicbadger/ansible-role-create-users/issues 7 | license: GPLv2 8 | min_ansible_version: 2.4 9 | platforms: 10 | - name: EL 11 | versions: 12 | - all 13 | - name: GenericUNIX 14 | versions: 15 | - all 16 | - any 17 | - name: Fedora 18 | versions: 19 | - all 20 | - name: opensuse 21 | versions: 22 | - all 23 | - name: Amazon 24 | versions: 25 | - all 26 | - name: GenericBSD 27 | versions: 28 | - all 29 | - any 30 | - name: FreeBSD 31 | versions: 32 | - all 33 | - name: Ubuntu 34 | versions: 35 | - all 36 | - name: SLES 37 | versions: 38 | - all 39 | - name: GenericLinux 40 | versions: 41 | - all 42 | - any 43 | - name: Debian 44 | versions: 45 | - all 46 | dependencies: [] 47 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure destination for compose file exists 3 | file: 4 | path: "{{ docker_compose_generator_output_path }}" 5 | state: directory 6 | 7 | - name: Find all compose files 8 | set_fact: 9 | compose_files: >- 10 | {{ 11 | query('filetree', services_directory + 12 | (docker_compose_hostname | default(inventory_hostname))) | 13 | selectattr('state', 'eq', 'file') | 14 | selectattr('path', 'regex', '.ya?ml$') | 15 | list 16 | }} 17 | 18 | - name: Build combined compose content 19 | set_fact: 20 | combined_compose: | 21 | # Generated by ironicbadger.docker-compose-generator 22 | # badger badger badger mushroom mushrooooom... 23 | 24 | services: 25 | {% for file in compose_files | sort(attribute='src') %} 26 | {% set service_name = file.src | dirname | basename %} 27 | {% if service_name not in (disabled_compose_files | default([])) %} 28 | {{ lookup('template', file.src) 29 | | regex_replace('^(---|services:)\s*\n*', '') 30 | | regex_replace('^', ' ') }} 31 | {% endif %} 32 | {% endfor %} 33 | 34 | - name: Write combined compose file 35 | copy: 36 | content: "{{ combined_compose }}" 37 | dest: "{{ docker_compose_generator_output_path }}/compose.yaml" 38 | owner: "{{ docker_compose_generator_uid }}" 39 | group: "{{ docker_compose_generator_gid }}" --------------------------------------------------------------------------------