diff --git a/drivers/docker/config_test.go b/drivers/docker/config_test.go index 47c71c55f..35506b672 100644 --- a/drivers/docker/config_test.go +++ b/drivers/docker/config_test.go @@ -56,6 +56,33 @@ func TestConfig_ParseJSON(t *testing.T) { Devices: []DockerDevice{}, }, }, + { + name: "nil values for 'volumes' field are safe", + input: `{"Config": {"image": "bash:3", "volumes": null}}`, + expected: TaskConfig{ + Image: "bash:3", + Mounts: []DockerMount{}, + Devices: []DockerDevice{}, + }, + }, + { + name: "nil values for 'args' field are safe", + input: `{"Config": {"image": "bash:3", "args": null}}`, + expected: TaskConfig{ + Image: "bash:3", + Mounts: []DockerMount{}, + Devices: []DockerDevice{}, + }, + }, + { + name: "nil values for string fields are safe", + input: `{"Config": {"image": "bash:3", "command": null}}`, + expected: TaskConfig{ + Image: "bash:3", + Mounts: []DockerMount{}, + Devices: []DockerDevice{}, + }, + }, } for _, c := range cases { diff --git a/helper/pluginutils/hclutils/util_test.go b/helper/pluginutils/hclutils/util_test.go index 1df74462b..9e6ba6d8e 100644 --- a/helper/pluginutils/hclutils/util_test.go +++ b/helper/pluginutils/hclutils/util_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/nomad/helper/pluginutils/hclspecutils" "github.com/hashicorp/nomad/helper/pluginutils/hclutils" "github.com/hashicorp/nomad/plugins/drivers" + "github.com/hashicorp/nomad/plugins/shared/hclspec" "github.com/kr/pretty" "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" @@ -377,3 +378,83 @@ func TestParseHclInterface_Hcl(t *testing.T) { }) } } + +func TestParseNullFields(t *testing.T) { + spec := hclspec.NewObject(map[string]*hclspec.Spec{ + "array_field": hclspec.NewAttr("array_field", "list(string)", false), + "string_field": hclspec.NewAttr("string_field", "string", false), + "boolean_field": hclspec.NewAttr("boolean_field", "bool", false), + "number_field": hclspec.NewAttr("number_field", "number", false), + "block_field": hclspec.NewBlock("block_field", false, hclspec.NewObject((map[string]*hclspec.Spec{ + "f": hclspec.NewAttr("f", "string", true), + }))), + "block_list_field": hclspec.NewBlockList("block_list_field", hclspec.NewObject((map[string]*hclspec.Spec{ + "f": hclspec.NewAttr("f", "string", true), + }))), + }) + + type Sub struct { + F string `codec:"f"` + } + + type TaskConfig struct { + Array []string `codec:"array_field"` + String string `codec:"string_field"` + Boolean bool `codec:"boolean_field"` + Number int64 `codec:"number_field"` + Block Sub `codec:"block_field"` + BlockList []Sub `codec:"block_list_field"` + } + + cases := []struct { + name string + json string + expected TaskConfig + }{ + { + "omitted fields", + `{"Config": {}}`, + TaskConfig{BlockList: []Sub{}}, + }, + { + "explicitly nil", + `{"Config": { + "array_field": null, + "string_field": null, + "boolean_field": null, + "number_field": null, + "block_field": null, + "block_list_field": null}}`, + TaskConfig{BlockList: []Sub{}}, + }, + { + // for sanity checking that the fields are actually set + "explicitly set to not null", + `{"Config": { + "array_field": ["a"], + "string_field": "a", + "boolean_field": true, + "number_field": 5, + "block_field": [{"f": "a"}], + "block_list_field": [{"f": "a"}, {"f": "b"}]}}`, + TaskConfig{ + Array: []string{"a"}, + String: "a", + Boolean: true, + Number: 5, + Block: Sub{"a"}, + BlockList: []Sub{{"a"}, {"b"}}, + }, + }, + } + + parser := hclutils.NewConfigParser(spec) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var tc TaskConfig + parser.ParseJson(t, c.json, &tc) + + require.EqualValues(t, c.expected, tc) + }) + } +} diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go index 4a046f58b..551360298 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/parser.go @@ -1242,7 +1242,13 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { panic("parseObjectCons called without peeker pointing to open brace") } - if forKeyword.TokenMatches(p.Peek()) { + // We must temporarily stop looking at newlines here while we check for + // a "for" keyword, since for expressions are _not_ newline-sensitive, + // even though object constructors are. + p.PushIncludeNewlines(false) + isFor := forKeyword.TokenMatches(p.Peek()) + p.PopIncludeNewlines() + if isFor { return p.finishParsingForExpr(open) } @@ -1377,6 +1383,8 @@ func (p *parser) parseObjectCons() (Expression, hcl.Diagnostics) { } func (p *parser) finishParsingForExpr(open Token) (Expression, hcl.Diagnostics) { + p.PushIncludeNewlines(false) + defer p.PopIncludeNewlines() introducer := p.Read() if !forKeyword.TokenMatches(introducer) { // Should never happen if callers are behaving diff --git a/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go b/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go index 73693ee14..bdc0e983e 100644 --- a/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go +++ b/vendor/github.com/hashicorp/hcl2/hcl/json/structure.go @@ -499,6 +499,8 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { return cty.DynamicVal, diags } return cty.ObjectVal(attrs), diags + case *nullVal: + return cty.NullVal(cty.DynamicPseudoType), nil default: // Default to DynamicVal so that ASTs containing invalid nodes can // still be partially-evaluated. diff --git a/vendor/vendor.json b/vendor/vendor.json index 0c78974b1..f8f968d03 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -213,12 +213,12 @@ {"path":"github.com/hashicorp/hcl/json/parser","checksumSHA1":"138aCV5n8n7tkGYMsMVQQnnLq+0=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"}, {"path":"github.com/hashicorp/hcl/json/scanner","checksumSHA1":"YdvFsNOMSWMLnY6fcliWQa0O5Fw=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"}, {"path":"github.com/hashicorp/hcl/json/token","checksumSHA1":"fNlXQCQEnb+B3k5UDL/r15xtSJY=","revision":"6e968a3fcdcbab092f5307fd0d85479d5af1e4dc","revisionTime":"2016-11-01T18:00:25Z"}, - {"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"RFEjfMQWPAVILXE2PhL6wDW8Zg4=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, - {"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"bUO4KS1yjAWa6miewgbUUsxYVfo=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, - {"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"mIcAvc+sEAQO5kIWuLVqnvXEU6Y=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, - {"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"56M/avlLyKDeMb0D8RqKcK2kja8=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, - {"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"6JRj4T/iQxIe/CoKXHDjPuupmL8=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, - {"path":"github.com/hashicorp/hcl2/hclwrite","checksumSHA1":"DQLzlvDUtHL1DkYDZrMx2vfKJUg=","revision":"fb2bc46cdbe36e247dac0c7dc185b34eaeb54c21","revisionTime":"2019-02-14T11:58:25Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"RFEjfMQWPAVILXE2PhL6wDW8Zg4=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"bUO4KS1yjAWa6miewgbUUsxYVfo=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"uMWQk/2xJyIqL6ILq83VYVxkuY8=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"Vagtn0ywFboMp7br/xvzeuNUFNc=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"6JRj4T/iQxIe/CoKXHDjPuupmL8=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, + {"path":"github.com/hashicorp/hcl2/hclwrite","checksumSHA1":"DQLzlvDUtHL1DkYDZrMx2vfKJUg=","revision":"fdf8e232b64f68d5335fa1be449b0160dadacdb5","revisionTime":"2019-03-05T17:45:54Z","version":"master","versionExact":"master"}, {"path":"github.com/hashicorp/logutils","checksumSHA1":"vt+P9D2yWDO3gdvdgCzwqunlhxU=","revision":"0dc08b1671f34c4250ce212759ebd880f743d883"}, {"path":"github.com/hashicorp/memberlist","checksumSHA1":"yAu2gPVXIh28yJ2If5gZPrf04kU=","revision":"1a62499c21db33d57691001d5e08a71ec857b18f","revisionTime":"2019-01-03T22:22:36Z"}, {"path":"github.com/hashicorp/net-rpc-msgpackrpc","checksumSHA1":"qnlqWJYV81ENr61SZk9c65R1mDo=","revision":"a14192a58a694c123d8fe5481d4a4727d6ae82f3"},