Skip to content

Commit

Permalink
feat: task groups and split screen (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Jun 8, 2024
1 parent c3cdf2e commit 869a790
Show file tree
Hide file tree
Showing 57 changed files with 1,514 additions and 2,545 deletions.
6 changes: 1 addition & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,15 @@ require (

require (
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/apparentlymart/go-versions v1.0.2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymanbagabas/go-udiff v0.2.0 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20240222125807-0344fda748f8 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand Down Expand Up @@ -71,8 +69,6 @@ require (
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Expand Down
80 changes: 2 additions & 78 deletions go.sum

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ func startApp(cfg config, stdout io.Writer) (*app, error) {

// Start daemons
task.StartEnqueuer(tasks)
run.StartScheduler(runs, workspaces)
waitTasks := task.StartRunner(ctx, logger, tasks, cfg.MaxTasks)

// Automatically load workspaces whenever modules are loaded.
Expand Down Expand Up @@ -206,7 +205,7 @@ func startApp(cfg config, stdout io.Writer) (*app, error) {
wg.Done()
}()

taskEvents := tasks.Subscribe()
taskEvents := tasks.TaskBroker.Subscribe()
wg.Add(1)
go func() {
for ev := range taskEvents {
Expand All @@ -215,14 +214,24 @@ func startApp(cfg config, stdout io.Writer) (*app, error) {
wg.Done()
}()

taskGroupEvents := tasks.GroupBroker.Subscribe()
wg.Add(1)
go func() {
for ev := range taskGroupEvents {
ch <- ev
}
wg.Done()
}()

// cleanup function to be invoked when app is terminated.
cleanup := func() {
// Cancel context
cancel()

// Close subscriptions
logger.Shutdown()
tasks.Shutdown()
tasks.TaskBroker.Shutdown()
tasks.GroupBroker.Shutdown()
modules.Shutdown()
workspaces.Shutdown()
states.Shutdown()
Expand All @@ -235,7 +244,7 @@ func startApp(cfg config, stdout io.Writer) (*app, error) {

// Remove all run artefacts (plan files etc,...)
for _, run := range runs.List(run.ListOptions{}) {
_ = os.RemoveAll(run.ArtefactsPath())
_ = os.RemoveAll(run.ArtefactsPath)
}

// Wait for running tasks to terminate. Canceling the context (above)
Expand Down
35 changes: 15 additions & 20 deletions internal/app/module_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,48 +31,43 @@ func TestModuleList(t *testing.T) {
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlA})
tm.Type("i")
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "completed init tasks: (3 successful; 0 errored; 0 canceled; 0 uncreated)")
return strings.Contains(s, "TaskGroup{init}") &&
matchPattern(t, `modules/a.*init.*exited`, s) &&
matchPattern(t, `modules/b.*init.*exited`, s) &&
matchPattern(t, `modules/c.*init.*exited`, s)
})

// Go back to module listing and format all modules
tm.Send(tea.KeyMsg{Type: tea.KeyEsc})
tm.Type("f")
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "completed format tasks: (3 successful; 0 errored; 0 canceled; 0 uncreated)")
return strings.Contains(s, "TaskGroup{format}") &&
matchPattern(t, `modules/a.*fmt.*exited`, s) &&
matchPattern(t, `modules/b.*fmt.*exited`, s) &&
matchPattern(t, `modules/c.*fmt.*exited`, s)
})

// Go back to module listing and validate all modules
tm.Send(tea.KeyMsg{Type: tea.KeyEsc})
tm.Type("v")
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "completed validate tasks: (3 successful; 0 errored; 0 canceled; 0 uncreated)")
return strings.Contains(s, "TaskGroup{validate}") &&
matchPattern(t, `modules/a.*validate.*exited`, s) &&
matchPattern(t, `modules/b.*validate.*exited`, s) &&
matchPattern(t, `modules/c.*validate.*exited`, s)
})

// Go back to module listing, and create plan on the current workspace of
// each module.
tm.Send(tea.KeyMsg{Type: tea.KeyEsc})
tm.Type("p")
// Expect all 3 modules to be in planned state
// Expect three plan tasks to be created and to reach planned state.
waitFor(t, tm, func(s string) bool {
return matchPattern(t, `modules/a.*default.*planned`, s) &&
return strings.Contains(s, "TaskGroup{plan}") &&
matchPattern(t, `modules/a.*default.*planned`, s) &&
matchPattern(t, `modules/b.*default.*planned`, s) &&
matchPattern(t, `modules/c.*default.*planned`, s)
})

