hclext

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2024 License: MPL-2.0 Imports: 10 Imported by: 50

Documentation

Overview

Package hclext is an extension of package hcl for TFLint.

The goal of this package is to work with nested hcl.BodyContent. In the various functions provided by the package hcl, hcl.Block nests hcl.Body as body. However, since hcl.Body is an interface, the nested body cannot be sent over a wire protocol.

In this package, redefine hcl.Block as hclext.Block nests BodyContent, not Body, which is an interface. Some functions and related structures have been redefined to make hclext.Block behave like the package hcl.

For example, Content/PartialContent takes hclext.BodySchema instead of hcl.BodySchema and returns hclext.BodyContent. In hclext.BodySchema, you can declare the structure of the nested body as the block schema. This allows you to send the schema and its results of configurations that contain nested bodies via gRPC.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func BindValue added in v0.14.0

func BindValue(val cty.Value, expr hcl.Expression) hcl.Expression

BindValue binds the passed value to an expression. This returns the bound expression.

func DecodeBody

func DecodeBody(body *BodyContent, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics

DecodeBody is a derivative of gohcl.DecodeBody the receives hclext.BodyContent instead of hcl.Body. Since hcl.Body is hard to send over a wire protocol, it is needed to support BodyContent. This method differs from gohcl.DecodeBody in several ways:

- Does not support decoding to map, cty.Value, hcl.Body, hcl.Expression. - Does not support `body` and `remain` tags.

  • Extraneous attributes are always ignored.

@see https://github.com/hashicorp/hcl/blob/v2.11.1/gohcl/decode.go

Example
src := `
noodle "foo" "bar" {
	type = "rice"

	bread "baz" {
		type  = "focaccia"
		baked = true
	}
	bread "quz" {
		type = "rye"
	}
}`
file, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
if diags.HasErrors() {
	panic(diags)
}

type Bread struct {
	// The `*,label` tag matches "bread" block labels.
	// The count of tags should be matched to count of block labels.
	Name string `hclext:"name,label"`
	// The `type` tag matches a "type" attribute inside of "bread" block.
	Type string `hclext:"type"`
	// The `baked,optional` tag matches a "baked" attribute, but it is optional.
	Baked bool `hclext:"baked,optional"`
}
type Noodle struct {
	Name    string `hclext:"name,label"`
	SubName string `hclext:"subname,label"`
	Type    string `hclext:"type"`
	// The `bread,block` tag matches "bread" blocks.
	// Multiple blocks are allowed because the field type is slice.
	Breads []Bread `hclext:"bread,block"`
}
type Config struct {
	// Only 1 block must be needed because the field type is not slice, not a pointer.
	Noodle Noodle `hclext:"noodle,block"`
}

target := &Config{}

schema := ImpliedBodySchema(target)
body, diags := Content(file.Body, schema)
if diags.HasErrors() {
	panic(diags)
}

diags = DecodeBody(body, nil, target)
if diags.HasErrors() {
	panic(diags)
}

fmt.Printf("- noodle: name=%s, subname=%s type=%s\n", target.Noodle.Name, target.Noodle.SubName, target.Noodle.Type)
for i, bread := range target.Noodle.Breads {
	fmt.Printf("  - bread[%d]: name=%s, type=%s baked=%t\n", i, bread.Name, bread.Type, bread.Baked)
}
Output:

- noodle: name=foo, subname=bar type=rice
  - bread[0]: name=baz, type=focaccia baked=true
  - bread[1]: name=quz, type=rye baked=false

func ParseExpression

func ParseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics)

ParseExpression is a wrapper that calls ParseExpression of hclsyntax and json based on the file extension. This function specializes in parsing intermediate expressions in the file, so it takes into account the hack on trailing newlines in heredoc.

Types

type Attribute

type Attribute struct {
	Name string
	Expr hcl.Expression

	Range     hcl.Range
	NameRange hcl.Range
}

Attribute represents an attribute from within a body.

func (*Attribute) AsNative added in v0.12.0

func (a *Attribute) AsNative() *hcl.Attribute

AsNative returns self as hcl.Attribute

func (*Attribute) Copy added in v0.14.0

func (a *Attribute) Copy() *Attribute

Copy returns a new Attribute based on the original. Note that expr can be a shallow copy. So strictly speaking Copy is not a deep copy.

type AttributeSchema

type AttributeSchema struct {
	Name     string
	Required bool
}

AttributeSchema represents the desired attribute. This structure is designed to have attributes similar to hcl.AttributeSchema.

