-
Notifications
You must be signed in to change notification settings - Fork 253
/
Copy pathbundlegraphloader.go
160 lines (138 loc) · 5 KB
/
bundlegraphloader.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
149
150
151
152
153
154
155
156
157
158
159
160
package registry
import (
"fmt"
"github.com/blang/semver/v4"
)
// BundleGraphLoader generates updated graphs by adding bundles to them, updating
// the graph implicitly via semantic version of each bundle
type BundleGraphLoader struct {
}
// AddBundleToGraph takes a bundle and an existing graph and updates the graph to insert the new bundle
// into each channel it is included in
func (g *BundleGraphLoader) AddBundleToGraph(bundle *Bundle, graph *Package, annotations *AnnotationsFile, skippatch bool) (*Package, error) {
bundleVersion, err := bundle.Version()
if err != nil {
return nil, fmt.Errorf("Unable to extract bundle version from bundle %s, can't insert in semver mode", bundle.BundleImage)
}
versionToAdd, err := semver.Make(bundleVersion)
if err != nil {
return nil, fmt.Errorf("Bundle version %s is not valid", bundleVersion)
}
newBundleKey := BundleKey{
CsvName: bundle.Name,
Version: versionToAdd.String(),
BundlePath: bundle.BundleImage,
}
// initialize the graph if it started empty
if graph.Name == "" {
graph.Name = bundle.Package
}
newDefaultChannel := annotations.Annotations.DefaultChannelName
if newDefaultChannel != "" {
graph.DefaultChannel = newDefaultChannel
}
if graph.DefaultChannel == "" {
// Infer default channel from channel list
if annotations.SelectDefaultChannel() == "" {
return nil, fmt.Errorf("Default channel is missing and can't be inferred")
}
graph.DefaultChannel = annotations.SelectDefaultChannel()
}
// generate the DAG for each channel the new bundle is being insert into
for _, channel := range bundle.Channels {
replaces := make(map[BundleKey]struct{}, 0)
// If the channel doesn't exist yet, initialize it
if !graph.HasChannel(channel) {
// create the channel and add a single node
newChannelGraph := Channel{
Head: newBundleKey,
Nodes: map[BundleKey]map[BundleKey]struct{}{
newBundleKey: nil,
},
}
if graph.Channels == nil {
graph.Channels = make(map[string]Channel, 1)
}
graph.Channels[channel] = newChannelGraph
continue
}
// find the version(s) it should sit between
channelGraph := graph.Channels[channel]
if channelGraph.Nodes == nil {
channelGraph.Nodes = make(map[BundleKey]map[BundleKey]struct{}, 1)
}
lowestAhead := BundleKey{}
greatestBehind := BundleKey{}
skipPatchCandidates := []BundleKey{}
// Iterate over existing nodes and compare the new node's version to find the
// lowest version above it and highest version below it (to insert between these nodes)
for node := range channelGraph.Nodes {
nodeVersion, err := semver.Make(node.Version)
if err != nil {
return nil, fmt.Errorf("Unable to parse existing bundle version stored in index %s %s %s",
node.CsvName, node.Version, node.BundlePath)
}
switch comparison := nodeVersion.Compare(versionToAdd); comparison {
case 0:
return nil, fmt.Errorf("Bundle version %s already added to index", bundleVersion)
case 1:
if lowestAhead.IsEmpty() {
lowestAhead = node
} else {
lowestAheadSemver, _ := semver.Make(lowestAhead.Version)
if nodeVersion.LT(lowestAheadSemver) {
lowestAhead = node
}
}
case -1:
if greatestBehind.IsEmpty() {
greatestBehind = node
} else {
greatestBehindSemver, _ := semver.Make(greatestBehind.Version)
if nodeVersion.GT(greatestBehindSemver) {
greatestBehind = node
}
}
}
// if skippatch mode is enabled, check each node to determine if z-updates should
// be replaced as well. Keep track of them to delete those nodes from the graph itself,
// just be aware of them for replacements
if skippatch {
if isSkipPatchCandidate(versionToAdd, nodeVersion) {
skipPatchCandidates = append(skipPatchCandidates, node)
replaces[node] = struct{}{}
}
}
}
// If we found a node behind the one we're adding, make the new node replace it
if !greatestBehind.IsEmpty() {
replaces[greatestBehind] = struct{}{}
}
// If we found a node ahead of the one we're adding, make the lowest to replace
// the new node. If we didn't find a node semantically ahead, the new node is
// the new channel head
if !lowestAhead.IsEmpty() {
channelGraph.Nodes[lowestAhead] = map[BundleKey]struct{}{
newBundleKey: struct{}{},
}
} else {
channelGraph.Head = newBundleKey
}
if skippatch {
// Remove the nodes that are now being skipped by a new patch version update
for _, candidate := range skipPatchCandidates {
delete(channelGraph.Nodes, candidate)
}
}
// add the node and update the graph
channelGraph.Nodes[newBundleKey] = replaces
graph.Channels[channel] = channelGraph
}
return graph, nil
}
// isSkipPatchCandidate returns true if version is equal to toCompare
// in major and minor positions and strictly greater in all others,
// indicating that toCompare can be skipped over to version.
func isSkipPatchCandidate(version, toCompare semver.Version) bool {
return (version.Major == toCompare.Major) && (version.Minor == toCompare.Minor) && version.GT(toCompare)
}