Skip to content

Commit

Permalink
Merge branch 'main' of github.com:terramate-io/terramate into i4k-fea…
Browse files Browse the repository at this point in the history
…t-tg-file-support

Signed-off-by: i4k <[email protected]>
  • Loading branch information
i4ki committed Nov 29, 2024
2 parents 927c2d3 + d69c277 commit 024b3d7
Show file tree
Hide file tree
Showing 25 changed files with 1,121 additions and 19 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,19 @@ Given a version number `MAJOR.MINOR.PATCH`, we increment the:
- Add support for tracking `file()` usages in Terragrunt files for enhancing the change detection.
- Now if you have Terragrunt modules that directly read files from elsewhere in the project, Terramate will
mark the stack changed whenever the aforementioned file changes.
- Add telemetry to collect anonymous usage metrics.
- This helps us to improve user experience by measuring which Terramate features are used most actively.
For further details, see [documentation](https://terramate.io/docs/cli/telemetry).
- Can be turned off by setting `terramate.config.telemetry.enabled = false` in the project configuration,
or by setting `disable_telemetry = true` in the user configuration.

### Fixed

- Fix the command-line parsing of `run` and `script run` which were not failing from unknown flags.
- Fix `create --all-terragrunt` creating Terragrunt stacks with cycles.
- Improve the error reporting of the Outputs Sharing feature.
- Fix crash in the Terragrunt integration when the project had modules with dependency paths outside the current Terramate project.
- A warning will be shown for such configurations.

## v0.11.3

Expand Down
146 changes: 142 additions & 4 deletions cmd/terramate/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/terramate-io/terramate/cmd/terramate/cli/cliconfig"
"github.com/terramate-io/terramate/cmd/terramate/cli/clitest"
"github.com/terramate-io/terramate/cmd/terramate/cli/out"
tel "github.com/terramate-io/terramate/cmd/terramate/cli/telemetry"
"github.com/terramate-io/terramate/config/filter"
"github.com/terramate-io/terramate/config/tag"
"github.com/terramate-io/terramate/errors"
Expand Down Expand Up @@ -696,37 +697,94 @@ func (c *cli) run() {
logger.Debug().Msg("Handle command.")

switch c.ctx.Command() {
case "fmt":
c.format()
case "fmt <files>":
case "fmt", "fmt <files>":
c.initAndSendAnalytics("fmt",
tel.BoolFlag("detailed-exit-code", c.parsedArgs.Fmt.DetailedExitCode),
)
c.format()
c.waitForAnalytics()
case "create <path>":
c.initAndSendAnalytics("create")
c.createStack()
c.waitForAnalytics()
case "create":
c.initAnalytics("create",
tel.BoolFlag("all-terragrunt", c.parsedArgs.Create.AllTerragrunt),
tel.BoolFlag("all-terraform", c.parsedArgs.Create.AllTerraform),
)
c.scanCreate()
c.waitForAnalytics()
case "list":
c.initAndSendAnalytics("list",
tel.BoolFlag("filter-changed", c.parsedArgs.Changed),
tel.BoolFlag("filter-tags", len(c.parsedArgs.Tags) != 0),
tel.StringFlag("filter-status", c.parsedArgs.List.Status),
tel.StringFlag("filter-drift-status", c.parsedArgs.List.DriftStatus),
tel.StringFlag("filter-deployment-status", c.parsedArgs.List.DeploymentStatus),
tel.StringFlag("filter-target", c.parsedArgs.List.Target),
tel.BoolFlag("run-order", c.parsedArgs.List.RunOrder),
)
c.setupGit()
c.setupChangeDetection(c.parsedArgs.List.EnableChangeDetection, c.parsedArgs.List.DisableChangeDetection)
c.printStacks()
c.waitForAnalytics()
case "run":
fatal("no command specified")
case "run <cmd>":
c.initAndSendAnalytics("run",
tel.BoolFlag("filter-changed", c.parsedArgs.Changed),
tel.BoolFlag("filter-tags", len(c.parsedArgs.Tags) != 0),
tel.StringFlag("filter-status", c.parsedArgs.Run.Status),
tel.StringFlag("filter-drift-status", c.parsedArgs.Run.DriftStatus),
tel.StringFlag("filter-deployment-status", c.parsedArgs.Run.DeploymentStatus),
tel.StringFlag("target", c.parsedArgs.Run.Target),
tel.BoolFlag("sync-deployment", c.parsedArgs.Run.SyncDeployment),
tel.BoolFlag("sync-drift", c.parsedArgs.Run.SyncDriftStatus),
tel.BoolFlag("sync-preview", c.parsedArgs.Run.SyncPreview),
tel.StringFlag("terraform-planfile", c.parsedArgs.Run.TerraformPlanFile),
tel.StringFlag("tofu-planfile", c.parsedArgs.Run.TofuPlanFile),
tel.StringFlag("layer", string(c.parsedArgs.Run.Layer)),
tel.BoolFlag("terragrunt", c.parsedArgs.Run.Terragrunt),
tel.BoolFlag("reverse", c.parsedArgs.Run.Reverse),
tel.BoolFlag("parallel", c.parsedArgs.Run.Parallel > 0),
tel.BoolFlag("output-sharing", c.parsedArgs.Run.EnableSharing),
tel.BoolFlag("output-mocks", c.parsedArgs.Run.MockOnFail),
)
c.setupGit()
c.setupChangeDetection(c.parsedArgs.Run.EnableChangeDetection, c.parsedArgs.Run.DisableChangeDetection)
c.setupSafeguards(c.parsedArgs.Run.runSafeguardsCliSpec)
c.runOnStacks()
c.waitForAnalytics()
case "generate":
c.initAnalytics("generate",
tel.BoolFlag("detailed-exit-code", c.parsedArgs.Generate.DetailedExitCode),
tel.BoolFlag("parallel", c.parsedArgs.Generate.Parallel > 0),
)
exitCode := c.generate()
stopProfiler(c.parsedArgs)
c.sendAnalytics()
c.waitForAnalytics()
os.Exit(exitCode)
case "experimental clone <srcdir> <destdir>":
c.initAndSendAnalytics("clone")
c.cloneStack()
c.waitForAnalytics()
case "experimental trigger":
c.initAndSendAnalytics("trigger")
c.triggerStackByFilter()
c.waitForAnalytics()
case "experimental trigger <stack>":
c.initAndSendAnalytics("trigger",
tel.StringFlag("stack", c.parsedArgs.Experimental.Trigger.Stack),
tel.BoolFlag("change", c.parsedArgs.Experimental.Trigger.Change),
tel.BoolFlag("ignore-change", c.parsedArgs.Experimental.Trigger.IgnoreChange),
)
c.triggerStack(c.parsedArgs.Experimental.Trigger.Stack)
c.waitForAnalytics()
case "experimental vendor download <source> <ref>":
c.initAndSendAnalytics("vendor-download")
c.vendorDownload()
c.waitForAnalytics()
case "debug show globals":
c.setupGit()
c.printStacksGlobals()
Expand All @@ -737,8 +795,10 @@ func (c *cli) run() {
c.setupGit()
c.printMetadata()
case "experimental run-graph":
c.initAndSendAnalytics("graph")
c.setupGit()
c.generateGraph()
c.waitForAnalytics()
case "debug show runtime-env":
c.setupGit()
c.printRuntimeEnv()
Expand All @@ -757,37 +817,115 @@ func (c *cli) run() {
case "experimental cloud info": // Deprecated
fallthrough
case "cloud info":
c.initAndSendAnalytics("cloud-info")
c.cloudInfo()
c.waitForAnalytics()
case "experimental cloud drift show": // Deprecated
fallthrough
case "cloud drift show":
c.initAndSendAnalytics("cloud-drift-show")
c.cloudDriftShow()
c.waitForAnalytics()
case "script list":
c.initAndSendAnalytics("script-list")
c.checkScriptEnabled()
c.printScriptList()
c.waitForAnalytics()
case "script tree":
c.initAndSendAnalytics("script-tree")
c.checkScriptEnabled()
c.printScriptTree()
c.waitForAnalytics()
case "script info":
c.checkScriptEnabled()
fatal("no script specified")
case "script info <cmds>":
c.initAndSendAnalytics("script-info")
c.checkScriptEnabled()
c.printScriptInfo()
c.waitForAnalytics()
case "script run":
c.checkScriptEnabled()
fatal("no script specified")
case "script run <cmds>":
c.initAnalytics("script-run",
tel.BoolFlag("filter-changed", c.parsedArgs.Changed),
tel.BoolFlag("filter-tags", len(c.parsedArgs.Tags) != 0),
tel.StringFlag("filter-status", c.parsedArgs.Script.Run.Status),
tel.StringFlag("filter-drift-status", c.parsedArgs.Script.Run.DriftStatus),
tel.StringFlag("filter-deployment-status", c.parsedArgs.Script.Run.DeploymentStatus),
tel.StringFlag("target", c.parsedArgs.Script.Run.Target),
tel.BoolFlag("reverse", c.parsedArgs.Script.Run.Reverse),
tel.BoolFlag("parallel", c.parsedArgs.Script.Run.Parallel > 0),
)
c.checkScriptEnabled()
c.setupGit()
c.setupChangeDetection(c.parsedArgs.Script.Run.EnableChangeDetection, c.parsedArgs.Script.Run.DisableChangeDetection)
c.setupSafeguards(c.parsedArgs.Script.Run.runSafeguardsCliSpec)
c.runScript()
c.sendAnalytics()
c.waitForAnalytics()
default:
fatal("unexpected command sequence")
}
}

func (c *cli) initAnalytics(cmd string, opts ...tel.MessageOpt) {
cpsigfile := filepath.Join(c.clicfg.UserTerramateDir, "checkpoint_signature")
anasigfile := filepath.Join(c.clicfg.UserTerramateDir, "analytics_signature")
credfile := filepath.Join(c.clicfg.UserTerramateDir, credfile)

r := tel.DefaultRecord
r.Set(
tel.Command(cmd),
tel.OrgName(c.cloudOrgName()),
tel.DetectFromEnv(credfile, cpsigfile, anasigfile),
tel.StringFlag("chdir", c.parsedArgs.Chdir),
)
r.Set(opts...)
}

func (c *cli) sendAnalytics() {
// There are several ways to disable this, but this requires the least amount of special handling.
// Prepare the record, but don't send it.
if !c.isTelemetryEnabled() {
return
}

tel.DefaultRecord.Send(tel.SendMessageParams{
Timeout: 100 * time.Millisecond,
})
}

func (c *cli) waitForAnalytics() {
if err := tel.DefaultRecord.WaitForSend(); err != nil {
logger := log.With().
Str("action", "cli.waitForAnalytics()").
Logger()
logger.Debug().Err(err).Msgf("failed to wait for analytics")
}
}

func (c *cli) initAndSendAnalytics(cmd string, opts ...tel.MessageOpt) {
c.initAnalytics(cmd, opts...)
c.sendAnalytics()
}

func (c *cli) isTelemetryEnabled() bool {
if c.clicfg.DisableTelemetry {
return false
}

cfg := c.rootNode()
if cfg.Terramate == nil ||
cfg.Terramate.Config == nil ||
cfg.Terramate.Config.Telemetry == nil ||
cfg.Terramate.Config.Telemetry.Enabled == nil {
return true
}
return *cfg.Terramate.Config.Telemetry.Enabled
}

func (c *cli) setupSafeguards(run runSafeguardsCliSpec) {
global := c.parsedArgs.deprecatedGlobalSafeguardsCliSpec

Expand Down Expand Up @@ -1471,7 +1609,7 @@ func (c *cli) initTerragrunt() {
for _, otherMod := range mod.After.Strings() {
// Parent stack modules must be excluded because of implicit filesystem ordering.
// Parent stacks are always executed before child stacks.
if mod.Path.HasPrefix(otherMod + "/") {
if otherMod == "/" || mod.Path.HasPrefix(otherMod+"/") {
continue
}
// after stacks must not be defined as child stacks
Expand Down
6 changes: 6 additions & 0 deletions cmd/terramate/cli/cliconfig/cliconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
type Config struct {
DisableCheckpoint bool
DisableCheckpointSignature bool
DisableTelemetry bool
UserTerramateDir string
}

Expand Down Expand Up @@ -76,6 +77,11 @@ func LoadFrom(fname string) (Config, error) {
return Config{}, err
}
cfg.DisableCheckpointSignature = val.True()
case "disable_telemetry":
if err := checkBoolType(val, name); err != nil {
return Config{}, err
}
cfg.DisableTelemetry = val.True()
case "user_terramate_dir":
if err := checkStrType(val, name); err != nil {
return Config{}, err
Expand Down
9 changes: 9 additions & 0 deletions cmd/terramate/cli/cliconfig/cliconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ func TestLoad(t *testing.T) {
},
},
},
{
name: "valid disable_telemetry",
cfg: `disable_telemetry = true`,
want: want{
cfg: cliconfig.Config{
DisableTelemetry: true,
},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions cmd/terramate/cli/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ func (c *cli) setupCloudConfig(requestedFeatures []string) error {
c.cloud.run.orgName = activeOrgs[0].Name
c.cloud.run.orgUUID = activeOrgs[0].UUID
}

return nil
}

Expand Down
7 changes: 5 additions & 2 deletions cmd/terramate/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ func (c *cli) runAll(
err := cmd.Run()
if err != nil {
if !task.MockOnFail {
errs.Append(errors.E(err, "failed to execute: %s (stderr: %s)", cmd.String(), stderr.Bytes()))
errs.Append(errors.E(err, "failed to execute: (cmd: %s) (stdout: %s) (stderr: %s)", cmd.String(), stdout.String(), stderr.String()))
c.cloudSyncAfter(cloudRun, runResult{ExitCode: -1}, errors.E(ErrRunCommandNotExecuted, err))
releaseResource()
failedTaskIndex = taskIndex
Expand All @@ -502,7 +502,10 @@ func (c *cli) runAll(
break tasksLoop
}

printer.Stderr.Warnf("failed to execute `sharing_backend` command: %v", err)
printer.Stderr.WarnWithDetails(
"failed to execute `sharing_backend` command",
errors.E(err, "(cmd: %s) (stdout: %s) (stderr: %s)", cmd.String(), stdout.String(), stderr.String()),
)
} else {
stdoutBytes := stdout.Bytes()
typ, err := json.ImpliedType(stdoutBytes)
Expand Down
13 changes: 13 additions & 0 deletions cmd/terramate/cli/script_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/fatih/color"
"github.com/hashicorp/go-uuid"
"github.com/terramate-io/terramate/cloud"
tel "github.com/terramate-io/terramate/cmd/terramate/cli/telemetry"
"github.com/terramate-io/terramate/config"
"github.com/terramate-io/terramate/errors"
"github.com/terramate-io/terramate/globals"
Expand Down Expand Up @@ -134,6 +135,18 @@ func (c *cli) runScript() {
task.UseTerragrunt = cmd.Options.UseTerragrunt
task.EnableSharing = cmd.Options.EnableSharing
task.MockOnFail = cmd.Options.MockOnFail

tel.DefaultRecord.Set(
tel.BoolFlag("sync-deployment", cmd.Options.CloudSyncDeployment),
tel.BoolFlag("sync-drift", cmd.Options.CloudSyncDriftStatus),
tel.BoolFlag("sync-preview", cmd.Options.CloudSyncPreview),
tel.StringFlag("terraform-planfile", cmd.Options.CloudTerraformPlanFile),
tel.StringFlag("tofu-planfile", cmd.Options.CloudTofuPlanFile),
tel.StringFlag("layer", string(cmd.Options.CloudSyncLayer)),
tel.BoolFlag("terragrunt", cmd.Options.UseTerragrunt),
tel.BoolFlag("output-sharing", cmd.Options.EnableSharing),
tel.BoolFlag("output-mocks", cmd.Options.MockOnFail),
)
}
run.Tasks = append(run.Tasks, task)
if task.CloudSyncDeployment || task.CloudSyncDriftStatus || task.CloudSyncPreview {
Expand Down
31 changes: 31 additions & 0 deletions cmd/terramate/cli/telemetry/_test_mock.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// TERRAMATE: GENERATED AUTOMATICALLY DO NOT EDIT

resource "local_file" "telemetry" {
content = <<-EOT
package telemetry // import "github.com/terramate-io/terramate/cmd/terramate/cli/telemetry"
var DefaultRecord = NewRecord()
func Endpoint() url.URL
func GenerateOrReadSignature(cpsigfile, anasigfile string) (string, bool)
func GenerateSignature() string
func ReadSignature(p string) string
func SendMessage(msg *Message, p SendMessageParams) <-chan error
type AuthType int
const AuthNone AuthType = iota ...
func DetectAuthTypeFromEnv(credpath string) AuthType
type Message struct{ ... }
type MessageOpt func(msg *Message)
func BoolFlag(name string, flag bool, ifCmds ...string) MessageOpt
func Command(cmd string) MessageOpt
func DetectFromEnv(cmd string, credfile, cpsigfile, anasigfile string) MessageOpt
func StringFlag(name string, flag string, ifCmds ...string) MessageOpt
type PlatformType int
const PlatformLocal PlatformType = iota ...
func DetectPlatformFromEnv() PlatformType
type Record struct{ ... }
func NewRecord() *Record
type SendMessageParams struct{ ... }
EOT

filename = "${path.module}/mock-telemetry.ignore"
}
Loading

0 comments on commit 024b3d7

Please sign in to comment.