type Attributes

type Attributes map[string]*Attribute

Attributes is a set of attributes keyed by their names. Please note that this is not strictly. Since hclext.BodyContent is the body from multiple files, top-level attributes can have the same name (it is not possible to specify the same name within a block). This exception is not considered here, as Terraform syntax does not allow top-level attributes.

func (Attributes) AsNative added in v0.12.0

func (as Attributes) AsNative() hcl.Attributes

AsNative returns self as hcl.Attributes

type Block

type Block struct {
	Type   string
	Labels []string
	Body   *BodyContent

	DefRange    hcl.Range
	TypeRange   hcl.Range
	LabelRanges []hcl.Range
}

Block represents a nested block within a hcl.Body. Unlike hcl.Block, this has Body as hclext.BodyContent (struct), not hcl.Body (interface). Since interface is hard to send over a wire protocol, it is designed to always return only the attributes based on the schema. Instead, the hclext.BlockSchema can now be nested to extract the attributes within the nested block.

func (*Block) Copy added in v0.14.0

func (b *Block) Copy() *Block

Copy returns a new Block based on the original.

type BlockSchema

type BlockSchema struct {
	Type       string
	LabelNames []string

	Body *BodySchema
}

BlockSchema represents the desired block header and body schema. Unlike hcl.BlockHeaderSchema, this can set nested body schema. Instead, hclext.Block can't handle abstract values like hcl.Body, so you need to specify all nested schemas at once.

type Blocks

type Blocks []*Block

Blocks is a sequence of Block.

func (Blocks) ByType

func (els Blocks) ByType() map[string]Blocks

ByType transforms the receiving block sequence into a map from type name to block sequences of only that type.

func (Blocks) OfType added in v0.12.0

func (els Blocks) OfType(typeName string) Blocks

OfType filters the receiving block sequence by block type name, returning a new block sequence including only the blocks of the requested type.

type BodyContent

type BodyContent struct {
	Attributes Attributes
	Blocks     Blocks
}

BodyContent is the result of applying a hclext.BodySchema to a hcl.Body. Unlike hcl.BodyContent, this does not have MissingItemRange. This difference is because hcl.BodyContent is the result for a single HCL file, while hclext.BodyContent is the result for a Terraform module.

func Content

func Content(body hcl.Body, schema *BodySchema) (*BodyContent, hcl.Diagnostics)

Content is a wrapper for hcl.Content for working with nested schemas. Convert hclext.BodySchema to hcl.BodySchema, and convert hcl.BodyContent to hclext.BodyContent. It processes the nested body recursively.

Example
src := `
noodle "foo" "bar" {
	type = "rice"

	bread "baz" {
		type  = "focaccia"
		baked = true
	}
	bread "quz" {
		type = "rye"
	}
}`
file, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
if diags.HasErrors() {
	panic(diags)
}

body, diags := Content(file.Body, &BodySchema{
	Blocks: []BlockSchema{
		{
			Type:       "noodle",
			LabelNames: []string{"name", "subname"},
			Body: &BodySchema{
				Attributes: []AttributeSchema{{Name: "type"}},
				Blocks: []BlockSchema{
					{
						Type:       "bread",
						LabelNames: []string{"name"},
						Body: &BodySchema{
							Attributes: []AttributeSchema{
								{Name: "type", Required: true},
								{Name: "baked"},
							},
						},
					},
				},
			},
		},
	},
})
if diags.HasErrors() {
	panic(diags)
}

for i, noodle := range body.Blocks {
	fmt.Printf("- noodle[%d]: labels=%s, attributes=%d\n", i, noodle.Labels, len(noodle.Body.Attributes))
	for i, bread := range noodle.Body.Blocks {
		fmt.Printf("  - bread[%d]: labels=%s, attributes=%d\n", i, bread.Labels, len(bread.Body.Attributes))
	}
}
Output:

- noodle[0]: labels=[foo bar], attributes=1
  - bread[0]: labels=[baz], attributes=2
  - bread[1]: labels=[quz], attributes=1

func PartialContent

func PartialContent(body hcl.Body, schema *BodySchema) (*BodyContent, hcl.Diagnostics)

PartialContent is a wrapper for hcl.PartialContent for working with nested schemas. Convert hclext.BodySchema to hcl.BodySchema, and convert hcl.BodyContent to hclext.BodyContent. It processes the nested body recursively. Unlike hcl.PartialContent, it does not return the rest of the body.

