Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 committed Sep 6, 2024
1 parent a80bf0b commit ebb75a3
Show file tree
Hide file tree
Showing 34 changed files with 427 additions and 359 deletions.
28 changes: 14 additions & 14 deletions internal/module/log_enricher.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package module

import (
"reflect"

"github.com/leg100/pug/internal/resource"
)

Expand All @@ -9,32 +11,30 @@ type logEnricher struct {
table *resource.Table[*Module]
}

// moduleResource is a resource that belongs to a module
type moduleResource interface {
Module() resource.Resource
}

func (e *logEnricher) EnrichLogRecord(args ...any) []any {
args = e.addModulePath(args...)
args = e.replaceIDWithModule(args...)
return args
}

// addModulePath checks if one of the log args is a resource that
// belongs to a module, and if so, adds the module to the args
// addModulePath checks if one of the log message args is a struct with a ModuleID
// field, and if so, looks up the corresponding module and adds it to the
// message.
func (e *logEnricher) addModulePath(args ...any) []any {
for _, arg := range args {
res, ok := arg.(moduleResource)
if !ok {
// does not belong to a module
v := reflect.Indirect(reflect.ValueOf(arg))
if v.Kind() != reflect.Struct {
continue
}
modResource := res.Module()
if modResource == nil {
// can belong to a module but not in this instance
f := v.FieldByName("ModuleID")
if f.IsZero() {
continue
}
mod, err := e.table.Get(modResource.GetID())
id, ok := f.Interface().(resource.ID)
if !ok {
continue
}
mod, err := e.table.Get(id)
if err != nil {
// module with id does not exist
continue
Expand Down
7 changes: 7 additions & 0 deletions internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type Module struct {

// The module's backend type
Backend string

// Dependencies on other modules
dependencies []resource.ID
}

// Options for constructing a module.
Expand Down Expand Up @@ -54,6 +57,10 @@ func (m *Module) LogValue() slog.Value {
)
}

func (m *Module) Dependencies() []resource.ID {
return m.dependencies
}

// find finds root modules that are descendents of the workdir and
// returns options for creating equivalent pug modules.
//
Expand Down
5 changes: 2 additions & 3 deletions internal/module/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ func (s *Service) Reload() (added []string, removed []string, err error) {

func (s *Service) loadTerragruntDependencies() error {
task, err := s.tasks.Create(task.Spec{
Parent: resource.GlobalResource,
Command: []string{"graph-dependencies"},
Wait: true,
})
Expand Down Expand Up @@ -190,7 +189,7 @@ func (s *Service) loadTerragruntDependenciesFromDigraph(r io.Reader) error {
dependencyIDs = append(dependencyIDs, mod.ID)
}
s.table.Update(mod.ID, func(existing *Module) error {
existing.Common = existing.WithDependencies(dependencyIDs...)
existing.dependencies = dependencyIDs
return nil
})
}
Expand Down Expand Up @@ -257,7 +256,7 @@ func (s *Service) updateSpec(moduleID resource.ID, spec task.Spec) (task.Spec, e
if err != nil {
return task.Spec{}, err
}
spec.Parent = mod
spec.ModuleID = &mod.ID
spec.Path = mod.Path
return spec, nil
}
77 changes: 41 additions & 36 deletions internal/plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,29 @@ import (
"path/filepath"

"github.com/leg100/pug/internal"
"github.com/leg100/pug/internal/logging"
"github.com/leg100/pug/internal/pubsub"
"github.com/leg100/pug/internal/resource"
"github.com/leg100/pug/internal/state"
"github.com/leg100/pug/internal/task"
"github.com/leg100/pug/internal/workspace"
)

type plan struct {
resource.Common

ModuleID *resource.ID
WorkspaceID *resource.ID
ModulePath string
HasChanges bool
ArtefactsPath string
Destroy bool
TargetAddrs []state.ResourceAddress

targetArgs []string
terragrunt bool
planFile bool
varsFileArg *string
targetArgs []string
terragrunt bool
planFile bool
varsFileArg *string
envs []string
moduleDependencies []resource.ID

// taskID is the ID of the plan task, and is only set once the task is
// created.
Expand All @@ -47,6 +50,7 @@ type CreateOptions struct {
type factory struct {
dataDir string
workdir internal.Workdir
modules moduleGetter
workspaces workspaceGetter
broker *pubsub.Broker[*plan]
terragrunt bool
Expand All @@ -57,12 +61,19 @@ func (f *factory) newPlan(workspaceID resource.ID, opts CreateOptions) (*plan, e
if err != nil {
return nil, fmt.Errorf("retrieving workspace: %w", err)
}
mod, err := f.modules.Get(ws.ModuleID)
if err != nil {
return nil, fmt.Errorf("retrieving module: %w", err)
}
plan := &plan{
Common: resource.New(resource.Plan, ws),
Destroy: opts.Destroy,
TargetAddrs: opts.TargetAddrs,
planFile: opts.planFile,
terragrunt: f.terragrunt,
Common: resource.New(resource.Plan, ws),
ModulePath: mod.Path,
Destroy: opts.Destroy,
TargetAddrs: opts.TargetAddrs,
planFile: opts.planFile,
terragrunt: f.terragrunt,
envs: []string{ws.TerraformEnv()},
moduleDependencies: mod.Dependencies(),
}
if opts.planFile {
plan.ArtefactsPath = filepath.Join(f.dataDir, fmt.Sprintf("%d", plan.Serial))
Expand All @@ -80,18 +91,6 @@ func (f *factory) newPlan(workspaceID resource.ID, opts CreateOptions) (*plan, e
return plan, nil
}

func (r *plan) WorkspaceID() resource.ID {
return r.Parent.GetID()
}

func (r *plan) WorkspaceName() string {
return r.Parent.String()
}

func (r *plan) ModulePath() string {
return r.Parent.GetParent().String()
}

func (r *plan) planPath() string {
return filepath.Join(r.ArtefactsPath, "plan")
}
Expand All @@ -100,14 +99,15 @@ func (r *plan) args() []string {
return append([]string{"-input"}, r.targetArgs...)
}

func (r *plan) planTaskSpec(logger logging.Interface) task.Spec {
func (r *plan) planTaskSpec() task.Spec {
// TODO: assert planFile is true first
spec := task.Spec{
Parent: r.Workspace(),
Path: r.ModulePath(),
Env: []string{workspace.TerraformEnv(r.WorkspaceName())},
Command: []string{"plan"},
Args: append(r.args(), "-out", r.planPath()),
ModuleID: r.ModuleID,
WorkspaceID: r.WorkspaceID,
Path: r.ModulePath,
Env: r.envs,
Command: []string{"plan"},
Args: append(r.args(), "-out", r.planPath()),
// TODO: explain why plan is blocking (?)
Blocking: true,
Description: "plan",
Expand Down Expand Up @@ -142,17 +142,14 @@ func (r *plan) applyTaskSpec() (task.Spec, error) {
return task.Spec{}, errors.New("plan does not have any changes to apply")
}
spec := task.Spec{
Parent: r.Workspace(),
Path: r.ModulePath(),
ModuleID: r.ModuleID,
WorkspaceID: r.WorkspaceID,
Path: r.ModulePath,
Command: []string{"apply"},
Args: r.args(),
Env: []string{workspace.TerraformEnv(r.WorkspaceName())},
Env: r.envs,
Blocking: true,
Description: "apply",
// If terragrunt is in use then respect module dependencies.
RespectModuleDependencies: r.terragrunt,
// Module dependencies are reversed for a destroy.
InverseDependencyOrder: r.Destroy,
BeforeExited: func(t *task.Task) (task.Summary, error) {
out, err := io.ReadAll(t.NewReader(false))
if err != nil {
Expand All @@ -169,6 +166,14 @@ func (r *plan) applyTaskSpec() (task.Spec, error) {
return report, nil
},
}
// If terragrunt is in use then respect module dependencies.
if r.terragrunt {
spec.Dependencies = &task.Dependencies{
ModuleIDs: r.moduleDependencies,
// Module dependencies are reversed for a destroy.
InverseDependencyOrder: r.Destroy,
}
}
if r.planFile {
spec.Args = append(spec.Args, r.planPath())
} else {
Expand Down
9 changes: 9 additions & 0 deletions internal/plan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,22 @@ func setupTest(t *testing.T) (*factory, *module.Module, *workspace.Workspace) {
ws, err := workspace.New(mod, "dev")
require.NoError(t, err)
factory := factory{
modules: &fakeModuleGetter{mod: mod},
workspaces: &fakeWorkspaceGetter{ws: ws},
dataDir: t.TempDir(),
workdir: workdir,
}
return &factory, mod, ws
}

type fakeModuleGetter struct {
mod *module.Module
}

func (f *fakeModuleGetter) Get(resource.ID) (*module.Module, error) {
return f.mod, nil
}

type fakeWorkspaceGetter struct {
ws *workspace.Workspace
}
Expand Down
2 changes: 1 addition & 1 deletion internal/plan/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (s *Service) Plan(workspaceID resource.ID, opts CreateOptions) (task.Spec,
}
s.table.Add(plan.ID, plan)

return plan.planTaskSpec(s.logger), nil
return plan.planTaskSpec(), nil
}

// Apply creates a task spec to auto-apply a plan, i.e. `terraform apply`. To
Expand Down
8 changes: 6 additions & 2 deletions internal/state/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,13 @@ func (s *Service) createTaskSpec(workspaceID resource.ID, opts task.Spec) (task.
if err != nil {
return task.Spec{}, err
}
opts.Parent = ws
mod, err := s.modules.Get(ws.ModuleID)
if err != nil {
return task.Spec{}, err
}
opts.WorkspaceID = &ws.ID
opts.Env = []string{ws.TerraformEnv()}
opts.Path = ws.ModulePath()
opts.Path = mod.Path

return opts, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,13 @@ func createDependentTasks(svc taskCreator, reverse bool, specs ...Spec) ([]*Task
// Build dependency graph. Each node in the graph is a module together with
// the specs that belong to that module. Once the graph is built and
// dependencies are established, only then are tasks created from the specs.
//
// Specs that don't belong to a module don't have any dependencies so tasks
// are built from these specs immediately.
for _, spec := range specs {
if mod := spec.Parent.Module(); mod != nil {
modID := mod.GetID()
node, ok := b.nodes[modID]
if !ok {
node = &dependencyGraphNode{module: mod}
}
node.specs = append(node.specs, spec)
b.nodes[modID] = node
} else {
b.createTask(spec)
node, ok := b.nodes[*spec.ModuleID]
if !ok {
node = &dependencyGraphNode{dependencies: spec.Dependencies.ModuleIDs}
}
node.specs = append(node.specs, spec)
b.nodes[*spec.ModuleID] = node
}
for id, v := range b.nodes {
if !v.visited {
Expand Down Expand Up @@ -72,7 +64,7 @@ type dependencyGraphBuilder struct {

// dependencyGraphNode represents a module in a dependency graph
type dependencyGraphNode struct {
module resource.Resource
dependencies []resource.ID
specs []Spec
created []resource.ID
in, out []*dependencyGraphNode
Expand All @@ -84,7 +76,7 @@ type dependencyGraphNode struct {
func (b *dependencyGraphBuilder) visit(id resource.ID, n *dependencyGraphNode) {
n.visited = true

for _, id := range n.module.Dependencies() {
for _, id := range n.dependencies {
if dep, ok := b.nodes[id]; ok {
if !dep.visited {
b.visit(id, dep)
Expand All @@ -108,7 +100,7 @@ func (b *dependencyGraphBuilder) visitAndCreateTasks(n *dependencyGraphNode) {
// For each spec, add dependencies on other tasks before creating task and
// adding its ID to the node
for _, spec := range n.specs {
spec.DependsOn = dependsOn
spec.dependsOn = dependsOn
if task := b.createTask(spec); task != nil {
n.created = append(n.created, task.ID)
}
Expand All @@ -128,7 +120,7 @@ func (b *dependencyGraphBuilder) visitAndCreateTasksInReverse(n *dependencyGraph
// For each spec, add dependencies on other tasks before creating task and
// adding its ID to the node
for _, spec := range n.specs {
spec.DependsOn = dependsOn
spec.dependsOn = dependsOn
if task := b.createTask(spec); task != nil {
n.created = append(n.created, task.ID)
}
Expand Down
Loading

0 comments on commit ebb75a3

Please sign in to comment.