Browse code

indicate builds that can't push

deads2k authored on 2015/06/18 04:48:29
Showing 8 changed files
... ...
@@ -8,12 +8,16 @@ import (
8 8
 	osgraph "github.com/openshift/origin/pkg/api/graph"
9 9
 	buildedges "github.com/openshift/origin/pkg/build/graph"
10 10
 	buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
11
+	imageedges "github.com/openshift/origin/pkg/image/graph"
12
+	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
11 13
 )
12 14
 
13 15
 // ImagePipeline represents a build, its output, and any inputs. The input
14 16
 // to a build may be another ImagePipeline.
15 17
 type ImagePipeline struct {
16
-	Image ImageTagLocation
18
+	Image               ImageTagLocation
19
+	DestinationResolved bool
20
+
17 21
 	Build *buildgraph.BuildConfigNode
18 22
 	// If set, the base image used by the build
19 23
 	BaseImage ImageTagLocation
... ...
@@ -95,6 +99,13 @@ func NewImagePipelineFromImageTagLocation(g osgraph.Graph, node graph.Node, imag
95 95
 		flow.Source = src
96 96
 	}
97 97
 
98
+	for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamGraphEdgeKind) {
99
+		covered.Insert(input.ID())
100
+		imageStreamNode := input.(*imagegraph.ImageStreamNode)
101
+
102
+		flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
103
+	}
104
+
98 105
 	return flow, covered
99 106
 }
100 107
 
