Skip to content

Commit 9549fb9

Browse files
committed
Adds a new hidden template DevOps Ansible to be used by other templates
1 parent 2580a44 commit 9549fb9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1698
-0
lines changed

cookiecutter.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
"title": "Documentation scaffold for Plone projects",
6060
"description": "Create a new documentation scaffold for Plone projects",
6161
"hidden": false
62+
},
63+
"devops_ansible": {
64+
"path": "./templates/devops/ansible",
65+
"title": "Ansible Playbooks for Plone",
66+
"description": "Ansible setup to manage a Docker Swarm cluster for Plone hosting.",
67+
"hidden": true
6268
}
6369
}
6470
}

news/+devops.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Adds a new hidden template DevOps Ansible to be used by other templates. @ericof
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ansible

templates/devops/ansible/Makefile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
SHELL := /bin/bash
2+
CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
3+
4+
5+
# We like colors
6+
# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects
7+
RED=`tput setaf 1`
8+
GREEN=`tput setaf 2`
9+
RESET=`tput sgr0`
10+
YELLOW=`tput setaf 3`
11+
12+
.PHONY: all
13+
all: build
14+
15+
BASE_FOLDER = $(shell git rev-parse --show-toplevel)
16+
VENV_FOLDER = ${BASE_FOLDER}/.venv
17+
BIN_FOLDER = ${VENV_FOLDER}/bin
18+
19+
TEMPLATE = devops_ansible
20+
DEVOPS_FOLDER_NAME = ansible
21+
22+
.PHONY: clean
23+
clean: ## Clean
24+
rm -rf $(ADDON_NAME)
25+
26+
$(VENV_FOLDER): ## cookieplone installation
27+
$(MAKE) -C $(BASE_FOLDER) sync
28+
29+
.PHONY: format
30+
format: $(VENV_FOLDER)## Format code
31+
@echo "$(GREEN)==> Formatting $(TEMPLATE) codebase $(RESET)"
32+
@uv run ruff format --config $(BASE_FOLDER)/pyproject.toml hooks
33+
@uv run ruff check --select I --fix --config $(BASE_FOLDER)/pyproject.toml hooks
34+
35+
.PHONY: generate
36+
generate: $(VENV_FOLDER) ## Create a sample package
37+
@echo "$(GREEN)==> Creating new test package$(RESET)"
38+
rm -rf $(DEVOPS_FOLDER_NAME)
39+
COOKIEPLONE_REPOSITORY=$(BASE_FOLDER) uv run cookieplone $(TEMPLATE) --no-input
40+
41+
.PHONY: test
42+
test: $(VENV_FOLDER)## Create a sample package and tests it
43+
@echo "$(GREEN)==> Creating new test package$(RESET)"
44+
@uv run pytest $(BASE_FOLDER)/tests/templates/devops/ansible
45+
46+
.PHONY: test-pdb
47+
test-pdb: $(VENV_FOLDER)## Stop on the first failed test
48+
@echo "$(GREEN)==> Test template, stop on first error$(RESET)"
49+
@uv run pytest $(BASE_FOLDER)/tests/templates/devops/ansible -x --pdb

