Skip to content

Commit 4d7a354

Browse files
DTOSS-12784: Tag-driven release pipeline with smoke tests
Replace python-semantic-release with an explicit v* tag trigger. Pushing a v* tag runs commit → test → build then deploys infra, app and smoke test to each environment in sequence (dev → preprod → prod) before promoting to the next. Prod requires a manual approval gate. Main-branch merges redeploy the latest release tag to dev then preprod in order (infra → app per environment) without triggering a new build. The deployment is idempotent — deploy.ps1 writes a VERSION file after each successful install and skips reinstallation if the tag matches. workflow_dispatch targets a single environment (dev / preprod / review) for surgical redeployment, e.g. when onboarding a new Arc-enabled VM. Smoke tests run via az connectedmachine run-command (no direct hospital network access required): checks all 4 Windows services are Running then sends a DICOM C-ECHO to verify the PACS server is accepting connections. Also fixes: - staging directory moved from C:\Windows\Temp to to avoid SYSTEM account access denial on Move-Item across security boundaries - em dash in a PowerShell string literal caused parse errors on Windows when the Arc Run Command handler writes the script without a UTF-8 BOM and PowerShell 5.1 misreads it as CP1252 - VERSION file written with no-BOM UTF-8 to avoid idempotency check always failing on Windows PowerShell 5.1
1 parent f633eb1 commit 4d7a354

5 files changed

Lines changed: 518 additions & 74 deletions

File tree

.github/workflows/cicd-2-main-branch.yaml

Lines changed: 123 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,23 @@ on:
44
push:
55
branches:
66
- main
7-
tags:
8-
- 'v*'
97
workflow_dispatch:
8+
inputs:
9+
environment:
10+
description: "Target environment for infra + app deployment"
11+
required: true
12+
type: choice
13+
default: dev
14+
options:
15+
- dev
16+
- preprod
17+
- review
1018

1119
concurrency: cicd-${{ github.ref }}
1220

1321
permissions:
14-
contents: write
22+
contents: read
1523
id-token: write
16-
attestations: write
1724
security-events: write
1825

1926
jobs:
@@ -26,101 +33,144 @@ jobs:
2633
uses: ./.github/workflows/stage-2-test.yaml
2734
secrets: inherit
2835

29-
release-stage:
36+
# Resolve the latest release tag once so downstream jobs can consume it.
37+
resolve:
38+
name: Resolve deployment targets
3039
needs: test-stage
31-
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
3240
runs-on: ubuntu-latest
3341
outputs:
34-
new_release_published: ${{ steps.release.outputs.new_release_published }}
35-
new_release_version: ${{ steps.release.outputs.new_release_version }}
42+
release_tag: ${{ steps.tag.outputs.release_tag }}
43+
deploy_dev: ${{ steps.envs.outputs.deploy_dev }}
44+
deploy_preprod: ${{ steps.envs.outputs.deploy_preprod }}
45+
deploy_review: ${{ steps.envs.outputs.deploy_review }}
46+
env:
47+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3648
steps:
37-
- name: Checkout code
38-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
39-
with:
40-
fetch-depth: 0
41-
token: ${{ secrets.GITHUB_TOKEN }}
42-
43-
- name: Read tool versions
44-
id: tool-versions
49+
- name: Compute target environments
50+
id: envs
4551
run: |
46-
echo "python=$(awk '/^python / {print $2}' .tool-versions)" >> "$GITHUB_OUTPUT"
47-
echo "uv=$(awk '/^uv / {print $2}' .tool-versions)" >> "$GITHUB_OUTPUT"
48-
49-
- name: Set up Python
50-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
51-
with:
52-
python-version: ${{ steps.tool-versions.outputs.python }}
53-
54-
- name: Install uv
55-
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
56-
with:
57-
version: ${{ steps.tool-versions.outputs.uv }}
58-
enable-cache: true
59-
60-
- name: Run semantic-release
61-
id: release
62-
env:
63-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52+
INPUT="${{ inputs.environment }}"
53+
case "$INPUT" in
54+
dev)
55+
echo "deploy_dev=true" >> "$GITHUB_OUTPUT"
56+
echo "deploy_preprod=false" >> "$GITHUB_OUTPUT"
57+
echo "deploy_review=false" >> "$GITHUB_OUTPUT"
58+
;;
59+
preprod)
60+
echo "deploy_dev=false" >> "$GITHUB_OUTPUT"
61+
echo "deploy_preprod=true" >> "$GITHUB_OUTPUT"
62+
echo "deploy_review=false" >> "$GITHUB_OUTPUT"
63+
;;
64+
review)
65+
echo "deploy_dev=false" >> "$GITHUB_OUTPUT"
66+
echo "deploy_preprod=false" >> "$GITHUB_OUTPUT"
67+
echo "deploy_review=true" >> "$GITHUB_OUTPUT"
68+
;;
69+
*)
70+
# push-to-main (empty input): deploy to both dev and preprod
71+
echo "deploy_dev=true" >> "$GITHUB_OUTPUT"
72+
echo "deploy_preprod=true" >> "$GITHUB_OUTPUT"
73+
echo "deploy_review=false" >> "$GITHUB_OUTPUT"
74+
;;
75+
esac
76+
77+
- name: Resolve latest release tag
78+
id: tag
6479
run: |
65-
uv pip install python-semantic-release --system
80+
LATEST_TAG=$(gh release list \
81+
--repo "${{ github.repository }}" \
82+
--limit 1 \
83+
--json tagName --jq '.[0].tagName' 2>/dev/null || echo "")
84+
if [[ -z "$LATEST_TAG" ]]; then
85+
echo "::error::No GitHub Release exists. Push a v* tag to create the first release."
86+
exit 1
87+
fi
88+
echo "release_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
89+
echo "Latest release: ${LATEST_TAG}"
6690
67-
# Detect next version (--print exits without side effects)
68-
VERSION=$(semantic-release version --print 2>/dev/null) || true
91+
# ---- dev ------------------------------------------------------------------
6992