101 108
new file mode 100644
... ...
@@ -0,0 +1,161 @@
0
+apiVersion: v1
1
+items:
2
+- apiVersion: v1
3
+  kind: BuildConfig
4
+  metadata:
5
+    creationTimestamp: null
6
+    labels:
7
+      app: ruby
8
+    name: ruby-hello-world
9
+  spec:
10
+    output:
11
+      to:
12
+        kind: ImageStreamTag
13
+        name: ruby-hello-world:latest
14
+    resources: {}
15
+    source:
16
+      git:
17
+        uri: https://github.com/openshift/ruby-hello-world
18
+      type: Git
19
+    strategy:
20
+      dockerStrategy:
21
+        from:
22
+          kind: ImageStreamTag
23
+          name: ruby-20-centos7:latest
24
+      type: Docker
25
+    triggers:
26
+    - github:
27
+        secret: LyddbeCAaw1a0x08xz9n
28
+      type: GitHub
29
+    - generic:
30
+        secret: ZnYJJeEvo1ri0Gk0f6YY
31
+      type: Generic
32
+    - imageChange: {}
33
+      type: ImageChange
34
+  status:
35
+    lastVersion: 0
36
+- apiVersion: v1
37
+  kind: Build
38
+  metadata:
39
+    creationTimestamp: null
40
+    labels:
41
+      app: ruby
42
+      buildconfig: ruby-hello-world
43
+    name: ruby-hello-world-1
44
+  spec:
45
+    output:
46
+      to:
47
+        kind: ImageStreamTag
48
+        name: ruby-hello-world:latest
49
+    resources: {}
50
+    serviceAccount: builder
51
+    source:
52
+      git:
53
+        uri: https://github.com/openshift/ruby-hello-world
54
+      type: Git
55
+    strategy:
56
+      dockerStrategy:
57
+        from:
58
+          kind: DockerImage
59
+          name: openshift/ruby-20-centos7:latest
60
+      type: Docker
61
+  status:
62
+    config:
63
+      name: ruby-hello-world
64
+    phase: New
65
+- apiVersion: v1
66
+  kind: ImageStream
67
+  metadata:
68
+    annotations:
69
+      openshift.io/image.dockerRepositoryCheck: 2015-07-06T19:05:12Z
70
+    creationTimestamp: null
71
+    labels:
72
+      app: ruby
73
+    name: ruby-20-centos7
74
+  spec:
75
+    dockerImageRepository: openshift/ruby-20-centos7
76
+  status:
77
+    dockerImageRepository: openshift/ruby-20-centos7
78
+- apiVersion: v1
79
+  kind: ImageStream
80
+  metadata:
81
+    creationTimestamp: null
82
+    labels:
83
+      app: ruby
84
+    name: ruby-hello-world
85
+  spec: {}
86
+  status:
87
+    dockerImageRepository: 172.30.222.57:5000/foo/ruby-hello-world
88
+- apiVersion: v1
89
+  kind: DeploymentConfig
90
+  metadata:
91
+    creationTimestamp: null
92
+    labels:
93
+      app: ruby
94
+    name: ruby-hello-world
95
+  spec:
96
+    replicas: 1
97
+    selector:
98
+      deploymentconfig: ruby-hello-world
99
+    strategy:
100
+      resources: {}
101
+      rollingParams:
102
+        intervalSeconds: 1
103
+        timeoutSeconds: 600
104
+        updatePeriodSeconds: 1
105
+      type: Rolling
106
+    template:
107
+      metadata:
108
+        creationTimestamp: null
109
+        labels:
110
+          deploymentconfig: ruby-hello-world
111
+      spec:
112
+        containers:
113
+        - image: library/ruby-hello-world:latest
114
+          imagePullPolicy: Always
115
+          name: ruby-hello-world
116
+          ports:
117
+          - containerPort: 8080
118
+            name: ruby-hello-world-tcp-8080
119
+            protocol: TCP
120
+          resources: {}
121
+          securityContext:
122
+            capabilities: {}
123
+            privileged: false
124
+          terminationMessagePath: /dev/termination-log
125
+        dnsPolicy: ClusterFirst
126
+        restartPolicy: Always
127
+    triggers:
128
+    - type: ConfigChange
129
+    - imageChangeParams:
130
+        automatic: true
131
+        containerNames:
132
+        - ruby-hello-world
133
+        from:
134
+          kind: ImageStreamTag
135
+          name: ruby-hello-world:latest
136
+      type: ImageChange
137
+  status: {}
138
+- apiVersion: v1
139
+  kind: Service
140
+  metadata:
141
+    creationTimestamp: null
142
+    labels:
143
+      app: ruby
144
+    name: ruby-hello-world
145
+  spec:
146
+    portalIP: ""
147
+    ports:
148
+    - name: ruby-hello-world-tcp-8080
149
+      nodePort: 0
150
+      port: 8080
151
+      protocol: TCP
152
+      targetPort: 8080
153
+    selector:
154
+      deploymentconfig: ruby-hello-world
155
+    sessionAffinity: None
156
+    type: ClusterIP
157
+  status:
158
+    loadBalancer: {}
159
+kind: List
160
+metadata: {}
0 161
new file mode 100644
... ...
@@ -0,0 +1,161 @@
0
+apiVersion: v1
1
+items:
2
+- apiVersion: v1
3
+  kind: BuildConfig
4
+  metadata:
5
+    creationTimestamp: null
6
+    labels:
7
+      app: ruby
8
+    name: ruby-hello-world
9
+  spec:
10
+    output:
11
+      to:
12
+        kind: ImageStreamTag
13
+        name: ruby-hello-world:latest
14
+    resources: {}
15
+    source:
16
+      git:
17
+        uri: https://github.com/openshift/ruby-hello-world
18
+      type: Git
19
+    strategy:
20
+      dockerStrategy:
21
+        from:
22
+          kind: ImageStreamTag
23
+          name: ruby-20-centos7:latest
24
+      type: Docker
25
+    triggers:
26
+    - github:
27
+        secret: LyddbeCAaw1a0x08xz9n
28
+      type: GitHub
29
+    - generic:
30
+        secret: ZnYJJeEvo1ri0Gk0f6YY
31
+      type: Generic
32
+    - imageChange: {}
33
+      type: ImageChange
34
+  status:
35
+    lastVersion: 0
36
+- apiVersion: v1
37
+  kind: Build
38
+  metadata:
39
+    creationTimestamp: null
40
+    labels:
41
+      app: ruby
42
+      buildconfig: ruby-hello-world
43
+    name: ruby-hello-world-1
44
+  spec:
45
+    output:
46
+      to:
47
+        kind: ImageStreamTag
48
+        name: ruby-hello-world:latest
49
+    resources: {}
50
+    serviceAccount: builder
51
+    source:
52
+      git:
53
+        uri: https://github.com/openshift/ruby-hello-world
54
+      type: Git
55
+    strategy:
56
+      dockerStrategy:
57
+        from:
58
+          kind: DockerImage
59
+          name: openshift/ruby-20-centos7:latest
60
+      type: Docker
61
+  status:
62
+    config:
63
+      name: ruby-hello-world
64
+    phase: New
65
+- apiVersion: v1
66
+  kind: ImageStream
67
+  metadata:
68
+    annotations:
69
+      openshift.io/image.dockerRepositoryCheck: 2015-07-06T19:05:12Z
70
+    creationTimestamp: null
71
+    labels:
72
+      app: ruby
73
+    name: ruby-20-centos7
74
+  spec:
75
+    dockerImageRepository: openshift/ruby-20-centos7
76
+  status:
77
+    dockerImageRepository: openshift/ruby-20-centos7
78
+- apiVersion: v1
79
+  kind: ImageStream
80
+  metadata:
81
+    creationTimestamp: null
82
+    labels:
83
+      app: ruby
84
+    name: ruby-hello-world
85
+  spec: {}
86
+  status:
87
+    dockerImageRepository: ""
88
+- apiVersion: v1
89
+  kind: DeploymentConfig
90
+  metadata:
91
+    creationTimestamp: null
92
+    labels:
93
+      app: ruby
94
+    name: ruby-hello-world
95
+  spec:
96
+    replicas: 1
97
+    selector:
98
+      deploymentconfig: ruby-hello-world
99
+    strategy:
100
+      resources: {}
101
+      rollingParams:
102
+        intervalSeconds: 1
103
+        timeoutSeconds: 600
104
+        updatePeriodSeconds: 1
105
+      type: Rolling
106
+    template:
107
+      metadata:
108
+        creationTimestamp: null
109
+        labels:
110
+          deploymentconfig: ruby-hello-world
111
+      spec:
112
+        containers:
113
+        - image: library/ruby-hello-world:latest
114
+          imagePullPolicy: Always
115
+          name: ruby-hello-world
116
+          ports:
117
+          - containerPort: 8080
118
+            name: ruby-hello-world-tcp-8080
119
+            protocol: TCP
120
+          resources: {}
121
+          securityContext:
122
+            capabilities: {}
123
+            privileged: false
124
+          terminationMessagePath: /dev/termination-log
125
+        dnsPolicy: ClusterFirst
126
+        restartPolicy: Always
127
+    triggers:
128
+    - type: ConfigChange
129
+    - imageChangeParams:
130
+        automatic: true
131
+        containerNames:
132
+        - ruby-hello-world
133
+        from:
134
+          kind: ImageStreamTag
135
+          name: ruby-hello-world:latest
136
+      type: ImageChange
137
+  status: {}
138
+- apiVersion: v1
139
+  kind: Service
140
+  metadata:
141
+    creationTimestamp: null
142
+    labels:
143
+      app: ruby
144
+    name: ruby-hello-world
145
+  spec:
146
+    portalIP: ""
147
+    ports:
148
+    - name: ruby-hello-world-tcp-8080
149
+      nodePort: 0
150
+      port: 8080
151
+      protocol: TCP
152
+      targetPort: 8080
153
+    selector:
154
+      deploymentconfig: ruby-hello-world
155
+    sessionAffinity: None
156
+    type: ClusterIP
157
+  status:
158
+    loadBalancer: {}
159
+kind: List
160
+metadata: {}
... ...
@@ -24,6 +24,7 @@ import (
24 24
 	deployedges "github.com/openshift/origin/pkg/deploy/graph"
25 25
 	deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
26 26
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
27
+	imageedges "github.com/openshift/origin/pkg/image/graph"
27 28
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
28 29
 	projectapi "github.com/openshift/origin/pkg/project/api"
29 30
 )
... ...
@@ -88,6 +89,8 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, err
88 88
 		kubeedges.AddExposedPodTemplateSpecEdges(g, service)
89 89
 	}
90 90
 
91
+	imageedges.AddAllImageStreamRefEdges(g)
92
+
91 93
 	return g, nil
92 94
 }
93 95
 
... ...
@@ -135,7 +138,7 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
135 135
 		for _, standaloneBC := range standaloneBCs {
136 136
 			fmt.Fprintln(out)
137 137
 			printLines(out, indent, 0, describeStandaloneBuildGroup(standaloneBC, namespace)...)
138
-			printLines(out, indent, 1, describeAdditionalBuildDetail(standaloneBC.Build, true)...)
138
+			printLines(out, indent, 1, describeAdditionalBuildDetail(standaloneBC.Build, standaloneBC.DestinationResolved, true)...)
139 139
 		}
140 140
 
141 141
 		if (len(services) == 0) && (len(standaloneDCs) == 0) && (len(standaloneBCs) == 0) {
... ...
@@ -145,6 +148,11 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
145 145
 
146 146
 		} else {
147 147
 			fmt.Fprintln(out)
148
+
149
+			if hasUnresolvedImageStreamTag(g) {
150
+				fmt.Fprintln(out, "Warning: Some of your builds are pointing to image streams, but the administrator has not configured the integrated Docker registry (oadm registry).")
151
+
152
+			}
148 153
 			fmt.Fprintln(out, "To see more, use 'oc describe service <name>' or 'oc describe dc <name>'.")
149 154
 			fmt.Fprintln(out, "You can use 'oc get all' to see a list of other objects.")
150 155
 		}
