Skip to content

Commit 6acab3c

Browse files
Copilotlonegunmanb
andcommitted
Add Azure / miniblue version of terraform-cli-output Killercoda lab
Agent-Logs-Url: https://github.com/lonegunmanb/terraform-tutorial/sessions/9e2a0373-21df-4bd4-aec7-a9e4cdcc4602 Co-authored-by: lonegunmanb <2233414+lonegunmanb@users.noreply.github.com>
1 parent 1f528b4 commit 6acab3c

12 files changed

Lines changed: 770 additions & 1 deletion

File tree

docs/terraform-cli-output.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,10 @@ BUCKET=$(cat infra-outputs.json | jq -r '.app_bucket.value')
149149
- 如果状态为空(从未执行过 `apply`),命令会报错:`No outputs found`
150150
- `terraform output` 是只读命令,不会修改状态或资源。
151151

152-
<KillercodaEmbed src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-output" />
152+
<KillercodaEmbed src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-output" title="实验环境(AWS / LocalStack 版)" />
153+
154+
<KillercodaEmbed
155+
src="https://killercoda.com/lonegunman-terraform-tutorial/course/terraform-tutorial/terraform-cli-output-azure"
156+
title="实验环境(Azure / miniblue 版)"
157+
desc="点击下方按钮在新标签页中打开 Azure 版 Killercoda 实验环境,预装了 Terraform + miniblue(Azure 本地模拟器),所有 output 子命令与 AWS 版完全一致。"
158+
/>

