Skip to content

Commit e7dd918

Browse files
committed
Merge branch 'main' into sphinx-docs
2 parents 4498563 + 2a0aa74 commit e7dd918

9 files changed

Lines changed: 365 additions & 0 deletions

File tree

.github/workflows/cla-check.yaml

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: CLA
2+
on:
3+
workflow_call:
4+
inputs:
5+
runner:
6+
description: 'The runner to use for the job'
7+
required: false
8+
type: string
9+
default: 'ubuntu-24.04'
10+
11+
permissions:
12+
contents: read
13+
pull-requests: write # Required to add labels and comments
14+
15+
jobs:
16+
check-cla:
17+
runs-on: ${{ inputs.runner }}
18+
steps:
19+
# --- Step 1: Check Base Branch ---
20+
- name: Checkout base branch and check for contributor status
21+
uses: actions/checkout@v5
22+
with:
23+
token: ${{ secrets.GITHUB_TOKEN }}
24+
ref: ${{ github.event.pull_request.base.sha }}
25+
26+
- name: Determine if contributor exists in base
27+
id: check_contributor_base
28+
run: |
29+
AUTHOR="${{ github.event.pull_request.user.login }}"
30+
if [ -f "CONTRIBUTORS" ]; then
31+
if grep -q "^$AUTHOR" CONTRIBUTORS; then
32+
echo "on_base=true" >> $GITHUB_OUTPUT
33+
echo "🎉 $AUTHOR has already signed the CLA on base branch."
34+
else
35+
echo "on_base=false" >> $GITHUB_OUTPUT
36+
echo "⚠️ $AUTHOR not on base. Proceeding to check PR branch."
37+
fi
38+
else
39+
# If CONTRIBUTORS file doesn't exist, we must check PR branch
40+
echo "on_base=undefined" >> $GITHUB_OUTPUT
41+
echo "🔴 CONTRIBUTORS file does not exist on base. Proceeding to check PR branch."
42+
fi
43+
44+
# --- Step 2: Check PR Branch ---
45+
- name: Checkout PR branch and check for contributor status
46+
# Only run if contributor wasn't found on the base branch
47+
if: steps.check_contributor_base.outputs.on_base != 'true'
48+
uses: actions/checkout@v5
49+
with:
50+
token: ${{ secrets.GITHUB_TOKEN }}
51+
ref: ${{ github.event.pull_request.head.sha }}
52+
53+
- name: Determine if contributor exists in PR branch
54+
id: check_contributor_pr
55+
# Only run if contributor wasn't found on the base branch
56+
if: steps.check_contributor_base.outputs.on_base != 'true'
57+
run: |
58+
AUTHOR="${{ github.event.pull_request.user.login }}"
59+
if grep -q "^$AUTHOR" CONTRIBUTORS; then
60+
echo "signed=true" >> $GITHUB_OUTPUT
61+
echo "✅ $AUTHOR has updated their CLA signature in CONTRIBUTORS file."
62+
else
63+
echo "signed=false" >> $GITHUB_OUTPUT
64+
echo "❌ $AUTHOR has not signed the CLA."
65+
fi
66+
67+
# --- Step 3: Manage PR Labels, Comments, and Final Status (Consolidated) ---
68+
- name: Manage CLA Status, Labels, and Comments
69+
uses: actions/github-script@v8
70+
# Using 'always()' here so this step runs regardless of previous
71+
# success/failure to manage labels correctly
72+
if: always()
73+
with:
74+
github-token: ${{ secrets.GITHUB_TOKEN }}
75+
script: |
76+
const signedOnBase = '${{ steps.check_contributor_base.outputs.on_base }}' === 'true';
77+
// Default signed status is false if the second check wasn't run
78+
const signedOnPr = '${{ steps.check_contributor_pr.outputs.signed }}' === 'true';
79+
const cla_met = signedOnBase || signedOnPr;
80+
const issue_number = context.issue.number;
81+
const owner = context.repo.owner;
82+
const repo = context.repo.repo;
83+
const author = context.payload.pull_request.user.login;
84+
85+
// Helper function to create or update a label with a specific color
86+
async function ensureLabel(name, color, description) {
87+
try {
88+
await github.rest.issues.updateLabel({ owner, repo, name, color, description });
89+
console.log(`Updated label: ${name} with color ${color}`);
90+
} catch (error) {
91+
// If update fails (label doesn't exist), create it
92+
await github.rest.issues.createLabel({ owner, repo, name, color, description });
93+
console.log(`Created new label: ${name} with color ${color}`);
94+
}
95+
}
96+
97+
// Define desired colors and descriptions for consistency
98+
const COLOR_SIGNED = '0052cc'; // Blue
99+
const COLOR_REQUIRED = 'b60205'; // Red
100+
101+
console.log(`CLA Met: ${cla_met} (Base: ${signedOnBase}, PR: ${signedOnPr})`);
102+
103+
if (cla_met) {
104+
await ensureLabel('cla-signed', COLOR_SIGNED, 'This contributor has signed the CLA.');
105+
console.log('✅ CLA condition met. Removing required label and adding signed label.');
106+
// Use Promise.allSettled for robust label management
107+
await Promise.allSettled([
108+
github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'cla-required' }),
109+
]);
110+
if ( signedOnBase === false ) {
111+
await Promise.allSettled([
112+
github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['cla-signed'] })
113+
]);
114+
}
115+
116+
} else {
117+
await ensureLabel('cla-required', COLOR_REQUIRED, 'CLA signature is required for this PR.');
118+
console.log('❌ CLA condition NOT met. Adding required label and ensuring signed label is absent.');
119+
120+
// Ensure labels are correct
121+
await Promise.allSettled([
122+
github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'cla-signed' }),
123+
github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['cla-required'] })
124+
]);
125+
126+
// Post CLA comment
127+
const commentBody = `Hello @${author}! 👋\n\nThank you for your contribution. Since this is your first time contributing to this repository, we ask that you sign our Contributor Licence Agreement (CLA).\n\n📄 [You can read the CLA here](https://github.com/MetOffice/simulation-systems/blob/github_wps/Momentum-CLA.md).\n\nTo agree to the CLA, please add your details (**GitHub username**, real name, organisation, email, and date) to the _CONTRIBUTORS_ file (create one, if required) in the development branch for this PR. After signing the CLA, you won't need to do this again for future PRs.`;
128+
129+
await github.rest.issues.createComment({ owner, repo, issue_number, body: commentBody });
130+
131+
// Fail the GitHub Action run
132+
console.error("⚠️ Please add yourself to the CONTRIBUTORS file to sign the CLA.");
133+
process.exit(1);
134+
}

