Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add terramate.config.order_of_execution.nested attribute. #1991

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
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
39 changes: 33 additions & 6 deletions e2etests/core/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ func TestCLIRunOrder(t *testing.T) {
t.Parallel()

type testcase struct {
name string
layout []string
filterTags []string
filterNoTags []string
workingDir string
want RunExpected
name string
layout []string
fsOrderingDisabled bool
filterTags []string
filterNoTags []string
workingDir string
want RunExpected
}

for _, tc := range []testcase{
Expand Down Expand Up @@ -542,6 +543,22 @@ func TestCLIRunOrder(t *testing.T) {
),
},
},
{
name: "stack-b after stack-a after parent - fs ordering disabled",
layout: []string{
`s:parent/stack-b:after=["/parent/stack-a"]`,
`s:parent/stack-a`,
`s:parent:after=["/parent/stack-b"]`,
},
fsOrderingDisabled: true,
want: RunExpected{
Stdout: nljoin(
"/parent/stack-a",
"/parent/stack-b",
"/parent",
),
},
},
{
name: "implicit order with tags - Zied case",
layout: []string{
Expand Down Expand Up @@ -919,6 +936,16 @@ func TestCLIRunOrder(t *testing.T) {
t.Parallel()
copiedLayout := make([]string, len(tc.layout))
copy(copiedLayout, tc.layout)
if tc.fsOrderingDisabled {
copiedLayout = append(copiedLayout,
fmt.Sprintf("f:disable_fs_ordering.tm:%s", Terramate(
Config(
Block("order_of_execution",
Bool("nested", false),
),
),
).String()))
}
if runtime.GOOS != "windows" {
copiedLayout = append(copiedLayout,

Expand Down
53 changes: 52 additions & 1 deletion hcl/hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,11 @@ type RunConfig struct {
Env *RunEnv
}

// OrderOfExecutionConfig represents the order_of_execution block.
type OrderOfExecutionConfig struct {
Nested *bool // If filesystem order is enabled.
}

// RunEnv represents Terramate run environment.
type RunEnv struct {
// Attributes is the collection of attribute definitions within the env block.
Expand Down Expand Up @@ -262,6 +267,7 @@ type RootConfig struct {
Generate *GenerateRootConfig
ChangeDetection *ChangeDetectionConfig
Run *RunConfig
OrderOfExecution *OrderOfExecutionConfig
Cloud *CloudConfig
Experiments []string
DisableSafeguards safeguard.Keywords
Expand Down Expand Up @@ -1759,7 +1765,16 @@ func (p *TerramateParser) parseRootConfig(cfg *RootConfig, block *ast.MergedBloc
}
}

errs.AppendWrap(ErrTerramateSchema, block.ValidateSubBlocks("git", "generate", "change_detection", "run", "cloud", "targets", "telemetry"))
errs.AppendWrap(ErrTerramateSchema, block.ValidateSubBlocks(
"git",
"generate",
"change_detection",
"run",
"order_of_execution",
"cloud",
"targets",
"telemetry",
))

gitBlock, ok := block.Blocks[ast.NewEmptyLabelBlockType("git")]
if ok {
Expand All @@ -1771,6 +1786,12 @@ func (p *TerramateParser) parseRootConfig(cfg *RootConfig, block *ast.MergedBloc
errs.Append(parseRunConfig(cfg, runBlock))
}

orderExecBlock, ok := block.Blocks[ast.NewEmptyLabelBlockType("order_of_execution")]
if ok {
cfg.OrderOfExecution = &OrderOfExecutionConfig{}
errs.Append(parseOrderOfExecutionConfig(cfg.OrderOfExecution, orderExecBlock))
}

cloudBlock, ok := block.Blocks[ast.NewEmptyLabelBlockType("cloud")]
if ok {
cfg.Cloud = &CloudConfig{}
Expand Down Expand Up @@ -1850,6 +1871,36 @@ func parseRunConfig(cfg *RootConfig, runBlock *ast.MergedBlock) error {
return errs.AsError()
}

func parseOrderOfExecutionConfig(cfg *OrderOfExecutionConfig, orderExecBlock *ast.MergedBlock) error {
errs := errors.L()
for _, attr := range orderExecBlock.Attributes {
value, err := attr.Expr.Value(nil)
if err != nil {
errs.Append(errors.E(err, "failed to evaluate terramate.config.order_of_execution.%s attribute", attr.Name))
continue
}

switch attr.Name {
case "nested":
if value.Type() != cty.Bool {
errs.Append(attrErr(attr,
"terramate.config.order_of_execution.nested is not a bool but %q",
value.Type().FriendlyName(),
))
continue
}
t := value.True()
cfg.Nested = &t
default:
errs.Append(errors.E(attr.NameRange,
"unrecognized attribute terramate.config.order_of_execution.%s",
attr.Name,
))
}
}
return errs.AsError()
}

func parseGenerateRootConfig(cfg *GenerateRootConfig, generateBlock *ast.MergedBlock) error {
errs := errors.L()

Expand Down
81 changes: 81 additions & 0 deletions hcl/hcl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
errtest "github.com/terramate-io/terramate/test/errors"
. "github.com/terramate-io/terramate/test/hclutils"
"github.com/terramate-io/terramate/test/hclutils/info"
. "github.com/terramate-io/terramate/test/hclwrite/hclutils"
)

type (
Expand All @@ -43,6 +44,8 @@ type (
)

func TestHCLParserTerramateBlock(t *testing.T) {
truth := true
falsy := false
for _, tc := range []testcase{
{
name: "unrecognized blocks",
Expand Down Expand Up @@ -594,6 +597,84 @@ func TestHCLParserTerramateBlock(t *testing.T) {
},
},
},
{
name: "terramate.config.order_of_execution.nested=false",
input: []cfgfile{
{
filename: "cfg.tm",
body: Terramate(
Config(
Block("order_of_execution",
Bool("nested", false),
),
),
).String(),
},
},
want: want{
config: hcl.Config{
Terramate: &hcl.Terramate{
Config: &hcl.RootConfig{
OrderOfExecution: &hcl.OrderOfExecutionConfig{
Nested: &falsy,
},
},
},
},
},
},
{
name: "empty terramate.config.order_of_execution block",
input: []cfgfile{
{
filename: "cfg.tm",
body: `
terramate {
config {
order_of_execution {
}
}
}
`,
},
},
want: want{
config: hcl.Config{
Terramate: &hcl.Terramate{
Config: &hcl.RootConfig{
OrderOfExecution: &hcl.OrderOfExecutionConfig{},
},
},
},
},
},
{
name: "terramate.config.order_of_execution.nested=true",
input: []cfgfile{
{
filename: "cfg.tm",
body: Terramate(
Config(
Block("order_of_execution",
Bool("nested", true),
),
),
).String(),
},
},
want: want{
config: hcl.Config{
Terramate: &hcl.Terramate{
Config: &hcl.RootConfig{
OrderOfExecution: &hcl.OrderOfExecutionConfig{
Nested: &truth,
},
},
},
},
},
},
} {
testParser(t, tc)
}
Expand Down
45 changes: 29 additions & 16 deletions run/order.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,28 +106,30 @@ func buildValidStackDAG[S ~[]E, E any](
Str("root", root.HostDir()).
Logger()

isParentStack := func(s1, s2 *config.Stack) bool {
return s1.Dir.HasPrefix(s2.Dir.String() + "/")
}

getStackDir := func(s E) string {
return getStack(s).Dir.String()
}

slices.SortStableFunc(items, func(a, b E) int {
return strings.Compare(getStack(a).Dir.String(), getStack(b).Dir.String())
})

for _, a := range items {
for _, b := range items {
if getStack(a).Dir == getStack(b).Dir {
continue
}
if isFsOrderingEnabled(root) {
isParentStack := func(s1, s2 *config.Stack) bool {
return s1.Dir.HasPrefix(s2.Dir.String() + "/")
}

if isParentStack(getStack(a), getStack(b)) {
logger.Debug().Msgf("stack %q runs before %q since it is its parent", getStackDir(a), getStackDir(b))
getStackDir := func(s E) string {
return getStack(s).Dir.String()
}

for _, a := range items {
for _, b := range items {
if getStack(a).Dir == getStack(b).Dir {
continue
}

if isParentStack(getStack(a), getStack(b)) {
logger.Debug().Msgf("stack %q runs before %q since it is its parent", getStackDir(a), getStackDir(b))

getStack(b).AppendBefore(getStack(a).Dir.String())
getStack(b).AppendBefore(getStack(a).Dir.String())
}
}
}
}
Expand Down Expand Up @@ -298,3 +300,14 @@ func toids(values config.List[*config.SortableStack]) []dag.ID {
}
return ids
}

func isFsOrderingEnabled(root *config.Root) bool {
cfg := root.Tree().Node
if cfg.Terramate != nil &&
cfg.Terramate.Config != nil &&
cfg.Terramate.Config.OrderOfExecution != nil &&
cfg.Terramate.Config.OrderOfExecution.Nested != nil {
return *cfg.Terramate.Config.OrderOfExecution.Nested
}
return true
}
Loading