github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 CPUCFSPeriod: 100000, 49 }, 50 expectedType: &docker.TaskConfig{}, 51 }, 52 { 53 name: "single string attr json", 54 config: hclutils.JsonConfigToInterface(t, ` 55 { 56 "Config": { 57 "image": "redis:3.2" 58 } 59 }`), 60 spec: dockerDecSpec, 61 expected: &docker.TaskConfig{ 62 Image: "redis:3.2", 63 Devices: []docker.DockerDevice{}, 64 Mounts: []docker.DockerMount{}, 65 CPUCFSPeriod: 100000, 66 }, 67 expectedType: &docker.TaskConfig{}, 68 }, 69 { 70 name: "number attr", 71 config: hclutils.HclConfigToInterface(t, ` 72 config { 73 image = "redis:3.2" 74 pids_limit = 2 75 }`), 76 spec: dockerDecSpec, 77 expected: &docker.TaskConfig{ 78 Image: "redis:3.2", 79 PidsLimit: 2, 80 Devices: []docker.DockerDevice{}, 81 Mounts: []docker.DockerMount{}, 82 CPUCFSPeriod: 100000, 83 }, 84 expectedType: &docker.TaskConfig{}, 85 }, 86 { 87 name: "number attr json", 88 config: hclutils.JsonConfigToInterface(t, ` 89 { 90 "Config": { 91 "image": "redis:3.2", 92 "pids_limit": "2" 93 } 94 }`), 95 spec: dockerDecSpec, 96 expected: &docker.TaskConfig{ 97 Image: "redis:3.2", 98 PidsLimit: 2, 99 Devices: []docker.DockerDevice{}, 100 Mounts: []docker.DockerMount{}, 101 CPUCFSPeriod: 100000, 102 }, 103 expectedType: &docker.TaskConfig{}, 104 }, 105 { 106 name: "number attr interpolated", 107 config: hclutils.HclConfigToInterface(t, ` 108 config { 109 image = "redis:3.2" 110 pids_limit = "${2 + 2}" 111 }`), 112 spec: dockerDecSpec, 113 expected: &docker.TaskConfig{ 114 Image: "redis:3.2", 115 PidsLimit: 4, 116 Devices: []docker.DockerDevice{}, 117 Mounts: []docker.DockerMount{}, 118 CPUCFSPeriod: 100000, 119 }, 120 expectedType: &docker.TaskConfig{}, 121 }, 122 { 123 name: "number attr interploated json", 124 config: hclutils.JsonConfigToInterface(t, ` 125 { 126 "Config": { 127 "image": "redis:3.2", 128 "pids_limit": "${2 + 2}" 129 } 130 }`), 131 spec: dockerDecSpec, 132 expected: &docker.TaskConfig{ 133 Image: "redis:3.2", 134 PidsLimit: 4, 135 Devices: []docker.DockerDevice{}, 136 Mounts: []docker.DockerMount{}, 137 CPUCFSPeriod: 100000, 138 }, 139 expectedType: &docker.TaskConfig{}, 140 }, 141 { 142 name: "multi attr", 143 config: hclutils.HclConfigToInterface(t, ` 144 config { 145 image = "redis:3.2" 146 args = ["foo", "bar"] 147 }`), 148 spec: dockerDecSpec, 149 expected: &docker.TaskConfig{ 150 Image: "redis:3.2", 151 Args: []string{"foo", "bar"}, 152 Devices: []docker.DockerDevice{}, 153 Mounts: []docker.DockerMount{}, 154 CPUCFSPeriod: 100000, 155 }, 156 expectedType: &docker.TaskConfig{}, 157 }, 158 { 159 name: "multi attr json", 160 config: hclutils.JsonConfigToInterface(t, ` 161 { 162 "Config": { 163 "image": "redis:3.2", 164 "args": ["foo", "bar"] 165 } 166 }`), 167 spec: dockerDecSpec, 168 expected: &docker.TaskConfig{ 169 Image: "redis:3.2", 170 Args: []string{"foo", "bar"}, 171 Devices: []docker.DockerDevice{}, 172 Mounts: []docker.DockerMount{}, 173 CPUCFSPeriod: 100000, 174 }, 175 expectedType: &docker.TaskConfig{}, 176 }, 177 { 178 name: "multi attr variables", 179 config: hclutils.HclConfigToInterface(t, ` 180 config { 181 image = "redis:3.2" 182 args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"] 183 pids_limit = "${NOMAD_ALLOC_INDEX + 2}" 184 }`), 185 spec: dockerDecSpec, 186 vars: vars, 187 expected: &docker.TaskConfig{ 188 Image: "redis:3.2", 189 Args: []string{"world", "2"}, 190 PidsLimit: 4, 191 Devices: []docker.DockerDevice{}, 192 Mounts: []docker.DockerMount{}, 193 CPUCFSPeriod: 100000, 194 }, 195 expectedType: &docker.TaskConfig{}, 196 }, 197 { 198 name: "multi attr variables json", 199 config: hclutils.JsonConfigToInterface(t, ` 200 { 201 "Config": { 202 "image": "redis:3.2", 203 "args": ["foo", "bar"] 204 } 205 }`), 206 spec: dockerDecSpec, 207 expected: &docker.TaskConfig{ 208 Image: "redis:3.2", 209 Args: []string{"foo", "bar"}, 210 Devices: []docker.DockerDevice{}, 211 Mounts: []docker.DockerMount{}, 212 CPUCFSPeriod: 100000, 213 }, 214 expectedType: &docker.TaskConfig{}, 215 }, 216 { 217 name: "port_map", 218 config: hclutils.HclConfigToInterface(t, ` 219 config { 220 image = "redis:3.2" 221 port_map { 222 foo = 1234 223 bar = 5678 224 } 225 }`), 226 spec: dockerDecSpec, 227 expected: &docker.TaskConfig{ 228 Image: "redis:3.2", 229 PortMap: map[string]int{ 230 "foo": 1234, 231 "bar": 5678, 232 }, 233 Devices: []docker.DockerDevice{}, 234 Mounts: []docker.DockerMount{}, 235 CPUCFSPeriod: 100000, 236 }, 237 expectedType: &docker.TaskConfig{}, 238 }, 239 { 240 name: "port_map json", 241 config: hclutils.JsonConfigToInterface(t, ` 242 { 243 "Config": { 244 "image": "redis:3.2", 245 "port_map": [{ 246 "foo": 1234, 247 "bar": 5678 248 }] 249 } 250 }`), 251 spec: dockerDecSpec, 252 expected: &docker.TaskConfig{ 253 Image: "redis:3.2", 254 PortMap: map[string]int{ 255 "foo": 1234, 256 "bar": 5678, 257 }, 258 Devices: []docker.DockerDevice{}, 259 Mounts: []docker.DockerMount{}, 260 CPUCFSPeriod: 100000, 261 }, 262 expectedType: &docker.TaskConfig{}, 263 }, 264 { 265 name: "devices", 266 config: hclutils.HclConfigToInterface(t, ` 267 config { 268 image = "redis:3.2" 269 devices = [ 270 { 271 host_path = "/dev/sda1" 272 container_path = "/dev/xvdc" 273 cgroup_permissions = "r" 274 }, 275 { 276 host_path = "/dev/sda2" 277 container_path = "/dev/xvdd" 278 } 279 ] 280 }`), 281 spec: dockerDecSpec, 282 expected: &docker.TaskConfig{ 283 Image: "redis:3.2", 284 Devices: []docker.DockerDevice{ 285 { 286 HostPath: "/dev/sda1", 287 ContainerPath: "/dev/xvdc", 288 CgroupPermissions: "r", 289 }, 290 { 291 HostPath: "/dev/sda2", 292 ContainerPath: "/dev/xvdd", 293 }, 294 }, 295 Mounts: []docker.DockerMount{}, 296 CPUCFSPeriod: 100000, 297 }, 298 expectedType: &docker.TaskConfig{}, 299 }, 300 { 301 name: "docker_logging", 302 config: hclutils.HclConfigToInterface(t, ` 303 config { 304 image = "redis:3.2" 305 network_mode = "host" 306 dns_servers = ["169.254.1.1"] 307 logging { 308 type = "syslog" 309 config { 310 tag = "driver-test" 311 } 312 } 313 }`), 314 spec: dockerDecSpec, 315 expected: &docker.TaskConfig{ 316 Image: "redis:3.2", 317 NetworkMode: "host", 318 DNSServers: []string{"169.254.1.1"}, 319 Logging: docker.DockerLogging{ 320 Type: "syslog", 321 Config: map[string]string{ 322 "tag": "driver-test", 323 }, 324 }, 325 Devices: []docker.DockerDevice{}, 326 Mounts: []docker.DockerMount{}, 327 CPUCFSPeriod: 100000, 328 }, 329 expectedType: &docker.TaskConfig{}, 330 }, 331 { 332 name: "docker_json", 333 config: hclutils.JsonConfigToInterface(t, ` 334 { 335 "Config": { 336 "image": "redis:3.2", 337 "devices": [ 338 { 339 "host_path": "/dev/sda1", 340 "container_path": "/dev/xvdc", 341 "cgroup_permissions": "r" 342 }, 343 { 344 "host_path": "/dev/sda2", 345 "container_path": "/dev/xvdd" 346 } 347 ] 348 } 349 }`), 350 spec: dockerDecSpec, 351 expected: &docker.TaskConfig{ 352 Image: "redis:3.2", 353 Devices: []docker.DockerDevice{ 354 { 355 HostPath: "/dev/sda1", 356 ContainerPath: "/dev/xvdc", 357 CgroupPermissions: "r", 358 }, 359 { 360 HostPath: "/dev/sda2", 361 ContainerPath: "/dev/xvdd", 362 }, 363 }, 364 Mounts: []docker.DockerMount{}, 365 CPUCFSPeriod: 100000, 366 }, 367 expectedType: &docker.TaskConfig{}, 368 }, 369 } 370 371 for _, c := range cases { 372 c := c 373 t.Run(c.name, func(t *testing.T) { 374 t.Logf("Val: % #v", pretty.Formatter(c.config)) 375 // Parse the interface 376 ctyValue, diag, errs := hclutils.ParseHclInterface(c.config, c.spec, c.vars) 377 if diag.HasErrors() { 378 for _, err := range errs { 379 t.Error(err) 380 } 381 t.FailNow() 382 } 383 384 // Test encoding 385 taskConfig := &drivers.TaskConfig{} 386 require.NoError(t, taskConfig.EncodeDriverConfig(ctyValue)) 387 388 // Test decoding 389 require.NoError(t, taskConfig.DecodeDriverConfig(c.expectedType)) 390 391 require.EqualValues(t, c.expected, c.expectedType) 392 393 }) 394 } 395 } 396 397 func TestParseNullFields(t *testing.T) { 398 spec := hclspec.NewObject(map[string]*hclspec.Spec{ 399 "array_field": hclspec.NewAttr("array_field", "list(string)", false), 400 "string_field": hclspec.NewAttr("string_field", "string", false), 401 "boolean_field": hclspec.NewAttr("boolean_field", "bool", false), 402 "number_field": hclspec.NewAttr("number_field", "number", false), 403 "block_field": hclspec.NewBlock("block_field", false, hclspec.NewObject((map[string]*hclspec.Spec{ 404 "f": hclspec.NewAttr("f", "string", true), 405 }))), 406 "block_list_field": hclspec.NewBlockList("block_list_field", hclspec.NewObject((map[string]*hclspec.Spec{ 407 "f": hclspec.NewAttr("f", "string", true), 408 }))), 409 }) 410 411 type Sub struct { 412 F string `codec:"f"` 413 } 414 415 type TaskConfig struct { 416 Array []string `codec:"array_field"` 417 String string `codec:"string_field"` 418 Boolean bool `codec:"boolean_field"` 419 Number int64 `codec:"number_field"` 420 Block Sub `codec:"block_field"` 421 BlockList []Sub `codec:"block_list_field"` 422 } 423 424 cases := []struct { 425 name string 426 json string 427 expected TaskConfig 428 }{ 429 { 430 "omitted fields", 431 `{"Config": {}}`, 432 TaskConfig{BlockList: []Sub{}}, 433 }, 434 { 435 "explicitly nil", 436 `{"Config": { 437 "array_field": null, 438 "string_field": null, 439 "boolean_field": null, 440 "number_field": null, 441 "block_field": null, 442 "block_list_field": null}}`, 443 TaskConfig{BlockList: []Sub{}}, 444 }, 445 { 446 // for sanity checking that the fields are actually set 447 "explicitly set to not null", 448 `{"Config": { 449 "array_field": ["a"], 450 "string_field": "a", 451 "boolean_field": true, 452 "number_field": 5, 453 "block_field": [{"f": "a"}], 454 "block_list_field": [{"f": "a"}, {"f": "b"}]}}`, 455 TaskConfig{ 456 Array: []string{"a"}, 457 String: "a", 458 Boolean: true, 459 Number: 5, 460 Block: Sub{"a"}, 461 BlockList: []Sub{{"a"}, {"b"}}, 462 }, 463 }, 464 } 465 466 parser := hclutils.NewConfigParser(spec) 467 for _, c := range cases { 468 t.Run(c.name, func(t *testing.T) { 469 var tc TaskConfig 470 parser.ParseJson(t, c.json, &tc) 471 472 require.EqualValues(t, c.expected, tc) 473 }) 474 } 475 } 476 477 func TestParseUnknown(t *testing.T) { 478 spec := hclspec.NewObject(map[string]*hclspec.Spec{ 479 "string_field": hclspec.NewAttr("string_field", "string", false), 480 "map_field": hclspec.NewAttr("map_field", "map(string)", false), 481 "list_field": hclspec.NewAttr("list_field", "map(string)", false), 482 "map_list_field": hclspec.NewAttr("map_list_field", "list(map(string))", false), 483 }) 484 cSpec, diags := hclspecutils.Convert(spec) 485 require.False(t, diags.HasErrors()) 486 487 cases := []struct { 488 name string 489 hcl string 490 }{ 491 { 492 "string field", 493 `config { string_field = "${MYENV}" }`, 494 }, 495 { 496 "map_field", 497 `config { map_field { key = "${MYENV}" }}`, 498 }, 499 { 500 "list_field", 501 `config { list_field = ["${MYENV}"]}`, 502 }, 503 { 504 "map_list_field", 505 `config { map_list_field { key = "${MYENV}"}}`, 506 }, 507 } 508 509 vars := map[string]cty.Value{} 510 511 for _, c := range cases { 512 t.Run(c.name, func(t *testing.T) { 513 inter := hclutils.HclConfigToInterface(t, c.hcl) 514 515 ctyValue, diag, errs := hclutils.ParseHclInterface(inter, cSpec, vars) 516 t.Logf("parsed: %# v", pretty.Formatter(ctyValue)) 517 518 require.NotNil(t, errs) 519 require.True(t, diag.HasErrors()) 520 require.Contains(t, errs[0].Error(), "no variable named") 521 }) 522 } 523 } 524 525 func TestParseInvalid(t *testing.T) { 526 dockerDriver := new(docker.Driver) 527 dockerSpec, err := dockerDriver.TaskConfigSchema() 528 require.NoError(t, err) 529 spec, diags := hclspecutils.Convert(dockerSpec) 530 require.False(t, diags.HasErrors()) 531 532 cases := []struct { 533 name string 534 hcl string 535 }{ 536 { 537 "invalid_field", 538 `config { image = "redis:3.2" bad_key = "whatever"}`, 539 }, 540 } 541 542 vars := map[string]cty.Value{} 543 544 for _, c := range cases { 545 t.Run(c.name, func(t *testing.T) { 546 inter := hclutils.HclConfigToInterface(t, c.hcl) 547 548 ctyValue, diag, errs := hclutils.ParseHclInterface(inter, spec, vars) 549 t.Logf("parsed: %# v", pretty.Formatter(ctyValue)) 550 551 require.NotNil(t, errs) 552 require.True(t, diag.HasErrors()) 553 require.Contains(t, errs[0].Error(), "Invalid label") 554 }) 555 } 556 }