Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Given a version number `MAJOR.MINOR.PATCH`, we increment the:

- Hide bundles in the `terrmate ui` promote list, if they reference other bundles that would not exist in the target environment.
- This prevents errors that would otherwise occur once the bundle has been promoted.
- Add support for `lets` block in bundles. Outside the `lets` block, expressions can be referenced as `bundle.let.<name>`.
- The block is evaluated after bundle inputs, but before exports and bundle stacks.

## 0.17.0

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.17.0
0.17.1-dev
6 changes: 6 additions & 0 deletions commands/scaffold/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,12 @@ func (s *Spec) Exec(ctx context.Context, cli commands.CLI) error {
return err
}

// Load bundle lets so `bundle.let.<name>` is available
// when evaluating scaffolding.path and scaffolding.name.
if err := config.LoadBundleLets(evalctx, selectedBundle.Define.Lets); err != nil {
return err
}

// Eval the bundle definition itself after the inputs have been collected to evaluate default label and path.
bundleDef, err := config.EvalBundleDefinition(evalctx, selectedBundle.Define)
if err != nil {
Expand Down
12 changes: 12 additions & 0 deletions commands/ui/change.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ func NewCreateChange(
return Change{}, err
}

if err := config.LoadBundleLets(schemactx.Evalctx, bde.Define.Lets); err != nil {
return Change{}, err
}

Comment thread
cursor[bot] marked this conversation as resolved.
// We check if the bundle has an explicit alias and add it to the context if yes.
alias, err := setupExplicitBundleAlias(schemactx.Evalctx, bde.Define)
if err != nil {
Expand Down Expand Up @@ -202,6 +206,10 @@ func NewReconfigChange(
return Change{}, err
}

if err := config.LoadBundleLets(schemactx.Evalctx, bde.Define.Lets); err != nil {
return Change{}, err
}

// This will only be set if there is an explicit alias.
newAlias, err := setupExplicitBundleAlias(schemactx.Evalctx, bde.Define)
if err != nil {
Expand Down Expand Up @@ -278,6 +286,10 @@ func NewPromoteChange(
return Change{}, err
}

if err := config.LoadBundleLets(schemactx.Evalctx, bde.Define.Lets); err != nil {
return Change{}, err
}

// This will only be set if there is an explicit alias.
newAlias, err := setupExplicitBundleAlias(schemactx.Evalctx, bde.Define)
if err != nil {
Expand Down
65 changes: 39 additions & 26 deletions config/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/terramate-io/terramate/hcl/ast"
"github.com/terramate-io/terramate/hcl/eval"
"github.com/terramate-io/terramate/hcl/info"
"github.com/terramate-io/terramate/lets"
"github.com/terramate-io/terramate/project"
"github.com/terramate-io/terramate/typeschema"
)
Expand Down Expand Up @@ -400,10 +401,18 @@ func EvalBundle(ctx context.Context, root *Root, resolveAPI resolve.API, evalctx

evalctx = evalctx.ChildContext()

filePath := inst.Info.Path()
bundleNS := map[string]cty.Value{
"class": cty.StringVal(evaluated.DefinitionMetadata.Class),
"uuid": uuidVal,
"environment": MakeEnvObject(evaluated.Environment),
"file": cty.ObjectVal(map[string]cty.Value{
"path": cty.ObjectVal(map[string]cty.Value{
"absolute": cty.StringVal(inst.Info.HostPath()),
"basename": cty.StringVal(path.Base(filePath.String())),
"relative": cty.StringVal(filePath.String()),
}),
}),
}

evalctx.SetNamespace("bundle", bundleNS)
Expand All @@ -428,6 +437,10 @@ func EvalBundle(ctx context.Context, root *Root, resolveAPI resolve.API, evalctx
return nil, err
}

if err := LoadBundleLets(evalctx, defineBundle.Lets); err != nil {
return nil, err
}

if defineBundle.Alias != nil {
evaluated.Alias, err = EvalString(evalctx, defineBundle.Alias.Expr, "alias")
if err != nil {
Expand All @@ -438,7 +451,7 @@ func EvalBundle(ctx context.Context, root *Root, resolveAPI resolve.API, evalctx
evaluated.Alias = fmt.Sprintf("%s:%s", inst.Workdir.String(), inst.Name)
}

evaluated.Exports, err = evalBundleExports(evalctx, inst, defineBundle, evaluated.Inputs)
evaluated.Exports, err = evalBundleExports(evalctx, defineBundle)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -625,33 +638,9 @@ func extractInputVars(rootNS string, attr *ast.Attribute) []string {
return results
}

func evalBundleExports(evalctx *eval.Context, inst *hcl.Bundle, def *hcl.DefineBundle, inputs map[string]cty.Value) (map[string]cty.Value, error) {
evalctx = evalctx.ChildContext()

func evalBundleExports(evalctx *eval.Context, def *hcl.DefineBundle) (map[string]cty.Value, error) {
exports := map[string]cty.Value{}

errs := errors.L()
filePath := inst.Info.Path()

filePathNS := cty.ObjectVal(map[string]cty.Value{
"absolute": cty.StringVal(inst.Info.HostPath()),
"basename": cty.StringVal(path.Base(filePath.String())),
"relative": cty.StringVal(filePath.String()),
})
fileNS := cty.ObjectVal(map[string]cty.Value{
"path": filePathNS,
})

// For the exports evaluation, move namespace "global" to "bundle.global".
globalsNamespace, _ := evalctx.GetNamespace("global")

bundleInputsNamespace := map[string]cty.Value{
"input": cty.ObjectVal(inputs),
"global": globalsNamespace,
"file": fileNS,
}
evalctx.SetNamespace("bundle", bundleInputsNamespace)
evalctx.SetNamespace("global", map[string]cty.Value{})
Comment thread
cursor[bot] marked this conversation as resolved.

for name, exportDef := range def.Exports {
val, err := evalctx.Eval(exportDef.Value.Expr)
Expand Down Expand Up @@ -1096,3 +1085,27 @@ func tryEvaluateExpr(evalctx *eval.Context, expr hhcl.Expression) (cty.Value, bo
tokens := ast.TokensForExpression(expr).Bytes()
return cty.StringVal(strings.TrimSpace(string(tokens))), false
}

// LoadBundleLets evaluates the bundle's lets block and exposes the result as `bundle.let.<name>`.
// Any inherited "let" namespace is discarded.
func LoadBundleLets(evalctx *eval.Context, letBlock *ast.MergedBlock) error {
evalctx.SetNamespace("let", map[string]cty.Value{})

if err := lets.Load(letBlock, evalctx); err != nil {
return err
}

letsVal, _ := evalctx.GetNamespace("let")

var bundleMap map[string]cty.Value
if bundleNS, ok := evalctx.GetNamespace("bundle"); ok {
bundleMap = bundleNS.AsValueMap()
} else {
bundleMap = map[string]cty.Value{}
}
bundleMap["let"] = letsVal
evalctx.SetNamespace("bundle", bundleMap)

evalctx.DeleteNamespace("let")
return nil
}
1 change: 1 addition & 0 deletions hcl/block_component_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Component struct {
FromBundleSource string

// In this case component comes from a bundle, this stores the values of the `bundle.` object.
// Bundle-level lets are exposed as `bundle.lets.<name>` within this object.
BundleObject *cty.Value
}

Expand Down
61 changes: 56 additions & 5 deletions hcl/block_define_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ type DefineComponent struct {

// DefineBundle represents a bundle defined in the "define" block.
type DefineBundle struct {
Alias *ast.Attribute

Alias *ast.Attribute
Metadata Metadata
Lets *ast.MergedBlock
Stacks map[string]*DefineStack
Inputs map[string]*DefineInput
Exports map[string]*DefineExport
Expand Down Expand Up @@ -213,6 +213,7 @@ func newDefineComponent() *DefineComponent {

func newDefineBundle() *DefineBundle {
return &DefineBundle{
Lets: ast.NewMergedBlock("lets", []string{}),
Stacks: make(map[string]*DefineStack),
Inputs: make(map[string]*DefineInput),
Exports: make(map[string]*DefineExport),
Expand Down Expand Up @@ -301,11 +302,16 @@ func (d *DefineBlockParser) Parse(p *TerramateParser, label ast.LabelBlockType,
return err
}

case "lets":
if err := setLets(define.Bundle.Lets, block); err != nil {
return err
}

default:
return errors.E(
ErrUnrecognizedDefineSubBlock,
block.RawOrigins[0].LabelRanges(),
`unexpected label %q, expected "metadata", "scaffolding" or "environments"`,
`unexpected label %q, expected "metadata", "scaffolding", "environments" or "lets"`,
label.Labels[1],
)
}
Expand Down Expand Up @@ -679,11 +685,22 @@ func parseDefineBundleBlock(label ast.LabelBlockType, block *ast.MergedBlock, re
if err := parseDefineEnvironmentsBlock(subBlock, &ret.Environments); err != nil {
return err
}
case "lets":
if labels.NumLabels != 0 {
return errors.E(
subBlock.RawOrigins[0].LabelRanges(),
`unexpected label %q, expected no labels`,
labels.Labels[0],
)
}
if err := setLets(ret.Lets, subBlock); err != nil {
return err
}
default:
return errors.E(
ErrUnrecognizedDefineSubBlock,
subBlock.RawOrigins[0].DefRange(),
`unexpected block type %q, expected "metadata", "stack", "input", "export", "scaffolding", "environments" or "uses schemas"`,
`unexpected block type %q, expected "metadata", "stack", "input", "export", "scaffolding", "environments", "lets" or "uses schemas"`,
subBlock.Type,
)
}
Expand All @@ -702,6 +719,10 @@ func parseDefineBundleBlock(label ast.LabelBlockType, block *ast.MergedBlock, re
if err := parseDefineEnvironmentsBlock(block, &ret.Environments); err != nil {
return err
}
case "lets":
if err := setLets(ret.Lets, block); err != nil {
return err
}
case "stack":
return errors.E(
block.RawOrigins[0].DefRange(),
Expand All @@ -720,7 +741,7 @@ func parseDefineBundleBlock(label ast.LabelBlockType, block *ast.MergedBlock, re
default:
return errors.E(
block.RawOrigins[0].DefRange(),
"unexpected block type %q, expected 'metadata', 'input', 'export', 'stack', 'scaffolding' or 'environments'",
"unexpected block type %q, expected 'metadata', 'input', 'export', 'stack', 'lets', 'scaffolding' or 'environments'",
block.Type,
)
}
Expand Down Expand Up @@ -766,6 +787,36 @@ func parseDefineBundleBlock(label ast.LabelBlockType, block *ast.MergedBlock, re
return nil
}

func setLets(target *ast.MergedBlock, source *ast.MergedBlock) error {
if err := validateLets(source); err != nil {
return err
}
errs := errors.L()
for _, attr := range source.Attributes {
if existing, ok := target.Attributes[attr.Name]; ok {
errs.Append(errors.E(
ErrTerramateSchema,
attr.NameRange,
`duplicate lets attribute %q (first defined at %s)`,
attr.Name, existing.Range.String(),
))
continue
}
target.Attributes[attr.Name] = attr
}
for lb, sub := range source.Blocks {
existing, ok := target.Blocks[lb]
if !ok {
target.Blocks[lb] = sub
continue
}
for _, raw := range sub.RawOrigins {
errs.Append(existing.MergeBlock(raw, true))
}
}
return errs.AsError()
}

func parseBlockAttributes(block *ast.MergedBlock, validAttrs map[string]**ast.Attribute, errKind errors.Kind) error {
errs := errors.L()
for _, attr := range block.Attributes {
Expand Down
Loading
Loading