Skip to content
Open
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
51 changes: 51 additions & 0 deletions .github/workflows/api.tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Backend API Tests
on:
workflow_call:
inputs:
tests_path:
required: true
type: string
continue_on_error:
required: false
type: boolean
default: false
image:
required: true
type: string

env:
TESTER_IMAGE: 027159582536.dkr.ecr.eu-west-1.amazonaws.com/backend-api-tests:stable
SECRET_ID: arn:aws:secretsmanager:eu-west-1:027159582536:secret:github-actions/repos/backend-api-tests-P0XRKc

jobs:
wait_for_deploy:
name: Wait for deploy
uses: ./.github/workflows/wait-for-deployment.yml
secrets: inherit
with:
image: ${{ inputs.image }}

api-tests:
name: Run test suite
needs:
- wait_for_deploy
runs-on: [self-hosted, large-runner]
steps:
- name: Generate AWS config
run: |
mkdir -p ${HOME}/.aws
cat << EOF > ${HOME}/.aws/config
[profile non-prod]
web_identity_token_file = /var/run/secrets/eks.amazonaws.com/serviceaccount/token
role_arn = ${{ secrets.NON_PROD_EKS_ROLE_ARN }}
EOF

- name: Get required secrets
run: |
aws secretsmanager get-secret-value --secret-id=${{ env.SECRET_ID }} --profile non-prod | \
jq -r '.SecretString | fromjson? | to_entries | map("\(.key)=\(.value|tostring)") | .[]' > .env

- name: Run tests in docker
continue-on-error: ${{ inputs.continue_on_error }}
run: |
docker run --rm --env-file=.env ${{ env.TESTER_IMAGE }} bundle exec rspec ${{ inputs.tests_path }}
71 changes: 71 additions & 0 deletions .github/workflows/devops.ai-assisted-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: AI-assisted label

on:
pull_request:
types: [opened, synchronize]

jobs:
label-ai-assisted:
runs-on: [self-hosted, standard-runner]
steps:
- name: Check for AI co-authorship and label PR
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = context.payload.pull_request.number;
const labelName = 'ai-assisted';

// Pattern matches Co-Authored-By with an @anthropic.com email
const aiCoAuthorPattern = /Co-Authored-By:.*@anthropic\.com/i;

// Check PR body
const prBody = context.payload.pull_request.body || '';
if (aiCoAuthorPattern.test(prBody)) {
core.info('AI co-author detected in PR body');
await addLabel();
return;
}

// Check commit messages
const commits = await github.paginate(
github.rest.pulls.listCommits,
{ owner, repo, pull_number: prNumber, per_page: 100 }
);

const found = commits.some(c => aiCoAuthorPattern.test(c.commit.message));
if (found) {
core.info('AI co-author detected in commit message(s)');
await addLabel();
return;
}

core.info('No AI co-author detected');

async function addLabel() {
try {
await github.rest.issues.getLabel({ owner, repo, name: labelName });
} catch (e) {
if (e.status === 404) {
core.info(`Creating label "${labelName}"`);
await github.rest.issues.createLabel({
owner,
repo,
name: labelName,
color: '9C27B0',
description: 'PR contains AI-assisted code'
});
} else {
throw e;
}
}

await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [labelName]
});
core.info(`Label "${labelName}" added to PR #${prNumber}`);
}
45 changes: 45 additions & 0 deletions .github/workflows/devops.cancel-ongoing-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: DevOps Workflow called to cancel ongoing ArgoCD application syncs for PR-environment before publishing a new image
on:
workflow_call: {}

jobs:
cancel-ongoing-sync:
runs-on: [self-hosted, standard-runner]
steps:
- name: generate aws config
run: |
mkdir -p ${HOME}/.aws
cat << EOF > ${HOME}/.aws/config
[profile non-prod]
web_identity_token_file = /var/run/secrets/eks.amazonaws.com/serviceaccount/token
role_arn = ${{ secrets.NON_PROD_EKS_ROLE_ARN }}
EOF

- name: generate kubeconfig
run: |
export EKS_CLUSTER_NAME=$(aws ssm get-parameter --name /devops/ci-target/cluster --query 'Parameter.Value' --output text --profile non-prod)
aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --profile non-prod
env -i PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/runner kubectl version

