@slamb2k/mad-skills 2.0.14 → 2.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,519 @@
1
+ # IaC Pipeline Templates
2
+
3
+ CI/CD pipeline templates for infrastructure-as-code. Each implements the
4
+ plan-on-PR, apply-on-merge pattern with environment promotion.
5
+
6
+ Template variables:
7
+ - `{IAC_TOOL}`: terraform, bicep, pulumi, cdk
8
+ - `{DEFAULT_BRANCH}`: main or master
9
+ - `{CLOUD_PROVIDER}`: azure, aws, gcp
10
+ - `{STATE_BACKEND}`: backend config details
11
+ - `{INFRA_DIR}`: path to IaC files (default: infra/)
12
+ - `{ENVIRONMENTS}`: list of environments (dev, staging, prod)
13
+
14
+ ---
15
+
16
+ ## GitHub Actions
17
+
18
+ ### Terraform
19
+
20
+ File: `.github/workflows/infra.yml`
21
+
22
+ ```yaml
23
+ name: Infrastructure
24
+
25
+ on:
26
+ pull_request:
27
+ paths:
28
+ - "infra/**"
29
+ - ".github/workflows/infra.yml"
30
+ push:
31
+ branches: [{DEFAULT_BRANCH}]
32
+ paths:
33
+ - "infra/**"
34
+ - ".github/workflows/infra.yml"
35
+ workflow_dispatch:
36
+ inputs:
37
+ environment:
38
+ description: "Target environment"
39
+ required: true
40
+ type: choice
41
+ options: [dev, staging, prod]
42
+ action:
43
+ description: "Action to perform"
44
+ required: true
45
+ type: choice
46
+ options: [plan, apply]
47
+
48
+ concurrency:
49
+ group: infra-${{ github.ref }}
50
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
51
+
52
+ permissions:
53
+ contents: read
54
+ pull-requests: write
55
+ id-token: write # For OIDC auth
56
+
57
+ env:
58
+ TF_IN_AUTOMATION: true
59
+ INFRA_DIR: infra
60
+
61
+ jobs:
62
+ # ── Plan (runs on every PR) ──────────────────────────────────
63
+ plan:
64
+ if: github.event_name == 'pull_request'
65
+ runs-on: ubuntu-latest
66
+ strategy:
67
+ matrix:
68
+ environment: [{ENVIRONMENTS_LIST}]
69
+ steps:
70
+ - uses: actions/checkout@v4
71
+
72
+ - uses: hashicorp/setup-terraform@v3
73
+ with:
74
+ terraform_version: "~1.9"
75
+
76
+ # Cloud auth — choose one based on provider
77
+ # Azure:
78
+ - uses: azure/login@v2
79
+ with:
80
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
81
+ # AWS (OIDC):
82
+ # - uses: aws-actions/configure-aws-credentials@v4
83
+ # with:
84
+ # role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
85
+ # aws-region: ${{ vars.AWS_REGION }}
86
+ # GCP (Workload Identity):
87
+ # - uses: google-github-actions/auth@v2
88
+ # with:
89
+ # workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
90
+ # service_account: ${{ secrets.SA_EMAIL }}
91
+
92
+ - name: Terraform Init
93
+ working-directory: ${{ env.INFRA_DIR }}
94
+ run: terraform init
95
+
96
+ - name: Terraform Plan
97
+ id: plan
98
+ working-directory: ${{ env.INFRA_DIR }}
99
+ run: |
100
+ terraform plan \
101
+ -var-file=environments/${{ matrix.environment }}.tfvars \
102
+ -out=plan-${{ matrix.environment }}.tfplan \
103
+ -no-color 2>&1 | tee plan-output.txt
104
+ continue-on-error: true
105
+
106
+ - name: Comment PR with plan
107
+ uses: actions/github-script@v7
108
+ with:
109
+ script: |
110
+ const fs = require('fs');
111
+ const plan = fs.readFileSync('${{ env.INFRA_DIR }}/plan-output.txt', 'utf8');
112
+ const truncated = plan.length > 60000 ? plan.slice(-60000) : plan;
113
+ const body = `### Terraform Plan — \`${{ matrix.environment }}\`
114
+ \`\`\`
115
+ ${truncated}
116
+ \`\`\`
117
+ *Triggered by ${{ github.actor }} in ${{ github.event.pull_request.head.ref }}*`;
118
+ github.rest.issues.createComment({
119
+ owner: context.repo.owner,
120
+ repo: context.repo.repo,
121
+ issue_number: context.issue.number,
122
+ body: body
123
+ });
124
+
125
+ - name: Fail if plan errored
126
+ if: steps.plan.outcome == 'failure'
127
+ run: exit 1
128
+
129
+ # ── Apply Dev (on merge to main) ────────────────────────────
130
+ apply-dev:
131
+ if: github.ref == 'refs/heads/{DEFAULT_BRANCH}' && github.event_name == 'push'
132
+ runs-on: ubuntu-latest
133
+ environment: dev
134
+ steps:
135
+ - uses: actions/checkout@v4
136
+
137
+ - uses: hashicorp/setup-terraform@v3
138
+ with:
139
+ terraform_version: "~1.9"
140
+
141
+ - uses: azure/login@v2
142
+ with:
143
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
144
+
145
+ - name: Terraform Init
146
+ working-directory: ${{ env.INFRA_DIR }}
147
+ run: terraform init
148
+
149
+ - name: Terraform Apply
150
+ working-directory: ${{ env.INFRA_DIR }}
151
+ run: |
152
+ terraform apply \
153
+ -var-file=environments/dev.tfvars \
154
+ -auto-approve
155
+
156
+ - name: Sync outputs to GitHub variables
157
+ working-directory: ${{ env.INFRA_DIR }}
158
+ run: |
159
+ REGISTRY_URL=$(terraform output -raw registry_url 2>/dev/null) || true
160
+ if [ -n "$REGISTRY_URL" ]; then
161
+ gh variable set REGISTRY_URL --body "$REGISTRY_URL"
162
+ fi
163
+ env:
164
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
165
+
166
+ # ── Apply Staging (manual dispatch or tag) ───────────────────
167
+ apply-staging:
168
+ if: >
169
+ github.event_name == 'workflow_dispatch' &&
170
+ github.event.inputs.environment == 'staging' &&
171
+ github.event.inputs.action == 'apply'
172
+ runs-on: ubuntu-latest
173
+ environment: staging
174
+ steps:
175
+ - uses: actions/checkout@v4
176
+
177
+ - uses: hashicorp/setup-terraform@v3
178
+ with:
179
+ terraform_version: "~1.9"
180
+
181
+ - uses: azure/login@v2
182
+ with:
183
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
184
+
185
+ - name: Terraform Init & Apply
186
+ working-directory: ${{ env.INFRA_DIR }}
187
+ run: |
188
+ terraform init
189
+ terraform apply \
190
+ -var-file=environments/staging.tfvars \
191
+ -auto-approve
192
+
193
+ # ── Apply Prod (manual dispatch with approval) ───────────────
194
+ apply-prod:
195
+ if: >
196
+ github.event_name == 'workflow_dispatch' &&
197
+ github.event.inputs.environment == 'prod' &&
198
+ github.event.inputs.action == 'apply'
199
+ runs-on: ubuntu-latest
200
+ environment: production # Requires approval in GitHub settings
201
+ steps:
202
+ - uses: actions/checkout@v4
203
+
204
+ - uses: hashicorp/setup-terraform@v3
205
+ with:
206
+ terraform_version: "~1.9"
207
+
208
+ - uses: azure/login@v2
209
+ with:
210
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
211
+
212
+ - name: Terraform Init & Apply
213
+ working-directory: ${{ env.INFRA_DIR }}
214
+ run: |
215
+ terraform init
216
+ terraform apply \
217
+ -var-file=environments/prod.tfvars \
218
+ -auto-approve
219
+ ```
220
+
221
+ ### Bicep
222
+
223
+ File: `.github/workflows/infra.yml`
224
+
225
+ ```yaml
226
+ name: Infrastructure
227
+
228
+ on:
229
+ pull_request:
230
+ paths: ["infra/**"]
231
+ push:
232
+ branches: [{DEFAULT_BRANCH}]
233
+ paths: ["infra/**"]
234
+ workflow_dispatch:
235
+ inputs:
236
+ environment:
237
+ required: true
238
+ type: choice
239
+ options: [dev, staging, prod]
240
+
241
+ permissions:
242
+ contents: read
243
+ pull-requests: write
244
+ id-token: write
245
+
246
+ env:
247
+ INFRA_DIR: infra
248
+
249
+ jobs:
250
+ # ── What-If (plan equivalent for Bicep) ──────────────────────
251
+ what-if:
252
+ if: github.event_name == 'pull_request'
253
+ runs-on: ubuntu-latest
254
+ strategy:
255
+ matrix:
256
+ environment: [{ENVIRONMENTS_LIST}]
257
+ steps:
258
+ - uses: actions/checkout@v4
259
+
260
+ - uses: azure/login@v2
261
+ with:
262
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
263
+
264
+ - name: Bicep What-If
265
+ run: |
266
+ az deployment sub what-if \
267
+ --location {REGION} \
268
+ --template-file ${{ env.INFRA_DIR }}/main.bicep \
269
+ --parameters ${{ env.INFRA_DIR }}/environments/${{ matrix.environment }}.bicepparam \
270
+ --no-pretty-print > whatif-output.txt 2>&1
271
+
272
+ - name: Comment PR
273
+ uses: actions/github-script@v7
274
+ with:
275
+ script: |
276
+ const fs = require('fs');
277
+ const output = fs.readFileSync('whatif-output.txt', 'utf8');
278
+ github.rest.issues.createComment({
279
+ owner: context.repo.owner,
280
+ repo: context.repo.repo,
281
+ issue_number: context.issue.number,
282
+ body: `### Bicep What-If — \`${{ matrix.environment }}\`\n\`\`\`\n${output.slice(-60000)}\n\`\`\``
283
+ });
284
+
285
+ # ── Deploy Dev ───────────────────────────────────────────────
286
+ deploy-dev:
287
+ if: github.ref == 'refs/heads/{DEFAULT_BRANCH}' && github.event_name == 'push'
288
+ runs-on: ubuntu-latest
289
+ environment: dev
290
+ steps:
291
+ - uses: actions/checkout@v4
292
+ - uses: azure/login@v2
293
+ with:
294
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
295
+
296
+ - name: Deploy
297
+ run: |
298
+ az deployment sub create \
299
+ --location {REGION} \
300
+ --template-file ${{ env.INFRA_DIR }}/main.bicep \
301
+ --parameters ${{ env.INFRA_DIR }}/environments/dev.bicepparam
302
+
303
+ # ── Deploy Staging/Prod (manual dispatch) ────────────────────
304
+ deploy-env:
305
+ if: github.event_name == 'workflow_dispatch'
306
+ runs-on: ubuntu-latest
307
+ environment: ${{ github.event.inputs.environment }}
308
+ steps:
309
+ - uses: actions/checkout@v4
310
+ - uses: azure/login@v2
311
+ with:
312
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
313
+
314
+ - name: Deploy
315
+ run: |
316
+ az deployment sub create \
317
+ --location {REGION} \
318
+ --template-file ${{ env.INFRA_DIR }}/main.bicep \
319
+ --parameters ${{ env.INFRA_DIR }}/environments/${{ github.event.inputs.environment }}.bicepparam
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Azure DevOps Pipelines
325
+
326
+ ### Terraform
327
+
328
+ File: `azure-pipelines-infra.yml`
329
+
330
+ ```yaml
331
+ trigger:
332
+ branches:
333
+ include: [{DEFAULT_BRANCH}]
334
+ paths:
335
+ include: [infra/*]
336
+
337
+ pr:
338
+ paths:
339
+ include: [infra/*]
340
+
341
+ variables:
342
+ infraDir: infra
343
+ serviceConnection: "{SERVICE_CONNECTION}"
344
+
345
+ stages:
346
+ - stage: Plan
347
+ condition: eq(variables['Build.Reason'], 'PullRequest')
348
+ jobs:
349
+ - job: TerraformPlan
350
+ pool:
351
+ vmImage: ubuntu-latest
352
+ steps:
353
+ - task: TerraformInstaller@1
354
+ inputs:
355
+ terraformVersion: latest
356
+
357
+ - task: TerraformTaskV4@4
358
+ displayName: Init
359
+ inputs:
360
+ provider: azurerm
361
+ command: init
362
+ workingDirectory: $(infraDir)
363
+ backendServiceArm: $(serviceConnection)
364
+ backendAzureRmResourceGroupName: "{STATE_RG}"
365
+ backendAzureRmStorageAccountName: "{STATE_STORAGE}"
366
+ backendAzureRmContainerName: tfstate
367
+ backendAzureRmKey: "{PROJECT}.tfstate"
368
+
369
+ - task: TerraformTaskV4@4
370
+ displayName: Plan
371
+ inputs:
372
+ provider: azurerm
373
+ command: plan
374
+ workingDirectory: $(infraDir)
375
+ commandOptions: "-var-file=environments/dev.tfvars"
376
+ environmentServiceNameAzureRM: $(serviceConnection)
377
+
378
+ - stage: ApplyDev
379
+ condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/{DEFAULT_BRANCH}'))
380
+ jobs:
381
+ - deployment: ApplyDev
382
+ environment: dev
383
+ pool:
384
+ vmImage: ubuntu-latest
385
+ strategy:
386
+ runOnce:
387
+ deploy:
388
+ steps:
389
+ - checkout: self
390
+ - task: TerraformInstaller@1
391
+ inputs:
392
+ terraformVersion: latest
393
+ - task: TerraformTaskV4@4
394
+ displayName: Init
395
+ inputs:
396
+ provider: azurerm
397
+ command: init
398
+ workingDirectory: $(infraDir)
399
+ backendServiceArm: $(serviceConnection)
400
+ backendAzureRmResourceGroupName: "{STATE_RG}"
401
+ backendAzureRmStorageAccountName: "{STATE_STORAGE}"
402
+ backendAzureRmContainerName: tfstate
403
+ backendAzureRmKey: "{PROJECT}.tfstate"
404
+ - task: TerraformTaskV4@4
405
+ displayName: Apply
406
+ inputs:
407
+ provider: azurerm
408
+ command: apply
409
+ workingDirectory: $(infraDir)
410
+ commandOptions: "-var-file=environments/dev.tfvars -auto-approve"
411
+ environmentServiceNameAzureRM: $(serviceConnection)
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Bootstrap Scripts
417
+
418
+ ### Azure state backend
419
+
420
+ ```bash
421
+ #!/bin/bash
422
+ # infra/bootstrap.sh — Run once to create Terraform state backend
423
+ set -euo pipefail
424
+
425
+ PROJECT="{PROJECT}"
426
+ LOCATION="{REGION}"
427
+ RG_NAME="rg-${PROJECT}-tfstate"
428
+ SA_NAME="st${PROJECT//[^a-z0-9]/}tfstate"
429
+
430
+ echo "Creating state backend for ${PROJECT}..."
431
+
432
+ az group create --name "$RG_NAME" --location "$LOCATION" --output none
433
+ az storage account create \
434
+ --name "$SA_NAME" \
435
+ --resource-group "$RG_NAME" \
436
+ --location "$LOCATION" \
437
+ --sku Standard_LRS \
438
+ --min-tls-version TLS1_2 \
439
+ --output none
440
+ az storage container create \
441
+ --name tfstate \
442
+ --account-name "$SA_NAME" \
443
+ --output none
444
+
445
+ echo "State backend ready:"
446
+ echo " Resource Group: $RG_NAME"
447
+ echo " Storage Account: $SA_NAME"
448
+ echo " Container: tfstate"
449
+ ```
450
+
451
+ ### AWS state backend
452
+
453
+ ```bash
454
+ #!/bin/bash
455
+ set -euo pipefail
456
+
457
+ PROJECT="{PROJECT}"
458
+ REGION="{REGION}"
459
+ BUCKET="${PROJECT}-tfstate"
460
+ TABLE="${PROJECT}-tflock"
461
+
462
+ echo "Creating state backend for ${PROJECT}..."
463
+
464
+ aws s3api create-bucket \
465
+ --bucket "$BUCKET" \
466
+ --region "$REGION" \
467
+ --create-bucket-configuration LocationConstraint="$REGION"
468
+
469
+ aws s3api put-bucket-versioning \
470
+ --bucket "$BUCKET" \
471
+ --versioning-configuration Status=Enabled
472
+
473
+ aws s3api put-bucket-encryption \
474
+ --bucket "$BUCKET" \
475
+ --server-side-encryption-configuration '{
476
+ "Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "aws:kms"}}]
477
+ }'
478
+
479
+ aws dynamodb create-table \
480
+ --table-name "$TABLE" \
481
+ --attribute-definitions AttributeName=LockID,AttributeType=S \
482
+ --key-schema AttributeName=LockID,KeyType=HASH \
483
+ --billing-mode PAY_PER_REQUEST
484
+
485
+ echo "State backend ready:"
486
+ echo " S3 Bucket: $BUCKET"
487
+ echo " DynamoDB Table: $TABLE"
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Output Sync Script
493
+
494
+ ### infra/sync-outputs.sh
495
+
496
+ Reads IaC outputs and sets them as CI/CD variables for /dock pipelines:
497
+
498
+ ```bash
499
+ #!/bin/bash
500
+ # Sync Terraform outputs to GitHub variables/secrets for /dock
501
+ set -euo pipefail
502
+
503
+ cd "$(dirname "$0")"
504
+
505
+ echo "Syncing infrastructure outputs..."
506
+
507
+ # Public values → GitHub Variables
508
+ REGISTRY_URL=$(terraform output -raw registry_url 2>/dev/null) || true
509
+ [ -n "$REGISTRY_URL" ] && gh variable set REGISTRY_URL --body "$REGISTRY_URL"
510
+
511
+ COMPUTE_ENDPOINT=$(terraform output -raw compute_endpoint 2>/dev/null) || true
512
+ [ -n "$COMPUTE_ENDPOINT" ] && gh variable set COMPUTE_ENDPOINT --body "$COMPUTE_ENDPOINT"
513
+
514
+ # Sensitive values → GitHub Secrets
515
+ DB_URL=$(terraform output -raw database_connection_string 2>/dev/null) || true
516
+ [ -n "$DB_URL" ] && gh secret set DATABASE_URL --body "$DB_URL"
517
+
518
+ echo "Outputs synced to GitHub."
519
+ ```