summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-05 11:10:10 +0300
committerPaul Buetow <paul@buetow.org>2026-07-05 11:10:10 +0300
commit5256a4d1e5a56510758bfac710ba2ad2a45af66e (patch)
treebff875458182a308979a575a78a26e61ff248a05
parent6cf5e1bf6e8f1551cf215bf838c27ec8281cf045 (diff)
initial Apply
-rw-r--r--examples/examples.go3
-rw-r--r--internal/resource/repository.go63
-rw-r--r--internal/resource/resource_test.go14
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 }