- name: get git variables
run: |
set -euo pipefail
REPOSITORY=${{github.repository}}
REPOSITORY_OWNER=${{github.repository_owner}}
REPOSITORY_NAME=${REPOSITORY##${REPOSITORY_OWNER}/}
echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> $GITHUB_ENV
GITHUB_REF=${{github.ref}}
PULL_NUMBER=$(echo "$GITHUB_REF" | awk -F / '{print $3}')
echo "PULL_NUMBER=${PULL_NUMBER}" >> $GITHUB_ENV

- name: cancel ongoing syncs
run: |
env -i PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/runner kubectl -n argocd get applications.argoproj.io | awk '/^${{env.REPOSITORY_NAME}}-.*-${{env.PULL_NUMBER}}/{print $1}' | while read app
do
ENV=$(echo ${app} | sed 's/^${{env.REPOSITORY_NAME}}-\(.*\)-${{env.PULL_NUMBER}}/\1/')
echo "::group::Cancelling sync in ${ENV}"
env -i PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/runner kubectl -n argocd patch app ${app} -p '{"operation":null,"spec":{"syncPolicy":{"automated":null}}}' --type merge && echo "Succeeded" || echo "Failed"
echo "::endgroup::"
done


32 changes: 32 additions & 0 deletions .github/workflows/devops.check-code-changes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: DevOps Workflow called to check if there were changes outside kubernetes configuration
on:
workflow_call: {}
jobs:
check-code-changes:
runs-on: [self-hosted, standard-runner]
outputs:
result: ${{ steps.code_changes.outputs.RESULT }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check if code changes
id: code_changes
run: |
set -x
# filter directories where no application code lives
directories=("kube" "\.github")
pattern=""
for dir in "${directories[@]}"; do
pattern+="-e ^$dir/ "
done

if (git diff --name-only origin/master | grep -v $pattern); then
echo "Code changes"
echo "RESULT=true" >> $GITHUB_OUTPUT
else
echo "No code changes"
echo "RESULT=false" >> $GITHUB_OUTPUT
fi

83 changes: 83 additions & 0 deletions .github/workflows/devops.ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Workflow called to run reusable CI steps
on:
workflow_call:
inputs:
dockerfile:
required: false
type: string
default: Dockerfile
image:
required: true
type: string
ci_image:
required: false
type: string
default: ci:test
command:
required: true
type: string
target:
required: false
type: string
default: test
github_user:
required: false
type: string
default: fm-cicd
skip_ci:
required: false
type: boolean
default: false
secrets: {}

env:
DOCKER_BUILDKIT: 1
GITHUB_USER: ${{ inputs.github_user }}
GITHUB_PASSWORD: ${{ secrets.API_TOKEN_GITHUB }}

jobs:
ci:
if: ${{ !inputs.skip_ci }}
timeout-minutes: 60
runs-on: [self-hosted, large-runner]
steps:
- name: checkout repository
uses: actions/checkout@v6

- name: pull built image
run: |
docker pull ${{ inputs.image }}

- name: build ci image
run: |
docker build --progress=plain --build-arg BUILDKIT_INLINE_CACHE=1 \
--secret id=github_user,env=GITHUB_USER \
--secret id=github_password,env=GITHUB_PASSWORD \
--cache-from ${{ inputs.image }} \
--target ${{ inputs.target }} \
-t ${{ inputs.ci_image }} \
-f ${{ inputs.dockerfile }} .

- name: prepare environment
run: |
mkdir -p ./coverage ./tmp ./log
chown -R 1000:1000 ./coverage ./tmp ./log

- name: run command
shell: bash
run: ${{ inputs.command }}

- name: check coverage data
run: |
COVERAGE_GENERATED=$([ "$(ls -A ./coverage/)" ] && echo true || echo false)
echo "COVERAGE_GENERATED=${COVERAGE_GENERATED}" >> $GITHUB_ENV
COMMAND_SHA=$(echo "${{ inputs.command }}" | sha256sum | cut -c1-8)
echo "COMMAND_SHA=${COMMAND_SHA}" >> $GITHUB_ENV

- name: upload coverage files
if: ${{ env.COVERAGE_GENERATED == 'true' }}
uses: actions/upload-artifact@v4
with:
name: coverage-${{ env.COMMAND_SHA }}
path: ./coverage
retention-days: 7
Loading