summaryrefslogtreecommitdiff
path: root/internal/resource/repository.go
blob: 0771ef8bda0bc507d30d49de70d6beb1aef7e793 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package resource

import (
	"fmt"
	"log"
	"sync"
)

var (
	repo repository
	once sync.Once
)

func getRepository() *repository {
	once.Do(func() {
		repo = newRepository()
	})
	return &repo
}

func ResetRepository() {
	repo = newRepository()
}

type repository struct {
	registered map[string]Resource
	mu         sync.Mutex
}

func newRepository() repository {
	return repository{
		registered: make(map[string]Resource),
	}
}

func (r *repository) register(res Resource) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if _, exists := r.registered[res.ID()]; exists {
		return fmt.Errorf("resource %v already registered", res)
	}

	r.registered[res.ID()] = res
	log.Printf("Registered resource %v\n", res)

	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(); err != nil {
			return fmt.Errorf("failed to apply %v: %w", res, err)
		}
	}

	return nil
}

func Apply() error {
	return getRepository().apply()
}