diff options
| -rw-r--r-- | examples/examples.go | 3 | ||||
| -rw-r--r-- | internal/resource/repository.go | 63 | ||||
| -rw-r--r-- | internal/resource/resource_test.go | 14 |
3 files changed, 70 insertions, 10 deletions
diff --git a/examples/examples.go b/examples/examples.go index b350576..9c42ce6 100644 --- a/examples/examples.go +++ b/examples/examples.go @@ -3,6 +3,7 @@ package examples import ( "os" + "codeberg.org/snonux/gonf/internal/resource" "codeberg.org/snonux/gonf/internal/resource/dir" "codeberg.org/snonux/gonf/internal/resource/file" "codeberg.org/snonux/gonf/internal/resource/link" @@ -69,5 +70,7 @@ func Run() error { _ = os.Link("/tmp/gonf_hello.txt", "/tmp/gonf_stale_hardlink") link.Have("/tmp/gonf_stale_hardlink", IsAbsent()) + resource.Apply() + return nil } diff --git a/internal/resource/repository.go b/internal/resource/repository.go index 8cb88b3..69617d4 100644 --- a/internal/resource/repository.go +++ b/internal/resource/repository.go @@ -11,11 +11,11 @@ var ( once sync.Once ) -func getRepository() repository { +func getRepository() *repository { once.Do(func() { repo = newRepository() }) - return repo + return &repo } func resetRepository() { @@ -24,17 +24,16 @@ func resetRepository() { type repository struct { registered map[string]Resource - mu *sync.Mutex + mu sync.Mutex } func newRepository() repository { return repository{ registered: make(map[string]Resource), - mu: new(sync.Mutex), } } -func (r repository) register(res Resource) error { +func (r *repository) register(res Resource) error { r.mu.Lock() defer r.mu.Unlock() @@ -47,3 +46,57 @@ func (r repository) register(res Resource) error { return nil } + +func (r *repository) apply() error { + r.mu.Lock() + defer r.mu.Unlock() + + visited := make(map[string]bool) + visiting := make(map[string]bool) + var order []Resource + + var visit func(id string) error + visit = func(id string) error { + if visiting[id] { + return fmt.Errorf("circular dependency detected involving %s", id) + } + if visited[id] { + return nil + } + + res, ok := r.registered[id] + if !ok { + return fmt.Errorf("resource %s is depended upon but not registered", id) + } + + visiting[id] = true + for depID := range res.dependsOn { + if err := visit(depID); err != nil { + return err + } + } + delete(visiting, id) + visited[id] = true + order = append(order, res) + return nil + } + + for id := range r.registered { + if err := visit(id); err != nil { + return err + } + } + + for _, res := range order { + log.Printf("Applying resource %v", res) + if err := res.Apply.Apply(); err != nil { + return fmt.Errorf("failed to apply %v: %w", res, err) + } + } + + return nil +} + +func Apply() error { + return getRepository().apply() +} diff --git a/internal/resource/resource_test.go b/internal/resource/resource_test.go index 947f0ac..8741f2a 100644 --- a/internal/resource/resource_test.go +++ b/internal/resource/resource_test.go @@ -6,7 +6,7 @@ import ( func TestResourceID(t *testing.T) { resetRepository() - res := Register("File", "/tmp/foo.txt") + res := Register("File", "/tmp/foo.txt", &mockApplier{}) expected := "File[/tmp/foo.txt]" if res.ID() != expected { t.Errorf("expected ID %s, got %s", expected, res.ID()) @@ -15,7 +15,7 @@ func TestResourceID(t *testing.T) { func TestResourceString(t *testing.T) { resetRepository() - res := Register("File", "/tmp/foo.txt") + res := Register("File", "/tmp/foo.txt", &mockApplier{}) expected := "File[/tmp/foo.txt]" if res.String() != expected { t.Errorf("expected String %s, got %s", expected, res.String()) @@ -26,7 +26,7 @@ func TestNew(t *testing.T) { resetRepository() type_ := "File" name := "/tmp/foo.txt" - res := Register(type_, name) + res := Register(type_, name, &mockApplier{}) if res.Type != type_ { t.Errorf("expected type %s, got %s", type_, res.Type) @@ -42,7 +42,7 @@ func TestNew(t *testing.T) { func TestRepositoryRegister(t *testing.T) { resetRepository() repo := getRepository() - res := Register("File", "/tmp/foo.txt") + res := Register("File", "/tmp/foo.txt", &mockApplier{}) // First registration already happened in New() // But we can try to register again via the repository directly @@ -51,8 +51,12 @@ func TestRepositoryRegister(t *testing.T) { } // Registration of a different resource should succeed - res2 := Register("File", "/tmp/bar.txt") + res2 := Register("File", "/tmp/bar.txt", &mockApplier{}) if err := repo.register(res2); err == nil { t.Error("expected error when registering the same resource twice, got nil") } } + +type mockApplier struct{} + +func (m *mockApplier) Apply() error { return nil } |
