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
23 changes: 23 additions & 0 deletions .github/cookiecutter-migrate.template.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
""" # noqa: E501

import hashlib
import json
import os
import subprocess
import tempfile
Expand All @@ -36,6 +37,28 @@ def main() -> None:
print("=" * 72)


def read_project_type() -> str | None:
"""Read the cookiecutter project type from the replay file."""
replay_path = Path(".cookiecutter-replay.json")
if not replay_path.exists():
return None

try:
data = json.loads(replay_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None

cookiecutter_data = data.get("cookiecutter")
if not isinstance(cookiecutter_data, dict):
return None

project_type = cookiecutter_data.get("type")
if not isinstance(project_type, str):
return None

return project_type


def apply_patch(patch_content: str) -> None:
"""Apply a patch using the patch utility."""
subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/auto-dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:
jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Auto-merge Dependabot PR
uses: frequenz-floss/dependabot-auto-approve@3cad5f42e79296505473325ac6636be897c8b8a1 # v1.3.2
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
needs: ["nox"]
# We skip this job only if nox was also skipped
if: always() && needs.nox.result != 'skipped'
runs-on: ubuntu-24.04
runs-on: ubuntu-slim
env:
DEPS_RESULT: ${{ needs.nox.result }}
steps:
Expand Down Expand Up @@ -161,7 +161,7 @@ jobs:
needs: ["test-installation"]
# We skip this job only if test-installation was also skipped
if: always() && needs.test-installation.result != 'skipped'
runs-on: ubuntu-24.04
runs-on: ubuntu-slim
env:
DEPS_RESULT: ${{ needs.test-installation.result }}
steps:
Expand Down Expand Up @@ -276,7 +276,7 @@ jobs:
# discussions to create the release announcement in the discussion forums
contents: write
discussions: write
runs-on: ubuntu-24.04
runs-on: ubuntu-slim
steps:
- name: Download distribution files
uses: actions/download-artifact@v6
Expand Down Expand Up @@ -318,7 +318,7 @@ jobs:
publish-to-pypi:
name: Publish packages to PyPI
needs: ["create-github-release"]
runs-on: ubuntu-24.04
runs-on: ubuntu-slim
permissions:
# For trusted publishing. See:
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dco-merge-queue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

jobs:
DCO:
runs-on: ubuntu-latest
runs-on: ubuntu-slim
if: ${{ github.actor != 'dependabot[bot]' }}
steps:
- run: echo "This DCO job runs on merge_queue event and doesn't check PR contents"
2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Labeler
# XXX: !!! SECURITY WARNING !!!
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release-notes-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
jobs:
check-release-notes:
name: Check release notes are updated
runs-on: ubuntu-latest
runs-on: ubuntu-slim
permissions:
pull-requests: read
steps:
Expand Down
13 changes: 10 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
This release migrates lightweight GitHub Actions workflow jobs to use the new cost-effective `ubuntu-slim` runner.

## Upgrading

Expand All @@ -13,7 +13,7 @@
All upgrading should be done via the migration script or regenerating the templates.

```bash
curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/v0.12/cookiecutter/migrate.py | python3
curl -sSL https://raw.githubusercontent.com/frequenz-floss/frequenz-repo-config-python/v0.14.0/cookiecutter/migrate.py | python3
```

But you might still need to adapt your code:
Expand All @@ -26,7 +26,14 @@ But you might still need to adapt your code:

### Cookiecutter template

<!-- Here new features for cookiecutter specifically -->
- Migrated lightweight workflow jobs to use the new `ubuntu-slim` runner for cost savings.
The following jobs now use `ubuntu-slim`:
- `ci.yaml`: `protolint`, `nox-all`, `test-installation-all`, `create-github-release`, `publish-to-pypi`
- `ci-pr.yaml`: `protolint`
- `auto-dependabot.yaml`: `auto-merge`
- `release-notes-check.yml`: `check-release-notes`
- `dco-merge-queue.yml`: `DCO`
- `labeler.yml`: `Label`

## Bug Fixes

Expand Down
184 changes: 184 additions & 0 deletions cookiecutter/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
""" # noqa: E501

import hashlib
import json
import os
import subprocess
import tempfile
Expand All @@ -32,10 +33,193 @@ def main() -> None:
"""Run the migration steps."""
# Add a separation line like this one after each migration step.
print("=" * 72)
print("Migrating workflows to use ubuntu-slim runner for lightweight jobs...")
migrate_to_ubuntu_slim()
print("=" * 72)
print("Migration script finished. Remember to follow any manual instructions.")
print("=" * 72)


def migrate_to_ubuntu_slim() -> None:
"""Migrate workflow files to use ubuntu-slim runner for lightweight jobs.

This updates several workflow files to use the new cost-effective ubuntu-slim
runner for jobs that are lightweight (e.g., labeling, release notes checks,
simple API calls).
"""
workflows_dir = Path(".github") / "workflows"
project_type = read_project_type()
include_protolint = project_type == "api"
if project_type is None:
include_protolint = True
manual_step(
"Unable to detect the cookiecutter project type from "
".cookiecutter-replay.json; protolint migrations will run anyway. "
"Please verify any protolint jobs and keep them only if this is an api "
"project."
)

