Skip to content

Commit e6a60ff

Browse files
authored
chore: loading plan.json with string indexs (for_each) (#132)
When loading in values from a `plan.json`, loading `maps` and `lists` have to be merged with an existing hcl context.
1 parent 5355be4 commit e6a60ff

File tree

5 files changed

+764
-0
lines changed

5 files changed

+764
-0
lines changed

plan.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ func loadResourcesToContext(ctx *tfcontext.Context, resources []*tfjson.StateRes
143143
continue
144144
}
145145
merged = hclext.MergeWithTupleElement(existing, int(asInt), val)
146+
case string:
147+
keyStr, ok := resource.Index.(string)
148+
if !ok {
149+
return fmt.Errorf("unable to convert index '%v' for %q to a string", resource.Name, resource.Index)
150+
}
151+
152+
if !existing.CanIterateElements() {
153+
continue
154+
}
155+
156+
instances := existing.AsValueMap()
157+
instances[keyStr] = val
158+
merged = cty.ObjectVal(instances)
146159
case nil:
147160
merged = hclext.MergeObjects(existing, val)
148161
default:

preview_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,20 @@ func Test_Extract(t *testing.T) {
443443
unknownTags: []string{},
444444
params: map[string]assertParam{},
445445
},
446+
{
447+
name: "plan_stringindex",
448+
dir: "plan_stringindex",
449+
expTags: map[string]string{},
450+
input: preview.Input{
451+
PlanJSONPath: "plan.json",
452+
},
453+
unknownTags: []string{},
454+
params: map[string]assertParam{
455+
"jetbrains_ide": ap().
456+
optVals("GO", "IU", "PY").
457+
optNames("GoLand 2024.3", "IntelliJ IDEA Ultimate 2024.3", "PyCharm Professional 2024.3"),
458+
},
459+
},
446460
} {
447461
t.Run(tc.name, func(t *testing.T) {
448462
t.Parallel()
@@ -588,6 +602,16 @@ func (a assertParam) def(str string) assertParam {
588602
})
589603
}
590604

605+
func (a assertParam) optNames(opts ...string) assertParam {
606+
return a.extend(func(t *testing.T, parameter types.Parameter) {
607+
var values []string
608+
for _, opt := range parameter.Options {
609+
values = append(values, opt.Name)
610+
}
611+
assert.ElementsMatch(t, opts, values, "parameter option names equality check")
612+
})
613+
}
614+
591615
func (a assertParam) optVals(opts ...string) assertParam {
592616
return a.extend(func(t *testing.T, parameter types.Parameter) {
593617
var values []string
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jetbrains_ides = ["GO", "IU", "PY"]

testdata/plan_stringindex/main.tf

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
terraform {
2+
required_providers {
3+
coder = {
4+
source = "coder/coder"
5+
}
6+
docker = {
7+
source = "kreuzwerker/docker"
8+
version = "~> 3.0.0"
9+
}
10+
envbuilder = {
11+
source = "coder/envbuilder"
12+
}
13+
}
14+
}
15+
16+
variable "jetbrains_ides" {
17+
type = list(string)
18+
description = "The list of IDE product codes."
19+
default = ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"]
20+
validation {
21+
condition = (
22+
alltrue([
23+
for code in var.jetbrains_ides : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"], code)
24+
])
25+
)
26+
error_message = "The jetbrains_ides must be a list of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"])}."
27+
}
28+
# check if the list is empty
29+
validation {
30+
condition = length(var.jetbrains_ides) > 0
31+
error_message = "The jetbrains_ides must not be empty."
32+
}
33+
# check if the list contains duplicates
34+
validation {
35+
condition = length(var.jetbrains_ides) == length(toset(var.jetbrains_ides))
36+
error_message = "The jetbrains_ides must not contain duplicates."
37+
}
38+
}
39+
40+
variable "releases_base_link" {
41+
type = string
42+
description = ""
43+
default = "https://data.services.jetbrains.com"
44+
validation {
45+
condition = can(regex("^https?://.+$", var.releases_base_link))
46+
error_message = "The releases_base_link must be a valid HTTP/S address."
47+
}
48+
}
49+
50+
variable "channel" {
51+
type = string
52+
description = "JetBrains IDE release channel. Valid values are release and eap."
53+
default = "release"
54+
validation {
55+
condition = can(regex("^(release|eap)$", var.channel))
56+
error_message = "The channel must be either release or eap."
57+
}
58+
}
59+
60+
61+
data "http" "jetbrains_ide_versions" {
62+
for_each = toset(var.jetbrains_ides)
63+
url = "${var.releases_base_link}/products/releases?code=${each.key}&latest=true&type=${var.channel}"
64+
}
65+
66+
variable "download_base_link" {
67+
type = string
68+
description = ""
69+
default = "https://download.jetbrains.com"
70+
validation {
71+
condition = can(regex("^https?://.+$", var.download_base_link))
72+
error_message = "The download_base_link must be a valid HTTP/S address."
73+
}
74+
}
75+
76+
variable "arch" {
77+
type = string
78+
description = "The target architecture of the workspace"
79+
default = "amd64"
80+
validation {
81+
condition = contains(["amd64", "arm64"], var.arch)
82+
error_message = "Architecture must be either 'amd64' or 'arm64'."
83+
}
84+
}
85+
86+
variable "jetbrains_ide_versions" {
87+
type = map(object({
88+
build_number = string
89+
version = string
90+
}))
91+
description = "The set of versions for each jetbrains IDE"
92+
default = {
93+
"IU" = {
94+
build_number = "243.21565.193"
95+
version = "2024.3"
96+
}
97+
"PS" = {
98+
build_number = "243.21565.202"
99+
version = "2024.3"
100+
}
101+
"WS" = {
102+
build_number = "243.21565.180"
103+
version = "2024.3"
104+
}
105+
"PY" = {
106+
build_number = "243.21565.199"
107+
version = "2024.3"
108+
}
109+
"CL" = {
110+
build_number = "243.21565.238"
111+
version = "2024.1"
112+
}
113+
"GO" = {
114+
build_number = "243.21565.208"
115+
version = "2024.3"
116+
}
117+
"RM" = {
118+
build_number = "243.21565.197"
119+
version = "2024.3"
120+
}
121+
"RD" = {
122+
build_number = "243.21565.191"
123+
version = "2024.3"
124+
}
125+
"RR" = {
126+
build_number = "243.22562.230"
127+
version = "2024.3"
128+
}
129+
}
130+
validation {
131+
condition = (
132+
alltrue([
133+
for code in keys(var.jetbrains_ide_versions) : contains(["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"], code)
134+
])
135+
)
136+
error_message = "The jetbrains_ide_versions must contain a map of valid product codes. Valid product codes are ${join(",", ["IU", "PS", "WS", "PY", "CL", "GO", "RM", "RD", "RR"])}."
137+
}
138+
}
139+
140+
locals {
141+
# AMD64 versions of the images just use the version string, while ARM64
142+
# versions append "-aarch64". Eg:
143+
#
144+
# https://download.jetbrains.com/idea/ideaIU-2025.1.tar.gz
145+
# https://download.jetbrains.com/idea/ideaIU-2025.1.tar.gz
146+
#
147+
# We rewrite the data map above dynamically based on the user's architecture parameter.
148+
#
149+
effective_jetbrains_ide_versions = {
150+
for k, v in var.jetbrains_ide_versions : k => {
151+
build_number = v.build_number
152+
version = var.arch == "arm64" ? "${v.version}-aarch64" : v.version
153+
}
154+
}
155+
156+
# When downloading the latest IDE, the download link in the JSON is either:
157+
#
158+
# linux.download_link
159+
# linuxARM64.download_link
160+
#
161+
download_key = var.arch == "arm64" ? "linuxARM64" : "linux"
162+
163+
jetbrains_ides = {
164+
"GO" = {
165+
icon = "/icon/goland.svg",
166+
name = "GoLand",
167+
identifier = "GO",
168+
version = local.effective_jetbrains_ide_versions["GO"].version
169+
},
170+
"WS" = {
171+
icon = "/icon/webstorm.svg",
172+
name = "WebStorm",
173+
identifier = "WS",
174+
version = local.effective_jetbrains_ide_versions["WS"].version
175+
},
176+
"IU" = {
177+
icon = "/icon/intellij.svg",
178+
name = "IntelliJ IDEA Ultimate",
179+
identifier = "IU",
180+
version = local.effective_jetbrains_ide_versions["IU"].version
181+
},
182+
"PY" = {
183+
icon = "/icon/pycharm.svg",
184+
name = "PyCharm Professional",
185+
identifier = "PY",
186+
version = local.effective_jetbrains_ide_versions["PY"].version
187+
},
188+
"CL" = {
189+
icon = "/icon/clion.svg",
190+
name = "CLion",
191+
identifier = "CL",
192+
version = local.effective_jetbrains_ide_versions["CL"].version
193+
},
194+
"PS" = {
195+
icon = "/icon/phpstorm.svg",
196+
name = "PhpStorm",
197+
identifier = "PS",
198+
version = local.effective_jetbrains_ide_versions["PS"].version
199+
},
200+
"RM" = {
201+
icon = "/icon/rubymine.svg",
202+
name = "RubyMine",
203+
identifier = "RM",
204+
version = local.effective_jetbrains_ide_versions["RM"].version
205+
},
206+
"RD" = {
207+
icon = "/icon/rider.svg",
208+
name = "Rider",
209+
identifier = "RD",
210+
version = local.effective_jetbrains_ide_versions["RD"].version
211+
},
212+
"RR" = {
213+
icon = "/icon/rustrover.svg",
214+
name = "RustRover",
215+
identifier = "RR",
216+
version = local.effective_jetbrains_ide_versions["RR"].version
217+
}
218+
}
219+
}
220+
data "coder_parameter" "jetbrains_ide" {
221+
type = "string"
222+
name = "jetbrains_ide"
223+
display_name = "JetBrains IDE"
224+
icon = "/icon/gateway.svg"
225+
mutable = true
226+
default = var.jetbrains_ides[0]
227+
228+
dynamic "option" {
229+
for_each = var.jetbrains_ides
230+
content {
231+
icon = local.jetbrains_ides[option.value].icon
232+
name = "${local.jetbrains_ides[option.value].name} ${local.jetbrains_ides[option.value].version}"
233+
value = option.value
234+
}
235+
}
236+
}

0 commit comments

Comments
 (0)