Skip to content

Commit 7ce6fc2

Browse files
committed
Add scenarios for Azure DevOps pipeline variable group error and Kubernetes deployment/service
- Created a new scenario for Azure DevOps pipeline failure due to incorrect variable group reference. - Added a scenario for building a Kubernetes Deployment and Service from scratch. - Updated command handlers and commands to include Azure DevOps and Ansible functionalities. - Enhanced documentation sections for Azure DevOps and Jenkins. - Adjusted difficulty tiers for new scenarios and updated command options. - Implemented validation for Kubernetes YAML configurations. - Added Terraform state lock error message guidance.
1 parent b06608c commit 7ce6fc2

15 files changed

Lines changed: 609 additions & 8 deletions
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
id: ansiblePlaybookWrongHostGroup
2+
kind: cicd
3+
title: Ansible Playbook Wrong Host Group
4+
description: An Ansible playbook fails because it targets a host group named app-servers, but the inventory only defines a group named web-servers.
5+
primaryFile: playbook.yml
6+
tips:
7+
- Inspect the inventory with ansible-inventory --list.
8+
- Run ansible-playbook playbook.yml --check to see why the playbook skips all hosts.
9+
- The hosts key in the playbook must match an actual inventory group name.
10+
- Fix the playbook and run ansible-playbook playbook.yml to apply.
11+
files:
12+
"playbook.yml": |
13+
- hosts: app-servers
14+
tasks:
15+
- name: Install nginx
16+
apt:
17+
name: nginx
18+
state: present
19+
become: true
20+
- name: Start nginx
21+
service:
22+
name: nginx
23+
state: started
24+
enabled: true
25+
become: true
26+
"inventory.ini": |
27+
[web-servers]
28+
web-01 ansible_host=10.0.1.10
29+
web-02 ansible_host=10.0.1.11
30+
"requirements.md": |
31+
# Web Server Requirements
32+
33+
- Install and start nginx on all web servers.
34+
- Use the inventory group name that matches the inventory file.
35+
- Run ansible-playbook playbook.yml --check to validate before applying.
36+
solution:
37+
summary: |
38+
The playbook targets the wrong host group app-servers. The inventory defines web-servers. Fix the hosts key to web-servers.
39+
steps:
40+
- Run ansible-inventory --list to list available host groups.
41+
- Review the playbook hosts key.
42+
- Change hosts: app-servers to hosts: web-servers.
43+
- Run ansible-playbook playbook.yml --check to validate.
44+
- Run ansible-playbook playbook.yml to apply the playbook.
45+
explanation: |
46+
Ansible playbooks specify the target inventory group with the hosts key. If the group name does not match any group in the inventory, the playbook runs against zero hosts and does nothing — or fails validation. The fix aligns the playbook host group with the inventory.
47+
outcome: |
48+
The playbook runs against web-servers, installs nginx, and starts the service on both web hosts.
49+
focusFileName: playbook.yml
50+
replacements:
51+
- fileName: playbook.yml
52+
search: |
53+
- hosts: app-servers
54+
replace: |
55+
- hosts: web-servers
56+
commands:
57+
- ansible-inventory --list
58+
- ansible-playbook playbook.yml --check
59+
- ansible-playbook playbook.yml
60+
backend:
61+
bucket: automation
62+
key: ansible-playbook
63+
table: runs
64+
locked: false
65+
lockId: null
66+
awsResources:
67+
- type: pipeline
68+
name: ansible-web
69+
id: run-442
70+
status: failed
71+
note: Playbook targets app-servers which does not match any inventory group.
72+
stateResources:
73+
- address: inventory.web-servers
74+
id: web-01, web-02
75+
- address: playbook.hosts
76+
id: app-servers
77+
flags:
78+
workflowFixed: false
79+
runPassing: false
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
id: azureDevOpsPipelineWrongVariableGroup
2+
kind: cicd
3+
title: Azure DevOps Pipeline Wrong Variable Group
4+
description: An Azure DevOps YAML pipeline fails to deploy because it references a variable group named prod-secrets while the actual group is named production-secrets.
5+
primaryFile: azure-pipelines.yml
6+
tips:
7+
- Inspect the failed pipeline run with az pipelines build list.
8+
- Run az pipelines build show --id 7231 to see the build details.
9+
- The error mentions the variable group name.
10+
- Run az pipelines variable-group list to find available groups.
11+
- Cross-check the group name in azure-pipelines.yml against the available groups.
12+
- Rerun the pipeline after the fix.
13+
files:
14+
"azure-pipelines.yml": |
15+
trigger:
16+
branches:
17+
include:
18+
- main
19+
20+
variables:
21+
- group: prod-secrets
22+
23+
stages:
24+
- stage: Build
25+
jobs:
26+
- job: BuildJob
27+
steps:
28+
- script: echo "Building application..."
29+
- stage: Deploy
30+
dependsOn: Build
31+
jobs:
32+
- job: DeployJob
33+
steps:
34+
- script: |
35+
echo "Deploying to production..."
36+
echo "Using SQL_PASSWORD from variable group: $SQL_PASSWORD"
37+
solution:
38+
summary: |
39+
The pipeline references a variable group named prod-secrets that does not exist. The actual variable group is named production-secrets.
40+
steps:
41+
- Run az pipelines build list to inspect recent pipeline runs.
42+
- Review the pipeline YAML to check the variable group reference.
43+
- Change the group name from prod-secrets to production-secrets in azure-pipelines.yml.
44+
- Rerun the pipeline with az pipelines run.
45+
explanation: |
46+
Azure DevOps variable groups are referenced by name in the pipeline YAML. If the name does not match an existing group, the pipeline fails at job start with a variable group resolution error.
47+
48+
The fix updates the group name to match the configured production-secrets group, allowing the deploy stage to access SQL_PASSWORD and other production variables.
49+
outcome: |
50+
The pipeline resolves production-secrets and the Deploy stage completes successfully.
51+
focusFileName: azure-pipelines.yml
52+
replacements:
53+
- fileName: azure-pipelines.yml
54+
search: |
55+
group: prod-secrets
56+
replace: |
57+
group: production-secrets
58+
commands:
59+
- az pipelines build list
60+
- az pipelines build show --id 7231
61+
- az pipelines variable-group list
62+
- az pipelines run
63+
backend:
64+
bucket: azure-devops
65+
key: azure-pipelines.yml
66+
table: pipeline-runs
67+
locked: false
68+
lockId: null
69+
awsResources:
70+
- type: pipeline
71+
name: deploy-platform
72+
id: build-7231
73+
status: failed
74+
note: Variable group prod-secrets was not found in this project.
75+
stateResources:
76+
- address: variable-group.prod-secrets
77+
id: not-found
78+
- address: variable-group.production-secrets
79+
id: SQL_PASSWORD, API_KEY, DB_HOST
80+
flags:
81+
workflowFixed: false
82+
runPassing: false

