Skip to content

Commit

Permalink
refactor: make ID an interface (#143)
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 authored Jan 15, 2025
1 parent ae3bcee commit b8a5e01
Show file tree
Hide file tree
Showing 50 changed files with 306 additions and 322 deletions.
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

0 comments on commit b8a5e01

Please sign in to comment.