|
| 1 | +package registry |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + "github.com/blang/semver" |
| 7 | +) |
| 8 | + |
| 9 | +// BundleGraphLoader generates updated graphs by adding bundles to them, updating |
| 10 | +// the graph implicitly via semantic version of each bundle |
| 11 | +type BundleGraphLoader struct { |
| 12 | +} |
| 13 | + |
| 14 | +// AddBundleToGraph takes a bundle and an existing graph and updates the graph to insert the new bundle |
| 15 | +// into each channel it is included in |
| 16 | +func (g *BundleGraphLoader) AddBundleToGraph(bundle *Bundle, graph *Package, newDefaultChannel string, skippatch bool) (*Package, error) { |
| 17 | + bundleVersion, err := bundle.Version() |
| 18 | + if err != nil { |
| 19 | + return nil, fmt.Errorf("Unable to extract bundle version from bundle %s, can't insert in semver mode", bundle.BundleImage) |
| 20 | + } |
| 21 | + |
| 22 | + versionToAdd, err := semver.Make(bundleVersion) |
| 23 | + if err != nil { |
| 24 | + return nil, fmt.Errorf("Bundle version %s is not valid", bundleVersion) |
| 25 | + } |
| 26 | + |
| 27 | + newBundleKey := BundleKey{ |
| 28 | + CsvName: bundle.Name, |
| 29 | + Version: versionToAdd.String(), |
| 30 | + BundlePath: bundle.BundleImage, |
| 31 | + } |
| 32 | + |
| 33 | + // initialize the graph if it started empty |
| 34 | + if graph.Name == "" { |
| 35 | + graph.Name = bundle.Package |
| 36 | + } |
| 37 | + if newDefaultChannel != "" { |
| 38 | + graph.DefaultChannel = newDefaultChannel |
| 39 | + } |
| 40 | + |
| 41 | + // generate the DAG for each channel the new bundle is being insert into |
| 42 | + for _, channel := range bundle.Channels { |
| 43 | + replaces := make(map[BundleKey]struct{}, 0) |
| 44 | + |
| 45 | + // If the channel doesn't exist yet, initialize it |
| 46 | + if !graph.HasChannel(channel) { |
| 47 | + // create the channel and add a single node |
| 48 | + newChannelGraph := Channel{ |
| 49 | + Head: newBundleKey, |
| 50 | + Nodes: map[BundleKey]map[BundleKey]struct{}{ |
| 51 | + newBundleKey: nil, |
| 52 | + }, |
| 53 | + } |
| 54 | + if graph.Channels == nil { |
| 55 | + graph.Channels = make(map[string]Channel, 1) |
| 56 | + } |
| 57 | + graph.Channels[channel] = newChannelGraph |
| 58 | + continue |
| 59 | + } |
| 60 | + |
| 61 | + // find the version(s) it should sit between |
| 62 | + channelGraph := graph.Channels[channel] |
| 63 | + if channelGraph.Nodes == nil { |
| 64 | + channelGraph.Nodes = make(map[BundleKey]map[BundleKey]struct{}, 1) |
| 65 | + } |
| 66 | + |
| 67 | + lowestAhead := BundleKey{} |
| 68 | + greatestBehind := BundleKey{} |
| 69 | + skipPatchCandidates := []BundleKey{} |
| 70 | + |
| 71 | + // Iterate over existing nodes and compare the new node's version to find the |
| 72 | + // lowest version above it and highest version below it (to insert between these nodes) |
| 73 | + for node := range channelGraph.Nodes { |
| 74 | + nodeVersion, err := semver.Make(node.Version) |
| 75 | + if err != nil { |
| 76 | + return nil, fmt.Errorf("Unable to parse existing bundle version stored in index %s %s %s", |
| 77 | + node.CsvName, node.Version, node.BundlePath) |
| 78 | + } |
| 79 | + |
| 80 | + switch comparison := nodeVersion.Compare(versionToAdd); comparison { |
| 81 | + case 0: |
| 82 | + return nil, fmt.Errorf("Bundle version %s already added to index", bundleVersion) |
| 83 | + case 1: |
| 84 | + if lowestAhead.IsEmpty() { |
| 85 | + lowestAhead = node |
| 86 | + } else { |
| 87 | + lowestAheadSemver, _ := semver.Make(lowestAhead.Version) |
| 88 | + if nodeVersion.LT(lowestAheadSemver) { |
| 89 | + lowestAhead = node |
| 90 | + } |
| 91 | + } |
| 92 | + case -1: |
| 93 | + if greatestBehind.IsEmpty() { |
| 94 | + greatestBehind = node |
| 95 | + } else { |
| 96 | + greatestBehindSemver, _ := semver.Make(greatestBehind.Version) |
| 97 | + if nodeVersion.GT(greatestBehindSemver) { |
| 98 | + greatestBehind = node |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + // if skippatch mode is enabled, check each node to determine if z-updates should |
| 104 | + // be replaced as well. Keep track of them to delete those nodes from the graph itself, |
| 105 | + // just be aware of them for replacements |
| 106 | + if skippatch { |
| 107 | + if isSkipPatchCandidate(versionToAdd, nodeVersion) { |
| 108 | + skipPatchCandidates = append(skipPatchCandidates, node) |
| 109 | + replaces[node] = struct{}{} |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + // If we found a node behind the one we're adding, make the new node replace it |
| 115 | + if !greatestBehind.IsEmpty() { |
| 116 | + replaces[greatestBehind] = struct{}{} |
| 117 | + } |
| 118 | + |
| 119 | + // If we found a node ahead of the one we're adding, make the lowest to replace |
| 120 | + // the new node. If we didn't find a node semantically ahead, the new node is |
| 121 | + // the new channel head |
| 122 | + if !lowestAhead.IsEmpty() { |
| 123 | + channelGraph.Nodes[lowestAhead] = map[BundleKey]struct{}{ |
| 124 | + newBundleKey: struct{}{}, |
| 125 | + } |
| 126 | + } else { |
| 127 | + channelGraph.Head = newBundleKey |
| 128 | + } |
| 129 | + |
| 130 | + if skippatch { |
| 131 | + // Remove the nodes that are now being skipped by a new patch version update |
| 132 | + for _, candidate := range skipPatchCandidates { |
| 133 | + delete(channelGraph.Nodes, candidate) |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + // add the node and update the graph |
| 138 | + channelGraph.Nodes[newBundleKey] = replaces |
| 139 | + graph.Channels[channel] = channelGraph |
| 140 | + } |
| 141 | + |
| 142 | + return graph, nil |
| 143 | +} |
| 144 | + |
| 145 | +func isSkipPatchCandidate(version, toCompare semver.Version) bool { |
| 146 | + return (version.Major == toCompare.Major) && (version.Minor == toCompare.Minor) && (version.Patch > toCompare.Patch) |
| 147 | +} |
0 commit comments