Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@
"title": "Documentation scaffold for Plone projects",
"description": "Create a new documentation scaffold for Plone projects",
"hidden": false
},
"devops_ansible": {
"path": "./templates/devops/ansible",
"title": "Ansible Playbooks for Plone",
"description": "Ansible setup to manage a Docker Swarm cluster for Plone hosting.",
"hidden": true
}
}
}
1 change: 1 addition & 0 deletions news/+devops.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds a new hidden template DevOps Ansible to be used by other templates. @ericof
1 change: 1 addition & 0 deletions templates/devops/ansible/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ansible
49 changes: 49 additions & 0 deletions templates/devops/ansible/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
SHELL := /bin/bash
CURRENT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))


# We like colors
# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects
RED=`tput setaf 1`
GREEN=`tput setaf 2`
RESET=`tput sgr0`
YELLOW=`tput setaf 3`

.PHONY: all
all: build

BASE_FOLDER = $(shell git rev-parse --show-toplevel)
VENV_FOLDER = ${BASE_FOLDER}/.venv
BIN_FOLDER = ${VENV_FOLDER}/bin

TEMPLATE = devops_ansible
DEVOPS_FOLDER_NAME = ansible

.PHONY: clean
clean: ## Clean
rm -rf $(ADDON_NAME)

$(VENV_FOLDER): ## cookieplone installation
$(MAKE) -C $(BASE_FOLDER) sync

.PHONY: format
format: $(VENV_FOLDER)## Format code
@echo "$(GREEN)==> Formatting $(TEMPLATE) codebase $(RESET)"
@uv run ruff format --config $(BASE_FOLDER)/pyproject.toml hooks
@uv run ruff check --select I --fix --config $(BASE_FOLDER)/pyproject.toml hooks

.PHONY: generate
generate: $(VENV_FOLDER) ## Create a sample package
@echo "$(GREEN)==> Creating new test package$(RESET)"
rm -rf $(DEVOPS_FOLDER_NAME)
COOKIEPLONE_REPOSITORY=$(BASE_FOLDER) uv run cookieplone $(TEMPLATE) --no-input

.PHONY: test
test: $(VENV_FOLDER)## Create a sample package and tests it
@echo "$(GREEN)==> Creating new test package$(RESET)"
@uv run pytest $(BASE_FOLDER)/tests/templates/devops/ansible

.PHONY: test-pdb
test-pdb: $(VENV_FOLDER)## Stop on the first failed test
@echo "$(GREEN)==> Test template, stop on first error$(RESET)"
@uv run pytest $(BASE_FOLDER)/tests/templates/devops/ansible -x --pdb
38 changes: 38 additions & 0 deletions templates/devops/ansible/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Cookieplone Ansible Playbooks

