Skip to content
Open
4 changes: 2 additions & 2 deletions .github/TEMPLATES/secret-mapping-opencrvs-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ traefik-cert:
type: tls
namespace: traefik
data:
- SSL_CRT: cert
- SSL_KEY: key
- SSL_CRT: tls.crt
- SSL_KEY: tls.key

# If backup is configured then workflow will use GitHub secrets for current environment
# If restore is configured then workflow will fetch secrets from source environment (usually production)
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
options:
- ""
env:
DEPENDENCIES_CHART_VERSION: "v1.9.12"
DEPENDENCIES_CHART_VERSION: "1.9.14"
TRAEFIK_CHART_VERSION: "39.0.0"
jobs:
approve:
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
--create-namespace \
-f environments/${ENV}/traefik/values.yaml \
-f environments/${ENV}/traefik/values.override.yaml
kubectl scale deployment traefik --replicas=1 --namespace traefik
kubectl scale deployment traefik --replicas=1 --namespace traefik || true

- name: Install OpenCRVS dependencies
id: deploy
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/deploy-opencrvs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ on:
core-image-tag:
description: "Tag of the core image"
required: true
default: "v1.9.12"
default: "1.9.14"
countryconfig-image-tag:
description: "Tag of the countryconfig image"
required: true
default: "v1.9.12"
default: "1.9.14"
data-seed-enabled:
description: "Data seeding during deployment"
required: false
Expand All @@ -36,7 +36,7 @@ on:

env:
# Assuming chart version matches core image tag
OPENCRVS_CHART_VERSION: "v1.9.12"
OPENCRVS_CHART_VERSION: "1.9.14"
jobs:
approve:
environment: ${{ inputs.environment }}
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
--atomic \
--wait \
--wait-for-jobs \
--set image.tag="$CORE_IMAGE_TAG" \
--set platform.tag="$CORE_IMAGE_TAG" \
--set countryconfig.image.tag="$COUNTRYCONFIG_IMAGE_TAG" \
--set countryconfig.image.name="$COUNTRYCONFIG_IMAGE_NAME" \
--set data_seed.env.ACTIVATE_USERS="${{ vars.ACTIVATE_USERS || 'false' }}" \
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/github-to-k8s-sync-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ jobs:
echo BACKUP_HOST=$BACKUP_HOST >> $env_file
echo BACKUP_HOST_PRIVATE_KEY=$BACKUP_HOST_PRIVATE_KEY >> $env_file
echo BACKUP_SERVER_USER=$BACKUP_SERVER_USER >> $env_file
echo POSTGRES_USER=$POSTGRES_USER >> $env_file
echo POSTGRES_PASSWORD=$POSTGRES_PASSWORD >> $env_file
grep BACKUP_HOST_PRIVATE_KEY $env_file || echo "No restore encryption passphrase to add"

- name: Preprocess mapping into Secret YAMLs
Expand Down Expand Up @@ -293,6 +291,7 @@ jobs:
echo "🚀 Applying $f within namespace"
kubectl get namespace $namespace >/dev/null 2>&1 || kubectl create namespace $namespace
yq eval . "$f" > /dev/null && echo "✅ $f is valid YAML" || echo "❌ $f has YAML errors" && cat "$f"
kubectl delete --ignore-not-found -n $namespace -f "$f"
kubectl apply -n $namespace -f "$f"
echo "Dropping last-applied-configuration annotation for secret $(basename ${f/.yaml/})"
kubectl annotate secret -n $namespace "$(basename ${f/.yaml/})" kubectl.kubernetes.io/last-applied-configuration-
Expand Down
216 changes: 216 additions & 0 deletions .github/workflows/init-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
# OpenCRVS is also distributed under the terms of the Civil Registration
# & Healthcare Disclaimer located at http://opencrvs.org/license.
#
# Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.

# Gitflow release initialisation workflow.
#
# Triggered by opencrvs-core's init-release workflow (or manually) with a
# semver version string (e.g. 1.7.2).
#
# What this workflow does, in order:
# 1. Derives the base branch:
# - patch release (z > 0) → release/<x>.<y>.<z-1>
# - minor/major (z = 0) → develop
# 2. Creates a release branch (release/<version>) from the base branch.
# 3. Bumps Chart.yaml and prepends a CHANGELOG section, then pushes
# both commits to the release branch.
# 4. Opens two PRs — both PRs already contain the version-bump commits:
# a. release/<version> → master (the release PR)
# b. release/<version> → develop (merge-back PR, keeps develop in sync)
# 5. Creates a draft GitHub release and immediately deletes the backing tag
# (the tag will be re-created by the publish workflow after the PR merges).
name: 11. Release - Start a new release
on:
workflow_dispatch:
inputs:
version:
type: string
required: true
description: 'Version to release'