scenarios/iac/terraform-blank-azure-web-app.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tips:
1010
- The Linux Web App must run on a Linux App Service Plan.
1111
- The web app must force HTTPS with https_only = true.
1212
files:
13-
"main.tf": "# Write your Azure Terraform configuration here.\n"
13+
"main.tf": "# Write your Azure Terraform configuration here."
1414
"requirements.md": |
1515
# Azure Web App Infrastructure Requirements
1616

scenarios/iac/terraform-blank-ec2-web-server.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ tips:
88
- The web server must accept HTTP traffic from the public internet.
99
- The web server should use a t3.micro instance with the Amazon Linux 2 AMI ami-0c55b159cbfafe1f0.
1010
files:
11-
"main.tf": "# Write your Terraform configuration here.\n"
11+
"main.tf": "# Write your Terraform configuration here."
1212
"requirements.md": |
1313
# Web Server Infrastructure Requirements
1414
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
id: kubernetesBlankDeploymentService
2+
kind: kubernetes
3+
title: Kubernetes From Scratch: Deployment and Service
4+
description: Build a complete Kubernetes Deployment and Service YAML from an empty file, including labels, container port, replicas, and a ClusterIP service.
5+
primaryFile: deployment.yaml
6+
tips:
7+
- The Deployment must use a container image of nginx:1.25.
8+
- The container must expose port 80.
9+
- The Deployment must run 2 replicas with the label app: web.
10+
- The Service must expose port 80 and target port 80.
11+
- Use a ClusterIP Service type.
12+
- Validate with kubectl apply --dry-run=server -f deployment.yaml.
13+
files:
14+
"deployment.yaml": "# Write your Kubernetes Deployment and Service YAML here."
15+
"requirements.md": |
16+
# Web Deployment Requirements
17+
18+
## Deployment
19+
- Image: nginx:1.25
20+
- Container port: 80
21+
- Replicas: 2
22+
- Labels: app: web
23+
24+
## Service
25+
- Type: ClusterIP
26+
- Port: 80
27+
- Target port: 80
28+
- Selector: app: web
29+
solution:
30+
summary: |
31+
The Kubernetes YAML was built from an empty file. The Deployment and Service now form a complete, deployable web application configuration.
32+
steps:
33+
- Read requirements.md for the deployment specifications.
34+
- Build a Deployment with the nginx:1.25 image, port 80, 2 replicas, and app: web labels.
35+
- Build a ClusterIP Service exposing port 80 and targeting app: web pods.
36+
- Run kubectl apply --dry-run=server -f deployment.yaml to validate.
37+
- Run check to complete the scenario.
38+
explanation: |
39+
Building Kubernetes manifests from scratch teaches the core workload model: a Deployment manages replicas and a Pod template with containers and ports. A Service exposes those Pods using label selectors. Every resource needs apiVersion, kind, metadata, and spec sections.
40+
outcome: |
41+
Kubernetes From Scratch: Deployment and Service is a complete, valid configuration with a Deployment and ClusterIP Service.
42+
focusFileName: deployment.yaml
43+
files:
44+
"deployment.yaml": |
45+
apiVersion: apps/v1
46+
kind: Deployment
47+
metadata:
48+
name: web
49+
labels:
50+
app: web
51+
spec:
52+
replicas: 2
53+
selector:
54+
matchLabels:
55+
app: web
56+
template:
57+
metadata:
58+
labels:
59+
app: web
60+
spec:
61+
containers:
62+
- name: nginx
63+
image: nginx:1.25
64+
ports:
65+
- containerPort: 80
66+
---
67+
apiVersion: v1
68+
kind: Service
69+
metadata:
70+
name: web
71+
labels:
72+
app: web
73+
spec:
74+
type: ClusterIP
75+
selector:
76+
app: web
77+
ports:
78+
- port: 80
79+
targetPort: 80
80+
commands:
81+
- kubectl apply --dry-run=server -f deployment.yaml
82+
- check
83+
backend:
84+
bucket: kubernetes
85+
key: web
86+
table: workloads
87+
locked: false
88+
lockId: null
89+
awsResources:
90+
- type: deployment
91+
name: web
92+
id: default/web
93+
status: failed
94+
note: YAML is empty. Build a Deployment and ClusterIP Service with nginx, port 80, and 2 replicas.
95+
stateResources:
96+
- address: deployment.web
97+
id: not-created
98+
flags:
99+
initialized: false
100+
kubernetesValidated: false

