-
Notifications
You must be signed in to change notification settings - Fork 303
/
target.go
148 lines (121 loc) · 3.45 KB
/
target.go
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package model
import (
"fmt"
"github.com/pkg/errors"
)
// An abstract build graph of targets and their dependencies.
// Each target should have a unique ID.
type TargetType string
type TargetName string
func (n TargetName) String() string { return string(n) }
const (
// Deployed k8s entities
TargetTypeK8s TargetType = "k8s"
// Image builds
TargetTypeImage TargetType = "image"
// Docker-compose service build and deploy
// TODO(nick): Currently, build and deploy are represented as a single target.
// In the future, we might have a separate build target and deploy target.
TargetTypeDockerCompose TargetType = "docker-compose"
// Runs a local command when triggered (manually or via changed dep)
TargetTypeLocal TargetType = "local"
// Aggregation of multiple targets into one UI view.
// TODO(nick): Currently used as the type for both Manifest and YAMLManifest, though
// we expect YAMLManifest to go away.
TargetTypeManifest TargetType = "manifest"
// Changes that affect all targets, rebuilding the target graph.
TargetTypeConfigs TargetType = "configs"
)
type TargetID struct {
Type TargetType
Name TargetName
}
func (id TargetID) Empty() bool {
return id.Type == "" || id.Name == ""
}
func (id TargetID) String() string {
if id.Empty() {
return ""
}
return fmt.Sprintf("%s:%s", id.Type, id.Name)
}
func TargetIDSet(tids []TargetID) map[TargetID]bool {
res := make(map[TargetID]bool)
for _, id := range tids {
res[id] = true
}
return res
}
type TargetSpec interface {
ID() TargetID
// Check to make sure the spec is well-formed.
// All TargetSpecs should throw an error in the case where the ID is empty.
Validate() error
DependencyIDs() []TargetID
}
type TargetStatus interface {
TargetID() TargetID
LastBuild() BuildRecord
}
type Target interface {
Spec() TargetSpec
Status() TargetStatus
}
// De-duplicate target ids, maintaining the same order.
func DedupeTargetIDs(ids []TargetID) []TargetID {
result := make([]TargetID, 0, len(ids))
dupes := make(map[TargetID]bool, len(ids))
for _, id := range ids {
if !dupes[id] {
dupes[id] = true
result = append(result, id)
}
}
return result
}
// Map all the targets by their target ID.
func MakeTargetMap(targets []TargetSpec) map[TargetID]TargetSpec {
result := make(map[TargetID]TargetSpec, len(targets))
for _, target := range targets {
result[target.ID()] = target
}
return result
}
// Create a topologically sorted list of targets. Returns an error
// if the targets can't be topologically sorted. (e.g., there's a cycle).
func TopologicalSort(targets []TargetSpec) ([]TargetSpec, error) {
targetMap := MakeTargetMap(targets)
result := make([]TargetSpec, 0, len(targets))
inResult := make(map[TargetID]bool, len(targets))
searching := make(map[TargetID]bool, len(targets))
var ensureInResult func(id TargetID) error
ensureInResult = func(id TargetID) error {
if inResult[id] {
return nil
}
if searching[id] {
return fmt.Errorf("Found a cycle at target: %s", id.Name)
}
searching[id] = true
current, ok := targetMap[id]
if !ok {
return fmt.Errorf("Missing target dependency: %s", id.Name)
}
for _, depID := range current.DependencyIDs() {
err := ensureInResult(depID)
if err != nil {
return err
}
}
result = append(result, current)
inResult[id] = true
return nil
}
for _, target := range targets {
err := ensureInResult(target.ID())
if err != nil {
return nil, errors.Wrap(err, "Internal error")
}
}
return result, nil
}