Skip to content
Draft
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 @@ -24,6 +24,8 @@ Given a version number `MAJOR.MINOR.PATCH`, we increment the:

### Added

- Add `generate_*.inherit` attribute for controlling if generate blocks must be inherited
into child stacks.
- Add experimental support for `tmgen` file extension for easy code generation/templating
of existing infrastructure. You can enable it with `terramate.config.experimental = ["tmgen"]`.

Expand Down
50 changes: 28 additions & 22 deletions cmd/terramate/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,20 @@ type cliSpec struct {
} `cmd:"" help:"List stacks."`

Run struct {
CloudStatus string `help:"Filter by Terramate Cloud status of the stack."`
CloudSyncDeployment bool `default:"false" help:"Synchronize the command as a new deployment to Terramate Cloud."`
CloudSyncDriftStatus bool `default:"false" help:"Synchronize the command as a new drift run to Terramate Cloud."`
CloudSyncPreview bool `default:"false" help:"Synchronize the command as a new preview to Terramate Cloud."`
CloudSyncLayer preview.Layer `default:"" help:"Set a customer layer for synchronizing a preview to Terramate Cloud."`
CloudSyncTerraformPlanFile string `default:"" help:"Add details of the Terraform Plan file to the synchronization to Terramate Cloud."`
DebugPreviewURL string `hidden:"true" default:"" help:"Create a debug preview URL to Terramate Cloud details."`
ContinueOnError bool `default:"false" help:"Do not stop execution when an error occurs."`
NoRecursive bool `default:"false" help:"Do not recurse into nested child stacks."`
DryRun bool `default:"false" help:"Plan the execution but do not execute it."`
Reverse bool `default:"false" help:"Reverse the order of execution."`
Eval bool `default:"false" help:"Evaluate command arguments as HCL strings interpolating Globals, Functions and Metadata."`
Terragrunt bool `default:"false" help:"Use terragrunt when generating planfile for Terramate Cloud sync."`
CloudStatus string `help:"Filter by Terramate Cloud status of the stack."`
CloudSyncDeployment bool `default:"false" help:"Synchronize the command as a new deployment to Terramate Cloud."`
CloudSyncDriftStatus bool `default:"false" help:"Synchronize the command as a new drift run to Terramate Cloud."`
CloudSyncPreview bool `default:"false" help:"Synchronize the command as a new preview to Terramate Cloud."`
CloudSyncLayer preview.Layer `default:"" help:"Set a customer layer for synchronizing a preview to Terramate Cloud."`
CloudSyncTerraformPlanFile string `default:"" help:"Add details of the Terraform Plan file to the synchronization to Terramate Cloud."`
DebugPreviewURL string `hidden:"true" default:"" help:"Create a debug preview URL to Terramate Cloud details."`
ContinueOnError bool `default:"false" help:"Do not stop execution when an error occurs."`
NoRecursive bool `default:"false" help:"Do not recurse into nested child stacks."`
DryRun bool `default:"false" help:"Plan the execution but do not execute it."`
Reverse bool `default:"false" help:"Reverse the order of execution."`
Eval bool `default:"false" help:"Evaluate command arguments as HCL strings interpolating Globals, Functions and Metadata."`
Terragrunt bool `default:"false" help:"Use terragrunt when generating planfile for Terramate Cloud sync."`
Global map[string]string `short:"g" help:"set/override globals. eg.: --global name=<expr>"`

// Note: 0 is not the real default value here, this is just a workaround.
// Kong doesn't support having 0 as the default value in case the flag isn't set, but K in case it's set without a value.
Expand All @@ -172,7 +173,8 @@ type cliSpec struct {
} `cmd:"" help:"Run command in the stacks"`

Generate struct {
DetailedExitCode bool `default:"false" help:"Return a detailed exit code: 0 nothing changed, 1 an error happened, 2 changes were made."`
Global map[string]string `short:"g" help:"set/override globals. eg.: --global name=<expr>"`
DetailedExitCode bool `default:"false" help:"Return a detailed exit code: 0 nothing changed, 1 an error happened, 2 changes were made."`
} `cmd:"" help:"Run Code Generation in stacks."`

Script struct {
Expand All @@ -188,7 +190,8 @@ type cliSpec struct {
DryRun bool `default:"false" help:"Plan the execution but do not execute it."`
Reverse bool `default:"false" help:"Reverse the order of execution."`

Cmds []string `arg:"" optional:"true" passthrough:"" help:"Script to execute."`
Cmds []string `arg:"" optional:"true" passthrough:"" help:"Script to execute."`
Global map[string]string `short:"g" help:"set/override globals. eg.: --global name=<expr>"`

// See above comment regarding for run --parallel.
Parallel int `short:"j" optional:"true" help:"Run independent stacks in parallel."`
Expand All @@ -202,8 +205,11 @@ type cliSpec struct {
Metadata struct{} `cmd:"" help:"Show metadata available in stacks."`
Globals struct{} `cmd:"" help:"Show globals available in stacks."`
GenerateOrigins struct {
Global map[string]string `short:"g" help:"set/override globals. eg.: --global name=<expr>"`
} `cmd:"" help:"Show details about generated code in stacks."`
RuntimeEnv struct{} `cmd:"" help:"Show available run-time environment variables (ENV) in stacks."`
RuntimeEnv struct {
Global map[string]string `short:"g" help:"set/override globals. eg.: --global name=<expr>"`
} `cmd:"" help:"Show available run-time environment variables (ENV) in stacks."`
} `cmd:"" help:"Show configuration details of stacks."`
} `cmd:"" help:"Debug Terramate configuration."`

Expand Down Expand Up @@ -1003,7 +1009,7 @@ func (c *cli) gencodeWithVendor() (generate.Report, download.Report) {

log.Debug().Msg("generating code")

report := generate.Do(c.cfg(), c.vendorDir(), vendorRequestEvents)
report := generate.Do(c.cfg(), c.vendorDir(), vendorRequestEvents, c.parsedArgs.Generate.Global)

log.Debug().Msg("code generation finished, waiting for vendor requests to be handled")

Expand Down Expand Up @@ -1629,7 +1635,7 @@ func (c *cli) printRuntimeEnv() {
}

for _, stackEntry := range c.filterStacks(report.Stacks) {
envVars, err := run.LoadEnv(c.cfg(), stackEntry.Stack)
envVars, err := run.LoadEnv(c.cfg(), stackEntry.Stack, c.parsedArgs.Debug.Show.RuntimeEnv.Global)
if err != nil {
fatal("loading stack run environment", err)
}
Expand Down Expand Up @@ -1822,7 +1828,7 @@ func (c *cli) generateDebug() {
selectedStacks[stack.Dir()] = struct{}{}
}

results, err := generate.Load(c.cfg(), c.vendorDir())
results, err := generate.Load(c.cfg(), c.vendorDir(), c.parsedArgs.Debug.Show.GenerateOrigins.Global)
if err != nil {
fatal("generate debug: loading generated code", err)
}
Expand Down Expand Up @@ -1861,7 +1867,7 @@ func (c *cli) printStacksGlobals() {

for _, stackEntry := range c.filterStacks(report.Stacks) {
stack := stackEntry.Stack
report := globals.ForStack(c.cfg(), stack)
report := globals.ForStack(c.cfg(), stack, nil)
if err := report.AsError(); err != nil {
fatal(sprintf("listing stacks globals: loading stack at %s", stack.Dir), err)
}
Expand Down Expand Up @@ -2123,7 +2129,7 @@ func envVarIsSet(val string) bool {
return val != "" && val != "0" && val != "false"
}

func (c *cli) checkOutdatedGeneratedCode() {
func (c *cli) checkOutdatedGeneratedCode(overrideGlobals map[string]string) {
logger := log.With().
Str("action", "checkOutdatedGeneratedCode()").
Logger()
Expand All @@ -2132,7 +2138,7 @@ func (c *cli) checkOutdatedGeneratedCode() {
return
}

outdatedFiles, err := generate.DetectOutdated(c.cfg(), c.vendorDir())
outdatedFiles, err := generate.DetectOutdated(c.cfg(), c.vendorDir(), overrideGlobals)
if err != nil {
fatal("failed to check outdated code on project", err)
}
Expand Down
10 changes: 6 additions & 4 deletions cmd/terramate/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ const (

// stackRun contains a list of tasks to be run per stack.
type stackRun struct {
Stack *config.Stack
Tasks []stackRunTask
Stack *config.Stack
Tasks []stackRunTask
OverrideGlobals map[string]string
}

// stackCloudRun is a stackRun, but with a single task, because the cloud API only supports
Expand Down Expand Up @@ -99,7 +100,7 @@ func (c *cli) runOnStacks() {
fatal("run expects a cmd", nil)
}

c.checkOutdatedGeneratedCode()
c.checkOutdatedGeneratedCode(c.parsedArgs.Run.Global)
c.checkCloudSync()

var stacks config.List[*config.SortableStack]
Expand Down Expand Up @@ -169,6 +170,7 @@ func (c *cli) runOnStacks() {
UseTerragrunt: c.parsedArgs.Run.Terragrunt,
},
},
OverrideGlobals: c.parsedArgs.Generate.Global,
}
if c.parsedArgs.Run.Eval {
run.Tasks[0].Cmd, err = c.evalRunArgs(run.Stack, run.Tasks[0].Cmd)
Expand Down Expand Up @@ -531,7 +533,7 @@ func (c *cli) loadAllStackEnvs(runs []stackRun) (map[prj.Path]runutil.EnvVars, e
errs := errors.L()
stackEnvs := map[prj.Path]runutil.EnvVars{}
for _, run := range runs {
env, err := runutil.LoadEnv(c.cfg(), run.Stack)
env, err := runutil.LoadEnv(c.cfg(), run.Stack, run.OverrideGlobals)
errs.Append(err)
stackEnvs[run.Stack.Dir] = env
}
Expand Down
13 changes: 8 additions & 5 deletions cmd/terramate/cli/script_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

func (c *cli) runScript() {
c.gitSafeguardDefaultBranchIsReachable()
c.checkOutdatedGeneratedCode()
c.checkOutdatedGeneratedCode(c.parsedArgs.Script.Run.Global)

var stacks config.List[*config.SortableStack]
if c.parsedArgs.Script.Run.NoRecursive {
Expand Down Expand Up @@ -75,9 +75,12 @@ func (c *cli) runScript() {
}

for _, st := range result.Stacks {
run := stackRun{Stack: st.Stack}
run := stackRun{
Stack: st.Stack,
OverrideGlobals: c.parsedArgs.Generate.Global,
}

ectx, err := scriptEvalContext(c.cfg(), st.Stack)
ectx, err := scriptEvalContext(c.cfg(), st.Stack, c.parsedArgs.Script.Run.Global)
if err != nil {
fatal("failed to get context", err)
}
Expand Down Expand Up @@ -196,8 +199,8 @@ func printScriptCommand(w io.Writer, stack *config.Stack, run stackRunTask) {
fmt.Fprintln(w, prompt, color.YellowString(strings.Join(run.Cmd, " ")))
}

func scriptEvalContext(root *config.Root, st *config.Stack) (*eval.Context, error) {
globalsReport := globals.ForStack(root, st)
func scriptEvalContext(root *config.Root, st *config.Stack, overrideGlobals map[string]string) (*eval.Context, error) {
globalsReport := globals.ForStack(root, st, overrideGlobals)
if err := globalsReport.AsError(); err != nil {
return nil, err
}
Expand Down
54 changes: 54 additions & 0 deletions cmd/terramate/e2etests/core/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,60 @@ func TestGenerateWarnsForTmGenNonInheritable(t *testing.T) {
})
}

func TestGenerateFileNonInheritableOutsideStackGeneratesWarning(t *testing.T) {
t.Parallel()

s := sandbox.NoGit(t, true)
s.BuildTree([]string{
"d:non-stack",
})

s.RootEntry().
CreateDir("non-stack").
CreateFile(
config.DefaultFilename,
GenerateFile(
Labels("test.txt"),
Bool("inherit", false),
Str("content", "test"),
).String(),
)

tmcli := NewCLI(t, s.RootDir())
AssertRunResult(t, tmcli.Run("generate"), RunExpected{
Stdout: "Nothing to do, generated code is up to date\n",
StderrRegex: "Warning: non-inheritable generate found outside a stack",
})
}

func TestGenerateHCLNonInheritableOutsideStackGeneratesWarning(t *testing.T) {
t.Parallel()

s := sandbox.NoGit(t, true)
s.BuildTree([]string{
"d:non-stack",
})

s.RootEntry().
CreateDir("non-stack").
CreateFile(
config.DefaultFilename,
GenerateHCL(
Labels("test.txt"),
Bool("inherit", false),
Content(
Str("hello", "world"),
),
).String(),
)

tmcli := NewCLI(t, s.RootDir())
AssertRunResult(t, tmcli.Run("generate"), RunExpected{
Stdout: "Nothing to do, generated code is up to date\n",
StderrRegex: "Warning: non-inheritable generate found outside a stack",
})
}

type str string

func (s str) String() string {
Expand Down
14 changes: 13 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,10 +508,22 @@ func processTmGenFiles(root *Root, cfg *hcl.Config, cfgdir string, dirEntries []
Type: "content",
Body: body,
}

// should never fail
inheritFalse, diags := hclsyntax.ParseExpression([]byte("false"), "<implicit block>", hhcl.InitialPos)
if diags.HasErrors() {
panic(errors.E(errors.ErrInternal, diags))
}

inheritAttr := &hclsyntax.Attribute{
Name: "inherit",
Expr: inheritFalse,
}

implicitGenBlock := hcl.GenHCLBlock{
IsImplicitBlock: true,
Dir: project.PrjAbsPath(root.HostDir(), cfgdir),
NonInheritable: true,
Inherit: inheritAttr,
Range: info.NewRange(root.HostDir(), hhcl.Range{
Filename: absFname,
Start: hhcl.InitialPos,
Expand Down
Loading