From 4a498eaa1bfa37dfccb288c85b0ec64c9963879d Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Thu, 15 Feb 2024 11:17:18 +0100 Subject: [PATCH] initial commit --- .gitignore | 11 ++++ .later.yml | 15 ++++++ .markdownlint.yml | 7 +++ .prettierignore | 1 + .woodpecker/docs.yaml | 47 +++++++++++++++++ .woodpecker/lint.yaml | 30 +++++++++++ .woodpecker/notify.yml | 26 ++++++++++ .woodpecker/test.yaml | 25 +++++++++ LICENSE | 21 ++++++++ README.md | 1 + defaults/main.yml | 52 +++++++++++++++++++ handlers/main.yml | 7 +++ meta/main.yml | 25 +++++++++ molecule/default/converge.yml | 8 +++ molecule/default/molecule.yml | 17 ++++++ molecule/default/prepare.yml | 11 ++++ molecule/default/tests/test_default.py | 12 +++++ molecule/requirements.yml | 9 ++++ pyproject.toml | 19 +++++++ tasks/instance.yml | 49 +++++++++++++++++ tasks/main.yml | 7 +++ templates/etc/sysconfig/woodpecker_agent.j2 | 21 ++++++++ .../system/woodpecker_agent.service.j2 | 49 +++++++++++++++++ 23 files changed, 470 insertions(+) create mode 100644 .gitignore create mode 100644 .later.yml create mode 100644 .markdownlint.yml create mode 100644 .prettierignore create mode 100644 .woodpecker/docs.yaml create mode 100644 .woodpecker/lint.yaml create mode 100644 .woodpecker/notify.yml create mode 100644 .woodpecker/test.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/prepare.yml create mode 100644 molecule/default/tests/test_default.py create mode 100644 molecule/requirements.yml create mode 100644 pyproject.toml create mode 100644 tasks/instance.yml create mode 100644 tasks/main.yml create mode 100644 templates/etc/sysconfig/woodpecker_agent.j2 create mode 100644 templates/etc/systemd/system/woodpecker_agent.service.j2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d97b7cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# ---> Ansible +*.retry +plugins +library + +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + diff --git a/.later.yml b/.later.yml new file mode 100644 index 0000000..2703cb9 --- /dev/null +++ b/.later.yml @@ -0,0 +1,15 @@ +--- +ansible: + custom_modules: + - iptables_raw + - openssl_pkcs12 + - proxmox_kvm + - ucr + - corenetworks_dns + - corenetworks_token + +rules: + exclude_files: + - "LICENSE*" + - "**/*.md" + - "**/*.ini" diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..da116c7 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,7 @@ +--- +default: True +MD013: False +MD041: False +MD024: False +MD004: + style: dash diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6b1d0bf --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +LICENSE diff --git a/.woodpecker/docs.yaml b/.woodpecker/docs.yaml new file mode 100644 index 0000000..f053ca8 --- /dev/null +++ b/.woodpecker/docs.yaml @@ -0,0 +1,47 @@ +--- +when: + - event: [pull_request] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +steps: + - name: generate + image: quay.io/thegeeklab/ansible-doctor + environment: + ANSIBLE_DOCTOR_EXCLUDE_FILES: molecule/ + ANSIBLE_DOCTOR_FORCE_OVERWRITE: "true" + ANSIBLE_DOCTOR_LOG_LEVEL: INFO + ANSIBLE_DOCTOR_ROLE_NAME: ${CI_REPO_NAME} + ANSIBLE_DOCTOR_TEMPLATE: readme + + - name: format + image: quay.io/thegeeklab/alpine-tools + commands: + - prettier -w README.md + + - name: diff + image: quay.io/thegeeklab/alpine-tools + commands: + - git diff --color=always README.md + + - name: publish + image: quay.io/thegeeklab/wp-git-action + settings: + action: + - commit + - push + author_email: ci-bot@rknet.org + author_name: ci-bot + branch: main + message: "[skip ci] automated docs update" + netrc_machine: gitea.rknet.org + netrc_password: + from_secret: gitea_token + when: + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +depends_on: + - test diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml new file mode 100644 index 0000000..ca4facd --- /dev/null +++ b/.woodpecker/lint.yaml @@ -0,0 +1,30 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +steps: + - name: ansible-later + image: quay.io/thegeeklab/ansible-later:4 + commands: + - ansible-later + environment: + FORCE_COLOR: "1" + + - name: python-format + image: docker.io/python:3.12 + commands: + - pip install -qq ruff + - ruff format --check --diff . + environment: + PY_COLORS: "1" + + - name: python-lint + image: docker.io/python:3.12 + commands: + - pip install -qq ruff + - ruff . + environment: + PY_COLORS: "1" diff --git a/.woodpecker/notify.yml b/.woodpecker/notify.yml new file mode 100644 index 0000000..9957125 --- /dev/null +++ b/.woodpecker/notify.yml @@ -0,0 +1,26 @@ +--- +when: + - event: [tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +runs_on: [success, failure] + +steps: + - name: matrix + image: quay.io/thegeeklab/wp-matrix + settings: + homeserver: + from_secret: matrix_homeserver + password: + from_secret: matrix_password + roomid: + from_secret: matrix_roomid + username: + from_secret: matrix_username + when: + - status: [success, failure] + +depends_on: + - docs diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml new file mode 100644 index 0000000..a4991f7 --- /dev/null +++ b/.woodpecker/test.yaml @@ -0,0 +1,25 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +variables: + - &molecule_base + image: quay.io/thegeeklab/molecule:6 + group: molecule + secrets: + - source: molecule_hcloud_token + target: HCLOUD_TOKEN + environment: + PY_COLORS: "1" + +steps: + - name: molecule-default + <<: *molecule_base + commands: + - molecule test -s default + +depends_on: + - lint diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3812eb4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Robert Kaussow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e870208 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# xoxys.woodpecker_agent diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..7cf3805 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,52 @@ +--- +woodpecker_agent_image: "docker.io/woodpeckerci/woodpecker-agent:latest" + +woodpecker_agent_service_started: True +woodpecker_agent_name: agent-1 + +# @var woodpecker_agent_volumes:description: Define required docker volumes. +woodpecker_agent_volumes: + - name: /var/run/docker.sock + dest: /var/run/docker.sock + type: bind + +# @var woodpecker_agent_network:description: Use a custom docker network for grafana. +# @var woodpecker_agent_network:value: $ "_unset_" + +# @var woodpecker_agent_exposed_ports:description: Ports you want to publish outside of docker. +woodpecker_agent_exposed_ports: [] + +woodpecker_agent_cap_add: [] +woodpecker_agent_cap_drop: [] + +woodpecker_agent_docker_args: + - --privileged + - --pids-limit=-1 + +woodpecker_agent_woodpecker_server: localhost:9000 +woodpecker_agent_max_workflows: 1 +woodpecker_agent_log_level: info + +# @var woodpecker_agent_filter_labels:description: Configures labels to filter pipelines that can be handled by this agent. +# @var woodpecker_agent_filter_labels:example: > +# woodpecker_agent_filter_labels: +# - key=value +# - second-key=* +# @end +woodpecker_agent_filter_labels: [] + +# @var woodpecker_agent_env:description: Custom environment variables to set for the agent container. +# @var woodpecker_agent_env:example: > +# woodpecker_agent_env: +# - name: WOODPECKER_GRPC_SECURE +# value: True +# @end +woodpecker_agent_env: [] + +# @var woodpecker_agent_instances:description: List of multiple agents to deploy. +# @var woodpecker_agent_instances:example: > +# woodpecker_agent_instances: +# - name: agent-1 +# filter_labels: [] +# @end +woodpecker_agent_instances: [] diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..234ba23 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Restart agent + service: + name: "woodpecker-agent-{{ __woodpecker_agent_name }}" + state: "{{ woodpecker_agent_service_started | ternary('restarted', 'stopped', 'restarted') }}" + daemon_reload: True + listen: __woodpecker_agent_restart diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..65ed1cc --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + # @meta author:value: [Robert Kaussow](https://gitea.rknet.org/xoxys) + author: "Robert Kaussow " + namespace: xoxys + role_name: woodpecker_agent + # @meta description: > + # [![Build Status](https://ci.rknet.org/api/badges/ansible/xoxys.woodpecker_agent/status.svg)](https://ci.rknet.org/repos/ansible/xoxys.woodpecker_agent) + # [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?label=license)](https://gitea.rknet.org/ansible/xoxys.woodpecker_agent/src/branch/main/LICENSE) + # + # Setup Woodpecker CI docker agent + # @end + description: Setup Woodpecker CI docker agent + license: MIT + min_ansible_version: "2.10" + platforms: + - name: EL + versions: + - "9" + galaxy_tags: [] +dependencies: [] +collections: + - xoxys.general + - community.general + - community.docker diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..041f8a9 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + vars: + woodpecker_agent_service_started: False + roles: + - role: xoxys.dockerengine + - role: xoxys.woodpecker_agent diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..d1e0b4f --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,17 @@ +--- +driver: + name: molecule_hetznercloud +dependency: + name: galaxy + options: + role-file: molecule/requirements.yml + requirements-file: molecule/requirements.yml +platforms: + - name: "rocky9-woodpecker-agent" + server_type: "cx11" + image: "rocky-9" +provisioner: + name: ansible + log: False +verifier: + name: testinfra diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..0df1d77 --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,11 @@ +--- +- name: Prepare + hosts: all + gather_facts: False + tasks: + - name: Bootstrap Python for Ansible + ansible.builtin.raw: | + command -v python3 python || + ((test -e /usr/bin/apt && (apt -y update && apt install -y python-minimal)) || + echo "Warning: Python not boostrapped due to unknown platform.") + changed_when: False diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py new file mode 100644 index 0000000..7066f50 --- /dev/null +++ b/molecule/default/tests/test_default.py @@ -0,0 +1,12 @@ +import os + +import testinfra.utils.ansible_runner + +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ["MOLECULE_INVENTORY_FILE"] +).get_hosts("all") + + +def test_service_file(host): + f = host.file("/etc/systemd/system/woodpecker-agent-agent-1.service") + assert f.exists diff --git a/molecule/requirements.yml b/molecule/requirements.yml new file mode 100644 index 0000000..5300ab9 --- /dev/null +++ b/molecule/requirements.yml @@ -0,0 +1,9 @@ +--- +collections: + - name: community.docker + +roles: + - src: https://gitea.rknet.org/ansible/xoxys.dockerengine.git + name: xoxys.dockerengine + scm: git + version: main diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d0f36cd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.ruff] +exclude = [".git","__pycache__"] + +line-length = 99 +indent-width = 4 + +ignore = ["W191", "E111", "E114", "E117", "S101", "S105"] +select = ["F", "E", "I", "W", "S"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "lf" + +[tool.pytest.ini_options] +filterwarnings = [ + "ignore::FutureWarning", + "ignore::DeprecationWarning", +] diff --git a/tasks/instance.yml b/tasks/instance.yml new file mode 100644 index 0000000..23feffe --- /dev/null +++ b/tasks/instance.yml @@ -0,0 +1,49 @@ +--- +- name: Register agent metadata + set_fact: + __woodpecker_agent_name: "{{ inst.name | default(woodpecker_agent_name) | regex_replace('[^a-z0-9]+', '-', ignorecase=True) }}" + __woodpecker_agent_volumes: "{{ inst.volumes | default(woodpecker_agent_volumes) }}" + __woodpecker_agent_cap_add: "{{ inst.cap_add | default(woodpecker_agent_cap_add) }}" + __woodpecker_agent_cap_drop: "{{ inst.cap_drop | default(woodpecker_agent_cap_drop) }}" + __woodpecker_agent_env: "{{ inst.env | default (woodpecker_agent_env) }}" + __woodpecker_agent_filter_labels: "{{ inst.filter_labels | default(woodpecker_agent_filter_labels) }}" + +- name: Create container volumes + community.docker.docker_volume: + name: "{{ item.name }}" + driver_options: "{{ item.options | default(omit) }}" + state: "{{ item.state | default('present') }}" + loop: "{{ __woodpecker_agent_volumes }}" + loop_control: + label: "{{ item.name }}" + when: item.type | default("volume") | lower == "volume" + register: __woodpecker_agent_volumes_raw + +- name: Register container volumes map + ansible.builtin.set_fact: + __woodpecker_agent_volumes_map: "{{ __woodpecker_agent_volumes_raw.results | json_query('[].volume') | items2dict(key_name='Name', value_name='Mountpoint') }}" + +- name: Deploy env file + ansible.builtin.template: + src: etc/sysconfig/woodpecker_agent.j2 + dest: "/etc/sysconfig/woodpecker-agent-{{ __woodpecker_agent_name }}" + owner: root + group: root + mode: "0600" + notify: __woodpecker_agent_restart + +- name: Create container specs + ansible.builtin.template: + src: etc/systemd/system/woodpecker_agent.service.j2 + dest: "/etc/systemd/system/woodpecker-agent-{{ __woodpecker_agent_name }}.service" + owner: root + group: root + mode: "0640" + notify: __woodpecker_agent_restart + +- name: Ensure service state + ansible.builtin.service: + name: "woodpecker-agent-{{ __woodpecker_agent_name }}.service" + state: "{{ woodpecker_agent_service_started | ternary('started', 'stopped', 'started') }}" + daemon_reload: True + enabled: True diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..b290b53 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- include_tasks: + file: instance.yml + loop: "{{ woodpecker_agent_instances }}" + loop_control: + loop_var: inst + no_log: True diff --git a/templates/etc/sysconfig/woodpecker_agent.j2 b/templates/etc/sysconfig/woodpecker_agent.j2 new file mode 100644 index 0000000..d9d25d9 --- /dev/null +++ b/templates/etc/sysconfig/woodpecker_agent.j2 @@ -0,0 +1,21 @@ +#jinja2: lstrip_blocks: True +{{ ansible_managed | comment }} +WOODPECKER_SERVER={{ inst.woodpecker_server | default(woodpecker_agent_woodpecker_server) }} + +WOODPECKER_LOG_LEVEL={{ inst.log_level | default(woodpecker_agent_log_level) }} +WOODPECKER_MAX_WORKFLOWS={{ inst.max_workflows | default(woodpecker_agent_max_workflows) }} +{% if __woodpecker_agent_filter_labels | length > 0 %} +WOODPECKER_FILTER_LABELS={{ __woodpecker_agent_filter_labels | join(",") }} +{% endif %} +{% if __woodpecker_agent_env | length > 0 %} + +{% for item in __woodpecker_agent_env %} +{{ item.name | upper }}={{ item.value }} +{% endfor %} +{% for item in inst.env_extra %} +{{ item.name | upper }}={{ item.value }} +{% endfor %} + +{% endif %} +WOODPECKER_BACKEND=docker +DOCKER_HOST=unix:///var/run/docker.sock diff --git a/templates/etc/systemd/system/woodpecker_agent.service.j2 b/templates/etc/systemd/system/woodpecker_agent.service.j2 new file mode 100644 index 0000000..41093d6 --- /dev/null +++ b/templates/etc/systemd/system/woodpecker_agent.service.j2 @@ -0,0 +1,49 @@ +#jinja2: lstrip_blocks: True +{{ ansible_managed | comment }} +[Unit] +Description=Woodpecker Agent ({{ __woodpecker_agent_name }}) + +Wants=docker.service +After=docker.service + +[Service] +Restart=on-failure +RestartSec=5s +EnvironmentFile=/etc/environment + +ExecStop=/usr/bin/docker pull {{ woodpecker_agent_image }} +ExecStop=/bin/sh -c '/usr/bin/docker ps | /bin/grep %p 1> /dev/null && /usr/bin/docker stop %p || true' + +ExecStartPre=/bin/sh -c '/usr/bin/docker ps | /bin/grep %p 1> /dev/null && /usr/bin/docker kill %p || true' +ExecStartPre=/bin/sh -c '/usr/bin/docker ps -a | /bin/grep %p 1> /dev/null && /usr/bin/docker rm %p || true' +ExecStartPre=/usr/bin/docker pull {{ woodpecker_agent_image }} + +ExecStart=/usr/bin/docker run --rm \ + --name %p \ + --hostname %p \ + --env-file /etc/sysconfig/woodpecker-agent-{{ __woodpecker_agent_name }} \ + {% if woodpecker_agent_network is defined and woodpecker_agent_network %} + --network {{ woodpecker_agent_network }} \ + {% endif %} + {% if woodpecker_agent_cap_add | length > 0 %} + --cap-add {{ woodpecker_agent_cap_add | join (" ") }} + {% endif %} + {% if woodpecker_agent_cap_drop | length > 0 %} + --cap-drop {{ woodpecker_agent_cap_drop | join (" ") }} + {% endif %} + {% for volume in woodpecker_agent_volumes %} + --mount '{{ "type=bind," if (volume.type | default(False) | lower == "bind") else "" }}src={{ volume.name }},target={{ volume.dest }}' \ + {% endfor %} + {% for port in woodpecker_agent_exposed_ports %} + --publish {{ port }} \ + {% endfor %} + {% for item in inst.docker_args | default(woodpecker_agent_docker_args) %} + {{ item }} \ + {% endfor %} + --health-interval 5s \ + --health-retries 5 \ + --health-timeout 10s \ + {{ woodpecker_agent_image }} + +[Install] +WantedBy=multi-user.target