jobs:
release_workflow:
runs-on: ubuntu-latest
steps:
# Derive base_branch and target_branch from the version number.
# - patch (z > 0): base = release/<x>.<y>.<z-1>, target = master
# - minor/major (z = 0): base = develop, target = master
- name: Determine the Base Branch
id: get_base_branch
run: |
version="${{ inputs.version }}"

if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version was provided as input: $version" >&2
echo "Please follow semver format for versioning, e/g 1.7.2"
exit 1
fi

IFS='.' read -r x y z <<< "$version"
if [ "$z" = "0" ]; then
base_branch="develop"
else
previous_version="$((x)).$((y)).$((z-1))"
base_branch="release/$previous_version"
fi
echo "target_branch=master" >> $GITHUB_OUTPUT
echo "base_branch=$base_branch" >> $GITHUB_OUTPUT

# Full history is required so yarn can traverse the commit graph.
- name: Checkout ${{ steps.get_base_branch.outputs.base_branch }}
uses: actions/checkout@v4
with:
ref: ${{ steps.get_base_branch.outputs.base_branch }}
fetch-depth: 0
token: ${{ secrets.INFRASTRUCTURE_WORKFLOW_TOKEN }}

- name: Check if release branch already exists
id: check_branch
run: |
if git ls-remote --exit-code --heads origin "release/${{ inputs.version }}" > /dev/null 2>&1; then
echo "Branch release/${{ inputs.version }} already exists, skipping creation."
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi

# Create the release branch, bump helm chart version, and update the
# CHANGELOG — then push. The PRs are opened only after these commits
# exist on the remote, so both PRs immediately include the version-bump
# commits.
# Skipped if the release branch was already created by a previous run.
- name: Create release branch and update version numbers and CHANGELOG
if: steps.check_branch.outputs.exists == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

git checkout -b "release/${{ inputs.version }}"
export VERSION="${{ inputs.version }}"
# Bump helm chart version: sed instead of yq to preserve formatting and comments.
sed -i \
-e "s/^\(\s*OPENCRVS_CHART_VERSION:\s*\).*/\1\"$VERSION\"/" \
-e '/core-image-tag:/,/default:/ s/default: ".*"/default: "'"$VERSION"'"/' \
-e '/countryconfig-image-tag:/,/default:/ s/default: ".*"/default: "'"$VERSION"'"/' \
.github/workflows/deploy-opencrvs.yml
sed -i \
-e "s/^\(\s*DEPENDENCIES_CHART_VERSION:\s*\).*/\1\"$VERSION\"/" \
.github/workflows/deploy-dependencies.yml
git add .
git commit -m "chore: update version to ${{ inputs.version }}"

# Prepend a new "Release Candidate" section to the top of CHANGELOG.md.
# sed removes the existing "# Changelog" header (lines 1-2) so we can
# rewrite it with the new section underneath.
sed -i '1,2d' CHANGELOG.md
cp CHANGELOG.md COPY_CHANGELOG.md
{
echo "# Changelog"
echo ""
echo "## ${{ inputs.version }} Release Candidate"
echo ""
cat COPY_CHANGELOG.md
} > CHANGELOG.md
rm COPY_CHANGELOG.md
git add CHANGELOG.md
git commit -m "docs: update changelog for ${{ inputs.version }} release candidate"

git push origin "release/${{ inputs.version }}"

# A dedicated merge-back branch is used for the develop PR so that any
# conflict-resolution commits do not land on the release branch itself.
- name: Create merge-back branch for develop PR
run: |
MERGEBACK_BRANCH="merge-back/release-${{ inputs.version }}-to-develop"
if git ls-remote --exit-code --heads origin "$MERGEBACK_BRANCH" > /dev/null 2>&1; then
echo "Branch $MERGEBACK_BRANCH already exists, skipping."
else
git fetch origin "release/${{ inputs.version }}"
git checkout -b "$MERGEBACK_BRANCH" "origin/release/${{ inputs.version }}"
git push origin "$MERGEBACK_BRANCH"
fi

# Two PRs are always created:
#
# PR 1 — release/<version> → master
# The release PR. Merging this ships the version to production.
#
# PR 2 — merge-back/release-<version>-to-develop → develop
# Uses a dedicated branch so conflict-resolution commits stay off
# the release branch.
# - z=0 (base is develop): carries only the two bump commits, clean diff.
# - z>0 (base is a prior release branch): also backports any patch fixes.
# Each PR is checked individually — one may already exist while the other
# does not, so both are always evaluated.
- name: Create Pull Requests
run: |
PR_BRANCH="release/${{ inputs.version }}"
MERGEBACK_BRANCH="merge-back/release-${{ inputs.version }}-to-develop"
TARGET_BRANCH="${{ steps.get_base_branch.outputs.target_branch }}"

if [ -f .github/TEMPLATES/RELEASE_PR_TEMPLATE.md ]; then
BODY=$(cat .github/TEMPLATES/RELEASE_PR_TEMPLATE.md)
else
BODY=""
fi