terraform-tutorial/structure.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
{ "path": "terraform-cli-validate" },
3131
{ "path": "terraform-cli-show" },
3232
{ "path": "terraform-cli-output" },
33+
{ "path": "terraform-cli-output-azure" },
3334
{ "path": "terraform-cli-import" },
3435
{ "path": "terraform-cli-import-azure" },
3536
{ "path": "terraform-cli-providers" },
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
services:
2+
miniblue:
3+
# 使用 fork 后的镜像(修复了 ARM 路由大小写敏感与 vnet privateEndpointVNetPolicies 不回传问题,兼容 azurerm v4),版本锁定
4+
image: ghcr.io/lonegunmanb/miniblue:sha-8cc1c25
5+
ports:
6+
- "4566:4566"
7+
- "4567:4567"
8+
deploy:
9+
resources:
10+
limits:
11+
memory: 512M
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
required_providers {
4+
azurerm = {
5+
source = "hashicorp/azurerm"
6+
version = "~> 4.0"
7+
}
8+
}
9+
}
10+
11+
provider "azurerm" {
12+
features {}
13+
14+
# miniblue HTTPS metadata endpoint
15+
metadata_host = "localhost:4567"
16+
resource_provider_registrations = "none"
17+
18+
# miniblue 接受任意凭据
19+
subscription_id = "00000000-0000-0000-0000-000000000000"
20+
tenant_id = "00000000-0000-0000-0000-000000000001"
21+
client_id = "miniblue"
22+
client_secret = "miniblue"
23+
}
24+
25+
variable "environment" {
26+
type = string
27+
default = "dev"
28+
description = "部署环境"
29+
}
30+
31+
variable "app_name" {
32+
type = string
33+
default = "myapp"
34+
description = "应用名称"
35+
}
36+
37+
variable "suffix" {
38+
type = string
39+
default = "lab"
40+
description = "资源名称后缀,用于避免命名冲突"
41+
}
42+
43+
locals {
44+
common_tags = {
45+
Environment = var.environment
46+
App = var.app_name
47+
ManagedBy = "Terraform"
48+
}
49+
}
50+
51+
resource "azurerm_resource_group" "main" {
52+
name = "${var.app_name}-${var.environment}-rg-${var.suffix}"
53+
location = "East US"
54+
tags = local.common_tags
55+
}
56+
57+
resource "azurerm_dns_zone" "app" {
58+
name = "${var.app_name}-${var.environment}-app-${var.suffix}.local"
59+
resource_group_name = azurerm_resource_group.main.name
60+
tags = local.common_tags
61+
}
62+
63+
resource "azurerm_dns_zone" "logs" {
64+
name = "${var.app_name}-${var.environment}-logs-${var.suffix}.local"
65+
resource_group_name = azurerm_resource_group.main.name
66+
tags = local.common_tags
67+
}
68+
69+
resource "azurerm_virtual_network" "net" {
70+
name = "${var.app_name}-${var.environment}-vnet-${var.suffix}"
71+
address_space = ["10.0.0.0/16"]
72+
location = azurerm_resource_group.main.location
73+
resource_group_name = azurerm_resource_group.main.name
74+
tags = local.common_tags
75+
}
76+
77+
# ── string output ──
78+
output "resource_group" {
79+
description = "资源组名称"
80+
value = azurerm_resource_group.main.name
81+
}
82+
83+
output "app_dns_zone" {
84+
description = "应用 DNS Zone 名称"
85+
value = azurerm_dns_zone.app.name
86+
}
87+
88+
output "logs_dns_zone" {
89+
description = "日志 DNS Zone 名称"
90+
value = azurerm_dns_zone.logs.name
91+
}
92+
93+
output "vnet" {
94+
description = "Virtual Network 名称"
95+
value = azurerm_virtual_network.net.name
96+
}
97+
98+
# ── sensitive output ──
99+
output "connection_string" {
100+
description = "应用连接信息(敏感)"
101+
sensitive = true
102+
value = "https://${azurerm_dns_zone.app.name}/api?token=miniblue-secret"
103+
}
104+
105+
# ── list output ──
106+
output "all_dns_zones" {
107+
description = "所有 DNS Zone 名称列表"
108+
value = [azurerm_dns_zone.app.name, azurerm_dns_zone.logs.name]
109+
}
110+
111+
# ── map output ──
112+
output "resource_summary" {
113+
description = "资源摘要"
114+
value = {
115+
resource_group = azurerm_resource_group.main.name
116+
app_dns_zone = azurerm_dns_zone.app.name
117+
logs_dns_zone = azurerm_dns_zone.logs.name
118+
vnet = azurerm_virtual_network.net.name
119+
environment = var.environment
120+
}
121+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#!/bin/bash
2+
# ─────────────────────────────────────────────────────────
3+
# setup-common.sh — shared setup functions for Killercoda scenarios
4+
#
5+
# This file is the SINGLE SOURCE OF TRUTH for common setup logic.
6+
# It is copied into each scenario's assets/ directory by:
7+
# npm run sync-setup (or automatically via prebuild)
8+
#
9+
# Usage in background.sh:
10+
# source /root/setup-common.sh
11+
# install_terraform
12+
# install_awscli # optional — AWS CLI v2 + awslocal wrapper
13+
# install_tflint # optional — only in scenarios that need it
14+
# start_localstack
15+
# install_theia_plugin
16+
# finish_setup
17+
# ─────────────────────────────────────────────────────────
18+
19+
TERRAFORM_VERSION="${TERRAFORM_VERSION:-1.14.8}"
20+
TFLINT_VERSION="${TFLINT_VERSION:-v0.61.0}"
21+
TERRAGRUNT_VERSION="${TERRAGRUNT_VERSION:-0.77.5}"
22+
# Note: the miniblue container image tag is pinned in scripts/miniblue-image.mjs
23+
# and propagated into every docker-compose.yml by `npm run sync-miniblue`.
24+
# There is no shell-side miniblue version variable — start_miniblue just runs
25+
# `docker compose up -d` against the pinned compose file.
26+
27+
install_terraform() {
28+
if ! command -v unzip > /dev/null 2>&1; then
29+
apt-get update -qq && apt-get install -y -qq unzip > /dev/null 2>&1
30+
fi
31+
32+
# Install Docker Compose v2 plugin (binary download — apt package not available on Killercoda)
33+
if ! docker compose version > /dev/null 2>&1; then
34+
mkdir -p /usr/local/lib/docker/cli-plugins
35+
curl --connect-timeout 10 --max-time 120 -fsSL \
36+
"https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64" \
37+
-o /usr/local/lib/docker/cli-plugins/docker-compose
38+
chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
39+
fi
40+
41+
curl --connect-timeout 10 --max-time 120 -fsSL \
42+
"https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" \
43+
-o /tmp/terraform.zip \
44+
&& unzip -o -q /tmp/terraform.zip -d /usr/local/bin/ \
45+
&& chmod +x /usr/local/bin/terraform \
46+
&& rm -f /tmp/terraform.zip
47+
48+
terraform version || echo "WARNING: terraform install failed"
49+
}
50+
51+
install_tflint() {
52+
curl --connect-timeout 10 --max-time 120 -fsSL \
53+
"https://github.com/terraform-linters/tflint/releases/download/${TFLINT_VERSION}/tflint_linux_amd64.zip" \
54+
-o /tmp/tflint.zip \
55+
&& unzip -o -q /tmp/tflint.zip -d /usr/local/bin/ \
56+
&& chmod +x /usr/local/bin/tflint \
57+
&& rm -f /tmp/tflint.zip
58+
59+
tflint --version || echo "WARNING: tflint install failed"
60+
}
61+
62+
install_terragrunt() {
63+
curl --connect-timeout 10 --max-time 120 -fsSL \
64+
"https://github.com/gruntwork-io/terragrunt/releases/download/v${TERRAGRUNT_VERSION}/terragrunt_linux_amd64" \
65+
-o /usr/local/bin/terragrunt \
66+
&& chmod +x /usr/local/bin/terragrunt
67+
68+
terragrunt --version || echo "WARNING: terragrunt install failed"
69+
}
70+
71+
install_awscli() {
72+
# Install AWS CLI v2 (official binary)
73+
curl --connect-timeout 10 --max-time 120 -fsSL \
74+
"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" \
75+
-o /tmp/awscliv2.zip \
76+
&& unzip -o -q /tmp/awscliv2.zip -d /tmp/ \
77+
&& /tmp/aws/install --update > /dev/null 2>&1 \
78+
&& rm -rf /tmp/awscliv2.zip /tmp/aws
79+
80+
aws --version || echo "WARNING: awscli install failed"
81+
82+
# Disable AWS CLI pager globally so output prints directly
83+
mkdir -p /root/.aws
84+
cat > /root/.aws/config <<'AWSCFG'
85+
[default]
86+
region = us-east-1
87+
output = json
88+
cli_pager =
89+
AWSCFG
90+
91+
# Install awscli-local (provides the 'awslocal' command)
92+
pip3 install --break-system-packages awscli-local > /dev/null 2>&1 \
93+
|| {
94+
# Fallback: create a shell wrapper if pip fails
95+
cat > /usr/local/bin/awslocal <<'WRAPPER'
96+
#!/bin/bash
97+
export AWS_PAGER=""
98+
exec aws --endpoint-url=http://localhost:4566 --region us-east-1 "$@"
99+
WRAPPER
100+
chmod +x /usr/local/bin/awslocal
101+
}
102+
103+
awslocal --version || echo "WARNING: awslocal install failed"
104+
}
105+
106+
start_localstack() {
107+
cd /root/workspace
108+
docker compose up -d
109+
110+
echo "Waiting for LocalStack to be ready..."
111+
for i in $(seq 1 60); do
112+
if curl -sf http://localhost:4566/_localstack/health > /dev/null 2>&1; then
113+
echo "LocalStack is ready."
114+
return 0
115+
fi
116+
sleep 2
117+
done
118+
echo "WARNING: LocalStack did not become healthy within 120 seconds"
119+
docker compose logs
120+
}
121+
122+
start_miniblue() {
123+
cd /root/workspace
124+
mkdir -p /root/.miniblue
125+
docker compose up -d
126+
127+
echo "Waiting for miniblue to be ready..."
128+
for i in $(seq 1 60); do
129+
if curl -sf http://localhost:4566/health > /dev/null 2>&1; then
130+
echo "miniblue is ready."
131+
# Trigger an HTTPS request so miniblue generates its self-signed cert.
132+
curl -sk https://localhost:4567/health > /dev/null 2>&1 || true
133+
134+
# Locate cert inside the container and copy to host.
135+
# The miniblue image is distroless (no shell), so we can't `docker exec`
136+
# to probe — `docker cp` itself is sufficient and silently fails on
137+
# missing paths. Try known paths in order.
138+
local cid
139+
cid=$(docker compose ps -q miniblue 2>/dev/null)
140+
for j in $(seq 1 30); do
141+
if [ -n "$cid" ]; then
142+
for p in /home/nonroot/.miniblue/cert.pem /root/.miniblue/cert.pem /app/.miniblue/cert.pem; do
143+
if docker cp "$cid:$p" /root/.miniblue/cert.pem 2>/dev/null; then
144+
break 2
145+
fi
146+
done
147+
fi
148+
sleep 1
149+
done
150+
151+
if [ ! -f /root/.miniblue/cert.pem ]; then
152+
echo "WARNING: failed to copy miniblue cert.pem out of container"
153+
else
154+
chmod 644 /root/.miniblue/cert.pem
155+
fi
156+
157+
# Make SSL_CERT_FILE available for all interactive shells
158+
cat > /etc/profile.d/miniblue.sh <<'PROF'
159+
export SSL_CERT_FILE=/root/.miniblue/cert.pem
160+
PROF
161+
chmod +x /etc/profile.d/miniblue.sh
162+
# Killercoda terminals are non-login interactive shells — they only source
163+
# ~/.bashrc, not /etc/profile.d/*. Append the export there too (idempotent).
164+
if ! grep -q 'SSL_CERT_FILE=/root/.miniblue/cert.pem' /root/.bashrc 2>/dev/null; then
165+
echo 'export SSL_CERT_FILE=/root/.miniblue/cert.pem' >> /root/.bashrc
166+
fi
167+
export SSL_CERT_FILE=/root/.miniblue/cert.pem
168+
return 0
169+
fi
170+
sleep 2
171+
done
172+
echo "WARNING: miniblue did not become healthy within 120 seconds"
173+
docker compose logs
174+
}
175+
176+
finish_setup() {
177+
touch /tmp/.setup-done
178+
}
179+
180+
install_azlocal() {
181+
# azlocal is a standalone Go CLI bundled inside the miniblue image at /azlocal
182+
# (analogous to awslocal for LocalStack). It uses HTTP on port 4566 and needs
183+
# no certificate or az CLI. Requires miniblue to be running.
184+
local cid
185+
cid=$(docker compose ps -q miniblue 2>/dev/null)
186+
if [ -z "$cid" ]; then
187+
cid=$(docker ps -q --filter "name=miniblue" | head -1)
188+
fi
189+
if [ -n "$cid" ]; then
190+
docker cp "$cid:/azlocal" /usr/local/bin/azlocal 2>/dev/null \
191+
&& chmod +x /usr/local/bin/azlocal
192+
fi
193+
azlocal --help > /dev/null 2>&1 || echo "WARNING: azlocal install failed"
194+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# 恭喜完成 terraform output 实战练习(Azure 版)!
2+
3+
## 知识总结
4+
5+
| 功能 | 命令 | 核心场景 |
6+
|------|------|---------|
7+
| 查看所有 output | terraform output | 快速检视基础设施的关键信息 |
8+
| 查看单个 output | terraform output NAME | 查询特定值(包括 sensitive) |
9+
| 原始字符串输出 | terraform output -raw NAME | Shell 脚本赋值(不带引号) |
10+
| JSON 格式输出 | terraform output -json | 自动化提取、CI/CD 传递值 |
11+
| 无颜色输出 | terraform output -no-color | 日志采集 |
12+
13+
## Azure 版要点回顾
14+
15+
- 本课程用 [miniblue](https://miniblue.io/) 在本地完整模拟 Azure REST API,无需真实订阅
16+
- azurerm provider 通过 metadata_host = "localhost:4567" 指向 miniblue 的 HTTPS 端点
17+
- 资源由 Resource Group + DNS Zone + Virtual Network 组成,配合 azlocal CLI 可验证 output 中的资源名是否真的存在于 miniblue
18+
- 所有 terraform output 子命令、参数、行为与 AWS / LocalStack 版完全一致——这正是 Terraform 多云抽象的价值所在
19+
20+
## 关键要点
21+
22+
- terraform output 只显示**根模块**的 output,子模块的 output 需在根模块显式暴露
23+
- 列出所有 output 时,sensitive 值显示为 &lt;sensitive&gt;;按名称查询时显示实际值
24+
- -json 和 -raw 输出中,sensitive 值均以明文显示
25+
- -raw 只支持 string、number、bool;复合类型必须用 -json
26+
- terraform output 是只读命令,不会修改状态或资源

0 commit comments

Comments
 (0)