Example
src := `
noodle "foo" "bar" {
	type = "rice"

	bread "baz" {
		type  = "focaccia"
		baked = true
	}
	bread "quz" {
		type = "rye"
	}
}`
file, diags := hclsyntax.ParseConfig([]byte(src), "test.tf", hcl.InitialPos)
if diags.HasErrors() {
	panic(diags)
}

body, diags := PartialContent(file.Body, &BodySchema{
	Blocks: []BlockSchema{
		{
			Type:       "noodle",
			LabelNames: []string{"name", "subname"},
			Body: &BodySchema{
				Blocks: []BlockSchema{
					{
						Type:       "bread",
						LabelNames: []string{"name"},
						Body: &BodySchema{
							Attributes: []AttributeSchema{
								{Name: "type", Required: true},
							},
						},
					},
				},
			},
		},
	},
})
if diags.HasErrors() {
	panic(diags)
}

for i, noodle := range body.Blocks {
	fmt.Printf("- noodle[%d]: labels=%s, attributes=%d\n", i, noodle.Labels, len(noodle.Body.Attributes))
	for i, bread := range noodle.Body.Blocks {
		fmt.Printf("  - bread[%d]: labels=%s, attributes=%d\n", i, bread.Labels, len(bread.Body.Attributes))
	}
}
Output:

- noodle[0]: labels=[foo bar], attributes=0
  - bread[0]: labels=[baz], attributes=1
  - bread[1]: labels=[quz], attributes=1

func (*BodyContent) Copy added in v0.14.0

func (b *BodyContent) Copy() *BodyContent

Copy returns a new BodyContent based on the original.

func (*BodyContent) IsEmpty added in v0.12.0

func (b *BodyContent) IsEmpty() bool

IsEmpty returns whether the body content is empty

func (*BodyContent) WalkAttributes added in v0.14.0

func (b *BodyContent) WalkAttributes(walker func(*Attribute) hcl.Diagnostics) hcl.Diagnostics

WalkAttributes visits all attributes with the passed walker function.

type BodySchema

type BodySchema struct {
	Mode       SchemaMode
	Attributes []AttributeSchema
	Blocks     []BlockSchema
}

BodySchema represents the desired body. This structure is designed to have attributes similar to hcl.BodySchema.

func ImpliedBodySchema

func ImpliedBodySchema(val interface{}) *BodySchema

ImpliedBodySchema is a derivative of gohcl.ImpliedBodySchema that produces hclext.BodySchema instead of hcl.BodySchema. Unlike gohcl.ImpliedBodySchema, it produces nested schemas. This method differs from gohcl.DecodeBody in several ways:

- Does not support `body` and `remain` tags. - Does not support partial schema.

@see https://github.com/hashicorp/hcl/blob/v2.11.1/gohcl/schema.go

type BoundExpr added in v0.14.0

type BoundExpr struct {
	Val cty.Value
	// contains filtered or unexported fields
}

BoundExpr represents an expression whose a value is bound. This is a wrapper for any expression, typically satisfying an interface to behave like the wrapped expression.

The difference is that when resolving a value with `Value()`, instead of resolving the variables with EvalContext, the bound value is returned directly.

func (BoundExpr) Range added in v0.14.0

func (e BoundExpr) Range() hcl.Range

Range delegates to the wrapped expression.

func (BoundExpr) StartRange added in v0.14.0

func (e BoundExpr) StartRange() hcl.Range

StartRange delegates to the wrapped expression.

func (BoundExpr) UnwrapExpression added in v0.18.0

func (e BoundExpr) UnwrapExpression() hcl.Expression

UnwrapExpression returns the original expression. This satisfies the hcl.unwrapExpression interface.

func (BoundExpr) Value added in v0.14.0

func (e BoundExpr) Value(*hcl.EvalContext) (cty.Value, hcl.Diagnostics)

Value returns the bound value.

func (BoundExpr) Variables added in v0.14.0

func (e BoundExpr) Variables() []hcl.Traversal

Variables delegates to the wrapped expression.

type SchemaMode added in v0.14.0

type SchemaMode int32

SchemaMode controls how the body's schema is declared.

const (
	// SchemaDefaultMode is a mode for explicitly declaring the structure of attributes and blocks.
	SchemaDefaultMode SchemaMode = iota
	// SchemaJustAttributesMode is the mode to extract body as attributes.
	// In this mode you don't need to declare schema for attributes or blocks.
	SchemaJustAttributesMode
)

func (SchemaMode) String added in v0.14.0

func (i SchemaMode) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL