github.com/hernad/nomad@v1.6.112/helper/pluginutils/hclspecutils/dec.go (about)

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