Browse code

add option to reverse buildchains

deads2k authored on 2016/04/11 23:40:50
Showing 7 changed files
... ...
@@ -1553,6 +1553,7 @@ _oadm_build-chain()
1553 1553
     flags+=("--all")
1554 1554
     flags+=("--output=")
1555 1555
     two_word_flags+=("-o")
1556
+    flags+=("--reverse")
1556 1557
     flags+=("--trigger-only")
1557 1558
     flags+=("--api-version=")
1558 1559
     flags+=("--certificate-authority=")
... ...
@@ -4398,6 +4398,7 @@ _oc_adm_build-chain()
4398 4398
     flags+=("--all")
4399 4399
     flags+=("--output=")
4400 4400
     two_word_flags+=("-o")
4401
+    flags+=("--reverse")
4401 4402
     flags+=("--trigger-only")
4402 4403
     flags+=("--api-version=")
4403 4404
     flags+=("--certificate-authority=")
... ...
@@ -2130,6 +2130,7 @@ _openshift_admin_build-chain()
2130 2130
     flags+=("--all")
2131 2131
     flags+=("--output=")
2132 2132
     two_word_flags+=("-o")
2133
+    flags+=("--reverse")
2133 2134
     flags+=("--trigger-only")
2134 2135
     flags+=("--api-version=")
2135 2136
     flags+=("--certificate-authority=")
... ...
@@ -7949,6 +7950,7 @@ _openshift_cli_adm_build-chain()
7949 7949
     flags+=("--all")
7950 7950
     flags+=("--output=")
7951 7951
     two_word_flags+=("-o")
7952
+    flags+=("--reverse")
7952 7953
     flags+=("--trigger-only")
7953 7954
     flags+=("--api-version=")
7954 7955
     flags+=("--certificate-authority=")
... ...
@@ -14690,6 +14692,7 @@ _openshift_ex_build-chain()
14690 14690
     flags+=("--all")
14691 14691
     flags+=("--output=")
14692 14692
     two_word_flags+=("-o")
14693
+    flags+=("--reverse")
14693 14694
     flags+=("--trigger-only")
14694 14695
     flags+=("--api-version=")
14695 14696
     flags+=("--certificate-authority=")
... ...
@@ -434,6 +434,17 @@ func RemoveInboundEdges(nodes []graph.Node) EdgeFunc {
434 434
 	}
435 435
 }
436 436
 
437
+func RemoveOutboundEdges(nodes []graph.Node) EdgeFunc {
438
+	return func(g Interface, from, to graph.Node, edgeKinds sets.String) bool {
439
+		for _, node := range nodes {
440
+			if node == from {
441
+				return false
442
+			}
443
+		}
444
+		return true
445
+	}
446
+}
447
+
437 448
 // EdgeSubgraph returns the directed subgraph with only the edges that match the
438 449
 // provided function.
439 450
 func (g Graph) EdgeSubgraph(edgeFn EdgeFunc) Graph {
... ...
@@ -23,9 +23,9 @@ import (
23 23
 )
24 24
 
25 25
 // NotFoundErr is returned when the imageStreamTag (ist) of interest cannot
26
-// be found in the graph. This doesn't mean though that the ist does not
26
+// be found in the graph. This doesn't mean though that the IST does not
27 27
 // exist. A user may have an image stream without a build configuration
28
-// pointing at it. In that case, the ist of interest simply doesn't have
28
+// pointing at it. In that case, the IST of interest simply doesn't have
29 29
 // other dependant ists
30 30
 type NotFoundErr string
31 31
 
... ...
@@ -79,7 +79,7 @@ func (d *ChainDescriber) MakeGraph() (osgraph.Graph, error) {
79 79
 // image stream tag (name:tag) in namespace. Namespace is needed here
80 80
 // because image stream tags with the same name can be found across
81 81
 // different namespaces.
82
-func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImages bool) (string, error) {
82
+func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImages, reverse bool) (string, error) {
83 83
 	g, err := d.MakeGraph()
84 84
 	if err != nil {
85 85
 		return "", err
... ...
@@ -96,8 +96,13 @@ func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImag
96 96
 		buildInputEdgeKinds = append(buildInputEdgeKinds, buildedges.BuildInputImageEdgeKind)
97 97
 	}
98 98
 
99
-	// Partition down to the subgraph containing the ist of interest
100
-	partitioned := partition(g, istNode, buildInputEdgeKinds)
99
+	// Partition down to the subgraph containing the imagestreamtag of interest
100
+	var partitioned osgraph.Graph
101
+	if reverse {
102
+		partitioned = partitionReverse(g, istNode, buildInputEdgeKinds)
103
+	} else {
104
+		partitioned = partition(g, istNode, buildInputEdgeKinds)
105
+	}
101 106
 
102 107
 	switch strings.ToLower(d.outputFormat) {
103 108
 	case "dot":
... ...
@@ -107,7 +112,7 @@ func (d *ChainDescriber) Describe(ist *imageapi.ImageStreamTag, includeInputImag
107 107
 		}
108 108
 		return string(data), nil
109 109
 	case "":
110
-		return d.humanReadableOutput(partitioned, d.namer, istNode), nil
110
+		return d.humanReadableOutput(partitioned, d.namer, istNode, reverse), nil
111 111
 	}
112 112
 
113 113
 	return "", fmt.Errorf("unknown specified format %q", d.outputFormat)
... ...
@@ -124,7 +129,7 @@ func partition(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) o
124 124
 	edgeFn := osgraph.EdgesOfKind(edgeKinds...)
125 125
 	sub := g.Subgraph(nodeFn, edgeFn)
126 126
 
127
-	// Filter out inbound edges to the ist of interest
127
+	// Filter out inbound edges to the IST of interest
128 128
 	edgeFn = osgraph.RemoveInboundEdges([]graph.Node{root})
129 129
 	sub = sub.Subgraph(nodeFn, edgeFn)
130 130
 
... ...
@@ -144,11 +149,46 @@ func partition(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) o
144 144
 	return sub.SubgraphWithNodes(desired, osgraph.ExistingDirectEdge)
145 145
 }
146 146
 
147
+// partitionReverse the graph down to a subgraph starting from the given root
148
+func partitionReverse(g osgraph.Graph, root graph.Node, buildInputEdgeKinds []string) osgraph.Graph {
149
+	// Filter out all but BuildConfig and ImageStreamTag nodes
150
+	nodeFn := osgraph.NodesOfKind(buildgraph.BuildConfigNodeKind, imagegraph.ImageStreamTagNodeKind)
151
+	// Filter out all but BuildInputImage and BuildOutput edges
152
+	edgeKinds := []string{}
153
+	edgeKinds = append(edgeKinds, buildInputEdgeKinds...)
154
+	edgeKinds = append(edgeKinds, buildedges.BuildOutputEdgeKind)
155
+	edgeFn := osgraph.EdgesOfKind(edgeKinds...)
156
+	sub := g.Subgraph(nodeFn, edgeFn)
157
+
158
+	// Filter out inbound edges to the IST of interest
159
+	edgeFn = osgraph.RemoveOutboundEdges([]graph.Node{root})
160
+	sub = sub.Subgraph(nodeFn, edgeFn)
161
+
162
+	// Check all paths leading from the root node, collect any
163
+	// node found in them, and create the desired subgraph
164
+	desired := []graph.Node{root}
165
+	paths := path.DijkstraAllPaths(sub)
166
+	for _, node := range sub.Nodes() {
167
+		if node == root {
168
+			continue
169
+		}
170
+		path, _, _ := paths.Between(node, root)
171
+		if len(path) != 0 {
172
+			desired = append(desired, node)
173
+		}
174
+	}
175
+	return sub.SubgraphWithNodes(desired, osgraph.ExistingDirectEdge)
176
+}
177
+
147 178
 // humanReadableOutput traverses the provided graph using DFS and outputs it
148 179
 // in a human-readable format. It starts from the provided root, assuming it
149 180
 // is an imageStreamTag node and continues to the rest of the graph handling
150 181
 // only imageStreamTag and buildConfig nodes.
151
-func (d *ChainDescriber) humanReadableOutput(g osgraph.Graph, f osgraph.Namer, root graph.Node) string {
182
+func (d *ChainDescriber) humanReadableOutput(g osgraph.Graph, f osgraph.Namer, root graph.Node, reverse bool) string {
183
+	if reverse {
184
+		g = g.EdgeSubgraph(osgraph.ReverseExistingDirectEdge)
185
+	}
186
+
152 187
 	var singleNamespace bool
153 188
 	if len(d.namespaces) == 1 && !d.namespaces.Has(kapi.NamespaceAll) {
154 189
 		singleNamespace = true
... ...
@@ -19,6 +19,7 @@ func TestChainDescriber(t *testing.T) {
19 19
 		testName         string
20 20
 		namespaces       sets.String
21 21
 		output           string
22
+		reverse          bool
22 23
 		defaultNamespace string
23 24
 		name             string
24 25
 		tag              string
... ...
@@ -161,6 +162,24 @@ func TestChainDescriber(t *testing.T) {
161 161
 				"\t\tistag/parent3img:latest":    1,
162 162
 			},
163 163
 		},
164
+		{
165
+			testName:         "human readable - multiple triggers - triggeronly - reverse",
166
+			name:             "child2img",
167
+			reverse:          true,
168
+			defaultNamespace: "test",
169
+			tag:              "latest",
170
+			path:             "../../../../pkg/cmd/experimental/buildchain/test/multiple-trigger-bcs.yaml",
171
+			namespaces:       sets.NewString("test"),
172
+			humanReadable: map[string]int{
173
+				"istag/child2img:latest":               1,
174
+				"\tbc/child2":                          1,
175
+				"\t\tistag/parent1img:latest":          1,
176
+				"\t\t\tbc/parent1":                     1,
177
+				"\t\t\t\tistag/ruby-22-centos7:latest": 2,
178
+				"\t\tistag/parent3img:latest":          1,
179
+				"\t\t\tbc/parent3":                     1,
180
+			},
181
+		},
164 182
 	}
165 183
 
166 184
 	for _, test := range tests {
... ...
@@ -174,7 +193,7 @@ func TestChainDescriber(t *testing.T) {
174 174
 		oc, _ := testclient.NewFixtureClients(o)
175 175
 		ist := imagegraph.MakeImageStreamTagObjectMeta(test.defaultNamespace, test.name, test.tag)
176 176
 
177
-		desc, err := NewChainDescriber(oc, test.namespaces, test.output).Describe(ist, test.includeInputImg)
177
+		desc, err := NewChainDescriber(oc, test.namespaces, test.output).Describe(ist, test.includeInputImg, test.reverse)
178 178
 		t.Logf("%s: output:\n%s\n\n", test.testName, desc)
179 179
 		if err != test.expectedErr {
180 180
 			t.Fatalf("%s: error mismatch: expected %v, got %v", test.testName, test.expectedErr, err)
... ...
@@ -49,6 +49,7 @@ type BuildChainOptions struct {
49 49
 	namespaces       sets.String
50 50
 	allNamespaces    bool
51 51
 	triggerOnly      bool
52
+	reverse          bool
52 53
 
53 54
 	output string
54 55
 
... ...
@@ -77,6 +78,7 @@ func NewCmdBuildChain(name, fullName string, f *clientcmd.Factory, out io.Writer
77 77
 
78 78
 	cmd.Flags().BoolVar(&options.allNamespaces, "all", false, "Build dependency tree for the specified image stream tag across all namespaces")
79 79
 	cmd.Flags().BoolVar(&options.triggerOnly, "trigger-only", true, "If true, only include dependencies based on build triggers. If false, include all dependencies.")
80
+	cmd.Flags().BoolVar(&options.reverse, "reverse", false, "If true, show the istags dependencies instead of its dependants.")
80 81
 	cmd.Flags().StringVarP(&options.output, "output", "o", "", "Output format of dependency tree")
81 82
 	return cmd
82 83
 }
... ...
@@ -160,7 +162,7 @@ func (o *BuildChainOptions) Validate() error {
160 160
 func (o *BuildChainOptions) RunBuildChain() error {
161 161
 	ist := imagegraph.MakeImageStreamTagObjectMeta2(o.defaultNamespace, o.name)
162 162
 
163
-	desc, err := describe.NewChainDescriber(o.c, o.namespaces, o.output).Describe(ist, !o.triggerOnly)
163
+	desc, err := describe.NewChainDescriber(o.c, o.namespaces, o.output).Describe(ist, !o.triggerOnly, o.reverse)
164 164
 	if err != nil {
165 165
 		if _, isNotFoundErr := err.(describe.NotFoundErr); isNotFoundErr {
166 166
 			name, tag, _ := imageapi.SplitImageStreamTag(o.name)