Skip to content

Commit 737d5b2

Browse files
committed
new project settup
1 parent 5602ad2 commit 737d5b2

1 file changed

Lines changed: 287 additions & 3 deletions

File tree

.github/workflows/cd-ecs.yml

Lines changed: 287 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ on:
4141
type: choice
4242
options: [staging, prod]
4343
default: staging
44+
operation:
45+
description: "Operation to run"
46+
required: true
47+
type: choice
48+
options: [deploy, rollback]
49+
default: deploy
50+
rollback_tag:
51+
description: "Required when operation=rollback (for example: staging-a1b2c3d or v1.2.3)"
52+
required: false
53+
type: string
4454

4555
permissions:
4656
contents: read
@@ -68,8 +78,9 @@ jobs:
6878
timeout-minutes: 20
6979
if: >
7080
(github.event_name == 'workflow_run'
71-
&& github.event.workflow_run.conclusion == 'success')|| (github.event_name == 'workflow_dispatch'
72-
&& inputs.environment == 'staging')
81+
&& github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch'
82+
&& inputs.environment == 'staging'
83+
&& inputs.operation != 'rollback')
7384
environment: staging
7485
strategy:
7586
matrix:
@@ -148,6 +159,92 @@ jobs:
148159
echo "Waiting 10 minutes for Terraform apply to complete..."
149160
sleep 600
150161
162+
# ---------------------------------------------------------------------------
163+
# 1b2. Trigger Ansible deploy on EC2 infra repo (staging)
164+
# ---------------------------------------------------------------------------
165+
ec2-deploy-staging:
166+
name: "EC2 Deploy [staging] via Ansible"
167+
runs-on: ubuntu-latest
168+
timeout-minutes: 5
169+
needs: [build-staging]
170+
environment: staging
171+
steps:
172+
- name: Dispatch ansible-ec2-deploy to infra3 repo
173+
id: dispatch_ec2_staging
174+
uses: actions/github-script@v7
175+
with:
176+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
177+
script: |
178+
const headSha = (context.payload.workflow_run && context.payload.workflow_run.head_sha) || context.sha;
179+
const imageTag = `staging-${headSha.substring(0, 7)}`;
180+
await github.rest.actions.createWorkflowDispatch({
181+
owner: context.repo.owner,
182+
repo: 'mypythonproject1-infra3',
183+
workflow_id: 'ansible-ec2-deploy.yml',
184+
ref: 'main',
185+
inputs: {
186+
environment: 'staging',
187+
operation: 'deploy',
188+
image_tag: imageTag,
189+
},
190+
});
191+
console.log(`Dispatched ansible-ec2-deploy on mypythonproject1-infra3 (staging, tag=${imageTag})`);
192+
core.setOutput('image_tag', imageTag);
193+
194+
ec2-verify-staging:
195+
name: "Verify EC2 Deploy [staging]"
196+
runs-on: ubuntu-latest
197+
timeout-minutes: 30
198+
needs: [ec2-deploy-staging]
199+
environment: staging
200+
steps:
201+
- name: Wait for infra3 ansible workflow result
202+
uses: actions/github-script@v7
203+
with:
204+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
205+
script: |
206+
const owner = context.repo.owner;
207+
const repo = 'mypythonproject1-infra3';
208+
const expectedTag = `${((context.payload.workflow_run && context.payload.workflow_run.head_sha) || context.sha).substring(0, 7)}`;
209+
const tagNeedle = `tag=staging-${expectedTag}`;
210+
const deadline = Date.now() + 30 * 60 * 1000;
211+
212+
let matchedRun = null;
213+
while (Date.now() < deadline) {
214+
const resp = await github.rest.actions.listWorkflowRuns({
215+
owner,
216+
repo,
217+
workflow_id: 'ansible-ec2-deploy.yml',
218+
event: 'workflow_dispatch',
219+
per_page: 20,
220+
});
221+
222+
matchedRun = resp.data.workflow_runs.find((r) => {
223+
const title = r.display_title || '';
224+
return title.includes('[staging]') && title.includes(tagNeedle);
225+
});
226+
227+
if (!matchedRun) {
228+
await new Promise((resolve) => setTimeout(resolve, 15000));
229+
continue;
230+
}
231+
232+
if (matchedRun.status !== 'completed') {
233+
await new Promise((resolve) => setTimeout(resolve, 15000));
234+
continue;
235+
}
236+
237+
if (matchedRun.conclusion !== 'success') {
238+
core.setFailed(`infra3 ansible deploy failed: ${matchedRun.html_url}`);
239+
return;
240+
}
241+
242+
core.notice(`infra3 ansible deploy succeeded: ${matchedRun.html_url}`);
243+
return;
244+
}
245+
246+
core.setFailed('Timed out waiting for infra3 ansible staging deployment run to complete');
247+
151248
# ---------------------------------------------------------------------------
152249
# 1c. ECS rolling deploy (staging)
153250
# ---------------------------------------------------------------------------
@@ -253,7 +350,9 @@ jobs:
253350
name: "Build & Push [production] (${{ matrix.service }})"
254351
runs-on: ubuntu-latest
255352
timeout-minutes: 20
256-
if: startsWith(github.ref, 'refs/tags/v')
353+
if: >
354+
startsWith(github.ref, 'refs/tags/v') ||
355+
(github.event_name == 'workflow_dispatch' && inputs.environment == 'prod' && inputs.operation != 'rollback')
257356
environment: prod
258357
strategy:
259358
matrix:
@@ -339,6 +438,191 @@ jobs:
339438
echo "Waiting 15 minutes for Terraform apply to complete..."
340439
sleep 900
341440
441+
# ---------------------------------------------------------------------------
442+
# 2c2. Trigger Ansible deploy on EC2 infra repo (production)
443+
# ---------------------------------------------------------------------------
444+
ec2-deploy-production:
445+
name: "EC2 Deploy [production] via Ansible"
446+
runs-on: ubuntu-latest
447+
timeout-minutes: 5
448+
needs: [build-production]
449+
environment: prod
450+
steps:
451+
- name: Dispatch ansible-ec2-deploy to infra3 repo
452+
id: dispatch_ec2_prod
453+
uses: actions/github-script@v7
454+
with:
455+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
456+
script: |
457+
const tag = context.ref.replace('refs/tags/', '');
458+
await github.rest.actions.createWorkflowDispatch({
459+
owner: context.repo.owner,
460+
repo: 'mypythonproject1-infra3',
461+
workflow_id: 'ansible-ec2-deploy.yml',
462+
ref: 'main',
463+
inputs: {
464+
environment: 'prod',
465+
operation: 'deploy',
466+
image_tag: tag,
467+
production_confirmation: 'APPROVE_PROD_DEPLOY',
468+
},
469+
});
470+
console.log(`Dispatched ansible-ec2-deploy on mypythonproject1-infra3 (prod, tag=${tag})`);
471+
core.setOutput('image_tag', tag);
472+
473+
ec2-verify-production:
474+
name: "Verify EC2 Deploy [production]"
475+
runs-on: ubuntu-latest
476+
timeout-minutes: 45
477+
needs: [ec2-deploy-production]
478+
environment: prod
479+
steps:
480+
- name: Wait for infra3 ansible workflow result
481+
uses: actions/github-script@v7
482+
with:
483+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
484+
script: |
485+
const owner = context.repo.owner;
486+
const repo = 'mypythonproject1-infra3';
487+
const tag = context.ref.replace('refs/tags/', '');
488+
const tagNeedle = `tag=${tag}`;
489+
const deadline = Date.now() + 45 * 60 * 1000;
490+
491+
let matchedRun = null;
492+
while (Date.now() < deadline) {
493+
const resp = await github.rest.actions.listWorkflowRuns({
494+
owner,
495+
repo,
496+
workflow_id: 'ansible-ec2-deploy.yml',
497+
event: 'workflow_dispatch',
498+
per_page: 20,
499+
});
500+
501+
matchedRun = resp.data.workflow_runs.find((r) => {
502+
const title = r.display_title || '';
503+
return title.includes('[prod]') && title.includes(tagNeedle);
504+
});
505+
506+
if (!matchedRun) {
507+
await new Promise((resolve) => setTimeout(resolve, 20000));
508+
continue;
509+
}
510+
511+
if (matchedRun.status !== 'completed') {
512+
await new Promise((resolve) => setTimeout(resolve, 20000));
513+
continue;
514+
}
515+
516+
if (matchedRun.conclusion !== 'success') {
517+
core.setFailed(`infra3 ansible deploy failed: ${matchedRun.html_url}`);
518+
return;
519+
}
520+
521+
core.notice(`infra3 ansible deploy succeeded: ${matchedRun.html_url}`);
522+
return;
523+
}
524+
525+
core.setFailed('Timed out waiting for infra3 ansible production deployment run to complete');
526+
527+
# ---------------------------------------------------------------------------
528+
# 3. Manual rollback on EC2 infra repo via Ansible
529+
# ---------------------------------------------------------------------------
530+
ec2-rollback-manual:
531+
name: "EC2 Rollback [manual] via Ansible"
532+
runs-on: ubuntu-latest
533+
timeout-minutes: 5
534+
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'rollback'
535+
environment: ${{ inputs.environment }}
536+
outputs:
537+
rollback_tag: ${{ steps.dispatch_rollback.outputs.rollback_tag }}
538+
steps:
539+
- name: Validate rollback tag input
540+
shell: bash
541+
run: |
542+
if [ -z "${{ inputs.rollback_tag }}" ]; then
543+
echo "::error::rollback_tag is required when operation=rollback"
544+
exit 1
545+
fi
546+
547+
- name: Dispatch ansible rollback to infra3 repo
548+
id: dispatch_rollback
549+
uses: actions/github-script@v7
550+
with:
551+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
552+
script: |
553+
const env = '${{ inputs.environment }}';
554+
const rollbackTag = '${{ inputs.rollback_tag }}';
555+
const isProd = env === 'prod';
556+
await github.rest.actions.createWorkflowDispatch({
557+
owner: context.repo.owner,
558+
repo: 'mypythonproject1-infra3',
559+
workflow_id: 'ansible-ec2-deploy.yml',
560+
ref: 'main',
561+
inputs: {
562+
environment: env,
563+
operation: 'rollback',
564+
image_tag: rollbackTag,
565+
production_confirmation: isProd ? 'APPROVE_PROD_DEPLOY' : '',
566+
},
567+
});
568+
console.log(`Dispatched ansible rollback on mypythonproject1-infra3 (${env}, tag=${rollbackTag})`);
569+
core.setOutput('rollback_tag', rollbackTag);
570+
571+
ec2-verify-rollback-manual:
572+
name: "Verify EC2 Rollback [manual]"
573+
runs-on: ubuntu-latest
574+
timeout-minutes: 45
575+
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'rollback'
576+
needs: [ec2-rollback-manual]
577+
environment: ${{ inputs.environment }}
578+
steps:
579+
- name: Wait for infra3 ansible rollback result
580+
uses: actions/github-script@v7
581+
with:
582+
github-token: ${{ secrets.INFRA_DEPLOY_TOKEN }}
583+
script: |
584+
const owner = context.repo.owner;
585+
const repo = 'mypythonproject1-infra3';
586+
const env = '${{ inputs.environment }}';
587+
const rollbackTag = '${{ inputs.rollback_tag }}';
588+
const envNeedle = `[${env}]`;
589+
const opNeedle = 'rollback';
590+
const tagNeedle = `tag=${rollbackTag}`;
591+
const timeoutMs = env === 'prod' ? 45 * 60 * 1000 : 30 * 60 * 1000;
592+
const pollMs = 20000;
593+
const deadline = Date.now() + timeoutMs;
594+
595+
let matchedRun = null;
596+
while (Date.now() < deadline) {
597+
const resp = await github.rest.actions.listWorkflowRuns({
598+
owner,
599+
repo,
600+
workflow_id: 'ansible-ec2-deploy.yml',
601+
event: 'workflow_dispatch',
602+
per_page: 20,
603+
});
604+
605+
matchedRun = resp.data.workflow_runs.find((r) => {
606+
const title = (r.display_title || '').toLowerCase();
607+
return title.includes(envNeedle.toLowerCase()) && title.includes(opNeedle) && title.includes(tagNeedle.toLowerCase());
608+
});
609+
610+
if (!matchedRun || matchedRun.status !== 'completed') {
611+
await new Promise((resolve) => setTimeout(resolve, pollMs));
612+
continue;
613+
}
614+
615+
if (matchedRun.conclusion !== 'success') {
616+
core.setFailed(`infra3 ansible rollback failed: ${matchedRun.html_url}`);
617+
return;
618+
}
619+
620+
core.notice(`infra3 ansible rollback succeeded: ${matchedRun.html_url}`);
621+
return;
622+
}
623+
624+
core.setFailed('Timed out waiting for infra3 ansible rollback run to complete');
625+
342626
# ---------------------------------------------------------------------------
343627
# 2d. ECS rolling deploy (production)
344628
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)