Skip to content

Commit a0525fd

Browse files
Copilotlonegunmanb
andcommitted
Add Azure (miniblue) version of terraform-cli-import scenario
Agent-Logs-Url: https://github.com/lonegunmanb/terraform-tutorial/sessions/3c1d9ae3-896c-41ac-8b41-0f34fd9a8fa1 Co-authored-by: lonegunmanb <2233414+lonegunmanb@users.noreply.github.com>
1 parent 2e6ab71 commit a0525fd

13 files changed

Lines changed: 695 additions & 1 deletion

File tree

docs/terraform-cli-import.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,10 @@ shell 中使用 `for_each` 的 key 时,注意引号的转义。Linux/macOS 中
135135
- 导入后务必运行 `terraform plan` 验证配置与实际状态的一致性。plan 有差异时应补全配置,否则下一次 apply 可能会修改或销毁已导入的资源。
136136
- provider 配置不能依赖 data source 的输出。
137137

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

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-show" },
3131
{ "path": "terraform-cli-output" },
3232
{ "path": "terraform-cli-import" },
33+
{ "path": "terraform-cli-import-azure" },
3334
{ "path": "terraform-cli-providers" },
3435
{ "path": "terraform-cli-state" },
3536
{ "path": "terraform-cli-workspace" },
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
miniblue:
3+
image: ghcr.io/lonegunmanb/miniblue:sha-8cc1c25
4+
ports:
5+
- "4566:4566"
6+
- "4567:4567"
7+
deploy:
8+
resources:
9+
limits:
10+
memory: 512M
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 用于 Step 2:for_each 导入
2+
variable "rg_envs" {
3+
type = set(string)
4+
default = ["dev", "staging"]
5+
}
6+
7+
resource "azurerm_resource_group" "per_env" {
8+
for_each = var.rg_envs
9+
name = "app-${each.key}-rg"
10+
location = "East US"
11+
tags = {
12+
Environment = each.key
13+
ManagedBy = "Terraform"
14+
}
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
}
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# 恭喜完成 terraform import 实战练习(Azure 版)!
2+
3+
## 知识总结
4+
5+
| 功能 | 方式 | 核心场景 |
6+
|------|------|---------|
7+
| 导入单个资源 | terraform import ADDR ID | 临时导入、快速接管 |
8+
| 导入到 count 资源 | terraform import 'ADDR[0]' ID | 多实例资源按索引导入 |
9+
| 导入到 for_each 资源 | terraform import 'ADDR["key"]' ID | 多实例资源按 key 导入 |
10+
11+
## Azure 版要点回顾
12+
13+
- azurerm 资源的 ID 是完整的 Azure Resource ID(形如 /subscriptions/{sub}/resourceGroups/{name}/providers/...),与 AWS 的简短 ID(桶名、实例 ID)不同
14+
- 本课程用 [miniblue](https://miniblue.io/) 在本地完整模拟 Azure REST API,无需真实订阅
15+
- 所有 import 子命令、参数、工作流与 AWS / LocalStack 版完全一致——这正是 Terraform 多云抽象的价值所在
16+
17+
## 导入工作流
18+
19+
```
20+
1. 在配置中声明 resource 块(可以是空的)
21+
2. 执行 terraform import
22+
3. 运行 terraform plan 查看差异
23+
4. 补全配置直到 plan 显示 No changes
24+
```
25+
26+
## 关键要点
27+
28+
- terraform import 不会自动生成配置,需要手动编写 resource 块
29+
- 每个远端资源只能导入到一个 Terraform 资源地址
30+
- 导入后务必运行 terraform plan 验证一致性
31+
- 声明式 import 块的用法请参考代码重构章节
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"title": "terraform import(Azure / miniblue 版):导入已有基础设施",
3+
"description": "通过 miniblue 模拟的 Azure 资源(Resource Group),掌握 terraform import 命令导入单个资源、补全配置、导入到 for_each 资源等核心能力",
4+
"details": {
5+
"intro": {
6+
"text": "init/init.md",
7+
"background": "init/background.sh",
8+
"foreground": "init/foreground.sh"
9+
},
10+
"steps": [
11+
{
12+
"title": "terraform import 命令:手动导入单个资源",
13+
"text": "step1/text.md"
14+
},
15+
{
16+
"title": "导入到 for_each 资源",
17+
"text": "step2/text.md"
18+
}
19+
],
20+
"finish": {
21+
"text": "finish/finish.md"
22+
},
23+
"assets": {
24+
"host01": [
25+
{"file": "setup-common.sh", "target": "/root", "chmod": "+x"},
26+
{"file": "main.tf", "target": "/root/workspace"},
27+
{"file": "docker-compose.yml", "target": "/root/workspace"},
28+
{"file": "foreach.tf", "target": "/root"}
29+
]
30+
}
31+
},
32+
"backend": {
33+
"imageid": "ubuntu"
34+
},
35+
"interface": {
36+
"layout": "editor-terminal"
37+
}
38+
}

0 commit comments

Comments
 (0)