Skip to content

Commit 8922b83

Browse files
committed
(ELI-466) more improvements and added concurrency to test deployments
1 parent c28aaea commit 8922b83

File tree

9 files changed

+222
-63
lines changed

9 files changed

+222
-63
lines changed

.act/act_tests.mk

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
ACT_IMAGE = ghcr.io/nhs-england-tools/github-runner-image:20230909-321fd1e-rt
2+
PREPROD_WORKFLOW = .github/workflows/cicd-4-preprod-deploy.yaml
3+
JOB = metadata
4+
5+
# Usage: make act EVENT=.act/workflow_run_preprod.json TRIGGER_TYPE=workflow_run
6+
act-preprod-deploy:
7+
@if [ -z "$(EVENT)" ]; then \
8+
echo "Usage: make act EVENT=<path-to-event-json>"; \
9+
exit 1; \
10+
fi
11+
@echo "Running act with event file: $(EVENT)"
12+
ACT=true act \
13+
-W $(PREPROD_WORKFLOW) \
14+
--job $(JOB) \
15+
--eventpath $(EVENT) \
16+
-P ubuntu-latest=$(ACT_IMAGE) \
17+
-s GITHUB_TOKEN="$$GITHUB_TOKEN" \
18+
-s GH_TOKEN="$$GITHUB_TOKEN" \
19+
--env GITHUB_REPOSITORY="$$REPO" \
20+
--env PREPROD_WORKFLOW_ID=182365668 \
21+
--env GITHUB_EVENT_NAME=$(TRIGGER_TYPE)
22+
23+
24+
#act-dev-deploy:
25+
# @if [ -z "$(EVENT)" ]; then \
26+
# echo "Usage: make act EVENT=<path-to-event-json>"; \
27+
# exit 1; \
28+
# fi
29+
# @echo "Running act with event file: $(EVENT)"
30+
# ACT=true act \
31+
# -W $(WORKFLOW) \
32+
# --job $(JOB) \
33+
# --eventpath $(EVENT) \
34+
# -P ubuntu-latest=$(ACT_IMAGE) \
35+
# -s GITHUB_TOKEN="$$GITHUB_TOKEN" \
36+
# -s GH_TOKEN="$$GITHUB_TOKEN" \
37+
# --env GITHUB_REPOSITORY="$$REPO" \
38+
# --env DEV_WORKFLOW_ID=143714547 \
39+
# --env GITHUB_EVENT_NAME=$(TRIGGER_TYPE)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"event_name": "workflow_run",
3+
"action": "completed",
4+
"workflow": {
5+
"name": "Test stage",
6+
"path": ".github/workflows/cicd-3-test-deploy.yaml"
7+
},
8+
"workflow_run": {
9+
"id": 18556637650,
10+
"name": "Test stage",
11+
"event": "push",
12+
"status": "completed",
13+
"conclusion": "success",
14+
"head_branch": "main",
15+
"head_sha": "501e5c4c311a765800af527021ed4c6a707c3474"
16+
},
17+
"repository": {
18+
"full_name": "NHSDigital/eligibility-signposting-api",
19+
"name": "eligibility-signposting-api",
20+
"owner": {"login": "NHSDigital"}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"event_name": "workflow_run",
3+
"action": "completed",
4+
"workflow": {
5+
"name": "Test stage",
6+
"path": ".github/workflows/cicd-3-test-deploy.yaml"
7+
},
8+
"workflow_run": {
9+
"id": 18556637650,
10+
"name": "Test stage",
11+
"event": "push",
12+
"status": "completed",
13+
"conclusion": "success",
14+
"head_branch": "main",
15+
"head_sha": "b2675604f38a7c89f3cfe66f8b927b39d7ddedf6"
16+
},
17+
"repository": {
18+
"full_name": "NHSDigital/eligibility-signposting-api",
19+
"name": "eligibility-signposting-api",
20+
"owner": {"login": "NHSDigital"}
21+
}
22+
}

