regulatf

package
v2.10.0 Latest Latest
Warning

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

Go to latest
Published: Nov 10, 2022 License: Apache-2.0 Imports: 18 Imported by: 0

README

regulatf

This package supports loading HCL configurations. Since it is a bit more complex than the other loaders, it has it's own package. What follows is a brief but complete overview of how it works.

It tries to mimic terraform behavior as much as possible by using terraform as a library. Since some packages inside terraform are in an /internal package, we vendor in terraform and rename the packages to make them accessible from our code. This is automated in the top-level Makefile.

names.go

names.go holds some utilities to work with names, for example parsing them from strings and rendering them back to strings. A name consists of two parts:

  • A module part (e.g. module.child1.). This is empty for the root module.
  • A local part (e.g. aws_s3_bucket.my_bucket or var.my_var). This local part can refer to arbitrarly deeply nested expressions, e.g. aws_security_group.invalid_sg_1.ingress[0].from_port.

Importantly, there are a number of AsXYZ() methods, for example AsModuleOutput(). These manipulate the names to point to their "real" location: for example, if you use module.child1.my_output in the root module, the "real" location is the output inside the child module, so module.child1.outputs.my_output. These methods form a big part of the logic and having it here allows us to keep the code in other files relatively clean.

moduletree.go

moduletree.go is responsible for parsing a directory of terraform files, including children (submodules). We end up with a hierarchical structure:

  • root module
    • child 1
    • child 2
      • grandchild

We can "walk" this tree using a visitor pattern. This visits every expression in every nested submodule, for example:

  • aws_security_group.invalid_sg_1.ingress[1].from_port = 22
  • module.child1.some_bucket.bucket_prefix = "foo"

Because we pass in both the full name (see above) as well as the expression, a visitor can store these in a flat rather than hierarchical map, which is more convenient to work with in Go.

This file uses an additional file moduleregister.go to deal with the locations of remote (downloaded) terraform modules.

valtree.go

A bit of an implementation detail, but worth discussing anyway, valtree.go is how we will store the values of these expressions once they have been evaluated. The terraform internals use cty.Value for this, but this does not suffice for our use case, since cty.Value is immutable. If we want to evaluate HCL in an iterative way, we would need to deconstruct and reconstruct these large values (representing the entire state) at every step.

So you can think of ValTree as a mutable version of cty.Value. We also use names for lookup and construction, which makes them even more convenient. We can build sparse or dense value trees and then "merge" them together; this is used heavily during evaluation.

As an example, these two expressions:

  • aws_security_group.invalid_sg_1.ingress[1].from_port = 22
  • module.child1.some_bucket.bucket_prefix = "foo"

Would eventually, during evaluation, result in the following sparse ValTree:

{
  "aws_security_group": {
    "invalid_sg_1": {
      "ingress": {
        1: {
          "from_port": 22
        }
      }
    }
  },
  "module": {
    "child1": {
      "some_bucket": {
        "bucket_prefix": "foo"
      }
    }
  }
}

regulatf.go

Finally, using these foundations, regulatf.go implements the main logic. This happens in the following imperative steps:

  1. We use the visitor from moduletree.go to obtain a full list of everything in the module and its children.

  2. For every expression, we can compute its dependencies (possibly renaming some using the logic in names.go). This gives us enough info to run a topological sort; which tells us the exact order in which all expressions should be evaluated.

  3. We run through and evaluate each expression.

    • We have a single big ValTree that holds everything we have evaluated so far.

    • For performance reasons, construct a sparse ValTree with only the exact dependencies of the expression, and pass this in as scope.

    • After evaluating, we build a sparse ValTree containing only the result of this expression, and merge that back into the big ValTree.

  4. We convert the big ValTree into the resources view (this involves only some minor bookkeeping like adding the id and _provider fields).

Documentation

Overview

Implements the `Data` interface. Doesn't really do anything.

Utilities for working with expressions.

This module contains utilities for parsing and traversing everything in a configuration tree.

Look in `/pkg/regulatf/README.md` for an explanation of how this works.

cty.Value utilities

Index

Constants

This section is empty.

Variables

View Source
var EmptyModuleName = []string{}

Functions

func ExprToLiteralString

func ExprToLiteralString(expr hcl.Expression) *string

func LocalNameToString

