Skip to content

Commit 320deda

Browse files
Feature/ELI-262 regression tests ci integration (#346)
* stashing * trigger for the regression tests now in place * commit workflow to test integration * using a different workflow to test the integration * using a different workflow to test the integration * returned missing steps that are required * asdf version bump * comment out the docker line that asdf does not support * added python to .tool-versions * added python to .tool-versions * added test tag value of main * added the parameters to the run_regression_tests.py call * removed Azure authentication method from run_regression_tests.py * removed Azure authentication method from run_regression_tests.py * force lowercase env name * use PAT token for now * use PAT token for now * Try to use standard GITHUB_TOKEN again * Go back to PAT * script works once the token issue is resolved * try access token directly instead of exporting to variable * rename env variable to TESTS_TOKEN * print out TESTS_TOKEN * use default github token * try PAT again * testing if i can retrieve a different secret * using with: instead of env: * using with: instead of env: * call secret in workflow_call: * inherit secrets * attempt at caching of poetry packages * remove inputs from workflow temporarily to avoid confusion * remove requirement for review to deploy * rename * rename * revert everything back to normal behaviour * comment out deployment of test to test the workflow still works with venv caching * fixed pr_label NoneType error * use the Python version from .tool-versions * use the Python version from .tool-versions. rename to take out the redundant word "run" * take out == * fix the python version grab * restored deploy to test workflow to its former glory
1 parent 0773a4e commit 320deda

File tree

8 files changed

+354
-1
lines changed

8 files changed

+354
-1
lines changed

.github/workflows/cicd-2-publish.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,13 @@ jobs:
129129
Author: "${{ github.actor }}"
130130
title: "Pushed to main"
131131
version: "${{ needs.metadata.outputs.version }}"
132+
133+
134+
regression_tests:
135+
needs: publish
136+
name: Regression Tests
137+
uses: ./.github/workflows/regression-tests.yml
138+
with:
139+
ENVIRONMENT: "dev"
140+
VERSION_NUMBER: "main"
141+
secrets: inherit

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,12 @@ jobs:
128128
echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=api-layer tf-command=apply"
129129
make terraform env=$ENVIRONMENT stack=api-layer tf-command=apply workspace=$WORKSPACE
130130
working-directory: ./infrastructure
131+
132+
regression_tests:
133+
needs: deploy
134+
name: Regression Tests
135+
uses: ./.github/workflows/regression-tests.yml
136+
with:
137+
ENVIRONMENT: "test"
138+
VERSION_NUMBER: "main"
139+
secrets: inherit

.github/workflows/cicd-3-test_auto.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,12 @@ jobs:
102102
echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=api-layer tf-command=apply"
103103
make terraform env=$ENVIRONMENT stack=api-layer tf-command=apply workspace=$WORKSPACE
104104
working-directory: ./infrastructure
105+
106+
regression_tests:
107+
needs: deploy
108+
name: Regression Tests
109+
uses: ./.github/workflows/regression-tests.yml
110+
with:
111+
ENVIRONMENT: "test"
112+
VERSION_NUMBER: "main"
113+
secrets: inherit

.github/workflows/cicd-4a-preprod-deploy.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,12 @@ jobs:
3030
ref: ${{ inputs.ref }}
3131
release_type: ${{ inputs.release_type }}
3232
secrets: inherit
33+
34+
regression_tests:
35+
needs: call
36+
name: Regression Tests
37+
uses: ./.github/workflows/regression-tests.yml
38+
with:
39+
ENVIRONMENT: "pre-prod"
40+
VERSION_NUMBER: "main"
41+
secrets: inherit
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: E2E Regression Tests
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
ENVIRONMENT:
7+
required: true
8+
type: string
9+
VERSION_NUMBER:
10+
required: true
11+
type: string
12+
PRODUCT:
13+
type: string
14+
15+
jobs:
16+
regression-tests:
17+
runs-on: ubuntu-22.04
18+
environment: ${{ inputs.ENVIRONMENT }}
19+
permissions:
20+
id-token: write
21+
contents: write
22+
23+
steps:
24+
- name: Checkout local github actions
25+
uses: actions/checkout@v5
26+
with:
27+
ref: ${{ env.BRANCH_NAME }}
28+
fetch-depth: 0
29+
30+
- name: Cache asdf
31+
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809
32+
with:
33+
path: |
34+
~/.asdf
35+
key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }}
36+
restore-keys: |
37+
${{ runner.os }}-asdf-
38+
39+
- name: Install asdf dependencies in .tool-versions
40+
uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302
41+
with:
42+
asdf_branch: v0.15.0
43+
env:
44+
PYTHON_CONFIGURE_OPTS: --enable-shared
45+
46+
# Use grep to find the line beginning with "python"
47+
- name: Get Python version from file
48+
run: |
49+
PYTHON_VERSION=$(grep "^python" .tool-versions | sed 's/python //g')
50+
echo "PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV
51+
52+
- name: setup python venv
53+
uses: actions/checkout@v5
54+
- uses: actions/setup-python@v6
55+
with:
56+
python-version: '${{ env.PYTHON_VERSION }}'
57+
cache: 'poetry' # caching poetry dependencies
58+
- run: poetry install
59+
60+
- name: Run Regression Testing
61+
working-directory: scripts
62+
if: ${{ (inputs.ENVIRONMENT != 'prod') && (inputs.ENVIRONMENT != 'ref') }}
63+
env:
64+
TARGET_ENVIRONMENT: ${{ inputs.ENVIRONMENT }}
65+
VERSION_NUMBER: ${{ inputs.VERSION_NUMBER }}
66+
TESTS_TOKEN: ${{ secrets.REGRESSION_TESTS_PAT }}
67+
run: |
68+
echo Running regression tests in the "$TARGET_ENVIRONMENT" environment.
69+
poetry run python run_regression_tests.py \
70+
--env="$TARGET_ENVIRONMENT" \
71+
--token="$TESTS_TOKEN" \
72+
--regression_test_repo_tag "$VERSION_NUMBER"