.github/workflows/umdp3_fixer.yaml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
name: Fix UMDP3
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
runner:
8+
description: 'The runner to use for the job'
9+
required: false
10+
type: string
11+
default: 'ubuntu-24.04'
12+
timeout:
13+
description: 'The time limit for the workflow, default 10 minutes'
14+
required: false
15+
type: number
16+
default: 10
17+
18+
permissions: read-all
19+
20+
jobs:
21+
umdp3_fixer:
22+
runs-on: ${{ inputs.runner }}
23+
timeout-minutes: ${{ inputs.timeout }}
24+
25+
steps:
26+
- name: Checkout Branch
27+
uses: actions/checkout@v4
28+
with:
29+
path: pr_branch
30+
token: ${{ secrets.GITHUB_TOKEN }}
31+
- name: Checkout SimSys_Scripts
32+
uses: actions/checkout@v4
33+
with:
34+
repository: MetOffice/SimSys_Scripts
35+
sparse-checkout: umdp3_fixer
36+
path: SimSys_Scripts
37+
- name: Set up Python 3.14
38+
uses: actions/setup-python@v5
39+
with:
40+
python-version: 3.14
41+
- name: Run UMDP3 Fixer
42+
working-directory: ./SimSys_Scripts/umdp3_fixer
43+
run: |
44+
python rosestem_branch_checker.py --source "${GITHUB_WORKSPACE}/pr_branch" --fixer_source . --col 80

