Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
57 changes: 57 additions & 0 deletions .github/workflows/update-roles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Update Ansible roles

on:
schedule:
- cron: "17 2 * * *"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

concurrency:
group: update-roles
cancel-in-progress: false

jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install dependencies
run: python -m pip install PyYAML

- name: Check pull request token
env:
ROLE_SYNC_TOKEN: ${{ secrets.ROLE_SYNC_TOKEN }}
run: |
if [ -z "$ROLE_SYNC_TOKEN" ]; then
echo "::error::Set the ROLE_SYNC_TOKEN repository secret before running this workflow."
exit 1
fi

- name: Sync roles from GitHub
run: python scripts/update_roles.py sync
env:
GITHUB_TOKEN: ${{ secrets.ROLE_SYNC_TOKEN }}

- name: Open or update role list pull request
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.ROLE_SYNC_TOKEN }}
branch: automation/update-ansible-roles
delete-branch: true
base: master
add-paths: _data/ansible_roles.yml
commit-message: Update Ansible role list
title: Update Ansible role list
body: |
Refresh `_data/ansible_roles.yml` from public `buluma/ansible-role-*` repositories.
Archived repositories are excluded.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ python scripts/update_roles.py remove role_name
python scripts/update_roles.py list
```

### Syncing roles from GitHub

```bash
python scripts/update_roles.py sync
```

This syncs public `buluma/ansible-role-*` repositories into the role list and
skips archived repositories. The **Update Ansible roles** workflow runs daily
or can be started manually from the Actions tab; it opens or updates a PR for
the generated change.

The workflow needs a repository secret named `ROLE_SYNC_TOKEN`: a fine-grained
token with **Contents: read and write** and **Pull requests: read and write**
access to this repository. The token lets the generated PR trigger the normal
PR CI workflow.

The table is automatically regenerated when the site builds. No manual editing
of `index.md` is needed for role changes.

Expand Down
4 changes: 4 additions & 0 deletions _data/ansible_roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
- cron
- crowd
- crun
- cve_2024_3094
- daemonize
- datadog
- debug
Expand Down Expand Up @@ -114,6 +115,7 @@
- kibana
- kubectl
- kubernetes
- lemp-stack
- libvirt
- local
- locale
Expand Down Expand Up @@ -171,6 +173,7 @@
- rclone
- reboot
- redis
- redmine-1
- remi
- repo_epel
- restore
Expand All @@ -187,6 +190,7 @@
- sensu
- service
- setuptools
- skeleton
- snmpd
- solr
- sonarqube
Expand Down
66 changes: 66 additions & 0 deletions scripts/update_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
python scripts/update_roles.py add <role_name> [<role_name> ...]
python scripts/update_roles.py remove <role_name> [<role_name> ...]
python scripts/update_roles.py list
python scripts/update_roles.py sync
python scripts/update_roles.py regenerate

The role list is stored in _data/ansible_roles.yml and is used by index.md
Expand All @@ -14,10 +15,19 @@

import sys
import os
import json
import urllib.error
import urllib.request
import yaml

ROLES_FILE = "_data/ansible_roles.yml"
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
GITHUB_OWNER = "buluma"
ROLE_REPOSITORY_PREFIX = "ansible-role-"
ROLE_NAME_OVERRIDES = {
"digitalocean_agent": "digitalocean-agent",
"netiq_sentinel_syslog_event_source": "netiq-sentinel-syslog-event-source",
}


def load_roles():
Expand Down Expand Up @@ -91,6 +101,59 @@ def cmd_list():
print(f" - {role}")


def cmd_sync():
"""Synchronize roles from non-archived public GitHub role repositories."""
roles = set()
page = 1
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")

while True:
url = (
f"https://api.github.com/users/{GITHUB_OWNER}/repos"
f"?type=public&per_page=100&page={page}"
)
request = urllib.request.Request(
url,
headers={
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
)
if token:
request.add_header("Authorization", f"Bearer {token}")

try:
with urllib.request.urlopen(request, timeout=30) as response:
repositories = json.load(response)
except urllib.error.HTTPError as error:
raise RuntimeError(
f"GitHub API request failed with HTTP {error.code} on page {page}"
) from error

if not isinstance(repositories, list):
raise RuntimeError("GitHub API returned an unexpected repository response")
if not repositories:
break

for repository in repositories:
name = repository.get("name", "")
if repository.get("archived") or not name.startswith(ROLE_REPOSITORY_PREFIX):
continue
repository_role = name.removeprefix(ROLE_REPOSITORY_PREFIX)
roles.add(ROLE_NAME_OVERRIDES.get(repository_role, repository_role))

page += 1

if not roles:
raise RuntimeError(
f"No active {ROLE_REPOSITORY_PREFIX} repositories found for {GITHUB_OWNER}; "
"refusing to replace the current role list"
)

save_roles(roles)
print(f"Synchronized roles from {GITHUB_OWNER}'s active public repositories")


def cmd_regenerate():
"""
Regenerate ansible_roles.yml from index.md Liquid template output.
Expand Down Expand Up @@ -169,6 +232,9 @@ def main():
elif command == "list":
cmd_list()

elif command == "sync":
cmd_sync()

elif command == "regenerate":
cmd_regenerate()

Expand Down
Loading