src/commandHandlers.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import {
1818
githubSecretSet as runGithubSecretSet,
1919
jenkinsBuildLog as runJenkinsBuildLog,
2020
jenkinsRebuild as runJenkinsRebuild,
21+
azPipelinesBuildList as runAzPipelinesBuildList,
22+
azPipelinesBuildShow as runAzPipelinesBuildShow,
23+
azPipelinesRun as runAzPipelinesRun,
24+
azPipelinesVariableGroupList as runAzPipelinesVariableGroupList,
25+
ansibleInventoryList as runAnsibleInventoryList,
26+
ansiblePlaybookCheck as runAnsiblePlaybookCheck,
27+
ansiblePlaybookRun as runAnsiblePlaybookRun,
2128
} from "./simulators/cicd";
2229
import {
2330
cloudsecSimulatePrincipalPolicy as runCloudsecSimulatePrincipalPolicy,
@@ -53,6 +60,7 @@ import {
5360
runKubectlRolloutRestart,
5461
runKubectlRolloutStatus,
5562
runKubectlScale,
63+
kubernetesBlankValidate as runKubernetesBlankValidate,
5664
runHelmLint,
5765
runHelmTemplate,
5866
runHelmUpgrade,
@@ -173,6 +181,13 @@ export function createCommandHandlers(context: CommandHandlerContext): CommandHa
173181
githubSecretSet: (name?: string) => withRuntimeRefresh(() => runGithubSecretSet(runtime(), name)),
174182
jenkinsBuildLog: () => runJenkinsBuildLog(runtime(), scenarioId()),
175183
jenkinsRebuild: () => withRuntimeRefresh(() => runJenkinsRebuild(runtime(), scenarioId(), activeFileName())),
184+
azPipelinesBuildList: () => withRuntimeRefresh(() => runAzPipelinesBuildList(runtime(), scenarioId())),
185+
azPipelinesBuildShow: () => withRuntimeRefresh(() => runAzPipelinesBuildShow(runtime(), scenarioId())),
186+
azPipelinesRun: () => withRuntimeRefresh(() => runAzPipelinesRun(runtime(), scenarioId(), activeFileName())),
187+
azPipelinesVariableGroupList: () => withRuntimeRefresh(() => runAzPipelinesVariableGroupList(runtime(), scenarioId())),
188+
ansibleInventoryList: () => withRuntimeRefresh(() => runAnsibleInventoryList(runtime(), scenarioId())),
189+
ansiblePlaybookCheck: () => withRuntimeRefresh(() => runAnsiblePlaybookCheck(runtime(), scenarioId())),
190+
ansiblePlaybookRun: () => withRuntimeRefresh(() => runAnsiblePlaybookRun(runtime(), scenarioId())),
176191
argocdAppGet: () => withRuntimeRefresh(() => runArgocdAppGet(runtime(), scenarioId())),
177192
fluxReconcileKustomization: () => withRuntimeRefresh(() => runFluxReconcileKustomization(runtime(), scenarioId())),
178193
terragruntInit: () => withRuntimeRefresh(() => runTerragruntInit(runtime(), scenarioId())),
@@ -230,6 +245,7 @@ export function createCommandHandlers(context: CommandHandlerContext): CommandHa
230245
kubectlRolloutRestart: () => withRuntimeRefresh(() => runKubectlRolloutRestart(runtime(), scenarioId())),
231246
kubectlRolloutStatus: () => withRuntimeRefresh(() => runKubectlRolloutStatus(runtime(), scenarioId())),
232247
kubectlScaleDeployment: () => withRuntimeRefresh(() => runKubectlScale(runtime())),
248+
kubernetesBlankDryRun: () => withRuntimeRefresh(() => runKubernetesBlankValidate(runtime(), scenarioId())),
233249
helmLint: () => withRuntimeRefresh(() => runHelmLint(runtime(), scenarioId())),
234250
helmTemplate: () => withRuntimeRefresh(() => runHelmTemplate(runtime(), scenarioId())),
235251
helmUpgrade: () => withRuntimeRefresh(() => runHelmUpgrade(runtime(), scenarioId())),

src/commands.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export type CommandHandlers = {
2121
githubSecretSet: (name?: string) => string[];
2222
jenkinsBuildLog: () => string[];
2323
jenkinsRebuild: () => string[];
24+
azPipelinesBuildList: () => string[];
25+
azPipelinesBuildShow: () => string[];
26+
azPipelinesRun: () => string[];
27+
azPipelinesVariableGroupList: () => string[];
28+
ansibleInventoryList: () => string[];
29+
ansiblePlaybookCheck: () => string[];
30+
ansiblePlaybookRun: () => string[];
2431
argocdAppGet: () => string[];
2532
fluxReconcileKustomization: () => string[];
2633
terragruntInit: () => string[];
@@ -78,6 +85,7 @@ export type CommandHandlers = {
7885
kubectlRolloutRestart: () => string[];
7986
kubectlRolloutStatus: () => string[];
8087
kubectlScaleDeployment: () => string[];
88+
kubernetesBlankDryRun: () => string[];
8189
helmLint: () => string[];
8290
helmTemplate: () => string[];
8391
helmUpgrade: () => string[];
@@ -156,6 +164,13 @@ export function dispatchCommand(input: string, runtime: Scenario, handlers: Comm
156164
if (args[0] === "gh" && args[1] === "secret" && args[2] === "set") return handlers.githubSecretSet(args[3]);
157165
if (input === "jenkins build log") return handlers.jenkinsBuildLog();
158166
if (input === "jenkins rebuild") return handlers.jenkinsRebuild();
167+
if (input === "az pipelines build list") return handlers.azPipelinesBuildList();
168+
if (input === "az pipelines build show --id 7231") return handlers.azPipelinesBuildShow();
169+
if (input === "az pipelines run") return handlers.azPipelinesRun();
170+
if (input === "az pipelines variable-group list") return handlers.azPipelinesVariableGroupList();
171+
if (input === "ansible-inventory --list") return handlers.ansibleInventoryList();
172+
if (input === "ansible-playbook playbook.yml --check") return handlers.ansiblePlaybookCheck();
173+
if (input === "ansible-playbook playbook.yml") return handlers.ansiblePlaybookRun();
159174
}
160175

161176
if (runtime.kind === "linux") {
@@ -192,6 +207,7 @@ export function dispatchCommand(input: string, runtime: Scenario, handlers: Comm
192207
if (input === "kubectl rollout restart deployment checkout-api") return handlers.kubectlRolloutRestart();
193208
if (input === "kubectl rollout status deployment checkout-api") return handlers.kubectlRolloutStatus();
194209
if (input === "kubectl scale deployment checkout-api --replicas=2") return handlers.kubectlScaleDeployment();
210+
if (input === "kubectl apply --dry-run=server -f deployment.yaml") return handlers.kubernetesBlankDryRun();
195211
if (input === "helm lint checkout ./chart") return handlers.helmLint();
196212
if (input === "helm template checkout ./chart") return handlers.helmTemplate();
197213
if (input === "helm upgrade checkout ./chart") return handlers.helmUpgrade();
@@ -331,7 +347,7 @@ function commandHelp(runtime: Scenario): string[] {
331347
}
332348

333349
if (runtime.kind === "cicd") {
334-
return ["Available commands:", " gh run view", " gh run rerun", " gh secret list", " gh secret set <name>", " jenkins build log", " jenkins rebuild", " check", " help"];
350+
return ["Available commands:", " gh run view", " gh run rerun", " gh secret list", " gh secret set <name>", " jenkins build log", " jenkins rebuild", " az pipelines build list", " az pipelines build show --id <id>", " az pipelines variable-group list", " az pipelines run", " ansible-inventory --list", " ansible-playbook playbook.yml --check", " ansible-playbook playbook.yml", " check", " help"];
335351
}
336352

337353
if (runtime.kind === "gitops") {
@@ -359,7 +375,7 @@ function commandHelp(runtime: Scenario): string[] {
359375
}
360376

361377
if (runtime.kind === "kubernetes") {
362-
return ["Available commands:", " kubectl get pods", " kubectl get events", " kubectl describe deployment checkout-api", " kubectl describe pod checkout-api", " kubectl logs checkout-api", " kubectl get hpa checkout-api", " kubectl describe hpa checkout-api", " kubectl get pdb checkout-api", " kubectl apply -f hpa.yaml", " kubectl apply -f pdb.yaml", " kubectl drain ip-10-0-4-21 --ignore-daemonsets --delete-emptydir-data", " kubectl auth can-i get configmaps --as system:serviceaccount:payments:checkout-api -n payments", " aws iam list-roles", " aws iam get-role --role-name <name>", " aws sts assume-role-with-web-identity", " kubectl rollout restart deployment checkout-api", " kubectl rollout status deployment checkout-api", " kubectl scale deployment checkout-api --replicas=2", " helm lint checkout ./chart", " helm template checkout ./chart", " helm upgrade checkout ./chart", " check", " help"];
378+
return ["Available commands:", " kubectl get pods", " kubectl get events", " kubectl describe deployment checkout-api", " kubectl describe pod checkout-api", " kubectl logs checkout-api", " kubectl get hpa checkout-api", " kubectl describe hpa checkout-api", " kubectl get pdb checkout-api", " kubectl apply -f hpa.yaml", " kubectl apply -f pdb.yaml", " kubectl apply --dry-run=server -f deployment.yaml", " kubectl drain ip-10-0-4-21 --ignore-daemonsets --delete-emptydir-data", " kubectl auth can-i get configmaps --as system:serviceaccount:payments:checkout-api -n payments", " aws iam list-roles", " aws iam get-role --role-name <name>", " aws sts assume-role-with-web-identity", " kubectl rollout restart deployment checkout-api", " kubectl rollout status deployment checkout-api", " kubectl scale deployment checkout-api --replicas=2", " helm lint checkout ./chart", " helm template checkout ./chart", " helm upgrade checkout ./chart", " check", " help"];
363379
}
364380

365381
if (runtime.kind === "appsec") {

0 commit comments

Comments
 (0)