migrations = {
"ci.yaml": [
{
"job": "nox-all",
"old": (
" if: always() && needs.nox.result != 'skipped'\n"
" runs-on: ubuntu-24.04"
),
"new": (
" if: always() && needs.nox.result != 'skipped'\n"
" runs-on: ubuntu-slim"
),
},
{
"job": "test-installation-all",
"old": (
" if: always() && needs.test-installation.result != 'skipped'\n"
" runs-on: ubuntu-24.04"
),
"new": (
" if: always() && needs.test-installation.result != 'skipped'\n"
" runs-on: ubuntu-slim"
),
},
{
"job": "create-github-release",
"old": " discussions: write\n runs-on: ubuntu-24.04",
"new": " discussions: write\n runs-on: ubuntu-slim",
},
{
"job": "publish-to-pypi",
"old": ' needs: ["create-github-release"]\n runs-on: ubuntu-24.04',
"new": ' needs: ["create-github-release"]\n runs-on: ubuntu-slim',
},
],
"auto-dependabot.yaml": [
{
"job": "auto-merge",
"old": (
" auto-merge:\n"
" if: github.actor == 'dependabot[bot]'\n"
" runs-on: ubuntu-latest"
),
"new": (
" auto-merge:\n"
" if: github.actor == 'dependabot[bot]'\n"
" runs-on: ubuntu-slim"
),
}
],
"release-notes-check.yml": [
{
"job": "check-release-notes",
"old": (
" check-release-notes:\n"
" name: Check release notes are updated\n"
" runs-on: ubuntu-latest"
),
"new": (
" check-release-notes:\n"
" name: Check release notes are updated\n"
" runs-on: ubuntu-slim"
),
}
],
"dco-merge-queue.yml": [
{
"job": "DCO",
"old": "jobs:\n DCO:\n runs-on: ubuntu-latest",
"new": "jobs:\n DCO:\n runs-on: ubuntu-slim",
}
],
"labeler.yml": [
{
"job": "Label",
"old": (
" Label:\n"
" permissions:\n"
" contents: read\n"
" pull-requests: write\n"
" runs-on: ubuntu-latest"
),
"new": (
" Label:\n"
" permissions:\n"
" contents: read\n"
" pull-requests: write\n"
" runs-on: ubuntu-slim"
),
}
],
}
if include_protolint:
protolint_rule = {
"job": "protolint",
"old": (
" protolint:\n"
" name: Check proto files with protolint\n"
" runs-on: ubuntu-24.04"
),
"new": (
" protolint:\n"
" name: Check proto files with protolint\n"
" runs-on: ubuntu-slim"
),
}
migrations.setdefault("ci-pr.yaml", []).append(protolint_rule)
migrations.setdefault("ci.yaml", []).append(protolint_rule)

for filename, rules in migrations.items():
filepath = workflows_dir / filename
if not filepath.exists():
print(f" Skipping {filepath} (file not found)")
continue

for rule in rules:
job = rule["job"]
old = rule["old"]
new = rule["new"]
try:
content = filepath.read_text(encoding="utf-8")
except FileNotFoundError:
continue

if old in content:
replace_file_contents_atomically(filepath, old, new)
print(f" Updated {filepath}: migrated job {job} to ubuntu-slim")
continue

if new in content:
print(f" Skipped {filepath}: already uses ubuntu-slim for job {job}")
continue

manual_step(
f" Pattern not found in {filepath}: please switch job {job} to use "
"`runs-on: ubuntu-slim` where appropriate."
)


def read_project_type() -> str | None:
"""Read the cookiecutter project type from the replay file."""
replay_path = Path(".cookiecutter-replay.json")
if not replay_path.exists():
return None

try:
data = json.loads(replay_path.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
return None

cookiecutter_data = data.get("cookiecutter")
if not isinstance(cookiecutter_data, dict):
return None

project_type = cookiecutter_data.get("type")
if not isinstance(project_type, str):
return None

return project_type


def apply_patch(patch_content: str) -> None:
"""Apply a patch using the patch utility."""
subprocess.run(["patch", "-p1"], input=patch_content.encode(), check=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:
jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
runs-on: ubuntu-slim
steps:
- name: Auto-merge Dependabot PR
uses: frequenz-floss/dependabot-auto-approve@3cad5f42e79296505473325ac6636be897c8b8a1 # v1.3.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
{% endraw %}{% if cookiecutter.type == "api" %}{% raw -%}
protolint:
name: Check proto files with protolint
runs-on: ubuntu-24.04
runs-on: ubuntu-slim

steps:
- name: Setup Git
Expand Down
Loading