github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/plugins/shared/hclspec/dec.go (about)

     1  package hclspec
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/hashicorp/hcl2/hcl"
     7  	"github.com/hashicorp/hcl2/hcl/hclsyntax"
     8  	"github.com/hashicorp/hcl2/hcldec"
     9  )
    10  
    11  var (
    12  	// nilSpecDiagnostic is the diagnostic value returned if a nil value is
    13  	// given
    14  	nilSpecDiagnostic = &hcl.Diagnostic{
    15  		Severity: hcl.DiagError,
    16  		Summary:  "nil spec given",
    17  		Detail:   "Can not convert a nil specification. Pass a valid spec",
    18  	}
    19  
    20  	// emptyPos is the position used when parsing hcl expressions
    21  	emptyPos = hcl.Pos{
    22  		Line:   0,
    23  		Column: 0,
    24  		Byte:   0,
    25  	}
    26  
    27  	// specCtx is the context used to evaluate expressions.
    28  	specCtx = &hcl.EvalContext{
    29  		Functions: specFuncs,
    30  	}
    31  )
    32  
    33  // Convert converts a Spec to an hcl specification.
    34  func Convert(spec *Spec) (hcldec.Spec, hcl.Diagnostics) {
    35  	if spec == nil {
    36  		return nil, hcl.Diagnostics([]*hcl.Diagnostic{nilSpecDiagnostic})
    37  	}
    38  
    39  	return decodeSpecBlock(spec, "")
    40  }
    41  
    42  // decodeSpecBlock is the recursive entry point that converts between the two
    43  // spec types.
    44  func decodeSpecBlock(spec *Spec, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
    45  	switch spec.Block.(type) {
    46  
    47  	case *Spec_Object:
    48  		return decodeObjectSpec(spec.GetObject())
    49  
    50  	case *Spec_Array:
    51  		return decodeArraySpec(spec.GetArray())
    52  
    53  	case *Spec_Attr:
    54  		return decodeAttrSpec(spec.GetAttr(), impliedName)
    55  
    56  	case *Spec_BlockValue:
    57  		return decodeBlockSpec(spec.GetBlockValue(), impliedName)
    58  
    59  	case *Spec_BlockList:
    60  		return decodeBlockListSpec(spec.GetBlockList(), impliedName)
    61  
    62  	case *Spec_BlockSet:
    63  		return decodeBlockSetSpec(spec.GetBlockSet(), impliedName)
    64  
    65  	case *Spec_BlockMap:
    66  		return decodeBlockMapSpec(spec.GetBlockMap(), impliedName)
    67  
    68  	case *Spec_Default:
    69  		return decodeDefaultSpec(spec.GetDefault())
    70  
    71  	case *Spec_Literal:
    72  		return decodeLiteralSpec(spec.GetLiteral())
    73  
    74  	default:
    75  		// Should never happen, because the above cases should be exhaustive
    76  		// for our schema.
    77  		var diags hcl.Diagnostics
    78  		diags = append(diags, &hcl.Diagnostic{
    79  			Severity: hcl.DiagError,
    80  			Summary:  "Invalid spec block",
    81  			Detail:   fmt.Sprintf("Blocks of type %T are not expected here.", spec.Block),
    82  		})
    83  		return nil, diags
    84  	}
    85  }
    86  
    87  func decodeObjectSpec(obj *Object) (hcldec.Spec, hcl.Diagnostics) {
    88  	var diags hcl.Diagnostics
    89  	spec := make(hcldec.ObjectSpec)
    90  	for attr, block := range obj.GetAttributes() {
    91  		propSpec, propDiags := decodeSpecBlock(block, attr)
    92  		diags = append(diags, propDiags...)
    93  		spec[attr] = propSpec
    94  	}
    95  
    96  	return spec, diags
    97  }
    98  
    99  func decodeArraySpec(a *Array) (hcldec.Spec, hcl.Diagnostics) {
   100  	values := a.GetValues()
   101  	var diags hcl.Diagnostics
   102  	spec := make(hcldec.TupleSpec, 0, len(values))
   103  	for _, block := range values {
   104  		elemSpec, elemDiags := decodeSpecBlock(block, "")
   105  		diags = append(diags, elemDiags...)
   106  		spec = append(spec, elemSpec)
   107  	}
   108  
   109  	return spec, diags
   110  }
   111  
   112  func decodeAttrSpec(attr *Attr, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
   113  	// Convert the string type to an hcl.Expression
   114  	typeExpr, diags := hclsyntax.ParseExpression([]byte(attr.GetType()), "proto", emptyPos)
   115  	if diags.HasErrors() {
   116  		return nil, diags
   117  	}
   118  
   119  	spec := &hcldec.AttrSpec{
   120  		Name:     impliedName,
   121  		Required: attr.GetRequired(),
   122  	}
   123  
   124  	if n := attr.GetName(); n != "" {
   125  		spec.Name = n
   126  	}
   127  
   128  	var typeDiags hcl.Diagnostics
   129  	spec.Type, typeDiags = evalTypeExpr(typeExpr)
   130  	diags = append(diags, typeDiags...)
   131  
   132  	if spec.Name == "" {
   133  		diags = append(diags, &hcl.Diagnostic{
   134  			Severity: hcl.DiagError,
   135  			Summary:  "Missing name in attribute spec",
   136  			Detail:   "The name attribute is required, to specify the attribute name that is expected in an input HCL file.",
   137  		})
   138  		return nil, diags
   139  	}
   140  
   141  	return spec, diags
   142  }
   143  
   144  func decodeBlockSpec(block *Block, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
   145  	spec := &hcldec.BlockSpec{
   146  		TypeName: impliedName,
   147  		Required: block.GetRequired(),
   148  	}
   149  
   150  	if n := block.GetName(); n != "" {
   151  		spec.TypeName = n
   152  	}
   153  
   154  	nested, diags := decodeBlockNestedSpec(block.GetNested())
   155  	spec.Nested = nested
   156  	return spec, diags
   157  }
   158  
   159  func decodeBlockListSpec(block *BlockList, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
   160  	spec := &hcldec.BlockListSpec{
   161  		TypeName: impliedName,
   162  		MinItems: int(block.GetMinItems()),
   163  		MaxItems: int(block.GetMaxItems()),
   164  	}
   165  
   166  	if n := block.GetName(); n != "" {
   167  		spec.TypeName = n
   168  	}
   169  
   170  	nested, diags := decodeBlockNestedSpec(block.GetNested())
   171  	spec.Nested = nested
   172  
   173  	if spec.TypeName == "" {
   174  		diags = append(diags, &hcl.Diagnostic{
   175  			Severity: hcl.DiagError,
   176  			Summary:  "Missing name in block_list spec",
   177  			Detail:   "The name attribute is required, to specify the block type name that is expected in an input HCL file.",
   178  		})
   179  		return nil, diags
   180  	}
   181  
   182  	return spec, diags
   183  }
   184  
   185  func decodeBlockSetSpec(block *BlockSet, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
   186  	spec := &hcldec.BlockSetSpec{
   187  		TypeName: impliedName,
   188  		MinItems: int(block.GetMinItems()),
   189  		MaxItems: int(block.GetMaxItems()),
   190  	}
   191  
   192  	if n := block.GetName(); n != "" {
   193  		spec.TypeName = n
   194  	}
   195  
   196  	nested, diags := decodeBlockNestedSpec(block.GetNested())
   197  	spec.Nested = nested
   198  
   199  	if spec.TypeName == "" {
   200  		diags = append(diags, &hcl.Diagnostic{
   201  			Severity: hcl.DiagError,
   202  			Summary:  "Missing name in block_set spec",
   203  			Detail:   "The name attribute is required, to specify the block type name that is expected in an input HCL file.",
   204  		})
   205  		return nil, diags
   206  	}
   207  
   208  	return spec, diags
   209  }
   210  
   211  func decodeBlockMapSpec(block *BlockMap, impliedName string) (hcldec.Spec, hcl.Diagnostics) {
   212  	spec := &hcldec.BlockMapSpec{
   213  		TypeName:   impliedName,
   214  		LabelNames: block.GetLabels(),
   215  	}
   216  
   217  	if n := block.GetName(); n != "" {
   218  		spec.TypeName = n
   219  	}
   220  
   221  	nested, diags := decodeBlockNestedSpec(block.GetNested())
   222  	spec.Nested = nested
   223  
   224  	if spec.TypeName == "" {
   225  		diags = append(diags, &hcl.Diagnostic{
   226  			Severity: hcl.DiagError,
   227  			Summary:  "Missing name in block_map spec",
   228  			Detail:   "The name attribute is required, to specify the block type name that is expected in an input HCL file.",
   229  		})
   230  		return nil, diags
   231  	}
   232  	if len(spec.LabelNames) < 1 {
   233  		diags = append(diags, &hcl.Diagnostic{
   234  			Severity: hcl.DiagError,
   235  			Summary:  "Invalid block label name list",
   236  			Detail:   "A block_map must have at least one label specified.",
   237  		})
   238  		return nil, diags
   239  	}
   240  
   241  	return spec, diags
   242  }
   243  
   244  func decodeBlockNestedSpec(spec *Spec) (hcldec.Spec, hcl.Diagnostics) {
   245  	if spec == nil {
   246  		return nil, hcl.Diagnostics([]*hcl.Diagnostic{
   247  			{
   248  				Severity: hcl.DiagError,
   249  				Summary:  "Missing spec block",
   250  				Detail:   "A block spec must have exactly one child spec specifying how to decode block contents.",
   251  			}})
   252  	}
   253  
   254  	return decodeSpecBlock(spec, "")
   255  }
   256  
   257  func decodeLiteralSpec(l *Literal) (hcldec.Spec, hcl.Diagnostics) {
   258  	// Convert the string value to an hcl.Expression
   259  	valueExpr, diags := hclsyntax.ParseExpression([]byte(l.GetValue()), "proto", emptyPos)
   260  	if diags.HasErrors() {
   261  		return nil, diags
   262  	}
   263  
   264  	value, valueDiags := valueExpr.Value(specCtx)
   265  	diags = append(diags, valueDiags...)
   266  	if diags.HasErrors() {
   267  		return nil, diags
   268  	}
   269  
   270  	return &hcldec.LiteralSpec{
   271  		Value: value,
   272  	}, diags
   273  }
   274  
   275  func decodeDefaultSpec(d *Default) (hcldec.Spec, hcl.Diagnostics) {
   276  	// Parse the primary
   277  	primary, diags := decodeSpecBlock(d.GetPrimary(), "")
   278  	if diags.HasErrors() {
   279  		return nil, diags
   280  	}
   281  
   282  	// Parse the default
   283  	def, defDiags := decodeSpecBlock(d.GetDefault(), "")
   284  	diags = append(diags, defDiags...)
   285  	if diags.HasErrors() {
   286  		return nil, diags
   287  	}
   288  
   289  	spec := &hcldec.DefaultSpec{
   290  		Primary: primary,
   291  		Default: def,
   292  	}
   293  
   294  	return spec, diags
   295  }