.tool-versions

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ vale 3.11.2
66
poetry 2.1.4
77
act 0.2.77
88
nodejs 22.18.0
9+
python 3.13.5
910

1011
# ==============================================================================
1112
# The section below is reserved for Docker image versions.
1213

1314
# TODO: Move this section - consider using a different file for the repository template dependencies.
14-
docker/ghcr.io/anchore/grype v0.92.2@sha256:651e558f9ba84f2a790b3449c8a57cbbf4f34e004f7d3f14ae8f8cbeede4cd33 # SEE: https://github.com/anchore/grype/pkgs/container/grype
15+
# docker/ghcr.io/anchore/grype v0.92.2@sha256:651e558f9ba84f2a790b3449c8a57cbbf4f34e004f7d3f14ae8f8cbeede4cd33 # SEE: https://github.com/anchore/grype/pkgs/container/grype
1516
# docker/ghcr.io/anchore/syft v0.92.0@sha256:63c60f0a21efb13e80aa1359ab243e49213b6cc2d7e0f8179da38e6913b997e0 # SEE: https://github.com/anchore/syft/pkgs/container/syft
1617
# docker/ghcr.io/gitleaks/gitleaks v8.18.0@sha256:fd2b5cab12b563d2cc538b14631764a1c25577780e3b7dba71657d58da45d9d9 # SEE: https://github.com/gitleaks/gitleaks/pkgs/container/gitleaks
1718
# docker/ghcr.io/igorshubovych/markdownlint-cli v0.37.0@sha256:fb3e79946fce78e1cde84d6798c6c2a55f2de11fc16606a40d49411e281d950d # SEE: https://github.com/igorshubovych/markdownlint-cli/pkgs/container/markdownlint-cli

scripts/check_python_licenses.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# known packages with dual licensing
5+
IGNORE_PACKAGES="PyGithub chardet text-unidecode pyzmq"
6+
LICENSES=$(poetry run pip-licenses --ignore-packages "${IGNORE_PACKAGES}")
7+
INCOMPATIBLE_LIBS=$(echo "$LICENSES" | grep 'GPL' || true)
8+
9+
if [[ -z $INCOMPATIBLE_LIBS ]]; then
10+
exit 0
11+
else
12+
echo "The following libraries were found which are not compatible with this project's license:"
13+
echo "$INCOMPATIBLE_LIBS"
14+
exit 1
15+
fi

