From 9fd8308fcb594333ba2498c6fc5b801a96f20de2 Mon Sep 17 00:00:00 2001 From: Srevin Saju Date: Thu, 14 Mar 2024 13:06:09 +0530 Subject: [PATCH] feat: forward parent lifecycles to child stages defined on the module In scenarios where lifecycles are defined on the module level, there was an issue preventing the same lifecycle phases from being applied to the child modules. This was very inconvenient when reusing multiple modules and the caller may have custom lifecycle phases, but the module's are not owned by the caller to make changes --- .../calculator/togomak.hcl | 30 +++++++++++++++++ .../module-phases-inheritance/togomak.hcl | 28 ++++++++++++++++ internal/behavior/models.go | 2 ++ internal/ci/module_run.go | 24 +++++++++++--- internal/ci/run.go | 14 +++++--- tests/togomak.hcl | 32 ++++++++++++++++++- 6 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 examples/module-phases-inheritance/calculator/togomak.hcl create mode 100644 examples/module-phases-inheritance/togomak.hcl diff --git a/examples/module-phases-inheritance/calculator/togomak.hcl b/examples/module-phases-inheritance/calculator/togomak.hcl new file mode 100644 index 0000000..95c9add --- /dev/null +++ b/examples/module-phases-inheritance/calculator/togomak.hcl @@ -0,0 +1,30 @@ +togomak { + version = 2 +} + +variable "a" { + type = number + description = "first variable" +} +variable "b" { + type = number + description = "second variable" +} + +variable "operation" { + type = string + description = "Operation to perform, any of: [add, subtract, multiply, divide]" +} + +stage "add" { + if = var.operation == "add" + script = "echo ${var.a} + ${var.b} is ${var.a + var.b}" +} + +stage "paths" { + script = <<-EOT + echo path.module: ${path.module} + echo path.cwd: ${path.cwd} + echo path.root: ${path.root} + EOT +} diff --git a/examples/module-phases-inheritance/togomak.hcl b/examples/module-phases-inheritance/togomak.hcl new file mode 100644 index 0000000..99f08b5 --- /dev/null +++ b/examples/module-phases-inheritance/togomak.hcl @@ -0,0 +1,28 @@ + togomak { + version = 2 +} + +locals { + a = 99 + b = 1 +} + +stage "paths" { + script = <<-EOT + echo path.module: ${path.module} + echo path.cwd: ${path.cwd} + echo path.root: ${path.root} + EOT +} + +module "add" { + source = "./calculator" + a = local.a + b = local.b + operation = "add" + + lifecycle { + phase = ["add"] + } +} + diff --git a/internal/behavior/models.go b/internal/behavior/models.go index 3649166..7bc6151 100644 --- a/internal/behavior/models.go +++ b/internal/behavior/models.go @@ -7,6 +7,8 @@ type Child struct { // Parent is the flag to indicate whether the program is running in parent mode Parent string + ParentLifecycles []string + // ParentParams is the list of parameters to be passed to the parent ParentParams []string } diff --git a/internal/ci/module_run.go b/internal/ci/module_run.go index 29ecffd..ba6c7f1 100644 --- a/internal/ci/module_run.go +++ b/internal/ci/module_run.go @@ -163,13 +163,28 @@ func (m *Module) run(conductor *Conductor, source string, evalCtx *hcl.EvalConte }) } + var parentLifecycles []string + if cfg.Behavior.Child.ParentLifecycles != nil { + parentLifecycles = cfg.Behavior.Child.ParentLifecycles + } + if m.Lifecycle != nil { + conductor.Eval().Mutex().RLock() + lifecyclePhases, d := m.Lifecycle.Phase.Value(evalCtx) + conductor.Eval().Mutex().RUnlock() + diags = diags.Extend(d) + for _, phase := range lifecyclePhases.AsValueSlice() { + parentLifecycles = append(parentLifecycles, phase.AsString()) + } + } + b := &behavior.Behavior{ Unattended: conductor.Config.Behavior.Unattended, Ci: conductor.Config.Behavior.Ci, Child: behavior.Child{ - Enabled: true, - Parent: "", - ParentParams: nil, + Enabled: true, + Parent: "", + ParentParams: nil, + ParentLifecycles: parentLifecycles, }, DryRun: false, } @@ -194,8 +209,7 @@ func (m *Module) run(conductor *Conductor, source string, evalCtx *hcl.EvalConte conductorOptions = append(conductorOptions, ConductorWithParser(conductor.Parser)) // populate input variables for the child conductor, which would be passed to the module - attrs, d := m.Body.JustAttributes() - diags = diags.Extend(d) + attrs, _ := m.Body.JustAttributes() for _, attr := range attrs { //we need to evaluate the values first within the parent's evaluation context //before sending it to the child goroutine and child conductor diff --git a/internal/ci/run.go b/internal/ci/run.go index 6f0c8fe..9ee8aa1 100644 --- a/internal/ci/run.go +++ b/internal/ci/run.go @@ -112,10 +112,16 @@ func BlockCanRun(runnable Block, conductor *Conductor, runnableId string, depGra ok = false overridden = false - // if the list is empty, we will assume that the runnable is not overridden + // if the list is empty, we will assume that the runnable is not overridden, // and we will run all module blocks. This is so that the child processoe var phases []cty.Value - var phasesDefined bool + + // if the parent config has lifecycles defined, we will append it to the child + for _, phase := range conductor.Config.Behavior.Child.ParentLifecycles { + phases = append(phases, cty.StringVal(phase)) + } + + var phasesDefined bool = len(phases) > 0 stage := runnable.(PhasedBlock) if stage.LifecycleConfig() != nil { evalContext := conductor.Eval().Context() @@ -126,8 +132,8 @@ func BlockCanRun(runnable Block, conductor *Conductor, runnableId string, depGra diags = diags.Extend(d) return false, false, diags } - phasesDefined = !phaseHcl.IsNull() - phases = phaseHcl.AsValueSlice() + phasesDefined = !phaseHcl.IsNull() || len(phases) > 0 + phases = append(phases, phaseHcl.AsValueSlice()...) } if runnable.Type() == blocks.ModuleBlock && len(phases) == 0 && !phasesDefined { diff --git a/tests/togomak.hcl b/tests/togomak.hcl index 0a944d4..fddc97b 100644 --- a/tests/togomak.hcl +++ b/tests/togomak.hcl @@ -49,6 +49,36 @@ stage "tests" { } } +stage "module_phase_test" { + pre_hook { + stage { + script = "echo ${ansi.fg.green}${each.key}${ansi.reset}: full" + } + } + + for_each = toset([ + "../examples/module-phases-inheritance/togomak.hcl", + ]) + + depends_on = [stage.build, stage.coverage_prepare] + args = [ + "./togomak_coverage", + "-C", dirname(each.key), + "--ci", "-v", "-v", "-v", + "add" + ] + + env { + name = "GOCOVERDIR" + value = local.coverage_data_dir + } + env { + name = "TOGOMAK_VAR_name" + value = "bot" + } + +} + stage "tests_dry_run" { pre_hook { @@ -82,7 +112,7 @@ stage "fmt" { } stage "cache" { - depends_on = [stage.fmt, stage.tests, stage.tests_dry_run] + depends_on = [stage.fmt, stage.tests, stage.tests_dry_run, stage.module_phase_test] script = "./togomak_coverage cache clean --recursive" }