... ...
@@ -153,6 +161,22 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error
153 153
 	})
154 154
 }
155 155
 
156
+// hasUnresolvedImageStreamTag checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
157
+func hasUnresolvedImageStreamTag(g osgraph.Graph) bool {
158
+	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
159
+		for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
160
+			for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(istNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
161
+				imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
162
+				if len(imageStreamNode.Status.DockerImageRepository) == 0 {
163
+					return true
164
+				}
165
+			}
166
+		}
167
+	}
168
+
169
+	return false
170
+}
171
+
156 172
 func printLines(out io.Writer, indent string, depth int, lines ...string) {
157 173
 	for i, s := range lines {
158 174
 		fmt.Fprintf(out, strings.Repeat(indent, depth))
... ...
@@ -172,7 +196,7 @@ func describeDeploymentInServiceGroup(deploy graphview.DeploymentConfigPipeline)
172 172
 			lines[0] = segments[0] + " <-"
173 173
 			lines = append(lines, segments[1])
174 174
 		}
175
-		lines = append(lines, describeAdditionalBuildDetail(deploy.Images[0].Build, includeLastPass)...)
175
+		lines = append(lines, describeAdditionalBuildDetail(deploy.Images[0].Build, deploy.Images[0].DestinationResolved, includeLastPass)...)
176 176
 		lines = append(lines, describeDeployments(deploy.Deployment, 3)...)
177 177
 		return lines
178 178
 	}
... ...
@@ -180,7 +204,7 @@ func describeDeploymentInServiceGroup(deploy graphview.DeploymentConfigPipeline)
180 180
 	lines := []string{fmt.Sprintf("%s deploys: %s", deploy.Deployment.Name, describeDeploymentConfigTrigger(deploy.Deployment.DeploymentConfig))}