func LocalNameToString(name LocalName) string

func ModuleNameToString

func ModuleNameToString(moduleName ModuleName) string

func PopulateTags

func PopulateTags(resource interface{})

func PrettyValTree

func PrettyValTree(tree ValTree) string

For debugging.

func TfFilePathJoin

func TfFilePathJoin(leading, trailing string) string

TfFilePathJoin is like `filepath.Join` but avoids cleaning the path. This allows to get unique paths for submodules including a parent module, e.g.:

.
examples/mssql/../../
examples/complete/../../

func ValTreeToValue

func ValTreeToValue(tree ValTree) cty.Value

Convert a ValTree to an immutable cty.Value. Since this is expensive, it should only be used for relatively sparse trees, if possible.

func ValTreeToVariables

func ValTreeToVariables(tree ValTree) map[string]cty.Value

Some HCL functions require it to be a map. Returns an empty map if we have anything but an object at the root.

func ValueToInt

func ValueToInt(val cty.Value) *int

func ValueToInterface

func ValueToInterface(val cty.Value) interface{}

func ValueToString

func ValueToString(val cty.Value) *string

Types

type Analysis

type Analysis struct {
	// Module metadata
	Modules map[string]*ModuleMeta

	// Resource metadata
	Resources map[string]*ResourceMeta

	// Holds all keys to expressions within resources.  This is necessary if
	// we want to do dependency analysis and something depends on "all of
	// a resource".
	ResourceExpressions map[string][]FullName

	// All known expressions
	Expressions map[string]hcl.Expression

	// All known blocks
	Blocks []FullName
	// contains filtered or unexported fields
}

func AnalyzeModuleTree

func AnalyzeModuleTree(mtree *ModuleTree) *Analysis

func (*Analysis) EnterResource

func (v *Analysis) EnterResource(name FullName, resource *ResourceMeta)

func (*Analysis) LeaveResource

func (v *Analysis) LeaveResource()

func (*Analysis) VisitBlock

func (v *Analysis) VisitBlock(name FullName)

func (*Analysis) VisitExpr

func (v *Analysis) VisitExpr(name FullName, expr hcl.Expression)

func (*Analysis) VisitModule

func (v *Analysis) VisitModule(name ModuleName, meta *ModuleMeta)

type Data

type Data struct {
}

func (*Data) GetCountAttr

func (*Data) GetForEachAttr

func (*Data) GetInputVariable

func (c *Data) GetInputVariable(v addrs.InputVariable, s tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)

func (*Data) GetLocalValue

func (*Data) GetModule

func (*Data) GetPathAttr

func (c *Data) GetPathAttr(attr addrs.PathAttr, diags tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics)

func (*Data) GetResource

func (*Data) GetTerraformAttr

func (*Data) StaticValidateReferences

func (c *Data) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics

type Evaluation

type Evaluation struct {
	Analysis *Analysis
	Modules  map[string]ValTree
}

func EvaluateAnalysis

func EvaluateAnalysis(analysis *Analysis) (*Evaluation, error)

func (*Evaluation) Location

func (v *Evaluation) Location(resourceKey string) []hcl.Range

func (*Evaluation) Resources

func (v *Evaluation) Resources() map[string]interface{}

type Fragment

type Fragment = interface{} // Either string or int

type FullName

type FullName struct {
	Module ModuleName
	Local  LocalName
}

func ArrayToFullName

func ArrayToFullName(parts []Fragment) FullName

func EmptyFullName

func EmptyFullName(module ModuleName) FullName

func StringToFullName

func StringToFullName(name string) (*FullName, error)

func (FullName) AddIndex

func (name FullName) AddIndex(i int) FullName

func (FullName) AddKey

func (name FullName) AddKey(k string) FullName

func (FullName) AddLocalName

func (name FullName) AddLocalName(after LocalName) FullName

func (FullName) AsModuleInput

func (name FullName) AsModuleInput() *FullName

Parses "module.child.var.foo" into "input.child.foo"

func (FullName) AsModuleOutput

func (name FullName) AsModuleOutput() *FullName

