Skip to content
Merged
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
4 changes: 2 additions & 2 deletions internal/logging/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (e *ReferenceUpdater[T]) UpdateArgs(args ...any) []any {
// Where an argument is of type resource.ID, try and retrieve the
// resource corresponding to the ID and replace argument with the
// resource.
if id, ok := arg.(resource.ID); ok {
if id, ok := arg.(resource.MonotonicID); ok {
t, err := e.Get(id)
if err != nil {
continue
Expand All @@ -68,7 +68,7 @@ func (e *ReferenceUpdater[T]) UpdateArgs(args ...any) []any {
if !f.IsValid() {
continue
}
id, ok := f.Interface().(resource.ID)
id, ok := f.Interface().(resource.MonotonicID)
if !ok {
continue
}
Expand Down
16 changes: 8 additions & 8 deletions internal/logging/enricher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import (
)

func TestReferenceUpdater(t *testing.T) {
res := &fakeResource{ID: resource.NewID(resource.Module)}
res := &fakeResource{MonotonicID: resource.NewMonotonicID(resource.Module)}
updater := &ReferenceUpdater[*fakeResource]{
Getter: &fakeResourceGetter{res: res},
Name: "fake",
Field: "FakeResourceID",
}

t.Run("replace resource id with resource", func(t *testing.T) {
args := []any{"fake", res.ID}
args := []any{"fake", res.MonotonicID}
got := updater.UpdateArgs(args...)

want := []any{"fake", res}
Expand All @@ -25,10 +25,10 @@ func TestReferenceUpdater(t *testing.T) {

t.Run("add resource when referenced from struct with pointer field", func(t *testing.T) {
type logMsgArg struct {
FakeResourceID *resource.ID
FakeResourceID *resource.MonotonicID
}

args := []any{"arg1", logMsgArg{FakeResourceID: &res.ID}}
args := []any{"arg1", logMsgArg{FakeResourceID: &res.MonotonicID}}
got := updater.UpdateArgs(args...)

want := append(args, "fake", res)
Expand All @@ -37,10 +37,10 @@ func TestReferenceUpdater(t *testing.T) {

t.Run("add resource when referenced from struct with non-pointer field", func(t *testing.T) {
type logMsgArg struct {
FakeResourceID resource.ID
FakeResourceID resource.MonotonicID
}

args := []any{"arg1", logMsgArg{FakeResourceID: res.ID}}
args := []any{"arg1", logMsgArg{FakeResourceID: res.MonotonicID}}
got := updater.UpdateArgs(args...)

want := append(args, "fake", res)
Expand All @@ -49,7 +49,7 @@ func TestReferenceUpdater(t *testing.T) {

t.Run("handle nil pointer from struct", func(t *testing.T) {
type logMsgArg struct {
FakeResourceID *resource.ID
FakeResourceID *resource.MonotonicID
}

args := []any{"arg1", logMsgArg{FakeResourceID: nil}}
Expand All @@ -60,7 +60,7 @@ func TestReferenceUpdater(t *testing.T) {
}

type fakeResource struct {
resource.ID
resource.MonotonicID
}

type fakeResourceGetter struct {
Expand Down
16 changes: 6 additions & 10 deletions internal/logging/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,19 @@ import (

// Message is the event payload for a log message
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.ID

ID resource.MonotonicID
Time time.Time
Level string
Message string `json:"msg"`
Attributes []Attr
}

func (m Message) GetID() resource.ID { return m.ID }

type Attr struct {
ID resource.MonotonicID
Key string
Value string

// 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.ID
}

func (a Attr) GetID() resource.ID { return a.ID }
2 changes: 1 addition & 1 deletion internal/logging/sort.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package logging

// BySerialDesc sorts log messages by their serial.
func BySerialDesc(i, j Message) int {
if i.Serial < j.Serial {
if i.ID.Serial < j.ID.Serial {
return 1
}
return -1
Expand Down
4 changes: 2 additions & 2 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{
ID: resource.NewID(resource.Log),
ID: resource.NewMonotonicID(resource.Log),
}
for d.ScanKeyval() {
switch string(d.Key()) {
Expand All @@ -37,7 +37,7 @@ func (w *writer) Write(p []byte) (int, error) {
msg.Attributes = append(msg.Attributes, Attr{
Key: string(d.Key()),
Value: string(d.Value()),
ID: resource.NewID(resource.LogAttr),
ID: resource.NewMonotonicID(resource.LogAttr),
})
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import (

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

// Path relative to pug working directory
Path string
// The module's current workspace.
CurrentWorkspaceID *resource.ID
CurrentWorkspaceID resource.ID

// The module's backend type
Backend string
Expand All @@ -41,7 +41,7 @@ type Options struct {
// New constructs a module.
func New(opts Options) *Module {
return &Module{
ID: resource.NewID(resource.Module),
ID: resource.NewMonotonicID(resource.Module),
Path: opts.Path,
Backend: opts.Backend,
}
Expand Down
10 changes: 5 additions & 5 deletions internal/module/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (s *Service) Init(moduleID resource.ID, upgrade bool) (task.Spec, error) {
args = append(args, "-upgrade")
}
spec := task.Spec{
ModuleID: &mod.ID,
ModuleID: mod.ID,
Path: mod.Path,
Identifier: InitTask,
Execution: task.Execution{
Expand All @@ -236,7 +236,7 @@ func (s *Service) Format(moduleID resource.ID) (task.Spec, error) {
return task.Spec{}, err
}
spec := task.Spec{
ModuleID: &mod.ID,
ModuleID: mod.ID,
Path: mod.Path,
Execution: task.Execution{
TerraformCommand: []string{"fmt"},
Expand All @@ -253,7 +253,7 @@ func (s *Service) Validate(moduleID resource.ID) (task.Spec, error) {
return task.Spec{}, err
}
spec := task.Spec{
ModuleID: &mod.ID,
ModuleID: mod.ID,
Path: mod.Path,
Execution: task.Execution{
TerraformCommand: []string{"validate"},
Expand Down Expand Up @@ -284,7 +284,7 @@ func (s *Service) GetByPath(path string) (*Module, error) {
// SetCurrent sets the current workspace for the module.
func (s *Service) SetCurrent(moduleID, workspaceID resource.ID) error {
_, err := s.table.Update(moduleID, func(existing *Module) error {
existing.CurrentWorkspaceID = &workspaceID
existing.CurrentWorkspaceID = workspaceID
return nil
})
return err
Expand All @@ -297,7 +297,7 @@ func (s *Service) Execute(moduleID resource.ID, program string, args ...string)
return task.Spec{}, err
}
spec := task.Spec{
ModuleID: &mod.ID,
ModuleID: mod.ID,
Path: mod.Path,
Execution: task.Execution{
Program: program,
Expand Down
4 changes: 2 additions & 2 deletions internal/module/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ digraph {
assert.Len(t, vpc.Dependencies(), 0)
// redis
if assert.Len(t, redis.Dependencies(), 1) {
assert.Equal(t, vpc.ID, redis.Dependencies()[0].GetID())
assert.Equal(t, vpc.ID, redis.Dependencies()[0])
}
// mysql
if assert.Len(t, mysql.Dependencies(), 1) {
assert.Equal(t, vpc.ID, mysql.Dependencies()[0].GetID())
assert.Equal(t, vpc.ID, mysql.Dependencies()[0])
}
// backend
if assert.Len(t, backend.Dependencies(), 3) {
Expand Down
19 changes: 9 additions & 10 deletions internal/plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import (
)

type plan struct {
resource.ID

ID resource.MonotonicID
ModuleID resource.ID
WorkspaceID resource.ID
ModulePath string
Expand All @@ -34,7 +33,7 @@ type plan struct {

// taskID is the ID of the plan task, and is only set once the task is
// created.
taskID *resource.ID
taskID resource.ID
}

type CreateOptions struct {
Expand Down Expand Up @@ -66,7 +65,7 @@ func (f *factory) newPlan(workspaceID resource.ID, opts CreateOptions) (*plan, e
return nil, fmt.Errorf("retrieving module: %w", err)
}
plan := &plan{
ID: resource.NewID(resource.Plan),
ID: resource.NewMonotonicID(resource.Plan),
ModuleID: mod.ID,
WorkspaceID: ws.ID,
ModulePath: mod.Path,
Expand All @@ -78,7 +77,7 @@ func (f *factory) newPlan(workspaceID resource.ID, opts CreateOptions) (*plan, e
moduleDependencies: mod.Dependencies(),
}
if opts.planFile {
plan.ArtefactsPath = filepath.Join(f.dataDir, fmt.Sprintf("%d", plan.Serial))
plan.ArtefactsPath = filepath.Join(f.dataDir, fmt.Sprintf("%d", plan.ID.Serial))
if err := os.MkdirAll(plan.ArtefactsPath, 0o755); err != nil {
return nil, fmt.Errorf("creating run artefacts directory: %w", err)
}
Expand All @@ -105,8 +104,8 @@ func (r *plan) planTaskSpec() task.Spec {
// TODO: assert planFile is true first
spec := task.Spec{
Identifier: PlanTask,
ModuleID: &r.ModuleID,
WorkspaceID: &r.WorkspaceID,
ModuleID: r.ModuleID,
WorkspaceID: r.WorkspaceID,
Path: r.ModulePath,
Env: r.envs,
Execution: task.Execution{
Expand All @@ -117,7 +116,7 @@ func (r *plan) planTaskSpec() task.Spec {
Blocking: true,
Description: "plan",
AfterCreate: func(t *task.Task) {
r.taskID = &t.ID
r.taskID = t.ID
},
BeforeExited: func(t *task.Task) (task.Summary, error) {
out, err := io.ReadAll(t.NewReader(false))
Expand Down Expand Up @@ -153,8 +152,8 @@ func (r *plan) applyTaskSpec() (task.Spec, error) {
}
spec := task.Spec{
Identifier: ApplyTask,
ModuleID: &r.ModuleID,
WorkspaceID: &r.WorkspaceID,
ModuleID: r.ModuleID,
WorkspaceID: r.WorkspaceID,
Path: r.ModulePath,
Execution: task.Execution{
TerraformCommand: []string{"apply"},
Expand Down
8 changes: 4 additions & 4 deletions internal/plan/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ func (s *Service) ReloadAfterApply(sub <-chan resource.Event[*task.Task]) {
if workspaceID == nil {
continue
}
if _, err := s.states.CreateReloadTask(*workspaceID); err != nil {
s.logger.Error("reloading state after apply", "error", err, "workspace", *workspaceID)
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)
s.logger.Debug("reloading state after apply", "workspace", workspaceID)
}
}
}
Expand Down Expand Up @@ -150,7 +150,7 @@ func (s *Service) Get(runID resource.ID) (*plan, error) {

func (s *Service) getByTaskID(taskID resource.ID) (*plan, error) {
for _, plan := range s.List() {
if plan.taskID != nil && *plan.taskID == taskID {
if plan.taskID != nil && plan.taskID == taskID {
return plan, nil
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/pubsub/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ type Logger interface {
}

// Broker allows clients to publish events and subscribe to events
type Broker[T resource.Resource] struct {
type Broker[T any] struct {
subs map[chan resource.Event[T]]struct{} // subscriptions
mu sync.Mutex // sync access to map
done chan struct{} // close when broker is shutting down
logger Logger
}

// NewBroker constructs a pub/sub broker.
func NewBroker[T resource.Resource](logger Logger) *Broker[T] {
func NewBroker[T any](logger Logger) *Broker[T] {
b := &Broker[T]{
subs: make(map[chan resource.Event[T]]struct{}),
done: make(chan struct{}),
Expand Down
2 changes: 1 addition & 1 deletion internal/resource/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type (
EventType string

// Event represents an event in the lifecycle of a resource
Event[T Resource] struct {
Event[T any] struct {
Type EventType
Payload T
}
Expand Down
46 changes: 5 additions & 41 deletions internal/resource/id.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,9 @@
package resource

import (
"fmt"
"sync"
)
// ID uniquely identifies a Pug resource.
type ID any

var (
// nextID provides the next ID for each kind
nextID map[Kind]uint = make(map[Kind]uint)
mu sync.Mutex
)

// ID is a unique identifier for a pug resource.
type ID struct {
Serial uint
Kind Kind
}

func NewID(kind Kind) ID {
mu.Lock()
defer mu.Unlock()

id := nextID[kind]
nextID[kind]++

return ID{
Serial: id,
Kind: kind,
}
}

func (id ID) String() string {
return fmt.Sprintf("#%d", id.Serial)
}

// GetID allows ID to be accessed via an interface value.
func (id ID) GetID() ID {
return id
}

// GetKind allows Kind to be accessed via an interface value.
func (id ID) GetKind() Kind {
return id.Kind
// Identifiable is a Pug resource with an identity.
type Identifiable interface {
GetID() ID
}
Loading
Loading