github.com/tonyhb/nomad@v0.11.8/helper/pluginutils/hclspecutils/dec.go (about)

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