scripts/run_regression_tests.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#!/usr/bin/env python
2+
3+
"""
4+
Script to generate user defined unique ID which can be used to
5+
check the status of the regression test run to be reported to the CI.
6+
"""
7+
import argparse
8+
from datetime import datetime, timedelta, timezone
9+
import random
10+
import string
11+
import time
12+
import requests
13+
from requests.auth import HTTPBasicAuth, AuthBase
14+
15+
# This should be set to a known good version of regression test repo
16+
GITHUB_API_URL = "https://api.github.com/repos/NHSDigital/eligibility-signposting-api-regression-tests/actions"
17+
GITHUB_RUN_URL = "https://github.com/NHSDigital/eligibility-signposting-api-regression-tests/actions/runs"
18+
19+
20+
class BearerAuth(AuthBase):
21+
def __init__(self, token):
22+
self.token = token
23+
24+
def __call__(self, r):
25+
r.headers["authorization"] = "Bearer " + self.token
26+
return r
27+
28+
29+
def get_headers():
30+
return {
31+
"Accept": "application/vnd.github+json",
32+
"X-GitHub-Api-Version": "2022-11-28",
33+
}
34+
35+
36+
def generate_unique_run_id(length=15):
37+
return "".join(random.choices(string.ascii_uppercase + string.digits, k=length))
38+
39+
40+
def generate_timestamp():
41+
delta_time = timedelta(minutes=2)
42+
date_time = (datetime.now(timezone.utc) - delta_time).strftime("%Y-%m-%dT%H:%M")
43+
print(f"Generated Date as: {date_time}")
44+
return date_time
45+
46+
47+
def trigger_test_run(
48+
env,
49+
pr_label,
50+
auth_header,
51+
run_id,
52+
regression_test_repo_tag,
53+
regression_test_workflow_tag,
54+
):
55+
body = {
56+
"ref": regression_test_workflow_tag,
57+
"inputs": {
58+
"id": run_id,
59+
"tags": "@regression",
60+
"environment": env.lower(),
61+
"pull_request_id": pr_label,
62+
"github_tag": regression_test_repo_tag,
63+
},
64+
}
65+
66+
response = requests.post(
67+
url=f"{GITHUB_API_URL}/workflows/regression_tests.yml/dispatches",
68+
headers=get_headers(),
69+
auth=auth_header,
70+
json=body,
71+
timeout=120,
72+
)
73+
74+
print(f"Dispatch workflow. Unique workflow identifier: {run_id}")
75+
assert (
76+
response.status_code == 204
77+
), f"Failed to trigger test run. Expected 204, got {response.status_code}. Response: {response.text}"
78+
79+
80+
def get_workflow_runs(auth_header, run_date_filter):
81+
print(f"Getting workflow runs after date: {run_date_filter}")
82+
response = requests.get(
83+
f"{GITHUB_API_URL}/runs?created=%3E{run_date_filter}",
84+
headers=get_headers(),
85+
auth=auth_header,
86+
timeout=120,
87+
)
88+
assert (
89+
response.status_code == 200
90+
), f"Unable to get workflow runs. Expected 200, got {response.status_code}"
91+
return response.json()["workflow_runs"]
92+
93+
94+
def get_jobs_for_workflow(jobs_url, auth_header):
95+
print("Getting jobs for workflow...")
96+
response = requests.get(jobs_url, auth=auth_header, timeout=120)
97+
assert (
98+
response.status_code == 200
99+
), f"Unable to get workflow jobs. Expected 200, got {response.status_code}"
100+
return response.json()["jobs"]
101+
102+
103+
def find_workflow(auth_header, run_id, run_date_filter):
104+
max_attempts = 5
105+
current_attempt = 0
106+
107+
while current_attempt < max_attempts:
108+
time.sleep(10)
109+
current_attempt = current_attempt + 1
110+
print(f"Attempt {current_attempt}")
111+
112+
workflow_runs = get_workflow_runs(auth_header, run_date_filter)
113+
for workflow in workflow_runs:
114+
time.sleep(3)
115+
current_workflow_id = workflow["id"]
116+
jobs_url = workflow["jobs_url"]
117+
118+
list_of_jobs = get_jobs_for_workflow(jobs_url, auth_header)
119+
120+
if list_of_jobs:
121+
job = list_of_jobs[0] # this is fine to get the first job
122+
steps = job["steps"]
123+
124+
if len(steps) >= 2:
125+
third_step = steps[2]
126+
if third_step["name"] == run_id:
127+
print(f"Workflow Job found! Using ID: {current_workflow_id}")
128+
return current_workflow_id
129+
else:
130+
print("Not enough steps have been executed for this run yet...")
131+
else:
132+
print("Jobs for this workflow run haven't populated yet...")
133+
print(
134+
"Processed all available workflows but no jobs were matching the Unique ID were found!"
135+
)
136+
return None
137+
138+
139+
def get_auth_header(token):
140+
return BearerAuth(token)
141+
142+
143+
def get_job(auth_header, workflow_id):
144+
job_request_url = f"{GITHUB_API_URL}/runs/{workflow_id}/jobs"
145+
job_response = requests.get(
146+
job_request_url,
147+
headers=get_headers(),
148+
auth=auth_header,
149+
)
150+
return job_response.json()["jobs"][0]
151+
152+
153+
def check_job(auth_header, workflow_id):
154+
print("Checking job status, please wait...")
155+
print("Current status:", end=" ")
156+
job = get_job(auth_header, workflow_id)
157+
job_status = job["status"]
158+
159+
while job_status != "completed":
160+
print(job_status)
161+
time.sleep(10)
162+
job = get_job(auth_header, workflow_id)
163+
job_status = job["status"]
164+
165+
return job["conclusion"]
166+
167+
168+
def main():
169+
parser = argparse.ArgumentParser()
170+
171+
parser.add_argument(
172+
"--pr_label",
173+
required=False,
174+
default="None",
175+
help="Please provide the PR number.",
176+
)
177+
parser.add_argument(
178+
"--env",
179+
required=True,
180+
help="Please provide the environment you wish to run in.",
181+
)
182+
parser.add_argument(
183+
"--token", required=False, help="Please provide the authentication token."
184+
)
185+
parser.add_argument(
186+
"--regression_test_repo_tag",
187+
required=True,
188+
help="Please provide the tag to run regression tests from.",
189+
)
190+
parser.add_argument(
191+
"--regression_test_workflow_tag",
192+
required=False,
193+
default="main",
194+
help="Please provide the tag to for the run_regression_test workflow.",
195+
)
196+
197+
arguments = parser.parse_args()
198+
199+
print(f"pr_label: {arguments.pr_label}")
200+
print(f"env: {arguments.env}")
201+
print(f"regression_tests_repo_tag: {arguments.regression_test_repo_tag}")
202+
print(f"regression_test_workflow_tag: {arguments.regression_test_workflow_tag}")
203+
204+
run_id = generate_unique_run_id()
205+
run_date_filter = generate_timestamp()
206+
auth_header = get_auth_header(arguments.token)
207+
208+
pr_label = arguments.pr_label.lower()
209+
trigger_test_run(
210+
arguments.env,
211+
pr_label,
212+
auth_header,
213+
run_id,
214+
arguments.regression_test_repo_tag,
215+
arguments.regression_test_workflow_tag,
216+
)
217+
218+
workflow_id = find_workflow(auth_header, run_id, run_date_filter)
219+
print(f"See {GITHUB_RUN_URL}/{workflow_id}/ for run details")
220+
job_status = check_job(auth_header, workflow_id)
221+
if job_status != "success":
222+
print("The regressions test step failed! There are likely test failures.")
223+
raise SystemError("Regression test failed")
224+
print("Success!")
225+
226+
227+
if __name__ == "__main__":
228+
main()

0 commit comments

Comments
 (0)