.act/manual_preprod_trigger.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"event_name": "workflow_dispatch",
3+
"ref": "refs/heads/main",
4+
"inputs": {
5+
"ref": "dev-20251015111601",
6+
"release_type": "minor",
7+
"reason": "manual promotion from test to pre-prod"
8+
},
9+
"repository": {
10+
"full_name": "NHSDigital/eligibility-signposting-api",
11+
"name": "eligibility-signposting-api",
12+
"owner": { "login": "NHSDigital" }
13+
},
14+
"sender": {
15+
"login": "tome"
16+
}
17+
}

.github/workflows/cicd-3-test-deploy.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ on:
55
workflows: ["2. CD | Deploy to Dev"]
66
types: [completed]
77

8+
concurrency:
9+
group: test-deployments
10+
cancel-in-progress: false
11+
812
permissions:
913
contents: read
1014
id-token: write
@@ -52,11 +56,6 @@ jobs:
5256
id-token: write
5357
contents: read
5458
steps:
55-
- name: "Acquire deploy lock"
56-
uses: softprops/turnstyle@v3
57-
with:
58-
poll-interval-seconds: 10
59-
6059
- name: "Checkout same commit"
6160
uses: actions/checkout@v5
6261
with:

.github/workflows/cicd-4-preprod-deploy.yml renamed to .github/workflows/cicd-4-preprod-deploy.yaml

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ on:
1717
description: "rc|patch|minor|major"
1818
required: true
1919
default: "rc"
20-
allow_older:
21-
description: "Allow deploying older than latest tested on main?"
22-
required: false
23-
type: choice
24-
options: ["false","true"]
25-
default: "false"
2620
reason:
2721
description: "Why are you doing a manual deployment?"
2822
required: true
@@ -42,37 +36,35 @@ jobs:
4236
latest_sha: ${{ steps.resolver.outputs.latest_test_sha }}
4337
release_type: ${{ steps.release_type.outputs.release_type }}
4438

39+
env:
40+
PREPROD_WORKFLOW_ID: "182365668"
41+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
42+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
4544
steps:
4645
- name: Checkout (full history & tags)
4746
uses: actions/checkout@v4
4847
with: { fetch-depth: 0 }
4948

50-
- name: Announce candidate ref
51-
id: announce
49+
- name: Force HTTPS remote for act
50+
if: env.ACT == 'true'
51+
env:
52+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5254
shell: bash
5355
run: |
5456
set -euo pipefail
55-
git fetch --tags --force --quiet
57+
# Mask the token in logs (defense in depth)
58+
echo "::add-mask::${GITHUB_TOKEN}"
59+
# Option 1: rewrite just 'origin' to tokenized HTTPS
60+
git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git"
61+
# Sanity check (will fail if token is missing/insufficient)
62+
git ls-remote --tags origin >/dev/null
5663
57-
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
58-
HEAD="${{ github.event.workflow_run.head_sha }}"
59-
TAG="$(git tag --points-at "$HEAD" | grep '^dev-' | head -n1 || true)"
60-
else
61-
HEAD="$(git rev-list -n1 "${{ github.event.inputs.ref }}")"
62-
TAG="${{ github.event.inputs.ref }}"
63-
fi
64-
65-
echo "CANDIDATE_TAG=$TAG"
66-
echo "CANDIDATE_SHA=$HEAD"
67-
68-
# Nice UI hints
69-
echo "::notice title=PreProd candidate::dev tag: ${TAG} | sha: ${HEAD}"
70-
{
71-
echo "### PreProd candidate"
72-
echo ""
73-
echo "- dev tag: \`${TAG:-<none>}\`"
74-
echo "- head sha: \`${HEAD:-<none>}\`"
75-
} >> "$GITHUB_STEP_SUMMARY"
64+
- name: Debug event
65+
run: |
66+
echo "GITHUB_EVENT_NAME=${GITHUB_EVENT_NAME}"
67+
echo "Payload:" && cat "$GITHUB_EVENT_PATH" || true
7668
7769
- name: Resolve THIS vs LATEST TEST + stale guard (auto only)
7870
id: resolver
@@ -81,11 +73,10 @@ jobs:
8173
EVENT_NAME: ${{ github.event_name }}
8274
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
8375
MANUAL_REF: ${{ github.event.inputs.ref }}
84-
ALLOW_OLDER: ${{ github.event.inputs.allow_older }}
8576
WORKFLOW_NAME: "3. CD | Deploy to Test"
8677
BRANCH: "main"
8778
LIMIT: "30"
88-
run: python3 scripts/pre-release_resolver.py
79+
run: python3 scripts/workflow/pre-release_resolver.py
8980