// Go back to module listing, and apply the plan for each module.
tm.Send(tea.KeyMsg{Type: tea.KeyEsc})
tm.Type("a")

// Confirm apply
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "Apply 3 runs? (y/N):")
})
tm.Type("y")

// Expect all 3 modules to be in applied state
waitFor(t, tm, func(s string) bool {
return matchPattern(t, `(?s)(applied.*)[3]`, s)
})
}

func TestModuleList_Reload(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions internal/app/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ func TestModule_ReloadWorkspaces(t *testing.T) {
// Reload workspaces for current module
tm.Send(tea.KeyMsg{Type: tea.KeyCtrlW})

// Expect message to inform user that reload has finished.
// Expect to be taken to task page showing output of `workspace list`.
waitFor(t, tm, func(s string) bool {
return strings.Contains(s, "completed reload-workspaces task successfully")
return strings.Contains(s, "Task{workspace list}(modules/a)") &&
strings.Contains(s, "* default") &&
strings.Contains(s, "dev")
})
}

Expand Down
14 changes: 1 addition & 13 deletions internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,8 @@ type Module struct {
// The module's current workspace.
CurrentWorkspaceID *resource.ID

// Whether module is initialized correctly. Nil means it is unknown.
// Whether module has been initialized in the past. Nil means it is unknown.
Initialized *bool
// Whether a terraform init is in progress.
InitInProgress bool

// Whether module is formatted correctly. Nil means it is unknown.
Formatted *bool
// Whether formatting is in progress.
FormatInProgress bool

// Whether module is valid. Nil means it is unknown.
Valid *bool
// Whether validation is in progress.
ValidationInProgress bool
}

// New constructs a module. Workdir is the pug working directory, and path is
Expand Down
54 changes: 0 additions & 54 deletions internal/module/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,9 @@ func (s *Service) Init(moduleID resource.ID) (*task.Task, error) {
// The terraform plugin cache is not concurrency-safe, so only allow one
// init task to run at any given time.
Exclusive: s.pluginCache,
AfterCreate: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.InitInProgress = true
return nil
})
},
AfterExited: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Initialized = internal.Bool(true)
mod.InitInProgress = false
return nil
})
},
AfterError: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Initialized = internal.Bool(false)
mod.InitInProgress = false
return nil
})
},
Expand Down Expand Up @@ -151,26 +137,6 @@ func (s *Service) Format(moduleID resource.ID) (*task.Task, error) {

return s.CreateTask(mod, task.CreateOptions{
Command: []string{"fmt"},
AfterCreate: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.FormatInProgress = true
return nil
})
},
AfterExited: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Formatted = internal.Bool(true)
mod.FormatInProgress = false
return nil
})
},
AfterError: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Formatted = internal.Bool(false)
mod.FormatInProgress = false
return nil
})
},
})
}

Expand All @@ -182,26 +148,6 @@ func (s *Service) Validate(moduleID resource.ID) (*task.Task, error) {

return s.CreateTask(mod, task.CreateOptions{
Command: []string{"validate"},
AfterCreate: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.ValidationInProgress = true
return nil
})
},
AfterExited: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Valid = internal.Bool(true)
mod.ValidationInProgress = false
return nil
})
},
AfterError: func(*task.Task) {
s.table.Update(moduleID, func(mod *Module) error {
mod.Valid = internal.Bool(false)
mod.ValidationInProgress = false
return nil
})
},
})
}

Expand Down
2 changes: 2 additions & 0 deletions internal/resource/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
Workspace
Run
Task
TaskGroup
Log
LogAttr
StateResource
Expand All @@ -20,6 +21,7 @@ func (k Kind) String() string {
"ws",
"run",
"task",
"tg",
"log",
"attr",
"res",
Expand Down
4 changes: 4 additions & 0 deletions internal/run/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var (
// parsePlanReport reads the logs from `terraform plan` and detects whether
// there were any changes and produces a report of the number of resource
// changes.
//
// TODO: parse bytes intead, to skip re-allocation
func parsePlanReport(logs string) (bool, Report, error) {
raw := internal.StripAnsi(logs)

Expand Down Expand Up @@ -60,6 +62,8 @@ func parsePlanReport(logs string) (bool, Report, error) {

// parseApplyReport reads the logs from `terraform apply` and produces a report
// of the changes made.
//
// TODO: parse bytes intead, to skip re-allocation
func parseApplyReport(logs string) (Report, error) {
matches := applyChangesRegex.FindStringSubmatch(logs)
if matches == nil {
Expand Down
Loading

0 comments on commit 869a790

Please sign in to comment.