.github/workflows/validate.yaml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: CI
2+
# This workflow is used for validating code quality on growss PRs.
3+
4+
on:
5+
pull_request:
6+
types: [opened, synchronize, reopened]
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: ${{ github.ref }}
11+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
12+
13+
permissions: read-all
14+
15+
jobs:
16+
17+
validate:
18+
name: QC
19+
runs-on: ubuntu-24.04
20+
timeout-minutes: 5
21+
22+
env:
23+
UV_CACHE_DIR: /tmp/.uv-cache
24+
UV_PYTHON: "3.14"
25+
26+
steps:
27+
- uses: actions/checkout@v5
28+
29+
- name: Setup uv
30+
uses: astral-sh/setup-uv@v7
31+
with:
32+
python-version: ${{ env.UV_PYTHON }}
33+
enable-cache: true
34+
35+
- name: Restore uv cache
36+
uses: actions/cache@v4
37+
with:
38+
path: ${{ env.UV_CACHE_DIR }}
39+
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
40+
restore-keys: |
41+
uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
42+
uv-${{ runner.os }}
43+
44+
- name: Install dependencies
45+
run: uv sync
46+
47+
- name: YAML Lint
48+
run: uv run yamllint -s .
49+
50+
- name: Python Lint
51+
if: always()
52+
run: uv run ruff check --respect-gitignore .
53+
- name: Python Format Check
54+
if: always()
55+
run: uv run ruff format --check --diff -s --respect-gitignore .
56+
57+
- name: Shell Check
58+
if: always()
59+
shell: bash
60+
run: |
61+
mapfile -d '' potential_files < <(
62+
find . -type f \( -name "*.*sh" -o ! -name "*.*" \) \
63+
-not -path "*.git/*" -not -path "*.venv/*" -print0
64+
)
65+
if [ ${#potential_files[@]} -eq 0 ]; then
66+
echo "No shell scripts to check."
67+
exit 0
68+
fi
69+
printf "%s\0" "${potential_files[@]}" \
70+
| xargs -0 file | grep "shell script" | cut -d: -f1 \
71+
| xargs -r uv run shellcheck -S warning \
72+
&& echo "All checks passed!"
73+
continue-on-error: true
74+
75+
- name: Minimize uv cache
76+
run: uv cache prune --ci

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Compiled Dynamic libraries
2+
*.so
3+
*.dylib
4+
*.dll
5+
6+
# Compiled Static libraries
7+
*.lai
8+
*.la
9+
*.a
10+
*.lib
11+
12+
# Executables
13+
*.exe
14+
*.out
15+
*.app
16+
17+
# uv
18+
.venv
19+
uv.lock
20+
21+
# Jetbrains
22+
.idea
23+
24+
# vscode
25+
.vscode
26+
27+
# Backup files
28+
.#*
29+
\#*#
30+
*~
31+
32+
# Python
33+
*.py[cod]
34+
*.pyo
35+
__pycache__
36+
.pytest_cache
37+
*.coverage

.yamllint

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
extends: default
3+
4+
ignore: |
5+
.venv/
6+
.svn/
7+
8+
rules:
9+
document-start: disable
10+
line-length: disable
11+
truthy: disable

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
Placeholder for a collection of
44
[reusable workflows](https://docs.github.com/en/actions/learn-github-actions/reusing-workflows)
55
for the Met Office Simulation Systems Repositories.
6+
7+
## Notes for contributors
8+
9+
When contributing to this repository, your changes will be automatically
10+
validated for YAML, Python, and Shell script correctness. We recommend manually
11+
checking your files before opening a pull request. For example, you can run
12+
`yamllint workflow-file.yaml` to verify YAML syntax.

cla_check/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# CLA Check
2+
3+
A reusable action to test that a contributor has agreed to a CLA by adding their
4+
username to the CONTRIBUTORS file in that repository.
5+
6+
## Usage
7+
8+
```yaml
9+
name: CLA Check
10+
11+
on:
12+
pull_request_target:
13+
14+
jobs:
15+
cla_check:
16+
uses: MetOffice/growss/.github/workflows/cla-check.yaml@main
17+
18+
# Optional
19+
with:
20+
runner: 'ubuntu-24.04'
21+
```

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[project]
2+
name = "growss"
3+
version = "0.1.0"
4+
description = "GitHub Reusable Workflows for Simulation Systems"
5+
readme = "README.md"
6+
requires-python = ">=3.12.9"
7+
dependencies = [
8+
"ruff==0.14.4",
9+
"shellcheck-py==0.11.0.1",
10+
"yamllint==1.37.1",
11+
]

umdp3_fixer_action/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# UMDP3 Fixer Action
2+
3+
This action runs the UMDP3 fixer script stored in
4+
[MetOffice/SimSys_Scripts](https://github.com/MetOffice/SimSys_Scripts).
5+
6+
## Usage
7+
8+
```yaml
9+
name: umdp3 Fixer
10+
11+
on:
12+
pull_request:
13+
types: [opened, synchronize, reopened]
14+
workflow_dispatch:
15+
16+
jobs:
17+
umdp3_fixer:
18+
uses: MetOffice/growss/.github/workflows/umdp3_fixer.yaml@main
19+
20+
# Optional non default inputs
21+
with:
22+
runner: 'ubuntu-latest'
23+
timeout: 15
24+
```

0 commit comments

Comments
 (0)