Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
505850b
Add production deployment configuration and CI/CD
cooper667 Jan 22, 2026
d212819
Update submodules to commits that exist in remotes
cooper667 Jan 22, 2026
ef26689
Update staging domain to dev.adr.fjelltopp.org
cooper667 Jan 26, 2026
e320355
Update staging domain to dev-adr.fjelltopp.org
cooper667 Jan 27, 2026
212cd3d
fix(submodules): update ckanext-unaids with CSRF token fix for file u…
cooper667 Jan 30, 2026
537d89e
docs(config): update CSRF comments now that FileUploader sends token
cooper667 Jan 30, 2026
86c34ec
fix(submodules): update ckanext-blob-storage with datapusher route fix
cooper667 Jan 30, 2026
55d9136
fix(submodules): update ckanext-blob-storage with trailing slash fix
cooper667 Jan 30, 2026
e01ec91
feat: Enable SAML2 Auth0 login on dev.adr.fjelltopp.org
cooper667 Jan 31, 2026
4e1289e
fix(docker): use deploy/production.ini for CKAN config
cooper667 Feb 1, 2026
338f36e
fix(saml): use correct Auth0 client ID for dev.adr.fjelltopp.org
cooper667 Feb 1, 2026
92405e9
fix(saml): use correct Auth0 tenant (dev-udfgla0l)
cooper667 Feb 1, 2026
f0016dc
refactor(config): use base.ini + secrets.ini → production.ini
cooper667 Feb 1, 2026
21d7e3b
refactor(config): split into base.ini + env.ini + secrets.ini
cooper667 Feb 1, 2026
c0ad19a
fix(submodules): revert ckanext-unaids to upstream fjelltopp repo
cooper667 Feb 2, 2026
e67b5be
fix(saml): use correct Auth0 name claim URI
cooper667 Feb 3, 2026
6348fdf
chore: update ckanext-blob-storage submodule
cooper667 Feb 3, 2026
8fe251f
chore: update ckanext-blob-storage submodule to 3335f65
cooper667 Feb 3, 2026
1590bb4
Merge branch 'ckan211-python310-migration-staging-1' into ckan211-pro…
cooper667 Feb 5, 2026
8765d8b
wip: hopefully brings the configs in line
ChasNelson1990 Feb 5, 2026
54802f9
Merge branch 'ckan211-prod-deploy-pr' of github.com:fjelltopp/adx_dev…
cooper667 Feb 6, 2026
d693ca2
correct ckan ini file
cooper667 Feb 6, 2026
1f84515
Fix duplicate ckan.resource_formats in base.ini
cooper667 Feb 6, 2026
2314ced
Remove unavailable officedocs_view plugin from plugins list
cooper667 Feb 6, 2026
b4759f1
Disable email_to to avoid CKAN 2.11 startup crash
cooper667 Feb 6, 2026
f630d95
Prevent Front Door from caching dynamic/error responses
cooper667 Feb 6, 2026
a92f4d7
Bump ckanext-scheming to fix hardcoded resource URLs
cooper667 Feb 7, 2026
b30ef3c
Bump ckanext-validation to fix CKAN 2.11 compatibility
cooper667 Feb 8, 2026
b67a917
Update ckanext-harvest and ckanext-dhis2harvester submodules
A-Souhei Feb 8, 2026
c028c3b
Fix supervisor worker paths for prod virtualenv and config
cooper667 Feb 12, 2026
362d426
Fix supervisor logging to use /dev/stdout instead of /dev/fd/1
cooper667 Feb 13, 2026
fa0c256
Bump ckanext-dhis2harvester with Python 3 compatibility fixes
cooper667 Feb 14, 2026
d2a65b9
Update ckanext-unaids: fix numpy/pandas compatibility for Python 3.10
A-Souhei Feb 19, 2026
16dfd16
Fix ckan-worker crash by running as root user
cooper667 Feb 20, 2026
d5d98b1
Add SMTP config for staging via Azure Communication Services
cooper667 Mar 4, 2026
953b97d
chore: bump submodules, add Solr rebuild script, and minor dev toolin…
A-Souhei Mar 16, 2026
94e1df0
chore: bump ckanext-unaids (Download All fixes + XSS hardening)
A-Souhei Mar 16, 2026
ee4bbc7
fix: Bump ckanext-unaids to fix Download All on staging
A-Souhei Mar 17, 2026
63bd123
chore: bump ckanext-unaids to 78f93c5
A-Souhei Apr 10, 2026
82bc772
ci: add manual workflow to rebuild Solr index
cooper667 Apr 10, 2026
7a2217c
chore: bump submodules with security and fix updates
A-Souhei Apr 17, 2026
d88b39b
ci: split staging build from production release workflow
cooper667 Apr 18, 2026
2db56fa
fix(saml): configure prod Auth0 IDP metadata URL
cooper667 Apr 18, 2026
82ecc93
uwsgi: bump workers + add max-requests/harakiri (#204)
cooper667 May 23, 2026
0d80ad5
chore: Nightly prod to staging sync (#203)
cooper667 May 24, 2026
a805cfe
chore: point submodules at merged CKAN 2.11 branches
A-Souhei May 21, 2026
369de2f
fix(deps): pin markupsafe==2.1.5 for CKAN 2.11 compatibility
A-Souhei May 22, 2026
000681c
chore(deploy): bump ckanext-unaids and datapusher to merged CKAN 2.11…
A-Souhei May 26, 2026
d60cb43
fix: downgrade setuptools after pipenv sync to restore pkg_resources
cooper667 May 26, 2026
e3b6801
fix(deps): pin setuptools<80 to restore pkg_resources for CKAN CLI
cooper667 May 26, 2026
17bae3d
fix: bump ckanext-restricted to fix site_read auth error on restricte…
cooper667 May 27, 2026
283fb9c
fix(ci): increase kubectl rollout timeout from 5m to 10m
cooper667 May 27, 2026
907562d
fix(sync): regenerate DataPusher API token after each nightly DB restore
cooper667 May 27, 2026
7073593
chore: bump ckanext-restricted to latest + site_read fix
cooper667 May 27, 2026
fd332a4
ci: add concurrency cancel-in-progress to deploy workflows
cooper667 May 27, 2026
f00c48d
Bump ckanext-restricted to d357aa2
cooper667 May 27, 2026
7780484
fix(sync): re-apply datastore GRANTs after restore so views generate
cooper667 May 28, 2026
29f49c6
enbale about
cooper667 May 28, 2026
07fb022
chore: remove legacy AWS-era Jenkins CI and base-image machinery
cooper667 May 29, 2026
62a3ae8
chore: stop gitignoring rebuild_solr_index.sh and .vuhitra/
cooper667 May 29, 2026
99a3046
ci: gate staging on extension tests (+ build-time caching) (#205)
cooper667 May 29, 2026
6ecce97
chore: restore submodule refs to branch tips
A-Souhei May 30, 2026
1afdad5
docs: update local-dev test instructions from nosetests to pytest
A-Souhei Jun 3, 2026
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns:
- "*"
224 changes: 224 additions & 0 deletions .github/workflows/build-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
name: Build and Deploy CKAN (staging)

on:
push:
branches: [master, ckan211-prod-deploy-pr]
pull_request:
# Runs the test job only (build/deploy are gated off pull_request below),
# so PRs get a visible test status check without deploying.
branches: [master, ckan211-prod-deploy-pr]
workflow_dispatch:
inputs:
image_tag:
description: "Image tag to deploy (e.g., sha-abc1234). Leave blank to build from the workflow's ref."
required: false
type: string

concurrency:
group: deploy-staging-${{ github.ref }}
cancel-in-progress: true

env:
ACR_NAME: adracr
IMAGE_NAME: ckan
NAMESPACE: adr-s
URL: https://dev.adr.fjelltopp.org

jobs:
test:
name: Extension tests
# Skip on a pure redeploy (workflow_dispatch with an existing image_tag);
# that image was already tested when it was built.
if: github.event_name != 'workflow_dispatch' || inputs.image_tag == ''
runs-on: ubuntu-latest
timeout-minutes: 45
env:
# Fresh runner DB — no need to restart the db container during testsetup.
SKIP_DB_RESTART: "True"
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive

# The dominant cost is `pipenv sync --dev` at bootstrap (CKAN + ~19
# extensions), which lands in the bind-mounted .adxvenv. Caching it on
# Pipfile.lock makes warm runs a near no-op. .adxvenv is gitignored and
# written by root in-container, so there are no ownership issues here.
# Restore + save are split (rather than the combined cache action) so the
# venv is saved even when the test step fails — otherwise the expensive
# `pipenv sync` is repeated cold on every triage run. Saved at job end.
- name: Restore Python venv
id: venv-cache
uses: actions/cache/restore@v4
with:
path: .adxvenv
key: adxvenv-${{ runner.os }}-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
adxvenv-${{ runner.os }}-

# Second cost: the entrypoint runs `yarn install` + build for the unaids
# React app on every boot. Cache its node_modules on the React yarn.lock.
- name: Restore unaids React node_modules
id: react-cache
uses: actions/cache/restore@v4
with:
path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules
key: react-nm-${{ runner.os }}-${{ hashFiles('submodules/ckanext-unaids/ckanext/unaids/react/yarn.lock') }}
restore-keys: |
react-nm-${{ runner.os }}-

- name: Prepare .env
run: cp dev.env .env

- name: Build images and start the dev stack
run: |
./adx build
./adx up

- name: Wait for CKAN bootstrap
run: |
for i in $(seq 1 90); do
if docker logs ckan 2>&1 | grep -q 'CKAN bootstrapping finished, environment ready'; then
echo "CKAN ready after ${i} checks"
exit 0
fi
echo "Waiting for CKAN bootstrap (${i}/90)…"
sleep 10
done
echo "::error::CKAN did not finish bootstrapping in time"
docker logs ckan
exit 1

- name: Create test databases
run: ./adx testsetup

- name: Run extension tests
run: |
# Run every suite (don't fail-fast) so one run shows the full picture,
# then fail at the end if any suite failed.
failed=()
for ext in unaids validation scheming dhis2harvester emailasusername; do
echo "::group::ckanext-${ext}"
if ./adx test "${ext}" --no-interaction; then
echo "ckanext-${ext}: PASS"
else
echo "ckanext-${ext}: FAIL"
failed+=("${ext}")
fi
echo "::endgroup::"
done
echo "--- summary ---"
if [ ${#failed[@]} -ne 0 ]; then
echo "::error::Failing suites: ${failed[*]}"
exit 1
fi
echo "All extension suites passed"

- name: Dump CKAN logs on failure
if: failure()
run: docker logs ckan || true

- name: Save Python venv
if: always() && steps.venv-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: .adxvenv
key: ${{ steps.venv-cache.outputs.cache-primary-key }}

- name: Save unaids React node_modules
if: always() && steps.react-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: submodules/ckanext-unaids/ckanext/unaids/react/node_modules
key: ${{ steps.react-cache.outputs.cache-primary-key }}

build:
needs: test
# Run when tests pass (success) or were skipped (redeploy), and we're
# actually building (not a redeploy of an existing tag).
if: >-
always()
&& github.event_name != 'pull_request'
&& needs.test.result != 'failure'
&& needs.test.result != 'cancelled'
&& (github.event_name != 'workflow_dispatch' || inputs.image_tag == '')
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
submodules: recursive

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.6.1
with:
images: ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=sha-,format=short

- name: Login to ACR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ${{ env.ACR_NAME }}.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}

- name: Build and push
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.10.0
with:
context: .
file: deploy/Dockerfile.prod
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

deploy:
needs: [test, build]
# Never deploy if tests failed/cancelled. Otherwise deploy when the image
# was built (success) or already exists (build skipped on redeploy).
if: >-
always()
&& github.event_name != 'pull_request'
&& needs.test.result != 'failure'
&& needs.test.result != 'cancelled'
&& (needs.build.result == 'success' || needs.build.result == 'skipped')
runs-on: ubuntu-latest
environment:
name: staging
url: ${{ env.URL }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Resolve image tag
id: params
run: |
if [[ -n "${{ inputs.image_tag }}" ]]; then
echo "image_tag=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
else
echo "image_tag=${{ needs.build.outputs.image_tag }}" >> $GITHUB_OUTPUT
fi

- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

- name: Deploy to AKS
run: |
kubectl create configmap ckan-env-config \
--from-file=env.ini=deploy/staging.ini \
-n ${{ env.NAMESPACE }} \
--dry-run=client -o yaml | kubectl apply -f -

kubectl set image deployment/ckan \
ckan=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.image_tag }} \
-n ${{ env.NAMESPACE }}
kubectl rollout status deployment/ckan -n ${{ env.NAMESPACE }} --timeout=10m
23 changes: 0 additions & 23 deletions .github/workflows/build_ckan.yml

This file was deleted.

38 changes: 38 additions & 0 deletions .github/workflows/reindex-solr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Rebuild Solr Index

on:
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- staging
- production

jobs:
reindex:
runs-on: ubuntu-latest
environment:
name: ${{ inputs.environment }}
steps:
- name: Set namespace
id: params
run: |
if [[ "${{ inputs.environment }}" == "production" ]]; then
echo "namespace=adr-p" >> $GITHUB_OUTPUT
else
echo "namespace=adr-s" >> $GITHUB_OUTPUT
fi

- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

- name: Rebuild Solr index
run: |
kubectl exec deployment/ckan -n ${{ steps.params.outputs.namespace }} -- \
ckan -c /tmp/production.ini search-index rebuild -i -q
94 changes: 94 additions & 0 deletions .github/workflows/release-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Release Deploy CKAN (production)

# Triggered on tag push (v*). Creating a GitHub Release publishes the tag,
# so the same workflow handles both `git push --tags` and the Release UI flow.
#
# This workflow does NOT build. It promotes an existing staging image
# (tagged `sha-<short>` by build-deploy.yml) to a `v*` tag in ACR,
# then rolls the `adr-p` deployment over to that image.

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
version:
description: "Version to deploy (e.g., v1.0.0). Must correspond to an existing tag."
required: true
type: string

concurrency:
group: deploy-production-${{ github.ref }}
cancel-in-progress: true

env:
ACR_NAME: adracr
IMAGE_NAME: ckan
NAMESPACE: adr-p
URL: https://adr-p.fjelltopp.org

jobs:
promote-and-deploy:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ env.URL }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0

- name: Resolve version and source sha
id: params
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ inputs.version }}"
git fetch origin tag "$VERSION" --no-tags
SHA=$(git rev-list -n 1 "$VERSION")
else
VERSION="${GITHUB_REF_NAME}"
SHA="${GITHUB_SHA}"
fi
SHORT_SHA=$(echo "$SHA" | cut -c1-7)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "source_tag=sha-$SHORT_SHA" >> $GITHUB_OUTPUT

- name: Login to ACR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
with:
registry: ${{ env.ACR_NAME }}.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}

- name: Verify source image exists in ACR
run: |
docker buildx imagetools inspect \
${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }} \
> /dev/null || { \
echo "::error::Source image ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }} not found. Push the tagged commit to master or ckan211-prod-deploy-pr first so staging CI builds the sha image."; \
exit 1; \
}

- name: Promote image (sha → version)
run: |
docker buildx imagetools create \
--tag ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.version }} \
${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.source_tag }}

- name: Setup kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_BASE64 }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

- name: Deploy to AKS
run: |
kubectl create configmap ckan-env-config \
--from-file=env.ini=deploy/production.ini \
-n ${{ env.NAMESPACE }} \
--dry-run=client -o yaml | kubectl apply -f -

kubectl set image deployment/ckan \
ckan=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ steps.params.outputs.version }} \
-n ${{ env.NAMESPACE }}
kubectl rollout status deployment/ckan -n ${{ env.NAMESPACE }} --timeout=10m
Loading
Loading