70-
if [[ -n "$VERSION" ]] && ! git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q .; then
71-
echo "Next version detected: $VERSION"
93+
deploy-infra-dev:
94+
name: Deploy infra (dev)
95+
needs: resolve
96+
if: needs.resolve.outputs.deploy_dev == 'true'
97+
permissions:
98+
id-token: write
99+
uses: ./.github/workflows/stage-4-deploy.yaml
100+
with:
101+
environments: '["dev"]'
102+
commit_sha: ${{ github.sha }}
103+
secrets: inherit
72104

73-
# Create and push the tag (no commit needed, tag push is not blocked by branch protection)
74-
git tag "v$VERSION"
75-
git push origin "v$VERSION"
105+
deploy-app-dev:
106+
name: Deploy app (dev)
107+
needs: [resolve, deploy-infra-dev]
108+
if: needs.resolve.outputs.deploy_dev == 'true'
109+
permissions:
110+
id-token: write
111+
uses: ./.github/workflows/stage-4-deploy-app.yaml
112+
with:
113+
environments: '["dev"]'
114+
release_tag: ${{ needs.resolve.outputs.release_tag }}
115+
commit_sha: ${{ github.sha }}
116+
secrets: inherit
76117

77-
echo "new_release_published=true" >> "$GITHUB_OUTPUT"
78-
echo "new_release_version=v$VERSION" >> "$GITHUB_OUTPUT"
79-
else
80-
echo "No new release needed (version: ${VERSION:-none}, tag may already exist)"
81-
echo "new_release_published=false" >> "$GITHUB_OUTPUT"
82-
echo "new_release_version=" >> "$GITHUB_OUTPUT"
83-
fi
118+
# ---- preprod --------------------------------------------------------------
119+
120+
deploy-infra-preprod:
121+
name: Deploy infra (preprod)
122+
needs: [resolve, deploy-app-dev]
123+
if: |
124+
always() &&
125+
needs.resolve.outputs.deploy_preprod == 'true' &&
126+
(needs.deploy-app-dev.result == 'success' || needs.deploy-app-dev.result == 'skipped')
127+
permissions:
128+
id-token: write
129+
uses: ./.github/workflows/stage-4-deploy.yaml
130+
with:
131+
environments: '["preprod"]'
132+
commit_sha: ${{ github.sha }}
133+
secrets: inherit
84134

85-
build-stage:
86-
needs: [test-stage, release-stage]
135+
deploy-app-preprod:
136+
name: Deploy app (preprod)
137+
needs: [resolve, deploy-infra-preprod]
87138
if: |
88139
always() &&
89-
needs.test-stage.result == 'success' &&
90-
(
91-
github.ref_type == 'tag' ||
92-
needs.release-stage.outputs.new_release_published == 'true'
93-
)
94-
uses: ./.github/workflows/stage-3-build.yaml
140+
needs.resolve.outputs.deploy_preprod == 'true' &&
141+
needs.deploy-infra-preprod.result == 'success'
142+
permissions:
143+
id-token: write
144+
uses: ./.github/workflows/stage-4-deploy-app.yaml
95145
with:
96-
version: ${{ needs.release-stage.outputs.new_release_version }}
97-
new_release_published: ${{ needs.release-stage.outputs.new_release_published == 'true' }}
146+
environments: '["preprod"]'
147+
release_tag: ${{ needs.resolve.outputs.release_tag }}
148+
commit_sha: ${{ github.sha }}
98149
secrets: inherit
99150

100-
deploy-stage:
101-
name: Deploy stage
102-
needs: [commit-stage, test-stage]
151+
# ---- review (manual only) -------------------------------------------------
152+
153+
deploy-infra-review:
154+
name: Deploy infra (review)
155+
needs: resolve
156+
if: needs.resolve.outputs.deploy_review == 'true'
103157
permissions:
104158
id-token: write
105159
uses: ./.github/workflows/stage-4-deploy.yaml
106160
with:
107-
environments: '["review", "dev", "preprod"]'
161+
environments: '["review"]'
108162
commit_sha: ${{ github.sha }}
109163
secrets: inherit
110164

111-
deploy-app-stage:
112-
name: Deploy app stage
113-
needs: [build-stage, release-stage]
114-
# Only deploy when a new release was published — no release means no deployable artifact.
115-
if: |
116-
always() &&
117-
needs.build-stage.result == 'success' &&
118-
needs.release-stage.outputs.new_release_published == 'true'
165+
deploy-app-review:
166+
name: Deploy app (review)
167+
needs: [resolve, deploy-infra-review]
168+
if: needs.resolve.outputs.deploy_review == 'true'
119169
permissions:
120170
id-token: write
121171
uses: ./.github/workflows/stage-4-deploy-app.yaml
122172
with:
123-
environments: '["dev", "preprod"]'
124-
release_tag: ${{ needs.release-stage.outputs.new_release_version }}
173+
environments: '["review"]'
174+
release_tag: ${{ needs.resolve.outputs.release_tag }}
125175
commit_sha: ${{ github.sha }}
126176
secrets: inherit

0 commit comments

Comments
 (0)