Skip to content

Commit

Permalink
refactor: remove resource ancestry abstraction (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Sep 7, 2024
1 parent a80bf0b commit cf66af7
Show file tree
Hide file tree
Showing 50 changed files with 564 additions and 595 deletions.
4 changes: 2 additions & 2 deletions internal/logging/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Message struct {
// A message is a pug resource, but only insofar as it makes it easier to
// handle consistently alongside all other resources (modules, workspaces,
// etc) in the TUI.
resource.Common
resource.ID

Time time.Time
Level string
Expand All @@ -26,5 +26,5 @@ type Attr struct {
// An attribute is a pug resource, but only insofar as it makes it easier to
// handle consistently alongside all other resources (modules, workspaces,
// etc) in the TUI.
resource.Common
resource.ID
}
8 changes: 4 additions & 4 deletions internal/logging/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (w *writer) Write(p []byte) (int, error) {
d := logfmt.NewDecoder(bytes.NewReader(p))
for d.ScanRecord() {
msg := Message{
Common: resource.New(resource.Log, resource.GlobalResource),
ID: resource.NewID(resource.Log),
}
for d.ScanKeyval() {
switch string(d.Key()) {
Expand All @@ -35,9 +35,9 @@ func (w *writer) Write(p []byte) (int, error) {
msg.Message = string(d.Value())
default:
msg.Attributes = append(msg.Attributes, Attr{
Key: string(d.Key()),
Value: string(d.Value()),
Common: resource.New(resource.LogAttr, msg),
Key: string(d.Key()),
Value: string(d.Value()),
ID: resource.NewID(resource.LogAttr),
})
}
}
Expand Down
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.IsValid() {
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
11 changes: 9 additions & 2 deletions internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

// Module is a terraform root module.
type Module struct {
resource.Common
resource.ID

// Path relative to pug working directory
Path string
Expand All @@ -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 All @@ -38,7 +41,7 @@ type Options struct {
// New constructs a module.
func New(opts Options) *Module {
return &Module{
Common: resource.New(resource.Module, resource.GlobalResource),
ID: resource.NewID(resource.Module),
Path: opts.Path,
Backend: opts.Backend,
}
Expand All @@ -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
}
81 changes: 44 additions & 37 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
resource.ID

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,21 @@ 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,
ID: resource.NewID(resource.Plan),
ModuleID: mod.ID,
WorkspaceID: ws.ID,
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 +93,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 +101,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 +144,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 +168,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
14 changes: 9 additions & 5 deletions internal/plan/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func NewService(opts ServiceOptions) *Service {
factory: &factory{
dataDir: opts.DataDir,
workdir: opts.Workdir,
modules: opts.Modules,
workspaces: opts.Workspaces,
broker: broker,
terragrunt: opts.Terragrunt,
Expand All @@ -77,12 +78,15 @@ func (s *Service) ReloadAfterApply(sub <-chan resource.Event[*task.Task]) {
if !IsApplyTask(event.Payload) {
continue
}
ws := event.Payload.Workspace()
if _, err := s.states.CreateReloadTask(ws.GetID()); err != nil {
s.logger.Error("reloading state after apply", "error", err, "workspace", ws)
workspaceID := event.Payload.WorkspaceID
if workspaceID == nil {
continue
}
s.logger.Debug("reloading state after apply", "workspace", ws)
if _, err := s.states.CreateReloadTask(*workspaceID); err != nil {
s.logger.Error("reloading state after apply", "error", err, "workspace", *workspaceID)
continue
}
s.logger.Debug("reloading state after apply", "workspace", *workspaceID)
}
}
}
Expand All @@ -98,7 +102,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
Loading

0 comments on commit cf66af7

Please sign in to comment.