Parses the use of an output (e.g. "module.child.x") to the fully expanded output name (e.g. module.child.output.x")

func (FullName) AsResourceName

func (name FullName) AsResourceName() (*FullName, int, LocalName)

Parses "aws_s3_bucket.my_bucket[3].bucket_prefix" into: - The resource name "aws_s3_bucket.my_bucket". - The count index "3", or -1 if not present. - The remainder, "bucket_prefix".

func (FullName) AsVariable added in v2.8.0

func (name FullName) AsVariable() (*FullName, *FullName, LocalName)

Parses "var.my_var.key" into "variable.my_var", "var.my_var" and "key".

func (FullName) IsBuiltin

func (name FullName) IsBuiltin() bool

Is this a builtin variable?

func (FullName) ToString

func (name FullName) ToString() string

type LocalName

type LocalName = []Fragment

func ExprAttributes

func ExprAttributes(expr hcl.Expression) []LocalName

ExprAttributes tries to gather all attributes that are being used in a given expression. For example, for:

aws_s3_bucket.bucket[count.index].acl

It would return [acl].

func TraversalToLocalName

func TraversalToLocalName(traversal hcl.Traversal) (LocalName, error)

TODO: Refactor to TraversalToName?

type ModuleMeta

type ModuleMeta struct {
	Dir                  string
	Recurse              bool
	Filepaths            []string
	MissingRemoteModules []string
	Location             *hcl.Range
}

type ModuleName

type ModuleName = []string

type ModuleTree

type ModuleTree struct {
	// contains filtered or unexported fields
}

We load the entire tree of submodules in one pass.

func ParseDirectory

func ParseDirectory(
	moduleRegister *TerraformModuleRegister,
	parserFs afero.Fs,
	dir string,
	varFiles []string,
) (*ModuleTree, error)

func ParseFiles

func ParseFiles(
	moduleRegister *TerraformModuleRegister,
	parserFs afero.Fs,
	recurse bool,
	dir string,
	filepaths []string,
	varfiles []string,
) (*ModuleTree, error)

func (*ModuleTree) FilePath

func (mtree *ModuleTree) FilePath() string

func (*ModuleTree) LoadedFiles

func (mtree *ModuleTree) LoadedFiles() []string

func (*ModuleTree) Walk

func (mtree *ModuleTree) Walk(v Visitor)

func (*ModuleTree) Warnings

func (mtree *ModuleTree) Warnings() []string

type ResourceMeta

type ResourceMeta struct {
	Data     bool
	Type     string
	Provider string
	Count    bool
	Location hcl.Range
}

type TerraformModuleRegister

type TerraformModuleRegister struct {
	// contains filtered or unexported fields
}

func NewTerraformRegister

func NewTerraformRegister(dir string) *TerraformModuleRegister

func (*TerraformModuleRegister) GetDir

func (r *TerraformModuleRegister) GetDir(source string) *string

type UnsupportedOperationDiag

type UnsupportedOperationDiag struct {
}

func (UnsupportedOperationDiag) Description

func (UnsupportedOperationDiag) FromExpr

func (UnsupportedOperationDiag) Severity

func (UnsupportedOperationDiag) Source

type ValTree

type ValTree = interface{}

A ValTree is a mutable, dynamic tree. Values will be either of these three things:

  • a map with int keys (which represents a sparse array)
  • a map with string keys
  • a cty.Value (at the leafs only)

func BuildValTree

func BuildValTree(name LocalName, sub ValTree) ValTree

Build a val tree consisting of a subtree nested under a given name.

func EmptyObjectValTree

func EmptyObjectValTree() ValTree

func LookupValTree

func LookupValTree(tree ValTree, name LocalName) ValTree

Look up a given subtree, returns nil if not found

func MergeValTree

func MergeValTree(left ValTree, right ValTree) ValTree

Merge all values from the right valtree into the left.

func SingletonValTree

func SingletonValTree(name LocalName, leaf cty.Value) ValTree

Create a nested ValTree consisting of a single value

func SparseValTree

func SparseValTree(original ValTree, name LocalName) ValTree

Create a sparse tree, only containing the value with the given name. Returns nil if this does not exist.

type Visitor

type Visitor interface {
	VisitModule(name ModuleName, meta *ModuleMeta)
	EnterResource(name FullName, resource *ResourceMeta)
	LeaveResource()
	VisitBlock(name FullName)
	VisitExpr(name FullName, expr hcl.Expression)
}

Jump to

Keyboard shortcuts

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