github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/plugins/shared/util_test.go (about) 1 package shared 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/hcl" 7 "github.com/hashicorp/hcl/hcl/ast" 8 hcl2 "github.com/hashicorp/hcl2/hcl" 9 "github.com/hashicorp/hcl2/hcldec" 10 "github.com/hashicorp/nomad/helper" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/kr/pretty" 13 "github.com/mitchellh/mapstructure" 14 "github.com/stretchr/testify/require" 15 "github.com/ugorji/go/codec" 16 "github.com/zclconf/go-cty/cty" 17 "github.com/zclconf/go-cty/cty/gocty" 18 ) 19 20 var ( 21 dockerSpec hcldec.Spec = hcldec.ObjectSpec(map[string]hcldec.Spec{ 22 "image": &hcldec.AttrSpec{ 23 Name: "image", 24 Type: cty.String, 25 Required: true, 26 }, 27 "args": &hcldec.AttrSpec{ 28 Name: "args", 29 Type: cty.List(cty.String), 30 }, 31 "pids_limit": &hcldec.AttrSpec{ 32 Name: "pids_limit", 33 Type: cty.Number, 34 }, 35 "port_map": &hcldec.BlockAttrsSpec{ 36 TypeName: "port_map", 37 ElementType: cty.String, 38 }, 39 40 "devices": &hcldec.BlockListSpec{ 41 TypeName: "devices", 42 Nested: hcldec.ObjectSpec(map[string]hcldec.Spec{ 43 "host_path": &hcldec.AttrSpec{ 44 Name: "host_path", 45 Type: cty.String, 46 }, 47 "container_path": &hcldec.AttrSpec{ 48 Name: "container_path", 49 Type: cty.String, 50 }, 51 "cgroup_permissions": &hcldec.DefaultSpec{ 52 Primary: &hcldec.AttrSpec{ 53 Name: "cgroup_permissions", 54 Type: cty.String, 55 }, 56 Default: &hcldec.LiteralSpec{ 57 Value: cty.StringVal(""), 58 }, 59 }, 60 }), 61 }, 62 }, 63 ) 64 ) 65 66 type dockerConfig struct { 67 Image string `cty:"image"` 68 Args []string `cty:"args"` 69 PidsLimit *int64 `cty:"pids_limit"` 70 PortMap map[string]string `cty:"port_map"` 71 Devices []DockerDevice `cty:"devices"` 72 } 73 74 type DockerDevice struct { 75 HostPath string `cty:"host_path"` 76 ContainerPath string `cty:"container_path"` 77 CgroupPermissions string `cty:"cgroup_permissions"` 78 } 79 80 func hclConfigToInterface(t *testing.T, config string) interface{} { 81 t.Helper() 82 83 // Parse as we do in the jobspec parser 84 root, err := hcl.Parse(config) 85 if err != nil { 86 t.Fatalf("failed to hcl parse the config: %v", err) 87 } 88 89 // Top-level item should be a list 90 list, ok := root.Node.(*ast.ObjectList) 91 if !ok { 92 t.Fatalf("root should be an object") 93 } 94 95 var m map[string]interface{} 96 if err := hcl.DecodeObject(&m, list.Items[0]); err != nil { 97 t.Fatalf("failed to decode object: %v", err) 98 } 99 100 var m2 map[string]interface{} 101 if err := mapstructure.WeakDecode(m, &m2); err != nil { 102 t.Fatalf("failed to weak decode object: %v", err) 103 } 104 105 return m2["config"] 106 } 107 108 func jsonConfigToInterface(t *testing.T, config string) interface{} { 109 t.Helper() 110 111 // Decode from json 112 dec := codec.NewDecoderBytes([]byte(config), structs.JsonHandle) 113 114 var m map[string]interface{} 115 err := dec.Decode(&m) 116 if err != nil { 117 t.Fatalf("failed to decode: %v", err) 118 } 119 120 return m["Config"] 121 } 122 123 func TestParseHclInterface_Hcl(t *testing.T) { 124 defaultCtx := &hcl2.EvalContext{ 125 Functions: GetStdlibFuncs(), 126 } 127 variableCtx := &hcl2.EvalContext{ 128 Functions: GetStdlibFuncs(), 129 Variables: map[string]cty.Value{ 130 "NOMAD_ALLOC_INDEX": cty.NumberIntVal(2), 131 "NOMAD_META_hello": cty.StringVal("world"), 132 }, 133 } 134 135 // XXX Useful for determining what cty thinks the type is 136 //implied, err := gocty.ImpliedType(&dockerConfig{}) 137 //if err != nil { 138 //t.Fatalf("implied type failed: %v", err) 139 //} 140 141 //t.Logf("Implied type: %v", implied.GoString()) 142 143 cases := []struct { 144 name string 145 config interface{} 146 spec hcldec.Spec 147 ctx *hcl2.EvalContext 148 expected interface{} 149 expectedType interface{} 150 }{ 151 { 152 name: "single string attr", 153 config: hclConfigToInterface(t, ` 154 config { 155 image = "redis:3.2" 156 }`), 157 spec: dockerSpec, 158 ctx: defaultCtx, 159 expected: &dockerConfig{ 160 Image: "redis:3.2", 161 Devices: []DockerDevice{}, 162 }, 163 expectedType: &dockerConfig{}, 164 }, 165 { 166 name: "single string attr json", 167 config: jsonConfigToInterface(t, ` 168 { 169 "Config": { 170 "image": "redis:3.2" 171 } 172 }`), 173 spec: dockerSpec, 174 ctx: defaultCtx, 175 expected: &dockerConfig{ 176 Image: "redis:3.2", 177 Devices: []DockerDevice{}, 178 }, 179 expectedType: &dockerConfig{}, 180 }, 181 { 182 name: "number attr", 183 config: hclConfigToInterface(t, ` 184 config { 185 image = "redis:3.2" 186 pids_limit = 2 187 }`), 188 spec: dockerSpec, 189 ctx: defaultCtx, 190 expected: &dockerConfig{ 191 Image: "redis:3.2", 192 PidsLimit: helper.Int64ToPtr(2), 193 Devices: []DockerDevice{}, 194 }, 195 expectedType: &dockerConfig{}, 196 }, 197 { 198 name: "number attr json", 199 config: jsonConfigToInterface(t, ` 200 { 201 "Config": { 202 "image": "redis:3.2", 203 "pids_limit": "2" 204 } 205 }`), 206 spec: dockerSpec, 207 ctx: defaultCtx, 208 expected: &dockerConfig{ 209 Image: "redis:3.2", 210 PidsLimit: helper.Int64ToPtr(2), 211 Devices: []DockerDevice{}, 212 }, 213 expectedType: &dockerConfig{}, 214 }, 215 { 216 name: "number attr interpolated", 217 config: hclConfigToInterface(t, ` 218 config { 219 image = "redis:3.2" 220 pids_limit = "${2 + 2}" 221 }`), 222 spec: dockerSpec, 223 ctx: defaultCtx, 224 expected: &dockerConfig{ 225 Image: "redis:3.2", 226 PidsLimit: helper.Int64ToPtr(4), 227 Devices: []DockerDevice{}, 228 }, 229 expectedType: &dockerConfig{}, 230 }, 231 { 232 name: "number attr interploated json", 233 config: jsonConfigToInterface(t, ` 234 { 235 "Config": { 236 "image": "redis:3.2", 237 "pids_limit": "${2 + 2}" 238 } 239 }`), 240 spec: dockerSpec, 241 ctx: defaultCtx, 242 expected: &dockerConfig{ 243 Image: "redis:3.2", 244 PidsLimit: helper.Int64ToPtr(4), 245 Devices: []DockerDevice{}, 246 }, 247 expectedType: &dockerConfig{}, 248 }, 249 { 250 name: "multi attr", 251 config: hclConfigToInterface(t, ` 252 config { 253 image = "redis:3.2" 254 args = ["foo", "bar"] 255 }`), 256 spec: dockerSpec, 257 ctx: defaultCtx, 258 expected: &dockerConfig{ 259 Image: "redis:3.2", 260 Args: []string{"foo", "bar"}, 261 Devices: []DockerDevice{}, 262 }, 263 expectedType: &dockerConfig{}, 264 }, 265 { 266 name: "multi attr json", 267 config: jsonConfigToInterface(t, ` 268 { 269 "Config": { 270 "image": "redis:3.2", 271 "args": ["foo", "bar"] 272 } 273 }`), 274 spec: dockerSpec, 275 ctx: defaultCtx, 276 expected: &dockerConfig{ 277 Image: "redis:3.2", 278 Args: []string{"foo", "bar"}, 279 Devices: []DockerDevice{}, 280 }, 281 expectedType: &dockerConfig{}, 282 }, 283 { 284 name: "multi attr variables", 285 config: hclConfigToInterface(t, ` 286 config { 287 image = "redis:3.2" 288 args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"] 289 pids_limit = "${NOMAD_ALLOC_INDEX + 2}" 290 }`), 291 spec: dockerSpec, 292 ctx: variableCtx, 293 expected: &dockerConfig{ 294 Image: "redis:3.2", 295 Args: []string{"world", "2"}, 296 PidsLimit: helper.Int64ToPtr(4), 297 Devices: []DockerDevice{}, 298 }, 299 expectedType: &dockerConfig{}, 300 }, 301 { 302 name: "multi attr variables json", 303 config: jsonConfigToInterface(t, ` 304 { 305 "Config": { 306 "image": "redis:3.2", 307 "args": ["foo", "bar"] 308 } 309 }`), 310 spec: dockerSpec, 311 ctx: defaultCtx, 312 expected: &dockerConfig{ 313 Image: "redis:3.2", 314 Args: []string{"foo", "bar"}, 315 Devices: []DockerDevice{}, 316 }, 317 expectedType: &dockerConfig{}, 318 }, 319 { 320 name: "port_map", 321 config: hclConfigToInterface(t, ` 322 config { 323 image = "redis:3.2" 324 port_map { 325 foo = "db" 326 bar = "db2" 327 } 328 }`), 329 spec: dockerSpec, 330 ctx: defaultCtx, 331 expected: &dockerConfig{ 332 Image: "redis:3.2", 333 PortMap: map[string]string{ 334 "foo": "db", 335 "bar": "db2", 336 }, 337 Devices: []DockerDevice{}, 338 }, 339 expectedType: &dockerConfig{}, 340 }, 341 { 342 name: "port_map json", 343 config: jsonConfigToInterface(t, ` 344 { 345 "Config": { 346 "image": "redis:3.2", 347 "port_map": [{ 348 "foo": "db", 349 "bar": "db2" 350 }] 351 } 352 }`), 353 spec: dockerSpec, 354 ctx: defaultCtx, 355 expected: &dockerConfig{ 356 Image: "redis:3.2", 357 PortMap: map[string]string{ 358 "foo": "db", 359 "bar": "db2", 360 }, 361 Devices: []DockerDevice{}, 362 }, 363 expectedType: &dockerConfig{}, 364 }, 365 { 366 name: "devices", 367 config: hclConfigToInterface(t, ` 368 config { 369 image = "redis:3.2" 370 devices = [ 371 { 372 host_path = "/dev/sda1" 373 container_path = "/dev/xvdc" 374 cgroup_permissions = "r" 375 }, 376 { 377 host_path = "/dev/sda2" 378 container_path = "/dev/xvdd" 379 } 380 ] 381 }`), 382 spec: dockerSpec, 383 ctx: defaultCtx, 384 expected: &dockerConfig{ 385 Image: "redis:3.2", 386 Devices: []DockerDevice{ 387 { 388 HostPath: "/dev/sda1", 389 ContainerPath: "/dev/xvdc", 390 CgroupPermissions: "r", 391 }, 392 { 393 HostPath: "/dev/sda2", 394 ContainerPath: "/dev/xvdd", 395 }, 396 }, 397 }, 398 expectedType: &dockerConfig{}, 399 }, 400 { 401 name: "devices json", 402 config: jsonConfigToInterface(t, ` 403 { 404 "Config": { 405 "image": "redis:3.2", 406 "devices": [ 407 { 408 "host_path": "/dev/sda1", 409 "container_path": "/dev/xvdc", 410 "cgroup_permissions": "r" 411 }, 412 { 413 "host_path": "/dev/sda2", 414 "container_path": "/dev/xvdd" 415 } 416 ] 417 } 418 }`), 419 spec: dockerSpec, 420 ctx: defaultCtx, 421 expected: &dockerConfig{ 422 Image: "redis:3.2", 423 Devices: []DockerDevice{ 424 { 425 HostPath: "/dev/sda1", 426 ContainerPath: "/dev/xvdc", 427 CgroupPermissions: "r", 428 }, 429 { 430 HostPath: "/dev/sda2", 431 ContainerPath: "/dev/xvdd", 432 }, 433 }, 434 }, 435 expectedType: &dockerConfig{}, 436 }, 437 } 438 439 for _, c := range cases { 440 t.Run(c.name, func(t *testing.T) { 441 t.Logf("Val: % #v", pretty.Formatter(c.config)) 442 // Parse the interface 443 ctyValue, diag := ParseHclInterface(c.config, c.spec, c.ctx) 444 if diag.HasErrors() { 445 for _, err := range diag.Errs() { 446 t.Error(err) 447 } 448 t.FailNow() 449 } 450 451 // Convert cty-value to go structs 452 require.NoError(t, gocty.FromCtyValue(ctyValue, c.expectedType)) 453 454 require.EqualValues(t, c.expected, c.expectedType) 455 456 }) 457 } 458 }