EXISTING_MASTER_PR=$(gh pr list --head "$PR_BRANCH" --base "$TARGET_BRANCH" --state open --json number --jq '.[0].number // ""')
if [ -z "$EXISTING_MASTER_PR" ]; then
gh pr create \
--base "$TARGET_BRANCH" \
--head "$PR_BRANCH" \
--title "Release v${{ inputs.version }}" \
--body "$BODY"
else
echo "PR $PR_BRANCH → $TARGET_BRANCH already exists (#$EXISTING_MASTER_PR), skipping."
fi

EXISTING_DEVELOP_PR=$(gh pr list --head "$MERGEBACK_BRANCH" --base "develop" --state open --json number --jq '.[0].number // ""')
if [ -z "$EXISTING_DEVELOP_PR" ]; then
gh pr create \
--base "develop" \
--head "$MERGEBACK_BRANCH" \
--title "chore: merge release v${{ inputs.version }} back to develop" \
--body "Merge release branch back to develop to include version bump and changelog updates."
else
echo "PR $MERGEBACK_BRANCH → develop already exists (#$EXISTING_DEVELOP_PR), skipping."
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Create a draft release so the release notes page exists immediately.
# The tag is deleted right after: the publish workflow re-creates it when
# the release PR is merged into master, which is the canonical tag point.
# Skipped if a release for this version already exists from a previous run.
- name: Create a draft release
run: |
TAG_NAME="v${{ inputs.version }}"
if gh release view "$TAG_NAME" > /dev/null 2>&1; then
echo "Release $TAG_NAME already exists, skipping."
else
TITLE="OpenCRVS - $TAG_NAME"
NOTES="Release notes for version ${{ inputs.version }}"
git tag "$TAG_NAME"
git push origin "$TAG_NAME"
gh release create "$TAG_NAME" \
--title "$TITLE" \
--notes "$NOTES" \
--draft
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Delete the tag
run: |
TAG_NAME="v${{ inputs.version }}"
if git ls-remote --exit-code --tags origin "$TAG_NAME" > /dev/null 2>&1; then
git tag -d "$TAG_NAME" 2>/dev/null || true
git push origin ":refs/tags/$TAG_NAME"
else
echo "Tag $TAG_NAME does not exist on remote, skipping."
fi
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Changelog

## 1.9.14 Release Candidate

Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ redis:
postgres:
auth_mode: auto
host: postgres-0.postgres.opencrvs-deps-{{env}}.svc.cluster.local

imagePullSecrets:
# Default value for credentials created while yarn environment:init
- name: dockerhub-credentials
platform:
imagePullSecrets:
# Default value for credentials created while yarn environment:init
- name: dockerhub-credentials

countryconfig:
secrets:
Expand Down
1 change: 1 addition & 0 deletions infrastructure/server-setup/tasks/all/tools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- zip
- python3-pip
- python3-kubernetes
- pigz

- name: 'Ensure python-3pexpect is absent'
apt:
Expand Down
45 changes: 45 additions & 0 deletions scripts/tools/update-traefik-cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
# OpenCRVS is also distributed under the terms of the Civil Registration
# & Healthcare Disclaimer located at http://opencrvs.org/license.
#
# Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS

set -e

CERT="<PATH_TO_CERT>"
KEY="<PATH_TO_KEY>"
NAMESPACE="traefik"
SECRET_NAME="traefik-cert"
BACKUP_SECRET=$(mktemp)

NEW_HASH=$(sha256sum "$CERT" | cut -d' ' -f1)
OLD_HASH=$(kubectl get secret "$SECRET_NAME" --ignore-not-found -n "$NAMESPACE" -o jsonpath='{.data.tls\.crt}' | base64 -d | sha256sum | cut -d' ' -f1)
if [ "$NEW_HASH" == "$OLD_HASH" ]; then
logger -t traefik-cert "No changes..."
exit 0
fi

logger -t traefik-cert "Starting TLS secret update on secret $SECRET_NAME in namespace ${NAMESPACE}"

kubectl get secret "$SECRET_NAME" -n "$NAMESPACE" -o yaml \
| grep -vE 'resourceVersion|uid|creationTimestamp' > "$BACKUP_SECRET" && \
logger -t traefik-cert "Backed up existing secret to $BACKUP_SECRET" && \
kubectl delete secret -n $NAMESPACE $SECRET_NAME --ignore-not-found && \
logger -t traefik-cert "Deleted existing secret" || \
logger -p user.warn -t traefik-cert "Failed to backup and delete existing secret. Trying to proceed with update..."

if kubectl create secret tls $SECRET_NAME \
--cert=$CERT \
--key=$KEY \
-n $NAMESPACE \
--dry-run=client -o yaml | kubectl apply -f -; then
logger -p user.info -t traefik-cert "TLS secret updated successfully"
else
logger -p user.err -t traefik-cert "TLS secret update FAILED"
kubectl apply -f $BACKUP_SECRET
logger -p user.warn -t traefik-cert "Restored previous secret version"
fi