181 181
 	for _, image := range deploy.Images {
182 182
 		lines = append(lines, describeImageInPipeline(image, deploy.Deployment.Namespace))
183
-		lines = append(lines, describeAdditionalBuildDetail(image.Build, includeLastPass)...)
183
+		lines = append(lines, describeAdditionalBuildDetail(image.Build, image.DestinationResolved, includeLastPass)...)
184 184
 		lines = append(lines, describeDeployments(deploy.Deployment, 3)...)
185 185
 	}
186 186
 	return lines
... ...
@@ -263,7 +287,7 @@ func describeBuildInPipeline(build *buildapi.BuildConfig, baseImage graphview.Im
263 263
 	}
264 264
 }
265 265
 
266
-func describeAdditionalBuildDetail(build *buildgraph.BuildConfigNode, includeSuccess bool) []string {
266
+func describeAdditionalBuildDetail(build *buildgraph.BuildConfigNode, pushTargetResolved bool, includeSuccess bool) []string {
267 267
 	if build == nil {
268 268
 		return nil
269 269
 	}
... ...
@@ -281,17 +305,17 @@ func describeAdditionalBuildDetail(build *buildgraph.BuildConfigNode, includeSuc
281 281
 	}
282 282
 
283 283
 	if pass != nil && includeSuccess {
284
-		out = append(out, describeBuildStatus(pass, &passTime, build.BuildConfig.Name))
284
+		out = append(out, describeBuildStatus(pass, &passTime, build.BuildConfig.Name, pushTargetResolved))
285 285
 	}
286 286
 	if fail != nil {
287
-		out = append(out, describeBuildStatus(fail, &failTime, build.BuildConfig.Name))
287
+		out = append(out, describeBuildStatus(fail, &failTime, build.BuildConfig.Name, pushTargetResolved))
288 288
 	}
289 289
 
290 290
 	active := build.ActiveBuilds
291 291
 	if len(active) > 0 {
292 292
 		activeOut := []string{}
293 293
 		for i := range active {
294
-			activeOut = append(activeOut, describeBuildStatus(&active[i], nil, build.BuildConfig.Name))
294
+			activeOut = append(activeOut, describeBuildStatus(&active[i], nil, build.BuildConfig.Name, pushTargetResolved))
295 295
 		}
296 296
 
297 297
 		if buildTimestamp(&active[0]).Before(last) {
... ...
@@ -306,7 +330,13 @@ func describeAdditionalBuildDetail(build *buildgraph.BuildConfigNode, includeSuc
306 306
 	return out
307 307
 }
308 308
 
309
-func describeBuildStatus(build *buildapi.Build, t *util.Time, parentName string) string {
309
+func describeBuildStatus(build *buildapi.Build, t *util.Time, parentName string, pushTargetResolved bool) string {
310
+	imageStreamFailure := ""
311
+	// if we're using an image stream and that image stream is the internal registry and that registry doesn't exist
312
+	if (build.Parameters.Output.To != nil) && !pushTargetResolved {
313
+		imageStreamFailure = " (can't push to image)"
314
+	}
315
+
310 316
 	if t == nil {
311 317
 		ts := buildTimestamp(build)
312 318
 		t = &ts
... ...
@@ -328,14 +358,14 @@ func describeBuildStatus(build *buildapi.Build, t *util.Time, parentName string)
328 328
 	}
329 329
 	switch build.Status {
330 330
 	case buildapi.BuildStatusComplete:
331
-		return fmt.Sprintf("build %s succeeded %s ago%s", name, time, revision)
331
+		return fmt.Sprintf("build %s succeeded %s ago%s%s", name, time, revision, imageStreamFailure)
332 332
 	case buildapi.BuildStatusError:
333
-		return fmt.Sprintf("build %s stopped with an error %s ago%s", name, time, revision)
333
+		return fmt.Sprintf("build %s stopped with an error %s ago%s%s", name, time, revision, imageStreamFailure)
334 334
 	case buildapi.BuildStatusFailed:
335
-		return fmt.Sprintf("build %s failed %s ago%s", name, time, revision)
335
+		return fmt.Sprintf("build %s failed %s ago%s%s", name, time, revision, imageStreamFailure)
336 336
 	default:
337 337
 		status := strings.ToLower(string(build.Status))
338
-		return fmt.Sprintf("build %s %s for %s%s", name, status, time, revision)
338
+		return fmt.Sprintf("build %s %s for %s%s%s", name, status, time, revision, imageStreamFailure)
339 339
 	}
340 340
 }
341 341
 
... ...
@@ -10,7 +10,10 @@ import (
10 10
 	ktestclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
11 11
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
12 12
 
13
+	osgraphtest "github.com/openshift/origin/pkg/api/graph/test"
14
+	buildedges "github.com/openshift/origin/pkg/build/graph"
13 15
 	"github.com/openshift/origin/pkg/client/testclient"
16
+	imageedges "github.com/openshift/origin/pkg/image/graph"
14 17
 	projectapi "github.com/openshift/origin/pkg/project/api"
15 18
 )
16 19
 
... ...
@@ -22,6 +25,34 @@ func mustParseTime(t string) time.Time {
22 22
 	return out
23 23
 }
24 24
 
25
+func TestUnpushableBuild(t *testing.T) {
26
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml")
27
+	if err != nil {
28
+		t.Fatalf("unexpected error: %v", err)
29
+	}
30
+
31
+	buildedges.AddAllInputOutputEdges(g)
32
+	imageedges.AddAllImageStreamRefEdges(g)
33
+
34
+	if e, a := true, hasUnresolvedImageStreamTag(g); e != a {
35
+		t.Errorf("expected %v, got %v", e, a)
36
+	}
37
+}
38
+
39
+func TestPushableBuild(t *testing.T) {
40
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/pushable-build.yaml")
41
+	if err != nil {
42
+		t.Fatalf("unexpected error: %v", err)
43
+	}
44
+
45
+	buildedges.AddAllInputOutputEdges(g)
46
+	imageedges.AddAllImageStreamRefEdges(g)
47
+
48
+	if e, a := false, hasUnresolvedImageStreamTag(g); e != a {
49
+		t.Errorf("expected %v, got %v", e, a)
50
+	}
51
+}
52
+
25 53
 func TestProjectStatus(t *testing.T) {
26 54
 	testCases := map[string]struct {
27 55
 		Path     string
28 56
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package graph
1
+
2
+import (
3
+	"github.com/gonum/graph"
4
+
5
+	osgraph "github.com/openshift/origin/pkg/api/graph"
6
+	imageapi "github.com/openshift/origin/pkg/image/api"
7
+	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
8
+)
9
+
10
+const (
11
+	// ReferencedImageStreamGraphEdgeKind is an edge that goes from an ImageStreamTag node back to an ImageStream
12
+	ReferencedImageStreamGraphEdgeKind = "ReferencedImageStreamGraphEdge"
13
+)
14
+
15
+// AddImageStreamRefEdge ensures that a directed edge exists between an IST Node and the IS it references
16
+func AddImageStreamRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamTagNode) {
17
+	isName, _, _ := imageapi.SplitImageStreamTag(node.Name)
18
+	imageStream := &imageapi.ImageStream{}
19
+	imageStream.Namespace = node.Namespace
20
+	imageStream.Name = isName
21
+
22
+	imageStreamNode := imagegraph.FindOrCreateSyntheticImageStreamNode(g, imageStream)
23
+	g.AddEdge(node, imageStreamNode, ReferencedImageStreamGraphEdgeKind)
24
+}
25
+
26
+// AddAllImageStreamRefEdges calls AddImageStreamRefEdge for every ImageStreamTagNode in the graph
27
+func AddAllImageStreamRefEdges(g osgraph.MutableUniqueGraph) {
28
+	for _, node := range g.(graph.Graph).NodeList() {
29
+		if istNode, ok := node.(*imagegraph.ImageStreamTagNode); ok {
30
+			AddImageStreamRefEdge(g, istNode)
31
+		}
32
+	}
33
+}
... ...
@@ -85,7 +85,7 @@ func EnsureImageStreamTagNode(g osgraph.MutableUniqueGraph, ist *imageapi.ImageS
85 85
 	return osgraph.EnsureUnique(g,
86 86
 		ImageStreamTagNodeName(ist),
87 87
 		func(node osgraph.Node) graph.Node {
88
-			return &ImageStreamTagNode{node, ist, false}
88
+			return &ImageStreamTagNode{node, ist, true}
89 89
 		},
90 90
 	).(*ImageStreamTagNode)
91 91
 }
... ...
@@ -95,21 +95,31 @@ func FindOrCreateSyntheticImageStreamTagNode(g osgraph.MutableUniqueGraph, ist *
95 95
 	return osgraph.EnsureUnique(g,
96 96
 		ImageStreamTagNodeName(ist),
97 97
 		func(node osgraph.Node) graph.Node {
98
-			return &ImageStreamTagNode{node, ist, true}
98
+			return &ImageStreamTagNode{node, ist, false}
99 99
 		},
100 100
 	).(*ImageStreamTagNode)
101 101
 }
102 102
 
103 103
 // EnsureImageStreamNode adds a graph node for the Image Stream if it does not already exist.
104
-func EnsureImageStreamNode(g osgraph.MutableUniqueGraph, stream *imageapi.ImageStream) graph.Node {
104
+func EnsureImageStreamNode(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) graph.Node {
105 105
 	return osgraph.EnsureUnique(g,
106
-		ImageStreamNodeName(stream),
106
+		ImageStreamNodeName(is),
107 107
 		func(node osgraph.Node) graph.Node {
108
-			return &ImageStreamNode{node, stream}
108
+			return &ImageStreamNode{node, is, true}
109 109
 		},
110 110
 	)
111 111
 }
112 112
 
113
+// FindOrCreateSyntheticImageStreamNode returns the existing ISNode or creates a synthetic node in its place
114
+func FindOrCreateSyntheticImageStreamNode(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) *ImageStreamNode {
115
+	return osgraph.EnsureUnique(g,
116
+		ImageStreamNodeName(is),
117
+		func(node osgraph.Node) graph.Node {
118
+			return &ImageStreamNode{node, is, false}
119
+		},
120
+	).(*ImageStreamNode)
121
+}
122
+
113 123
 // EnsureImageLayerNode adds a graph node for the layer if it does not already exist.
114 124
 func EnsureImageLayerNode(g osgraph.MutableUniqueGraph, layer string) graph.Node {
115 125
 	return osgraph.EnsureUnique(g,
... ...
@@ -25,6 +25,12 @@ func ImageStreamNodeName(o *imageapi.ImageStream) osgraph.UniqueName {
25 25
 type ImageStreamNode struct {
26 26
 	osgraph.Node
27 27
 	*imageapi.ImageStream
28
+
29
+	IsFound bool
30
+}
31
+
32
+func (n ImageStreamNode) Found() bool {
33
+	return n.IsFound
28 34
 }
29 35
 
30 36
 func (n ImageStreamNode) Object() interface{} {
... ...
@@ -47,11 +53,11 @@ type ImageStreamTagNode struct {
47 47
 	osgraph.Node
48 48
 	*imageapi.ImageStreamTag
49 49
 
50
-	Synthetic bool
50
+	IsFound bool
51 51
 }
52 52
 
53
-func (n ImageStreamTagNode) IsSynthetic() bool {
54
-	return n.Synthetic
53
+func (n ImageStreamTagNode) Found() bool {
54
+	return n.IsFound
55 55
 }
56 56
 
57 57
 func (n ImageStreamTagNode) ImageSpec() string {