[![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)
[![Built with Cookieplone](https://img.shields.io/badge/built%20with-Cookiecutter-ff69b4.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/)
[![License](https://img.shields.io/github/license/plone/cookieplone-templates)](../../../LICENSE)
[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

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.


## Prerequisites

- [uv](https://docs.astral.sh/uv/) is the recommended tool for managing Python versions and project dependencies.


### uv

To install uv, use the following command, or visit the [uv installation page](https://docs.astral.sh/uv/getting-started/installation/) for alternative methods.

```shell
curl -LsSf https://astral.sh/uv/install.sh | sh
```


## Generate the playbooks 🎉

```shell
uvx cookieplone devops_ansible
```

## License 📜

This project is licensed under the [MIT License](/LICENSE).


## Let's get building! 🚀

Happy coding!
82 changes: 82 additions & 0 deletions templates/devops/ansible/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"title": "Ansible",
"description": "Ansible setup to manage a Docker Swarm cluster for Plone hosting",
"folder_name": "{{ cookiecutter.title|replace(' ', '')|replace('-', '_')|replace('.', '')|lower }}",
"hostname": "{{ cookiecutter.folder_name }}.example.com",
"hostname_or_ip": "{{ cookiecutter.hostname }}",
"stack_name": "{{ cookiecutter.hostname|replace('.', '-') }}",
"stack_prefix": "{{ cookiecutter.hostname | extract_host | replace('.', '-') }}",
"stack_location": "etc/stacks/{{ cookiecutter.hostname }}.yml",
"author": "Plone Community",
"email": "[email protected]",
"repository_type": ["github", "gitlab_com", "gitlab"],
"organization": "collective",
"repository_url": "https://github.com/{{ cookiecutter.organization }}/{{ cookiecutter.folder_name }}",
"container_registry": ["github", "docker_hub", "gitlab"],
"initialize_git": ["1", "0"],
"__folder_name": "{{ cookiecutter.folder_name }}",
"__project_slug": "{{ cookiecutter.folder_name }}",
"__devops_host": "{{ cookiecutter.hostname | extract_host }}",
"__devops_stack_name": "{{ cookiecutter.stack_name }}",
"__devops_stack_prefix": "{{ cookiecutter.stack_prefix }}",
"__devops_swarm_public_network": "nw-public",
"__devops_swarm_stack_network": "nw-internal",
"__devops_traefik_version": "v2.11",
"__devops_traefik_stack_include_ui": "no",
"__devops_cronjob_version": "1.14",
"__devops_project_user": "500",
"__repository_url": "{{ cookiecutter.repository_url }}",
"__container_registry_prefix": "{{ cookiecutter.container_registry | image_prefix }}",
"__container_image_prefix": "{{ cookiecutter.__container_registry_prefix }}{{ cookiecutter.organization }}/{{ cookiecutter.__project_slug }}",
"__year": "{% now 'local', '%Y' %}",
"__generator_date_long": "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}",
"__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 }}",
"__documentation_starter_format": "1",
"__prompts__": {
"title": "Title",
"description": "A short description for this repository",
"folder_name": "Name of the folder to be created",
"hostname": "Public URL of the server",
"hostname_or_ip": "Address of the server (IP or hostname)",
"repository_type": {
"__prompt__": "Repository Type",
"github": "GitHub",
"gitlab_com": "GitLab.com",
"gitlab": "GitLab Self-Hosted"
},
"organization": "GitHub / GitLab Username or Organization",
"repository_url": "Repository URL",
"container_registry": {
"__prompt__": "Container Registry",
"github": "GitHub Container Registry",
"docker_hub": "Docker Hub",
"gitlab": "GitLab"
},
"stack_name": "Docker Swarm Stack Name",
"stack_prefix": "A 5 to 10 character prefix for the stack",
"author": "Author",
"email": "Author E-mail",
"initialize_git": {
"__prompt__": "Initialize Git Repository?",
"1": "Yes",
"0": "No"
}
},
"_copy_without_render": [
"etc/.ssh",
"etc/base",
"etc/docker",
"etc/keys",
"etc/ssh",
"playbooks",
"requirements",
"tasks"
],
"_extensions": [
"cookieplone.filters.pascal_case",
"cookieplone.filters.extract_host",
"cookieplone.filters.image_prefix"
],
"__cookieplone_repository_path": "",
"__cookieplone_template": ""
}
70 changes: 70 additions & 0 deletions templates/devops/ansible/hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from collections import OrderedDict
from copy import deepcopy
from pathlib import Path
from random import choice
from string import ascii_letters, digits

from cookieplone.utils import console, git

context: OrderedDict = {{cookiecutter}}


def generate_vaultpass(context: OrderedDict, output_dir: Path):
"""Generate a vault password file for Ansible."""
vault_pass_path = output_dir / ".vault_pass"
value = "".join(choice(ascii_letters + digits) for _ in range(32)) # noQA: S311
if not vault_pass_path.exists():
vault_pass_path.write_text(value)
console.print(f"Vault password file created at {vault_pass_path}")
else:
console.print(f"Vault password file already exists at {vault_pass_path}")


def handle_git_initialization(context: OrderedDict, output_dir: Path):
"""Initialize a Git repository for the documentation codebase."""
git.initialize_repository(output_dir)


def main():
"""Final fixes."""
output_dir = Path().cwd()
initialize_git = bool(int(context.get("initialize_git")))
# Cleanup / Git
actions = [
[
generate_vaultpass,
"Generate Ansible vault password",
True,
],
[
handle_git_initialization,
"Initialize Git repository",
initialize_git,
],
]
for func, title, enabled in actions:
if not int(enabled):
continue
new_context = deepcopy(context)
console.print(f" -> {title}")
func(new_context, output_dir)

msg = """
[bold blue]{{ cookiecutter.title }}[/bold blue]

Now, enter the {{ cookiecutter.folder_name }} folder, start using your
playbooks.

Sorry for the convenience,
The Plone Community.
"""
console.panel(
title="New Ansible setup created",
subtitle="",
msg=msg,
url="https://plone.org/",
)


if __name__ == "__main__":
main()
17 changes: 17 additions & 0 deletions templates/devops/ansible/hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Pre generation hook."""

from collections import OrderedDict
from pathlib import Path

output_path = Path().resolve()

context: OrderedDict = {{cookiecutter}}


def main():
"""Validate context."""
pass


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
exclude_paths:
- .cache/ # implicit unless exclude_paths is defined in config
- .github/
- .ansible-lint
- etc/
- keys/
- roles/
- "~/.ansible/roles/"
- "inventory/**/vault.yml"

enable_list:
- fqcn-builtins
- no-log-password # opt-in
- no-same-owner # opt-in

kinds:
- playbook: "./playbooks/**/*.{yml,yaml}"
- tasks: "./tasks/**/*.{yml,yaml}"
- yaml: "./inventory/.{yml,yaml}"

skip_list:
- role-name

# Offline mode disables installation of requirements.yml
offline: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# EditorConfig Configurtaion file, for more details see:
# http://EditorConfig.org
# EditorConfig is a convention description, that could be interpreted
# by multiple editors to enforce common coding conventions for specific
# file types

# top-most EditorConfig file:
# Will ignore other EditorConfig files in Home directory or upper tree level.
root = true


[*] # For All Files
# Unix-style newlines with a newline ending every file
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Set default charset
charset = utf-8
# Indent style default
indent_style = space
# Max Line Length - a hard line wrap, should be disabled
max_line_length = off

[*.{py,cfg,ini}]
# 4 space indentation
indent_size = 4

[*.{html,dtml,pt,zpt,xml,zcml,js,json,less,css,yml,yaml,sh}]
# 2 space indentation
indent_size = 2

[{Makefile,.gitmodules}]
# Tab indentation (no size specified, but view as 4 spaces)
indent_style = tab
indent_size = unset
tab_width = unset
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ANSIBLE_REMOTE_PORT=22
DEPLOY_ENV=prod
DEPLOY_HOST={{ cookiecutter.hostname }}
DEPLOY_PORT=22
DEPLOY_USER=plone
DOCKER_CONFIG=.docker
STACK_NAME={{ cookiecutter.__devops_stack_name }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
.env
.venv
.galaxy_install_info
.Python
.vagrant
.vault_pass.txt
*.retry
etc/keys/*
!etc/keys/.gitkeep
!roles/.gitkeep
roles/*
.install.stamp
Loading