9081
- name: Resolve release_type (labels → default rc)
9182
id: release_type
@@ -96,7 +87,7 @@ jobs:
9687
THIS_SHA: ${{ steps.resolver.outputs.this_sha }}
9788
LATEST_TEST_SHA: ${{ steps.resolver.outputs.latest_test_sha }}
9889
MANUAL_RELEASE_TYPE: ${{ github.event.inputs.release_type }}
99-
run: python3 scripts/release_type_resolver.py
90+
run: python3 scripts/workflow/release_type_resolver.py
10091

10192
deploy:
10293
name: "Call base-deploy.yml (PreProd)"

scripts/workflow/ci_utils.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,22 @@
1010
import os
1111
import subprocess
1212
import sys
13-
from typing import List, Optional, NoReturn
13+
from typing import List, Optional, NoReturn, Any
1414

1515
BRANCH = os.getenv("BRANCH", "main")
16+
REPO_FALLBACK = "NHSDigital/eligibility-signposting-api"
1617

1718
def fail(msg: str) -> NoReturn:
1819
print(f"::error::{msg}", file=sys.stderr)
1920
sys.exit(1)
2021

21-
def run(cmd: List[str], *, check: bool = True) -> subprocess.CompletedProcess:
22-
# cp = completed process (will use this to refer)
23-
return subprocess.run(cmd, check=check, capture_output=True, text=True)
22+
def run(cmd: List[str], check: bool = True, **kwargs) -> subprocess.CompletedProcess:
23+
cp = subprocess.run(cmd, check=False, capture_output=True, text=True, **kwargs)
24+
if check and cp.returncode != 0:
25+
raise subprocess.CalledProcessError(
26+
cp.returncode, cmd, output=cp.stdout, stderr=cp.stderr
27+
)
28+
return cp
2429

2530
def ensure_token() -> None:
2631
if not (os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN")):
@@ -36,10 +41,22 @@ def git_ok(args: List[str]) -> bool:
3641
def is_ancestor(older: str, newer: str) -> bool:
3742
return subprocess.run(["git", "merge-base", "--is-ancestor", older, newer]).returncode == 0
3843

39-
def gh_json(args: List[str]) -> list:
40-
cp = run(["gh", *args])
41-
raw = cp.stdout.strip()
42-
return json.loads(raw) if raw else []
44+
def gh_json(args: List[str]) -> Any:
45+
# Map GITHUB_TOKEN -> GH_TOKEN (gh prefers GH_TOKEN)
46+
if "GH_TOKEN" not in os.environ and os.environ.get("GITHUB_TOKEN"):
47+
os.environ["GH_TOKEN"] = os.environ["GITHUB_TOKEN"]
48+
49+
# Ensure repo is explicit (act containers often need this)
50+
repo = os.environ.get("GITHUB_REPOSITORY") or REPO_FALLBACK
51+
base = ["gh", *args]
52+
if "--repo" not in args and "-R" not in args:
53+
base.extend(["--repo", repo])
54+
55+
cp = run(base, check=True)
56+
try:
57+
return json.loads(cp.stdout or "null")
58+
except json.JSONDecodeError as e:
59+
raise RuntimeError(f"Failed to parse gh JSON: {e}\nSTDOUT:\n{cp.stdout}\nSTDERR:\n{cp.stderr}")
4360

4461
def gh_api(path: str, jq: Optional[str] = None) -> List[str]:
4562
"""

0 commit comments

Comments
 (0)