block attr spec
This commit is contained in:
parent
d6b291b00d
commit
42b432d18d
|
@ -5,10 +5,8 @@ import (
|
|||
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/hashicorp/hcl2/gohcl"
|
||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcldec"
|
||||
"github.com/hashicorp/hcl2/hclparse"
|
||||
"github.com/hashicorp/nomad/helper"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/kr/pretty"
|
||||
|
@ -19,12 +17,6 @@ import (
|
|||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Martin suggests writing a function that takes a spec and map[string]interface{}
|
||||
and essentially fixes the []map[string]interface{} -> map[string]interface{}
|
||||
*/
|
||||
|
||||
var (
|
||||
dockerSpec hcldec.Spec = hcldec.ObjectSpec(map[string]hcldec.Spec{
|
||||
"image": &hcldec.AttrSpec{
|
||||
|
@ -40,11 +32,9 @@ var (
|
|||
Name: "pids_limit",
|
||||
Type: cty.Number,
|
||||
},
|
||||
"port_map": &hcldec.AttrSpec{
|
||||
Name: "port_map",
|
||||
|
||||
// This should be a block. cty.Map(cty.String)
|
||||
Type: cty.List(cty.Map(cty.String)),
|
||||
"port_map": &hcldec.BlockAttrsSpec{
|
||||
TypeName: "port_map",
|
||||
ElementType: cty.String,
|
||||
},
|
||||
|
||||
"devices": &hcldec.BlockListSpec{
|
||||
|
@ -74,11 +64,11 @@ var (
|
|||
)
|
||||
|
||||
type dockerConfig struct {
|
||||
Image string `cty:"image"`
|
||||
Args []string `cty:"args"`
|
||||
PidsLimit *int64 `cty:"pids_limit"`
|
||||
PortMap []map[string]string `cty:"port_map"`
|
||||
Devices []DockerDevice `cty:"devices"`
|
||||
Image string `cty:"image"`
|
||||
Args []string `cty:"args"`
|
||||
PidsLimit *int64 `cty:"pids_limit"`
|
||||
PortMap map[string]string `cty:"port_map"`
|
||||
Devices []DockerDevice `cty:"devices"`
|
||||
}
|
||||
|
||||
type DockerDevice struct {
|
||||
|
@ -175,11 +165,11 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "single string attr json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2"
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2"
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -188,14 +178,13 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "number attr",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
pids_limit = 2
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
pids_limit = 2
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -208,12 +197,12 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "number attr json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"pids_limit": "2"
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"pids_limit": "2"
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -223,14 +212,13 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "number attr interpolated",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
pids_limit = "${2 + 2}"
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
pids_limit = "${2 + 2}"
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -243,12 +231,12 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "number attr interploated json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"pids_limit": "${2 + 2}"
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"pids_limit": "${2 + 2}"
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -258,14 +246,13 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "multi attr",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
args = ["foo", "bar"]
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
args = ["foo", "bar"]
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -278,12 +265,12 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "multi attr json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"args": ["foo", "bar"]
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"args": ["foo", "bar"]
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -293,15 +280,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "multi attr variables",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"]
|
||||
pids_limit = "${NOMAD_ALLOC_INDEX + 2}"
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"]
|
||||
pids_limit = "${NOMAD_ALLOC_INDEX + 2}"
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: variableCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -315,12 +301,12 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "multi attr variables json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"args": ["foo", "bar"]
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"args": ["foo", "bar"]
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -330,26 +316,24 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "port_map",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
port_map {
|
||||
foo = "db"
|
||||
bar = "db2"
|
||||
}
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
port_map {
|
||||
foo = "db"
|
||||
bar = "db2"
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
Image: "redis:3.2",
|
||||
PortMap: []map[string]string{
|
||||
{
|
||||
"foo": "db",
|
||||
"bar": "db2",
|
||||
}},
|
||||
PortMap: map[string]string{
|
||||
"foo": "db",
|
||||
"bar": "db2",
|
||||
},
|
||||
Devices: []DockerDevice{},
|
||||
},
|
||||
expectedType: &dockerConfig{},
|
||||
|
@ -357,74 +341,44 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "port_map json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"port_map": [{
|
||||
"foo": "db",
|
||||
"bar": "db2"
|
||||
}]
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"port_map": [{
|
||||
"foo": "db",
|
||||
"bar": "db2"
|
||||
}]
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
Image: "redis:3.2",
|
||||
PortMap: []map[string]string{
|
||||
{
|
||||
"foo": "db",
|
||||
"bar": "db2",
|
||||
}},
|
||||
PortMap: map[string]string{
|
||||
"foo": "db",
|
||||
"bar": "db2",
|
||||
},
|
||||
Devices: []DockerDevice{},
|
||||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
/*
|
||||
{
|
||||
name: "port_map non-list json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"port_map": {
|
||||
"foo": "db",
|
||||
"bar": "db2"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
Image: "redis:3.2",
|
||||
PortMap: []map[string]string{
|
||||
{
|
||||
"foo": "db",
|
||||
"bar": "db2",
|
||||
}},
|
||||
Devices: []DockerDevice{},
|
||||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
*/
|
||||
// ------------------------------------------------
|
||||
{
|
||||
name: "devices",
|
||||
config: hclConfigToInterface(t, `
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
devices = [
|
||||
{
|
||||
host_path = "/dev/sda1"
|
||||
container_path = "/dev/xvdc"
|
||||
cgroup_permissions = "r"
|
||||
},
|
||||
{
|
||||
host_path = "/dev/sda2"
|
||||
container_path = "/dev/xvdd"
|
||||
}
|
||||
]
|
||||
}`),
|
||||
config {
|
||||
image = "redis:3.2"
|
||||
devices = [
|
||||
{
|
||||
host_path = "/dev/sda1"
|
||||
container_path = "/dev/xvdc"
|
||||
cgroup_permissions = "r"
|
||||
},
|
||||
{
|
||||
host_path = "/dev/sda2"
|
||||
container_path = "/dev/xvdd"
|
||||
}
|
||||
]
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -446,22 +400,22 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
{
|
||||
name: "devices json",
|
||||
config: jsonConfigToInterface(t, `
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"devices": [
|
||||
{
|
||||
"host_path": "/dev/sda1",
|
||||
"container_path": "/dev/xvdc",
|
||||
"cgroup_permissions": "r"
|
||||
},
|
||||
{
|
||||
"host_path": "/dev/sda2",
|
||||
"container_path": "/dev/xvdd"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`),
|
||||
{
|
||||
"Config": {
|
||||
"image": "redis:3.2",
|
||||
"devices": [
|
||||
{
|
||||
"host_path": "/dev/sda1",
|
||||
"container_path": "/dev/xvdc",
|
||||
"cgroup_permissions": "r"
|
||||
},
|
||||
{
|
||||
"host_path": "/dev/sda2",
|
||||
"container_path": "/dev/xvdd"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`),
|
||||
spec: dockerSpec,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
|
@ -480,7 +434,6 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
// ------------------------------------------------
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
@ -503,190 +456,3 @@ func TestParseHclInterface_Hcl(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
var (
|
||||
dockerSpec2 hcldec.Spec = hcldec.ObjectSpec(map[string]hcldec.Spec{
|
||||
"image": &hcldec.AttrSpec{
|
||||
Name: "image",
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"args": &hcldec.AttrSpec{
|
||||
Name: "args",
|
||||
Type: cty.List(cty.String),
|
||||
},
|
||||
//"port_map": &hcldec.AttrSpec{
|
||||
//Name: "port_map",
|
||||
//Type: cty.List(cty.Map(cty.String)),
|
||||
//},
|
||||
|
||||
//"devices": &hcldec.AttrSpec{
|
||||
//Name: "devices",
|
||||
//Type: cty.List(cty.Object(map[string]cty.Type{
|
||||
//"host_path": cty.String,
|
||||
//"container_path": cty.String,
|
||||
//"cgroup_permissions": cty.String,
|
||||
//})),
|
||||
//Type: cty.Tuple([]cty.Type{cty.Object(map[string]cty.Type{
|
||||
//"host_path": cty.String,
|
||||
//"container_path": cty.String,
|
||||
//"cgroup_permissions": cty.String,
|
||||
//})}),
|
||||
//},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func configToHcl2Interface(t *testing.T, config string) interface{} {
|
||||
t.Helper()
|
||||
|
||||
// Parse as we do in the jobspec parser
|
||||
file, diag := hclparse.NewParser().ParseHCL([]byte(config), "config")
|
||||
if diag.HasErrors() {
|
||||
t.Fatalf("failed to hcl parse the config: %v", diag.Error())
|
||||
}
|
||||
|
||||
//t.Logf("Body: % #v", pretty.Formatter(file.Body))
|
||||
|
||||
var c struct {
|
||||
m map[string]interface{}
|
||||
}
|
||||
implied, partial := gohcl.ImpliedBodySchema(&c)
|
||||
t.Logf("partial=%v implied=% #v", partial, pretty.Formatter(implied))
|
||||
|
||||
contents, diag := file.Body.Content(implied)
|
||||
if diag.HasErrors() {
|
||||
t.Fatalf("failed to get contents: %v", diag.Error())
|
||||
}
|
||||
|
||||
t.Fatalf("content=% #v", pretty.Formatter(contents))
|
||||
|
||||
//defaultCtx := &hcl2.EvalContext{
|
||||
//Functions: GetStdlibFuncs(),
|
||||
//}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestParseHclInterface_Hcl2(t *testing.T) {
|
||||
t.SkipNow()
|
||||
|
||||
defaultCtx := &hcl2.EvalContext{
|
||||
Functions: GetStdlibFuncs(),
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
config string
|
||||
spec hcldec.Spec
|
||||
ctx *hcl2.EvalContext
|
||||
expected interface{}
|
||||
expectedType interface{}
|
||||
}{
|
||||
{
|
||||
name: "single attr",
|
||||
config: `
|
||||
image = "redis:3.2"
|
||||
`,
|
||||
spec: dockerSpec2,
|
||||
ctx: defaultCtx,
|
||||
expected: &dockerConfig{
|
||||
Image: "redis:3.2",
|
||||
},
|
||||
expectedType: &dockerConfig{},
|
||||
},
|
||||
//{
|
||||
//name: "multi attr",
|
||||
//config: `
|
||||
//config {
|
||||
//image = "redis:3.2"
|
||||
//args = ["foo", "bar"]
|
||||
//}`,
|
||||
//spec: dockerSpec2,
|
||||
//ctx: defaultCtx,
|
||||
//expected: &dockerConfig{
|
||||
//Image: "redis:3.2",
|
||||
//Args: []string{"foo", "bar"},
|
||||
//},
|
||||
//expectedType: &dockerConfig{},
|
||||
//},
|
||||
//{
|
||||
//name: "port_map",
|
||||
//config: `
|
||||
//config {
|
||||
//image = "redis:3.2"
|
||||
//port_map {
|
||||
//foo = "db"
|
||||
//}
|
||||
//}`,
|
||||
//spec: dockerSpec,
|
||||
//ctx: defaultCtx,
|
||||
//expected: &dockerConfig{
|
||||
//Image: "redis:3.2",
|
||||
//PortMap: []map[string]string{{"foo": "db"}},
|
||||
//},
|
||||
//expectedType: &dockerConfig{},
|
||||
//},
|
||||
|
||||
//{
|
||||
//name: "devices",
|
||||
//config: `
|
||||
//config {
|
||||
//image = "redis:3.2"
|
||||
//devices = [
|
||||
//{
|
||||
//host_path = "/dev/sda1"
|
||||
//container_path = "/dev/xvdc"
|
||||
//cgroup_permissions = "r"
|
||||
//},
|
||||
//{
|
||||
//host_path = "/dev/sda2"
|
||||
//container_path = "/dev/xvdd"
|
||||
//}
|
||||
//]
|
||||
//}`,
|
||||
//spec: dockerSpec,
|
||||
//ctx: defaultCtx,
|
||||
//expected: &dockerConfig{
|
||||
//Image: "redis:3.2",
|
||||
//Args: []string{"foo", "bar"},
|
||||
//Devices: []DockerDevice{
|
||||
//{
|
||||
//HostPath: "/dev/sda1",
|
||||
//ContainerPath: "/dev/xvdc",
|
||||
//CgroupPermissions: "r",
|
||||
//},
|
||||
//{
|
||||
//HostPath: "/dev/sda2",
|
||||
//ContainerPath: "/dev/xvdd",
|
||||
//},
|
||||
//},
|
||||
//},
|
||||
//expectedType: &dockerConfig{},
|
||||
//},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
// Convert the config to a value
|
||||
v := configToHcl2Interface(t, c.config)
|
||||
t.Logf("value: % #v", pretty.Formatter(v))
|
||||
|
||||
// Parse the interface
|
||||
ctyValue, diag := ParseHclInterface(v, c.spec, c.ctx)
|
||||
if diag.HasErrors() {
|
||||
for _, err := range diag.Errs() {
|
||||
t.Error(err)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Convert cty-value to go structs
|
||||
require.NoError(t, gocty.FromCtyValue(ctyValue, c.expectedType))
|
||||
|
||||
require.EqualValues(t, c.expected, c.expectedType)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package hcldec
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
@ -765,6 +766,163 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []block
|
|||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||
}
|
||||
|
||||
// A BlockAttrsSpec is a Spec that interprets a single block as if it were
|
||||
// a map of some element type. That is, each attribute within the block
|
||||
// becomes a key in the resulting map and the attribute's value becomes the
|
||||
// element value, after conversion to the given element type. The resulting
|
||||
// value is a cty.Map of the given element type.
|
||||
//
|
||||
// This spec imposes a validation constraint that there be exactly one block
|
||||
// of the given type name and that this block may contain only attributes. The
|
||||
// block does not accept any labels.
|
||||
//
|
||||
// This is an alternative to an AttrSpec of a map type for situations where
|
||||
// block syntax is desired. Note that block syntax does not permit dynamic
|
||||
// keys, construction of the result via a "for" expression, etc. In most cases
|
||||
// an AttrSpec is preferred if the desired result is a map whose keys are
|
||||
// chosen by the user rather than by schema.
|
||||
type BlockAttrsSpec struct {
|
||||
TypeName string
|
||||
ElementType cty.Type
|
||||
Required bool
|
||||
}
|
||||
|
||||
func (s *BlockAttrsSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
// leaf node
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockAttrsSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||
return []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: s.TypeName,
|
||||
LabelNames: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockAttrsSpec) nestedSpec() Spec {
|
||||
// This is an odd case: we aren't actually going to apply a nested spec
|
||||
// in this case, since we're going to interpret the body directly as
|
||||
// attributes, but we need to return something non-nil so that the
|
||||
// decoder will recognize this as a block spec. We won't actually be
|
||||
// using this for anything at decode time.
|
||||
return noopSpec{}
|
||||
}
|
||||
|
||||
// specNeedingVariables implementation
|
||||
func (s *BlockAttrsSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
|
||||
block, _ := s.findBlock(content)
|
||||
if block == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var vars []hcl.Traversal
|
||||
|
||||
attrs, diags := block.Body.JustAttributes()
|
||||
if diags.HasErrors() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, attr := range attrs {
|
||||
vars = append(vars, attr.Expr.Variables()...)
|
||||
}
|
||||
|
||||
// We'll return the variables references in source order so that any
|
||||
// error messages that result are also in source order.
|
||||
sort.Slice(vars, func(i, j int) bool {
|
||||
return vars[i].SourceRange().Start.Byte < vars[j].SourceRange().Start.Byte
|
||||
})
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
block, other := s.findBlock(content)
|
||||
if block == nil {
|
||||
if s.Required {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Missing %s block", s.TypeName),
|
||||
Detail: fmt.Sprintf(
|
||||
"A block of type %q is required here.", s.TypeName,
|
||||
),
|
||||
Subject: &content.MissingItemRange,
|
||||
})
|
||||
}
|
||||
return cty.NullVal(cty.Map(s.ElementType)), diags
|
||||
}
|
||||
if other != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
|
||||
Detail: fmt.Sprintf(
|
||||
"Only one block of type %q is allowed. Previous definition was at %s.",
|
||||
s.TypeName, block.DefRange.String(),
|
||||
),
|
||||
Subject: &other.DefRange,
|
||||
})
|
||||
}
|
||||
|
||||
attrs, attrDiags := block.Body.JustAttributes()
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
if len(attrs) == 0 {
|
||||
return cty.MapValEmpty(s.ElementType), diags
|
||||
}
|
||||
|
||||
vals := make(map[string]cty.Value, len(attrs))
|
||||
for name, attr := range attrs {
|
||||
attrVal, attrDiags := attr.Expr.Value(ctx)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
attrVal, err := convert.Convert(attrVal, s.ElementType)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute value",
|
||||
Detail: fmt.Sprintf("Invalid value for attribute of %q block: %s.", s.TypeName, err),
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
})
|
||||
attrVal = cty.UnknownVal(s.ElementType)
|
||||
}
|
||||
|
||||
vals[name] = attrVal
|
||||
}
|
||||
|
||||
return cty.MapVal(vals), diags
|
||||
}
|
||||
|
||||
func (s *BlockAttrsSpec) impliedType() cty.Type {
|
||||
return cty.Map(s.ElementType)
|
||||
}
|
||||
|
||||
func (s *BlockAttrsSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
block, _ := s.findBlock(content)
|
||||
if block == nil {
|
||||
return content.MissingItemRange
|
||||
}
|
||||
return block.DefRange
|
||||
}
|
||||
|
||||
func (s *BlockAttrsSpec) findBlock(content *hcl.BodyContent) (block *hcl.Block, other *hcl.Block) {
|
||||
for _, candidate := range content.Blocks {
|
||||
if candidate.Type != s.TypeName {
|
||||
continue
|
||||
}
|
||||
if block != nil {
|
||||
return block, candidate
|
||||
}
|
||||
block = candidate
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// A BlockLabelSpec is a Spec that returns a cty.String representing the
|
||||
// label of the block its given body belongs to, if indeed its given body
|
||||
// belongs to a block. It is a programming error to use this in a non-block
|
||||
|
@ -1038,3 +1196,28 @@ func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []
|
|||
// not super-accurate, because there's nothing better to return.
|
||||
return s.Wrapped.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
||||
// noopSpec is a placeholder spec that does nothing, used in situations where
|
||||
// a non-nil placeholder spec is required. It is not exported because there is
|
||||
// no reason to use it directly; it is always an implementation detail only.
|
||||
type noopSpec struct {
|
||||
}
|
||||
|
||||
func (s noopSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return cty.NullVal(cty.DynamicPseudoType), nil
|
||||
}
|
||||
|
||||
func (s noopSpec) impliedType() cty.Type {
|
||||
return cty.DynamicPseudoType
|
||||
}
|
||||
|
||||
func (s noopSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
func (s noopSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// No useful range for a noopSpec, and nobody should be calling this anyway.
|
||||
return hcl.Range{
|
||||
Filename: "noopSpec",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func MismatchMessage(got, want cty.Type) string {
|
|||
|
||||
default:
|
||||
// If we have nothing better to say, we'll just state what was required.
|
||||
return want.FriendlyNameForConstraint() + " required"
|
||||
return want.FriendlyNameForConstraint() + " required; got %v" + got.GoString()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
{"path":"github.com/go-ole/go-ole","checksumSHA1":"IvHj/4iR2nYa/S3cB2GXoyDG/xQ=","comment":"v1.2.0-4-g5005588","revision":"085abb85892dc1949567b726dff00fa226c60c45","revisionTime":"2017-07-12T17:44:59Z"},
|
||||
{"path":"github.com/go-ole/go-ole/oleutil","comment":"v1.2.0-4-g5005588","revision":"50055884d646dd9434f16bbb5c9801749b9bafe4"},
|
||||
{"path":"github.com/gogo/protobuf/proto","checksumSHA1":"I460dM/HmGE9DWimQvd1hJkYosU=","revision":"616a82ed12d78d24d4839363e8f3c5d3f20627cf","revisionTime":"2017-11-09T18:15:19Z"},
|
||||
{"path":"github.com/golang/protobuf/proto","checksumSHA1":"Pyou8mceOASSFxc7GeXZuVdSMi0=","origin":"github.com/hashicorp/nomad/vendor/github.com/golang/protobuf/proto","revision":"b4deda0973fb4c70b50d226b1af49f3da59f5265","revisionTime":"2018-04-30T18:52:41Z","version":"v1","versionExact":"v1.1.0"},
|
||||
{"path":"github.com/golang/protobuf/proto","checksumSHA1":"Pyou8mceOASSFxc7GeXZuVdSMi0=","revision":"b4deda0973fb4c70b50d226b1af49f3da59f5265","revisionTime":"2018-04-30T18:52:41Z","version":"v1","versionExact":"v1.1.0"},
|
||||
{"path":"github.com/golang/protobuf/ptypes","checksumSHA1":"/s0InJhSrxhTpqw5FIKgSMknCfM=","revision":"b4deda0973fb4c70b50d226b1af49f3da59f5265","revisionTime":"2018-04-30T18:52:41Z","version":"v1","versionExact":"v1.1.0"},
|
||||
{"path":"github.com/golang/protobuf/ptypes/any","checksumSHA1":"3eqU9o+NMZSLM/coY5WDq7C1uKg=","revision":"b4deda0973fb4c70b50d226b1af49f3da59f5265","revisionTime":"2018-04-30T18:52:41Z","version":"v1","versionExact":"v1.1.0"},
|
||||
{"path":"github.com/golang/protobuf/ptypes/duration","checksumSHA1":"ZIF0rnVzNLluFPqUebtJrVonMr4=","revision":"b4deda0973fb4c70b50d226b1af49f3da59f5265","revisionTime":"2018-04-30T18:52:41Z","version":"v1","versionExact":"v1.1.0"},
|
||||
|
@ -180,13 +180,13 @@
|
|||
{"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/ext/userfunc","checksumSHA1":"N2+7qc9e8zYkNy1itC+kWTKBTIo=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"BRJaQcKriVKEirVC7YxBxPufQF0=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"LotrMqeWeTv/rNOGUHRs9iVBjoQ=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"RNoOVGaFtYqaPMyARZuHc2OejDs=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"4Cr8I/nepYf4eRCl5hiazPf+afs=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"iIVMnRuvfOy/tJ1zE9rVcjD/01A=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hclparse","checksumSHA1":"IzmftuG99BqNhbFGhxZaGwtiMtM=","revision":"77c0b55a597ce9ff855c699dba2a99c1632690e1","revisionTime":"2018-08-01T15:45:22Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/ext/userfunc","checksumSHA1":"N2+7qc9e8zYkNy1itC+kWTKBTIo=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/gohcl","checksumSHA1":"BRJaQcKriVKEirVC7YxBxPufQF0=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl","checksumSHA1":"LotrMqeWeTv/rNOGUHRs9iVBjoQ=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/hclsyntax","checksumSHA1":"RNoOVGaFtYqaPMyARZuHc2OejDs=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcl/json","checksumSHA1":"4Cr8I/nepYf4eRCl5hiazPf+afs=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hcldec","checksumSHA1":"wQ3hLj4s+5jN6LePSpT0XTTvdXA=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/hcl2/hclparse","checksumSHA1":"IzmftuG99BqNhbFGhxZaGwtiMtM=","revision":"6743a2254ba3d642b7d3a0be506259a0842819ac","revisionTime":"2018-08-10T01:10:00Z"},
|
||||
{"path":"github.com/hashicorp/logutils","revision":"0dc08b1671f34c4250ce212759ebd880f743d883"},
|
||||
{"path":"github.com/hashicorp/memberlist","checksumSHA1":"1zk7IeGClUqBo+Phsx89p7fQ/rQ=","revision":"23ad4b7d7b38496cd64c241dfd4c60b7794c254a","revisionTime":"2017-02-08T21:15:06Z"},
|
||||
{"path":"github.com/hashicorp/net-rpc-msgpackrpc","revision":"a14192a58a694c123d8fe5481d4a4727d6ae82f3"},
|
||||
|
|
Loading…
Reference in New Issue