github.com/bigcommerce/nomad@v0.9.3-bc/helper/pluginutils/hclutils/util_test.go (about) 1 package hclutils_test 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/hcl2/hcldec" 7 "github.com/hashicorp/nomad/drivers/docker" 8 "github.com/hashicorp/nomad/helper/pluginutils/hclspecutils" 9 "github.com/hashicorp/nomad/helper/pluginutils/hclutils" 10 "github.com/hashicorp/nomad/plugins/drivers" 11 "github.com/hashicorp/nomad/plugins/shared/hclspec" 12 "github.com/kr/pretty" 13 "github.com/stretchr/testify/require" 14 "github.com/zclconf/go-cty/cty" 15 ) 16 17 func TestParseHclInterface_Hcl(t *testing.T) { 18 dockerDriver := new(docker.Driver) 19 dockerSpec, err := dockerDriver.TaskConfigSchema() 20 require.NoError(t, err) 21 dockerDecSpec, diags := hclspecutils.Convert(dockerSpec) 22 require.False(t, diags.HasErrors()) 23 24 vars := map[string]cty.Value{ 25 "NOMAD_ALLOC_INDEX": cty.NumberIntVal(2), 26 "NOMAD_META_hello": cty.StringVal("world"), 27 } 28 29 cases := []struct { 30 name string 31 config interface{} 32 spec hcldec.Spec 33 vars map[string]cty.Value 34 expected interface{} 35 expectedType interface{} 36 }{ 37 { 38 name: "single string attr", 39 config: hclutils.HclConfigToInterface(t, ` 40 config { 41 image = "redis:3.2" 42 }`), 43 spec: dockerDecSpec, 44 expected: &docker.TaskConfig{ 45 Image: "redis:3.2", 46 Devices: []docker.DockerDevice{}, 47 Mounts: []docker.DockerMount{}, 48 }, 49 expectedType: &docker.TaskConfig{}, 50 }, 51 { 52 name: "single string attr json", 53 config: hclutils.JsonConfigToInterface(t, ` 54 { 55 "Config": { 56 "image": "redis:3.2" 57 } 58 }`), 59 spec: dockerDecSpec, 60 expected: &docker.TaskConfig{ 61 Image: "redis:3.2", 62 Devices: []docker.DockerDevice{}, 63 Mounts: []docker.DockerMount{}, 64 }, 65 expectedType: &docker.TaskConfig{}, 66 }, 67 { 68 name: "number attr", 69 config: hclutils.HclConfigToInterface(t, ` 70 config { 71 image = "redis:3.2" 72 pids_limit = 2 73 }`), 74 spec: dockerDecSpec, 75 expected: &docker.TaskConfig{ 76 Image: "redis:3.2", 77 PidsLimit: 2, 78 Devices: []docker.DockerDevice{}, 79 Mounts: []docker.DockerMount{}, 80 }, 81 expectedType: &docker.TaskConfig{}, 82 }, 83 { 84 name: "number attr json", 85 config: hclutils.JsonConfigToInterface(t, ` 86 { 87 "Config": { 88 "image": "redis:3.2", 89 "pids_limit": "2" 90 } 91 }`), 92 spec: dockerDecSpec, 93 expected: &docker.TaskConfig{ 94 Image: "redis:3.2", 95 PidsLimit: 2, 96 Devices: []docker.DockerDevice{}, 97 Mounts: []docker.DockerMount{}, 98 }, 99 expectedType: &docker.TaskConfig{}, 100 }, 101 { 102 name: "number attr interpolated", 103 config: hclutils.HclConfigToInterface(t, ` 104 config { 105 image = "redis:3.2" 106 pids_limit = "${2 + 2}" 107 }`), 108 spec: dockerDecSpec, 109 expected: &docker.TaskConfig{ 110 Image: "redis:3.2", 111 PidsLimit: 4, 112 Devices: []docker.DockerDevice{}, 113 Mounts: []docker.DockerMount{}, 114 }, 115 expectedType: &docker.TaskConfig{}, 116 }, 117 { 118 name: "number attr interploated json", 119 config: hclutils.JsonConfigToInterface(t, ` 120 { 121 "Config": { 122 "image": "redis:3.2", 123 "pids_limit": "${2 + 2}" 124 } 125 }`), 126 spec: dockerDecSpec, 127 expected: &docker.TaskConfig{ 128 Image: "redis:3.2", 129 PidsLimit: 4, 130 Devices: []docker.DockerDevice{}, 131 Mounts: []docker.DockerMount{}, 132 }, 133 expectedType: &docker.TaskConfig{}, 134 }, 135 { 136 name: "multi attr", 137 config: hclutils.HclConfigToInterface(t, ` 138 config { 139 image = "redis:3.2" 140 args = ["foo", "bar"] 141 }`), 142 spec: dockerDecSpec, 143 expected: &docker.TaskConfig{ 144 Image: "redis:3.2", 145 Args: []string{"foo", "bar"}, 146 Devices: []docker.DockerDevice{}, 147 Mounts: []docker.DockerMount{}, 148 }, 149 expectedType: &docker.TaskConfig{}, 150 }, 151 { 152 name: "multi attr json", 153 config: hclutils.JsonConfigToInterface(t, ` 154 { 155 "Config": { 156 "image": "redis:3.2", 157 "args": ["foo", "bar"] 158 } 159 }`), 160 spec: dockerDecSpec, 161 expected: &docker.TaskConfig{ 162 Image: "redis:3.2", 163 Args: []string{"foo", "bar"}, 164 Devices: []docker.DockerDevice{}, 165 Mounts: []docker.DockerMount{}, 166 }, 167 expectedType: &docker.TaskConfig{}, 168 }, 169 { 170 name: "multi attr variables", 171 config: hclutils.HclConfigToInterface(t, ` 172 config { 173 image = "redis:3.2" 174 args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"] 175 pids_limit = "${NOMAD_ALLOC_INDEX + 2}" 176 }`), 177 spec: dockerDecSpec, 178 vars: vars, 179 expected: &docker.TaskConfig{ 180 Image: "redis:3.2", 181 Args: []string{"world", "2"}, 182 PidsLimit: 4, 183 Devices: []docker.DockerDevice{}, 184 Mounts: []docker.DockerMount{}, 185 }, 186 expectedType: &docker.TaskConfig{}, 187 }, 188 { 189 name: "multi attr variables json", 190 config: hclutils.JsonConfigToInterface(t, ` 191 { 192 "Config": { 193 "image": "redis:3.2", 194 "args": ["foo", "bar"] 195 } 196 }`), 197 spec: dockerDecSpec, 198 expected: &docker.TaskConfig{ 199 Image: "redis:3.2", 200 Args: []string{"foo", "bar"}, 201 Devices: []docker.DockerDevice{}, 202 Mounts: []docker.DockerMount{}, 203 }, 204 expectedType: &docker.TaskConfig{}, 205 }, 206 { 207 name: "port_map", 208 config: hclutils.HclConfigToInterface(t, ` 209 config { 210 image = "redis:3.2" 211 port_map { 212 foo = 1234 213 bar = 5678 214 } 215 }`), 216 spec: dockerDecSpec, 217 expected: &docker.TaskConfig{ 218 Image: "redis:3.2", 219 PortMap: map[string]int{ 220 "foo": 1234, 221 "bar": 5678, 222 }, 223 Devices: []docker.DockerDevice{}, 224 Mounts: []docker.DockerMount{}, 225 }, 226 expectedType: &docker.TaskConfig{}, 227 }, 228 { 229 name: "port_map json", 230 config: hclutils.JsonConfigToInterface(t, ` 231 { 232 "Config": { 233 "image": "redis:3.2", 234 "port_map": [{ 235 "foo": 1234, 236 "bar": 5678 237 }] 238 } 239 }`), 240 spec: dockerDecSpec, 241 expected: &docker.TaskConfig{ 242 Image: "redis:3.2", 243 PortMap: map[string]int{ 244 "foo": 1234, 245 "bar": 5678, 246 }, 247 Devices: []docker.DockerDevice{}, 248 Mounts: []docker.DockerMount{}, 249 }, 250 expectedType: &docker.TaskConfig{}, 251 }, 252 { 253 name: "devices", 254 config: hclutils.HclConfigToInterface(t, ` 255 config { 256 image = "redis:3.2" 257 devices = [ 258 { 259 host_path = "/dev/sda1" 260 container_path = "/dev/xvdc" 261 cgroup_permissions = "r" 262 }, 263 { 264 host_path = "/dev/sda2" 265 container_path = "/dev/xvdd" 266 } 267 ] 268 }`), 269 spec: dockerDecSpec, 270 expected: &docker.TaskConfig{ 271 Image: "redis:3.2", 272 Devices: []docker.DockerDevice{ 273 { 274 HostPath: "/dev/sda1", 275 ContainerPath: "/dev/xvdc", 276 CgroupPermissions: "r", 277 }, 278 { 279 HostPath: "/dev/sda2", 280 ContainerPath: "/dev/xvdd", 281 }, 282 }, 283 Mounts: []docker.DockerMount{}, 284 }, 285 expectedType: &docker.TaskConfig{}, 286 }, 287 { 288 name: "docker_logging", 289 config: hclutils.HclConfigToInterface(t, ` 290 config { 291 image = "redis:3.2" 292 network_mode = "host" 293 dns_servers = ["169.254.1.1"] 294 logging { 295 type = "syslog" 296 config { 297 tag = "driver-test" 298 } 299 } 300 }`), 301 spec: dockerDecSpec, 302 expected: &docker.TaskConfig{ 303 Image: "redis:3.2", 304 NetworkMode: "host", 305 DNSServers: []string{"169.254.1.1"}, 306 Logging: docker.DockerLogging{ 307 Type: "syslog", 308 Config: map[string]string{ 309 "tag": "driver-test", 310 }, 311 }, 312 Devices: []docker.DockerDevice{}, 313 Mounts: []docker.DockerMount{}, 314 }, 315 expectedType: &docker.TaskConfig{}, 316 }, 317 { 318 name: "docker_json", 319 config: hclutils.JsonConfigToInterface(t, ` 320 { 321 "Config": { 322 "image": "redis:3.2", 323 "devices": [ 324 { 325 "host_path": "/dev/sda1", 326 "container_path": "/dev/xvdc", 327 "cgroup_permissions": "r" 328 }, 329 { 330 "host_path": "/dev/sda2", 331 "container_path": "/dev/xvdd" 332 } 333 ] 334 } 335 }`), 336 spec: dockerDecSpec, 337 expected: &docker.TaskConfig{ 338 Image: "redis:3.2", 339 Devices: []docker.DockerDevice{ 340 { 341 HostPath: "/dev/sda1", 342 ContainerPath: "/dev/xvdc", 343 CgroupPermissions: "r", 344 }, 345 { 346 HostPath: "/dev/sda2", 347 ContainerPath: "/dev/xvdd", 348 }, 349 }, 350 Mounts: []docker.DockerMount{}, 351 }, 352 expectedType: &docker.TaskConfig{}, 353 }, 354 } 355 356 for _, c := range cases { 357 c := c 358 t.Run(c.name, func(t *testing.T) { 359 t.Logf("Val: % #v", pretty.Formatter(c.config)) 360 // Parse the interface 361 ctyValue, diag := hclutils.ParseHclInterface(c.config, c.spec, c.vars) 362 if diag.HasErrors() { 363 for _, err := range diag.Errs() { 364 t.Error(err) 365 } 366 t.FailNow() 367 } 368 369 // Test encoding 370 taskConfig := &drivers.TaskConfig{} 371 require.NoError(t, taskConfig.EncodeDriverConfig(ctyValue)) 372 373 // Test decoding 374 require.NoError(t, taskConfig.DecodeDriverConfig(c.expectedType)) 375 376 require.EqualValues(t, c.expected, c.expectedType) 377 378 }) 379 } 380 } 381 382 func TestParseNullFields(t *testing.T) { 383 spec := hclspec.NewObject(map[string]*hclspec.Spec{ 384 "array_field": hclspec.NewAttr("array_field", "list(string)", false), 385 "string_field": hclspec.NewAttr("string_field", "string", false), 386 "boolean_field": hclspec.NewAttr("boolean_field", "bool", false), 387 "number_field": hclspec.NewAttr("number_field", "number", false), 388 "block_field": hclspec.NewBlock("block_field", false, hclspec.NewObject((map[string]*hclspec.Spec{ 389 "f": hclspec.NewAttr("f", "string", true), 390 }))), 391 "block_list_field": hclspec.NewBlockList("block_list_field", hclspec.NewObject((map[string]*hclspec.Spec{ 392 "f": hclspec.NewAttr("f", "string", true), 393 }))), 394 }) 395 396 type Sub struct { 397 F string `codec:"f"` 398 } 399 400 type TaskConfig struct { 401 Array []string `codec:"array_field"` 402 String string `codec:"string_field"` 403 Boolean bool `codec:"boolean_field"` 404 Number int64 `codec:"number_field"` 405 Block Sub `codec:"block_field"` 406 BlockList []Sub `codec:"block_list_field"` 407 } 408 409 cases := []struct { 410 name string 411 json string 412 expected TaskConfig 413 }{ 414 { 415 "omitted fields", 416 `{"Config": {}}`, 417 TaskConfig{BlockList: []Sub{}}, 418 }, 419 { 420 "explicitly nil", 421 `{"Config": { 422 "array_field": null, 423 "string_field": null, 424 "boolean_field": null, 425 "number_field": null, 426 "block_field": null, 427 "block_list_field": null}}`, 428 TaskConfig{BlockList: []Sub{}}, 429 }, 430 { 431 // for sanity checking that the fields are actually set 432 "explicitly set to not null", 433 `{"Config": { 434 "array_field": ["a"], 435 "string_field": "a", 436 "boolean_field": true, 437 "number_field": 5, 438 "block_field": [{"f": "a"}], 439 "block_list_field": [{"f": "a"}, {"f": "b"}]}}`, 440 TaskConfig{ 441 Array: []string{"a"}, 442 String: "a", 443 Boolean: true, 444 Number: 5, 445 Block: Sub{"a"}, 446 BlockList: []Sub{{"a"}, {"b"}}, 447 }, 448 }, 449 } 450 451 parser := hclutils.NewConfigParser(spec) 452 for _, c := range cases { 453 t.Run(c.name, func(t *testing.T) { 454 var tc TaskConfig 455 parser.ParseJson(t, c.json, &tc) 456 457 require.EqualValues(t, c.expected, tc) 458 }) 459 } 460 }