templates/devops/ansible/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Cookieplone Ansible Playbooks
2+
3+
[![Cookieplone Templates: CI](https://github.com/plone/cookieplone-templates/actions/workflows/main.yml/badge.svg)](https://github.com/plone/cookieplone-templates/blob/main/.github/workflows/main.yml)
4+
[![Built with Cookieplone](https://img.shields.io/badge/built%20with-Cookiecutter-ff69b4.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/)
5+
[![License](https://img.shields.io/github/license/plone/cookieplone-templates)](../../../LICENSE)
6+
[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
7+
8+
Powered by [Cookieplone](https://github.com/plone/cookieplone) and [Cookiecutter](https://github.com/cookiecutter/cookiecutter), [Cookieplone devops Ansible](https://github.com/plone/cookieplone-templates/tree/main/templates/devops/ansible) is intended to be used by Plone developers to create Ansible playbooks to setup a Docker Swarm cluster.
9+
10+
11+
## Prerequisites
12+
13+
- [uv](https://docs.astral.sh/uv/) is the recommended tool for managing Python versions and project dependencies.
14+
15+
16+
### uv
17+
18+
To install uv, use the following command, or visit the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/) for alternative methods.
19+
20+
```shell
21+
curl -LsSf https://astral.sh/uv/install.sh | sh
22+
```
23+
24+
25+
## Generate the playbooks 🎉
26+
27+
```shell
28+
uvx cookieplone devops_ansible
29+
```
30+
31+
## License 📜
32+
33+
This project is licensed under the [MIT License](/LICENSE).
34+
35+
36+
## Let's get building! 🚀
37+
38+
Happy coding!
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"title": "Ansible",
3+
"description": "Ansible setup to manage a Docker Swarm cluster for Plone hosting",
4+
"folder_name": "{{ cookiecutter.title|replace(' ', '')|replace('-', '_')|replace('.', '')|lower }}",
5+
"hostname": "{{ cookiecutter.folder_name }}.example.com",
6+
"hostname_or_ip": "{{ cookiecutter.hostname }}",
7+
"stack_name": "{{ cookiecutter.hostname|replace('.', '-') }}",
8+
"stack_prefix": "{{ cookiecutter.hostname | extract_host | replace('.', '-') }}",
9+
"stack_location": "etc/stacks/{{ cookiecutter.hostname }}.yml",
10+
"author": "Plone Community",
11+
"email": "[email protected]",
12+
"repository_type": ["github", "gitlab_com", "gitlab"],
13+
"organization": "collective",
14+
"repository_url": "https://github.com/{{ cookiecutter.organization }}/{{ cookiecutter.folder_name }}",
15+
"container_registry": ["github", "docker_hub", "gitlab"],
16+
"initialize_git": ["1", "0"],
17+
"__folder_name": "{{ cookiecutter.folder_name }}",
18+
"__project_slug": "{{ cookiecutter.folder_name }}",
19+
"__devops_host": "{{ cookiecutter.hostname | extract_host }}",
20+
"__devops_stack_name": "{{ cookiecutter.stack_name }}",
21+
"__devops_stack_prefix": "{{ cookiecutter.stack_prefix }}",
22+
"__devops_swarm_public_network": "nw-public",
23+
"__devops_swarm_stack_network": "nw-internal",
24+
"__devops_traefik_version": "v2.11",
25+
"__devops_traefik_stack_include_ui": "no",
26+
"__devops_cronjob_version": "1.14",
27+
"__devops_project_user": "500",
28+
"__repository_url": "{{ cookiecutter.repository_url }}",
29+
"__container_registry_prefix": "{{ cookiecutter.container_registry | image_prefix }}",
30+
"__container_image_prefix": "{{ cookiecutter.__container_registry_prefix }}{{ cookiecutter.organization }}/{{ cookiecutter.__project_slug }}",
31+
"__year": "{% now 'local', '%Y' %}",
32+
"__generator_date_long": "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}",
33+
"__generator_signature": "This was generated by the [cookieplone-templates documentation_starter template](https://github.com/plone/cookieplone-templates/tree/main/documentation_starter) on {{ cookiecutter.__generator_date_long }}",
34+
"__documentation_starter_format": "1",
35+
"__prompts__": {
36+
"title": "Title",
37+
"description": "A short description for this repository",
38+
"folder_name": "Name of the folder to be created",
39+
"hostname": "Public URL of the server",
40+
"hostname_or_ip": "Address of the server (IP or hostname)",
41+
"repository_type": {
42+
"__prompt__": "Repository Type",
43+
"github": "GitHub",
44+
"gitlab_com": "GitLab.com",
45+
"gitlab": "GitLab Self-Hosted"
46+
},
47+
"organization": "GitHub / GitLab Username or Organization",
48+
"repository_url": "Repository URL",
49+
"container_registry": {
50+
"__prompt__": "Container Registry",
51+
"github": "GitHub Container Registry",
52+
"docker_hub": "Docker Hub",
53+
"gitlab": "GitLab"
54+
},
55+
"stack_name": "Docker Swarm Stack Name",
56+
"stack_prefix": "A 5 to 10 character prefix for the stack",
57+
"author": "Author",
58+
"email": "Author E-mail",
59+
"initialize_git": {
60+
"__prompt__": "Initialize Git Repository?",
61+
"1": "Yes",
62+
"0": "No"
63+
}
64+
},
65+
"_copy_without_render": [
66+
"etc/.ssh",
67+
"etc/base",
68+
"etc/docker",
69+
"etc/keys",
70+
"etc/ssh",
71+
"playbooks",
72+
"requirements",
73+
"tasks"
74+
],
75+
"_extensions": [
76+
"cookieplone.filters.pascal_case",
77+
"cookieplone.filters.extract_host",
78+
"cookieplone.filters.image_prefix"
79+
],
80+
"__cookieplone_repository_path": "",
81+
"__cookieplone_template": ""
82+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from collections import OrderedDict
2+
from copy import deepcopy
3+
from pathlib import Path
4+
from random import choice
5+
from string import ascii_letters, digits
6+
7+
from cookieplone.utils import console, git
8+
9+
context: OrderedDict = {{cookiecutter}}
10+
11+
12+
def generate_vaultpass(context: OrderedDict, output_dir: Path):
13+
"""Generate a vault password file for Ansible."""
14+
vault_pass_path = output_dir / ".vault_pass"
15+
value = "".join(choice(ascii_letters + digits) for _ in range(32)) # noQA: S311
16+
if not vault_pass_path.exists():
17+
vault_pass_path.write_text(value)
18+
console.print(f"Vault password file created at {vault_pass_path}")
19+
else:
20+
console.print(f"Vault password file already exists at {vault_pass_path}")
21+
22+
23+
def handle_git_initialization(context: OrderedDict, output_dir: Path):
24+
"""Initialize a Git repository for the documentation codebase."""
25+
git.initialize_repository(output_dir)
26+
27+
28+
def main():
29+
"""Final fixes."""
30+
output_dir = Path().cwd()
31+
initialize_git = bool(int(context.get("initialize_git")))
32+
# Cleanup / Git
33+
actions = [
34+
[
35+
generate_vaultpass,
36+
"Generate Ansible vault password",
37+
True,
38+
],
39+
[
40+
handle_git_initialization,
41+
"Initialize Git repository",
42+
initialize_git,
43+
],
44+
]
45+
for func, title, enabled in actions:
46+
if not int(enabled):
47+
continue
48+
new_context = deepcopy(context)
49+
console.print(f" -> {title}")
50+
func(new_context, output_dir)
51+
52+
msg = """
53+
[bold blue]{{ cookiecutter.title }}[/bold blue]
54+
55+
Now, enter the {{ cookiecutter.folder_name }} folder, start using your
56+
playbooks.
57+
58+
Sorry for the convenience,
59+
The Plone Community.
60+
"""
61+
console.panel(
62+
title="New Ansible setup created",
63+
subtitle="",
64+
msg=msg,
65+
url="https://plone.org/",
66+
)
67+
68+
69+
if __name__ == "__main__":
70+
main()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Pre generation hook."""
2+
3+
from collections import OrderedDict
4+
from pathlib import Path
5+
6+
output_path = Path().resolve()
7+
8+
context: OrderedDict = {{cookiecutter}}
9+
10+
11+
def main():
12+
"""Validate context."""
13+
pass
14+
15+
16+
if __name__ == "__main__":
17+
main()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
exclude_paths:
3+
- .cache/ # implicit unless exclude_paths is defined in config
4+
- .github/
5+
- .ansible-lint
6+
- etc/
7+
- keys/
8+
- roles/
9+
- "~/.ansible/roles/"
10+
- "inventory/**/vault.yml"
11+
12+
enable_list:
13+
- fqcn-builtins
14+
- no-log-password # opt-in
15+
- no-same-owner # opt-in
16+
17+
kinds:
18+
- playbook: "./playbooks/**/*.{yml,yaml}"
19+
- tasks: "./tasks/**/*.{yml,yaml}"
20+
- yaml: "./inventory/.{yml,yaml}"
21+
22+
skip_list:
23+
- role-name
24+
25+
# Offline mode disables installation of requirements.yml
26+
offline: true
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# EditorConfig Configurtaion file, for more details see:
2+
# http://EditorConfig.org
3+
# EditorConfig is a convention description, that could be interpreted
4+
# by multiple editors to enforce common coding conventions for specific
5+
# file types
6+
7+
# top-most EditorConfig file:
8+
# Will ignore other EditorConfig files in Home directory or upper tree level.
9+
root = true
10+
11+
12+
[*] # For All Files
13+
# Unix-style newlines with a newline ending every file
14+
end_of_line = lf
15+
insert_final_newline = true
16+
trim_trailing_whitespace = true
17+
# Set default charset
18+
charset = utf-8
19+
# Indent style default
20+
indent_style = space
21+
# Max Line Length - a hard line wrap, should be disabled
22+
max_line_length = off
23+
24+
[*.{py,cfg,ini}]
25+
# 4 space indentation
26+
indent_size = 4
27+
28+
[*.{html,dtml,pt,zpt,xml,zcml,js,json,less,css,yml,yaml,sh}]
29+
# 2 space indentation
30+
indent_size = 2
31+
32+
[{Makefile,.gitmodules}]
33+
# Tab indentation (no size specified, but view as 4 spaces)
34+
indent_style = tab
35+
indent_size = unset
36+
tab_width = unset

0 commit comments

Comments
 (0)