diff --git a/cmd/terramate/cli/cli.go b/cmd/terramate/cli/cli.go index 156e12438..7f0e1d0ec 100644 --- a/cmd/terramate/cli/cli.go +++ b/cmd/terramate/cli/cli.go @@ -671,7 +671,7 @@ func (c *cli) printRunOrder() { } logger.Debug().Msg("Get run order.") - order, reason, err := run.Sort(c.root(), stacks, c.parsedArgs.Changed) + orderedStacks, reason, err := run.Sort(c.root(), stacks) if err != nil { if errors.Is(err, dag.ErrCycleDetected) { log.Fatal(). @@ -685,7 +685,7 @@ func (c *cli) printRunOrder() { } } - for _, s := range order { + for _, s := range orderedStacks { c.log(s.Name()) } } @@ -828,7 +828,7 @@ func (c *cli) runOnStacks() { logger.Trace().Msg("Get order of stacks to run command on.") - orderedStacks, reason, err := run.Sort(c.root(), stacks, c.parsedArgs.Changed) + orderedStacks, reason, err := run.Sort(c.root(), stacks) if err != nil { if errors.Is(err, dag.ErrCycleDetected) { logger.Fatal(). diff --git a/cmd/terramate/e2etests/run_test.go b/cmd/terramate/e2etests/run_test.go index f451ac420..b6d2ff2b0 100644 --- a/cmd/terramate/e2etests/run_test.go +++ b/cmd/terramate/e2etests/run_test.go @@ -32,9 +32,10 @@ import ( func TestCLIRunOrder(t *testing.T) { type testcase struct { - name string - layout []string - want runExpected + name string + layout []string + workingDir string + want runExpected } for _, tc := range []testcase{ @@ -432,6 +433,20 @@ stack-c stack-a stack-d stack-z +`, + }, + }, + { + name: `run order selects only stacks inside working dir`, + layout: []string{ + `s:stacks/stack-a:after=["/stacks/stack-b", "/parent-stack"]`, + `s:stacks/stack-b:before=["/parent-stack"]`, + `s:parent-stack`, + }, + workingDir: "stacks", + want: runExpected{ + Stdout: `stack-b +stack-a `, }, }, @@ -440,7 +455,12 @@ stack-z s := sandbox.New(t) s.BuildTree(tc.layout) - cli := newCLI(t, s.RootDir()) + wd := s.RootDir() + if tc.workingDir != "" { + wd = filepath.Join(wd, tc.workingDir) + } + + cli := newCLI(t, wd) assertRunResult(t, cli.stacksRunOrder(), tc.want) }) } @@ -638,7 +658,6 @@ func TestRunOrderNotChangedStackIgnored(t *testing.T) { // stack must run after stack2 but stack2 didn't change. stack2 := s.CreateStack("stack2") - stack := s.CreateStack("stack") stackMainTf := stack.CreateFile(mainTfFileName, "# some code") stackConfig, err := hcl.NewConfig(stack.Path()) @@ -671,8 +690,6 @@ func TestRunOrderNotChangedStackIgnored(t *testing.T) { mainTfFileName, ), runExpected{Stdout: wantRun}) - wantRun = mainTfContents - cli = newCLI(t, stack.Path()) assertRunResult(t, cli.run( "run", @@ -690,6 +707,47 @@ func TestRunOrderNotChangedStackIgnored(t *testing.T) { ), runExpected{}) } +func TestRunIgnoresAfterBeforeStackRefsOutsideWorkingDir(t *testing.T) { + const testfile = "testfile" + + s := sandbox.New(t) + + s.BuildTree([]string{ + "s:parent-stack", + `s:stacks/stack-1:before=["/parent-stack"]`, + `s:stacks/stack-2:after=["/parent-stack"]`, + fmt.Sprintf("f:parent-stack/%s:parent-stack\n", testfile), + fmt.Sprintf("f:stacks/stack-1/%s:stack-1\n", testfile), + fmt.Sprintf("f:stacks/stack-2/%s:stack-2\n", testfile), + }) + + git := s.Git() + git.CommitAll("first commit") + + cat := test.LookPath(t, "cat") + assertRun := func(wd string, want string) { + cli := newCLI(t, filepath.Join(s.RootDir(), wd)) + + assertRunResult(t, cli.run( + "run", + cat, + testfile, + ), runExpected{Stdout: want}) + + assertRunResult(t, cli.run( + "run", + "--changed", + cat, + testfile, + ), runExpected{Stdout: want}) + } + + assertRun(".", "stack-1\nparent-stack\nstack-2\n") + assertRun("stacks", "stack-1\nstack-2\n") + assertRun("stacks/stack-1", "stack-1\n") + assertRun("stacks/stack-2", "stack-2\n") +} + func TestRunOrderAllChangedStacksExecuted(t *testing.T) { const ( mainTfFileName = "main.tf" diff --git a/run/order.go b/run/order.go index 42dc1a8f7..aa7603cec 100644 --- a/run/order.go +++ b/run/order.go @@ -27,30 +27,23 @@ type visited map[string]struct{} // Sort computes the final execution order for the given list of stacks. // In the case of multiple possible orders, it returns the lexicographic sorted // path. -func Sort(root string, stacks []stack.S, changed bool) ([]stack.S, string, error) { - logger := log.With(). - Str("action", "RunOrder()"). - Str("path", root). - Logger() - - logger.Debug(). - Msg("Create new directed acyclic graph.") +func Sort(root string, stacks []stack.S) ([]stack.S, string, error) { d := dag.New() - - logger.Trace(). - Msg("Create new stack loader.") loader := stack.NewLoader(root) - logger.Trace(). - Msg("Add stacks to loader.") for _, stack := range stacks { loader.Set(stack.PrjAbsPath(), stack) } visited := visited{} - logger.Trace(). - Msg("Range over stacks.") + logger := log.With(). + Str("action", "run.Sort()"). + Str("root", root). + Logger() + + logger.Trace().Msg("Sorting stacks.") + for _, stack := range stacks { if _, ok := visited[stack.PrjAbsPath()]; ok { continue @@ -65,30 +58,48 @@ func Sort(root string, stacks []stack.S, changed bool) ([]stack.S, string, error } } - logger.Trace(). - Msg("Validate DAG.") + logger.Trace().Msg("Validate DAG.") + reason, err := d.Validate() if err != nil { return nil, reason, err } - logger.Trace(). - Msg("Get topologically order DAG.") + logger.Trace().Msg("Get topologically order DAG.") + order := d.Order() orderedStacks := make([]stack.S, 0, len(order)) - logger.Trace(). - Msg("Get ordered stacks.") + logger.Trace().Msg("Get ordered stacks.") + + isSelectedStack := func(s stack.S) bool { + // Stacks may be added on the DAG from after/before references + // but they should not be on the final order if they are not part + // of the previously selected stacks passed as a parameter. + // This is important for change detection to work on ordering and + // also for filtering by working dir. + for _, stack := range stacks { + if s.PrjAbsPath() == stack.PrjAbsPath() { + return true + } + } + return false + } + for _, id := range order { val, err := d.Node(id) if err != nil { return nil, "", fmt.Errorf("calculating run-order: %w", err) } s := val.(stack.S) - if s.IsChanged() == changed { - orderedStacks = append(orderedStacks, s) + if !isSelectedStack(s) { + logger.Trace(). + Str("stack", s.PrjAbsPath()). + Msg("ignoring since not part of selected stacks") + continue } + orderedStacks = append(orderedStacks, s) } return orderedStacks, "", nil