Browse code

add oc status warnings for missing is/istag/dockref/isimg for bc

gabemontero authored on 2015/11/04 00:40:38
Showing 11 changed files
... ...
@@ -84,6 +84,12 @@ func NewImagePipelineFromBuildConfigNode(g osgraph.Graph, bcNode *buildgraph.Bui
84 84
 
85 85
 			flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
86 86
 		}
87
+		// this will handle the imagestream image case
88
+		for _, input := range g.SuccessorNodesByEdgeKind(buildOutputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
89
+			imageStreamNode := input.(*imagegraph.ImageStreamNode)
90
+
91
+			flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
92
+		}
87 93
 
88 94
 		// TODO handle the DockerImage case
89 95
 	}
... ...
@@ -124,6 +130,12 @@ func NewImagePipelineFromImageTagLocation(g osgraph.Graph, node graph.Node, imag
124 124
 
125 125
 		flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
126 126
 	}
127
+	for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
128
+		covered.Insert(input.ID())
129
+		imageStreamNode := input.(*imagegraph.ImageStreamNode)
130
+
131
+		flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
132
+	}
127 133
 
128 134
 	return flow, covered
129 135
 }
130 136
new file mode 100644
... ...
@@ -0,0 +1,143 @@
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
+          namespace: openshift
25
+      type: Docker
26
+    triggers:
27
+    - github:
28
+        secret: LyddbeCAaw1a0x08xz9n
29
+      type: GitHub
30
+    - generic:
31
+        secret: ZnYJJeEvo1ri0Gk0f6YY
32
+      type: Generic
33
+    - imageChange: {}
34
+      type: ImageChange
35
+  status:
36
+    lastVersion: 0
37
+- apiVersion: v1
38
+  kind: BuildConfig
39
+  metadata:
40
+    creationTimestamp: null
41
+    labels:
42
+      name: test
43
+    name: test
44
+  spec:
45
+    output:
46
+      to:
47
+        kind: ImageStreamTag
48
+        name: test:latest
49
+    resources: {}
50
+    source:
51
+      git:
52
+        uri: https://github.com/openshift/origin
53
+      type: Git
54
+    strategy:
55
+      sourceStrategy:
56
+        from:
57
+          kind: DockerImage
58
+          name: openshift/ruby-20-centos7
59
+      type: Docker
60
+    triggers:
61
+    - github:
62
+        secret: LyddbeCAaw1a0x08xz9n
63
+      type: GitHub
64
+    - generic:
65
+        secret: ZnYJJeEvo1ri0Gk0f6YY
66
+      type: Generic
67
+    - imageChange: {}
68
+      type: ImageChange
69
+  status:
70
+    lastVersion: 0
71
+- apiVersion: v1
72
+  kind: BuildConfig
73
+  metadata:
74
+    creationTimestamp: null
75
+    labels:
76
+      name: test2
77
+    name: test2
78
+  spec:
79
+    output:
80
+      to:
81
+        kind: ImageStreamTag
82
+        name: test2:latest
83
+    resources: {}
84
+    source:
85
+      git:
86
+        uri: https://github.com/openshift/origin
87
+      type: Git
88
+    strategy:
89
+      sourceStrategy:
90
+        from:
91
+          kind: ImageStreamImage
92
+          name: ruby-20-centos7@603bfa418
93
+          namespace: openshift
94
+      type: Docker
95
+    triggers:
96
+    - github:
97
+        secret: LyddbeCAaw1a0x08xz9n
98
+      type: GitHub
99
+    - generic:
100
+        secret: ZnYJJeEvo1ri0Gk0f6YY
101
+      type: Generic
102
+    - imageChange: {}
103
+      type: ImageChange
104
+  status:
105
+    lastVersion: 0
106
+- apiVersion: v1
107
+  kind: BuildConfig
108
+  metadata:
109
+    creationTimestamp: null
110
+    labels:
111
+      name: test3
112
+    name: test3
113
+  spec:
114
+    output:
115
+      to:
116
+        kind: ImageStreamTag
117
+        name: test3:latest
118
+    resources: {}
119
+    source:
120
+      git:
121
+        uri: https://github.com/openshift/origin
122
+      type: Git
123
+    strategy:
124
+      sourceStrategy:
125
+        from:
126
+          kind: ImageStream
127
+          name: ruby-20-centos7
128
+          namespace: openshift
129
+      type: Docker
130
+    triggers:
131
+    - github:
132
+        secret: LyddbeCAaw1a0x08xz9n
133
+      type: GitHub
134
+    - generic:
135
+        secret: ZnYJJeEvo1ri0Gk0f6YY
136
+      type: Generic
137
+    - imageChange: {}
138
+      type: ImageChange
139
+  status:
140
+    lastVersion: 0
141
+kind: List
142
+metadata: {}
0 143
new file mode 100644
... ...
@@ -0,0 +1,191 @@
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
+          namespace: openshift
25
+      type: Docker
26
+    triggers:
27
+    - github:
28
+        secret: LyddbeCAaw1a0x08xz9n
29
+      type: GitHub
30
+    - generic:
31
+        secret: ZnYJJeEvo1ri0Gk0f6YY
32
+      type: Generic
33
+    - imageChange: {}
34
+      type: ImageChange
35
+  status:
36
+    lastVersion: 0
37
+- apiVersion: v1
38
+  kind: BuildConfig
39
+  metadata:
40
+    creationTimestamp: null
41
+    labels:
42
+      app: ruby
43
+    name: ruby-hello-world-2
44
+  spec:
45
+    output:
46
+      to:
47
+        kind: ImageStreamTag
48
+        name: ruby-hello-world-2:latest
49
+    resources: {}
50
+    source:
51
+      git:
52
+        uri: https://github.com/openshift/ruby-hello-world-2
53
+      type: Git
54
+    strategy:
55
+      dockerStrategy:
56
+        from:
57
+          kind: ImageStream
58
+          name: ruby-20-centos7
59
+          namespace: openshift
60
+      type: Docker
61
+    triggers:
62
+    - github:
63
+        secret: LyddbeCAaw1a0x08xz9n
64
+      type: GitHub
65
+    - generic:
66
+        secret: ZnYJJeEvo1ri0Gk0f6YY
67
+      type: Generic
68
+    - imageChange: {}
69
+      type: ImageChange
70
+  status:
71
+    lastVersion: 0
72
+- apiVersion: v1
73
+  kind: BuildConfig
74
+  metadata:
75
+    creationTimestamp: null
76
+    labels:
77
+      name: test
78
+    name: test
79
+  spec:
80
+    output:
81
+      to:
82
+        kind: ImageStreamTag
83
+        name: test:latest
84
+    resources: {}
85
+    source:
86
+      git:
87
+        uri: https://github.com/openshift/origin
88
+      type: Git
89
+    strategy:
90
+      sourceStrategy:
91
+        from:
92
+          kind: DockerImage
93
+          name: openshift/ruby-20-centos7
94
+      type: Docker
95
+    triggers:
96
+    - github:
97
+        secret: LyddbeCAaw1a0x08xz9n
98
+      type: GitHub
99
+    - generic:
100
+        secret: ZnYJJeEvo1ri0Gk0f6YY
101
+      type: Generic
102
+    - imageChange: {}
103
+      type: ImageChange
104
+  status:
105
+    lastVersion: 0
106
+- apiVersion: v1
107
+  kind: BuildConfig
108
+  metadata:
109
+    creationTimestamp: null
110
+    labels:
111
+      name: test2
112
+    name: test2
113
+  spec:
114
+    output:
115
+      to:
116
+        kind: ImageStreamTag
117
+        name: test2:latest
118
+    resources: {}
119
+    source:
120
+      git:
121
+        uri: https://github.com/openshift/origin
122
+      type: Git
123
+    strategy:
124
+      sourceStrategy:
125
+        from:
126
+          kind: ImageStreamImage
127
+          name: ruby-20-centos7@603bfa418
128
+          namespace: openshift
129
+      type: Docker
130
+    triggers:
131
+    - github:
132
+        secret: LyddbeCAaw1a0x08xz9n
133
+      type: GitHub
134
+    - generic:
135
+        secret: ZnYJJeEvo1ri0Gk0f6YY
136
+      type: Generic
137
+    - imageChange: {}
138
+      type: ImageChange
139
+  status:
140
+    lastVersion: 0
141
+- apiVersion: v1
142
+  kind: BuildConfig
143
+  metadata:
144
+    creationTimestamp: null
145
+    labels:
146
+      name: test3
147
+    name: test3
148
+  spec:
149
+    output:
150
+      to:
151
+        kind: ImageStreamTag
152
+        name: test3:latest
153
+    resources: {}
154
+    source:
155
+      git:
156
+        uri: https://github.com/openshift/origin
157
+      type: Git
158
+    strategy:
159
+      sourceStrategy:
160
+        from:
161
+          kind: ImageStream
162
+          name: ruby-20-centos7
163
+          namespace: openshift
164
+      type: Docker
165
+    triggers:
166
+    - github:
167
+        secret: LyddbeCAaw1a0x08xz9n
168
+      type: GitHub
169
+    - generic:
170
+        secret: ZnYJJeEvo1ri0Gk0f6YY
171
+      type: Generic
172
+    - imageChange: {}
173
+      type: ImageChange
174
+  status:
175
+    lastVersion: 0
176
+- apiVersion: v1
177
+  kind: ImageStream
178
+  metadata:
179
+    annotations:
180
+      openshift.io/image.dockerRepositoryCheck: 2015-11-09T19:41:58Z
181
+    creationTimestamp: null
182
+    labels:
183
+      app: ruby
184
+    name: ruby-20-centos7
185
+    namespace: openshift
186
+  spec:
187
+    dockerImageRepository: openshift/ruby-20-centos7
188
+  status: {}
189
+kind: List
190
+metadata: {}
0 191
new file mode 100644
... ...
@@ -0,0 +1,163 @@
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
+          namespace: openshift
25
+      type: Docker
26
+    triggers:
27
+    - github:
28
+        secret: LyddbeCAaw1a0x08xz9n
29
+      type: GitHub
30
+    - generic:
31
+        secret: ZnYJJeEvo1ri0Gk0f6YY
32
+      type: Generic
33
+    - imageChange: {}
34
+      type: ImageChange
35
+  status:
36
+    lastVersion: 0
37
+- apiVersion: v1
38
+  kind: BuildConfig
39
+  metadata:
40
+    creationTimestamp: null
41
+    labels:
42
+      name: test
43
+    name: test
44
+  spec:
45
+    output:
46
+      to:
47
+        kind: ImageStreamTag
48
+        name: test:latest
49
+    resources: {}
50
+    source:
51
+      git:
52
+        uri: https://github.com/openshift/origin
53
+      type: Git
54
+    strategy:
55
+      sourceStrategy:
56
+        from:
57
+          kind: DockerImage
58
+          name: openshift/ruby-20-centos7
59
+      type: Docker
60
+    triggers:
61
+    - github:
62
+        secret: LyddbeCAaw1a0x08xz9n
63
+      type: GitHub
64
+    - generic:
65
+        secret: ZnYJJeEvo1ri0Gk0f6YY
66
+      type: Generic
67
+    - imageChange: {}
68
+      type: ImageChange
69
+  status:
70
+    lastVersion: 0
71
+- apiVersion: v1
72
+  kind: BuildConfig
73
+  metadata:
74
+    creationTimestamp: null
75
+    labels:
76
+      name: test2
77
+    name: test2
78
+  spec:
79
+    output:
80
+      to:
81
+        kind: ImageStreamTag
82
+        name: test2:latest
83
+    resources: {}
84
+    source:
85
+      git:
86
+        uri: https://github.com/openshift/origin
87
+      type: Git
88
+    strategy:
89
+      sourceStrategy:
90
+        from:
91
+          kind: ImageStreamImage
92
+          name: ruby-20-centos7@603bfa418
93
+          namespace: openshift
94
+      type: Docker
95
+    triggers:
96
+    - github:
97
+        secret: LyddbeCAaw1a0x08xz9n
98
+      type: GitHub
99
+    - generic:
100
+        secret: ZnYJJeEvo1ri0Gk0f6YY
101
+      type: Generic
102
+    - imageChange: {}
103
+      type: ImageChange
104
+  status:
105
+    lastVersion: 0
106
+- apiVersion: v1
107
+  kind: BuildConfig
108
+  metadata:
109
+    creationTimestamp: null
110
+    labels:
111
+      name: test3
112
+    name: test3
113
+  spec:
114
+    output:
115
+      to:
116
+        kind: ImageStreamTag
117
+        name: test3:latest
118
+    resources: {}
119
+    source:
120
+      git:
121
+        uri: https://github.com/openshift/origin
122
+      type: Git
123
+    strategy:
124
+      sourceStrategy:
125
+        from:
126
+          kind: ImageStream
127
+          name: ruby-20-centos7
128
+          namespace: openshift
129
+      type: Docker
130
+    triggers:
131
+    - github:
132
+        secret: LyddbeCAaw1a0x08xz9n
133
+      type: GitHub
134
+    - generic:
135
+        secret: ZnYJJeEvo1ri0Gk0f6YY
136
+      type: Generic
137
+    - imageChange: {}
138
+      type: ImageChange
139
+  status:
140
+    lastVersion: 0
141
+- apiVersion: v1
142
+  kind: ImageStream
143
+  metadata:
144
+    annotations:
145
+      openshift.io/image.dockerRepositoryCheck: 2015-11-09T19:41:58Z
146
+    creationTimestamp: null
147
+    labels:
148
+      app: ruby
149
+    name: ruby-20-centos7
150
+    namespace: openshift
151
+  spec:
152
+    dockerImageRepository: openshift/ruby-20-centos7
153
+  status:
154
+    dockerImageRepository: openshift/ruby-20-centos7
155
+    tags:
156
+    - items:
157
+      - created: 2015-11-09T19:41:58Z
158
+        dockerImageReference: openshift/ruby-20-centos7@sha256:603bfa418e1ff0a486f3d795a8b69a2f8f5eed488fbf5c286bffd8ab77b8e7a5
159
+        image: sha256:603bfa418e1ff0a486f3d795a8b69a2f8f5eed488fbf5c286bffd8ab77b8e7a5
160
+      tag: latest
161
+kind: List
162
+metadata: {}
... ...
@@ -4,30 +4,38 @@ import (
4 4
 	"fmt"
5 5
 	"sort"
6 6
 	"strings"
7
+	"time"
7 8
 
8 9
 	"github.com/gonum/graph"
9 10
 	"github.com/gonum/graph/topo"
10 11
 
12
+	"k8s.io/kubernetes/pkg/api/unversioned"
13
+
11 14
 	osgraph "github.com/openshift/origin/pkg/api/graph"
12 15
 	buildapi "github.com/openshift/origin/pkg/build/api"
13 16
 	buildedges "github.com/openshift/origin/pkg/build/graph"
14 17
 	buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
18
+	imageapi "github.com/openshift/origin/pkg/image/api"
15 19
 	imageedges "github.com/openshift/origin/pkg/image/graph"
16 20
 	imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
17 21
 )
18 22
 
19 23
 const (
20
-	TagNotAvailableWarning     = "ImageStreamTagNotAvailable"
21
-	LatestBuildFailedErr       = "LatestBuildFailed"
22
-	MissingRequiredRegistryErr = "MissingRequiredRegistry"
23
-	MissingImageStreamErr      = "MissingImageStream"
24
-	CyclicBuildConfigWarning   = "CyclicBuildConfig"
24
+	TagNotAvailableWarning         = "ImageStreamTagNotAvailable"
25
+	LatestBuildFailedErr           = "LatestBuildFailed"
26
+	MissingRequiredRegistryErr     = "MissingRequiredRegistry"
27
+	MissingOutputImageStreamErr    = "MissingOutputImageStream"
28
+	CyclicBuildConfigWarning       = "CyclicBuildConfig"
29
+	MissingImageStreamTagWarning   = "MissingImageStreamTag"
30
+	MissingImageStreamImageWarning = "MissingImageStreamImage"
25 31
 )
26 32
 
27 33
 // FindUnpushableBuildConfigs checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
28 34
 func FindUnpushableBuildConfigs(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
29 35
 	markers := []osgraph.Marker{}
30 36
 
37
+	// note, unlike with Inputs, ImageStreamImage is not a valid type for build output
38
+
31 39
 bc:
32 40
 	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
33 41
 		for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
... ...
@@ -40,7 +48,7 @@ bc:
40 40
 						RelatedNodes: []graph.Node{istNode},
41 41
 
42 42
 						Severity: osgraph.ErrorSeverity,
43
-						Key:      MissingImageStreamErr,
43
+						Key:      MissingOutputImageStreamErr,
44 44
 						Message: fmt.Sprintf("%s is pushing to %s that is using %s, but that image stream does not exist.",
45 45
 							f.ResourceName(bcNode), f.ResourceName(istNode), f.ResourceName(imageStreamNode)),
46 46
 					})
... ...
@@ -68,6 +76,75 @@ bc:
68 68
 	return markers
69 69
 }
70 70
 
71
+// FindMissingInputImageStreams checks all build configs and confirms that their From element exists
72
+//
73
+// Precedence of failures:
74
+// 1. A build config's input points to an image stream that does not exist
75
+// 2. A build config's input uses an image stream tag reference in an existing image stream, but no images within the image stream have that tag assigned
76
+// 3. A build config's input uses an image stream image reference in an exisiting image stream, but no images within the image stream have the supplied image hexadecimal ID
77
+func FindMissingInputImageStreams(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
78
+	markers := []osgraph.Marker{}
79
+
80
+	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
81
+		for _, bcInputNode := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputImageEdgeKind) {
82
+			switch bcInputNode.(type) {
83
+			case *imagegraph.ImageStreamTagNode:
84
+
85
+				for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
86
+					imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
87
+
88
+					// note, BuildConfig.Spec.BuildSpec.Strategy.[Docker|Source|Custom]Stragegy.From Input of ImageStream has been converted to ImageStreamTag on the vX to api conversion
89
+					// prior to our reaching this point in the code; so there is not need to check for that type vs. ImageStreamTag or ImageStreamImage;
90
+
91
+					tagNode, _ := bcInputNode.(*imagegraph.ImageStreamTagNode)
92
+					imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
93
+					if _, ok := imageStream.Status.Tags[tagNode.ImageTag()]; !ok {
94
+
95
+						markers = append(markers, osgraph.Marker{
96
+							Node: bcNode,
97
+							RelatedNodes: []graph.Node{bcInputNode,
98
+								imageStreamNode},
99
+							Severity:   osgraph.WarningSeverity,
100
+							Key:        MissingImageStreamTagWarning,
101
+							Message:    fmt.Sprintf("%s builds from %s, but the image stream tag does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
102
+							Suggestion: osgraph.Suggestion(fmt.Sprintf("examine analysis of build config outputs from this command and see if they build %s", f.ResourceName(bcInputNode))),
103
+						})
104
+
105
+					}
106
+
107
+				}
108
+
109
+			case *imagegraph.ImageStreamImageNode:
110
+
111
+				for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
112
+					imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
113
+
114
+					imageNode, _ := bcInputNode.(*imagegraph.ImageStreamImageNode)
115
+					imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
116
+					found, imageID, suggestion := validImageStreamImage(imageNode, imageStream)
117
+					if !found {
118
+
119
+						markers = append(markers, osgraph.Marker{
120
+							Node: bcNode,
121
+							RelatedNodes: []graph.Node{bcInputNode,
122
+								imageStreamNode},
123
+							Severity:   osgraph.WarningSeverity,
124
+							Key:        MissingImageStreamImageWarning,
125
+							Message:    fmt.Sprintf("%s builds from %s, but the image stream image does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
126
+							Suggestion: osgraph.Suggestion(fmt.Sprintf(suggestion, imageID, f.ResourceName(imageStreamNode))),
127
+						})
128
+
129
+					}
130
+
131
+				}
132
+
133
+			}
134
+
135
+		}
136
+	}
137
+	return markers
138
+}
139
+
71 140
 // FindCircularBuilds checks all build configs for cycles
72 141
 func FindCircularBuilds(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
73 142
 	// Filter out all but ImageStreamTag and BuildConfig nodes
... ...
@@ -160,6 +237,44 @@ func FindPendingTags(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
160 160
 	return markers
161 161
 }
162 162
 
163
+// validImageStreamImage will cycle through the imageStream.Status.Tags.[]TagEvent.DockerImageReference and  determine whether an image with the hexadecimal image id
164
+// associated with an ImageStreamImage reference in fact exists in a given ImageStream; on return, this method returns a true if does exist, and as well as the hexadecimal image
165
+// id from the ImageStreamImage, as well as the appropriate message to add to the marker if the image was not found
166
+func validImageStreamImage(imageNode *imagegraph.ImageStreamImageNode, imageStream *imageapi.ImageStream) (bool, string, string) {
167
+	dockerImageReference, err := imageapi.ParseDockerImageReference(imageNode.Name)
168
+	if err == nil {
169
+		for _, tagEventList := range imageStream.Status.Tags {
170
+			for _, tagEvent := range tagEventList.Items {
171
+				if strings.Contains(tagEvent.DockerImageReference, dockerImageReference.ID) {
172
+					return true, dockerImageReference.ID, ""
173
+				}
174
+			}
175
+		}
176
+	}
177
+
178
+	// check the images stream to see if any import images are in flight or have failed
179
+	annotation, ok := imageStream.Annotations[imageapi.DockerImageRepositoryCheckAnnotation]
180
+	if !ok {
181
+		return false, dockerImageReference.ID, "import the image with hexadecimal ID %s into the image stream %s"
182
+	}
183
+
184
+	if checkTime, err := time.Parse(time.RFC3339, annotation); err == nil {
185
+		// this time based annotation is set by pkg/image/controller/controller.go whenever import/tag operations are performed; unless
186
+		// in the midst of an import/tag operation, it stays set and serves as a timestamp for when the last operation occurred;
187
+		// so we will check if the image stream has been updated "recently";
188
+		// in case it is a slow link to the remote repo, see if if the check annotation occured within the last 5 minutes; if so, consider that as potentially "in progress"
189
+		compareTime := checkTime.Add(5 * time.Minute)
190
+		currentTime, _ := time.Parse(time.RFC3339, unversioned.Now().UTC().Format(time.RFC3339))
191
+		if compareTime.Before(currentTime) {
192
+			return false, dockerImageReference.ID, "import the image with hexadecimal ID %s into the image stream %s"
193
+		}
194
+
195
+		return false, dockerImageReference.ID, "a import of the image with hexadecimal ID %s into the image stream %s could be in progress; check again after a couple of minutes"
196
+
197
+	}
198
+	return false, dockerImageReference.ID, "an error occurred importing the image with hexadecimal ID %s into the image stream %s; inspect the images stream annotations for details"
199
+}
200
+
163 201
 // buildPointsToTag returns the buildConfig that points to the provided imageStreamTag.
164 202
 func buildPointsToTag(g osgraph.Graph, istag graph.Node) (*buildgraph.BuildConfigNode, bool) {
165 203
 	for _, bcNode := range g.PredecessorNodesByEdgeKind(istag, buildedges.BuildOutputEdgeKind) {
... ...
@@ -20,6 +20,7 @@ func TestUnpushableBuild(t *testing.T) {
20 20
 
21 21
 	buildedges.AddAllInputOutputEdges(g)
22 22
 	imageedges.AddAllImageStreamRefEdges(g)
23
+	imageedges.AddAllImageStreamImageRefEdges(g)
23 24
 
24 25
 	markers := FindUnpushableBuildConfigs(g, osgraph.DefaultNamer)
25 26
 	if e, a := 1, len(markers); e != a {
... ...
@@ -49,13 +50,14 @@ func TestUnpushableBuild(t *testing.T) {
49 49
 	}
50 50
 	buildedges.AddAllInputOutputEdges(g)
51 51
 	imageedges.AddAllImageStreamRefEdges(g)
52
+	imageedges.AddAllImageStreamImageRefEdges(g)
52 53
 
53 54
 	markers = FindUnpushableBuildConfigs(g, osgraph.DefaultNamer)
54 55
 	if e, a := 1, len(markers); e != a {
55 56
 		t.Fatalf("expected %v, got %v", e, a)
56 57
 	}
57 58
 
58
-	if got, expected := markers[0].Key, MissingImageStreamErr; got != expected {
59
+	if got, expected := markers[0].Key, MissingOutputImageStreamErr; got != expected {
59 60
 		t.Fatalf("expected marker key %q, got %q", expected, got)
60 61
 	}
61 62
 }
... ...
@@ -68,12 +70,65 @@ func TestPushableBuild(t *testing.T) {
68 68
 
69 69
 	buildedges.AddAllInputOutputEdges(g)
70 70
 	imageedges.AddAllImageStreamRefEdges(g)
71
+	imageedges.AddAllImageStreamImageRefEdges(g)
71 72
 
72 73
 	if e, a := 0, len(FindUnpushableBuildConfigs(g, osgraph.DefaultNamer)); e != a {
73 74
 		t.Errorf("expected %v, got %v", e, a)
74 75
 	}
75 76
 }
76 77
 
78
+func TestImageStreamPresent(t *testing.T) {
79
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/prereq-image-present.yaml")
80
+	if err != nil {
81
+		t.Fatalf("unexpected error: %v", err)
82
+	}
83
+
84
+	buildedges.AddAllInputOutputEdges(g)
85
+	imageedges.AddAllImageStreamRefEdges(g)
86
+	imageedges.AddAllImageStreamImageRefEdges(g)
87
+
88
+	if e, a := 0, len(FindMissingInputImageStreams(g, osgraph.DefaultNamer)); e != a {
89
+		t.Errorf("expected %v, got %v", e, a)
90
+	}
91
+}
92
+
93
+func TestImageStreamTagMissing(t *testing.T) {
94
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/prereq-image-present-notag.yaml")
95
+	if err != nil {
96
+		t.Fatalf("unexpected error: %v", err)
97
+	}
98
+	buildedges.AddAllInputOutputEdges(g)
99
+	imageedges.AddAllImageStreamRefEdges(g)
100
+	imageedges.AddAllImageStreamImageRefEdges(g)
101
+
102
+	markers := FindMissingInputImageStreams(g, osgraph.DefaultNamer)
103
+	if e, a := 4, len(markers); e != a {
104
+		t.Fatalf("expected %v, got %v", e, a)
105
+	}
106
+
107
+	for _, marker := range markers {
108
+		if got, expected1, expected2 := marker.Key, MissingImageStreamImageWarning, MissingImageStreamTagWarning; got != expected1 && got != expected2 {
109
+			t.Fatalf("expected marker key %q or %q, got %q", expected1, expected2, got)
110
+		}
111
+	}
112
+}
113
+
114
+func TestImageStreamMissing(t *testing.T) {
115
+	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/prereq-image-not-present.yaml")
116
+	if err != nil {
117
+		t.Fatalf("unexpected error: %v", err)
118
+	}
119
+	buildedges.AddAllInputOutputEdges(g)
120
+	imageedges.AddAllImageStreamRefEdges(g)
121
+	imageedges.AddAllImageStreamImageRefEdges(g)
122
+
123
+	markers := FindMissingInputImageStreams(g, osgraph.DefaultNamer)
124
+	if e, a := 3, len(markers); e != a {
125
+		t.Fatalf("expected %v, got %v", e, a)
126
+	}
127
+
128
+}
129
+
77 130
 func TestBuildConfigNoOutput(t *testing.T) {
78 131
 	g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bc-missing-output.yaml")
79 132
 	if err != nil {
... ...
@@ -114,6 +169,7 @@ func TestPendingImageStreamTag(t *testing.T) {
114 114
 	buildedges.AddAllInputOutputEdges(g)
115 115
 	buildedges.AddAllBuildEdges(g)
116 116
 	imageedges.AddAllImageStreamRefEdges(g)
117
+	imageedges.AddAllImageStreamImageRefEdges(g)
117 118
 
118 119
 	// Drop the build to showcase a TagNotAvailable warning (should happen when no
119 120
 	// build is new, pending, or running currently)
... ...
@@ -139,6 +195,7 @@ func TestLatestBuildFailed(t *testing.T) {
139 139
 	buildedges.AddAllInputOutputEdges(g)
140 140
 	buildedges.AddAllBuildEdges(g)
141 141
 	imageedges.AddAllImageStreamRefEdges(g)
142
+	imageedges.AddAllImageStreamImageRefEdges(g)
142 143
 
143 144
 	markers := FindPendingTags(g, osgraph.DefaultNamer)
144 145
 	if e, a := 1, len(markers); e != a {
... ...
@@ -112,6 +112,7 @@ func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, set
112 112
 	deployedges.AddAllTriggerEdges(g)
113 113
 	deployedges.AddAllDeploymentEdges(g)
114 114
 	imageedges.AddAllImageStreamRefEdges(g)
115
+	imageedges.AddAllImageStreamImageRefEdges(g)
115 116
 	routeedges.AddAllRouteEdges(g)
116 117
 
117 118
 	return g, forbiddenResources, nil
... ...
@@ -338,6 +339,7 @@ func getMarkerScanners(logsCommandName, securityPolicyCommandFormat, setProbeCom
338 338
 		buildanalysis.FindCircularBuilds,
339 339
 		buildanalysis.FindPendingTags,
340 340
 		deployanalysis.FindDeploymentConfigTriggerErrors,
341
+		buildanalysis.FindMissingInputImageStreams,
341 342
 		func(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
342 343
 			return deployanalysis.FindDeploymentConfigReadinessWarnings(g, f, setProbeCommandName)
343 344
 		},
... ...
@@ -70,6 +70,9 @@ func doesImageStreamExist(g osgraph.Graph, istag graph.Node) (graph.Node, bool)
70 70
 	for _, imagestream := range g.SuccessorNodesByEdgeKind(istag, imageedges.ReferencedImageStreamGraphEdgeKind) {
71 71
 		return imagestream, imagestream.(*imagegraph.ImageStreamNode).Found()
72 72
 	}
73
+	for _, imagestream := range g.SuccessorNodesByEdgeKind(istag, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
74
+		return imagestream, imagestream.(*imagegraph.ImageStreamNode).Found()
75
+	}
73 76
 	return nil, false
74 77
 }
75 78
 
... ...
@@ -18,6 +18,7 @@ func TestMissingImageStreamTag(t *testing.T) {
18 18
 	buildedges.AddAllInputOutputEdges(g)
19 19
 	deployedges.AddAllTriggerEdges(g)
20 20
 	imageedges.AddAllImageStreamRefEdges(g)
21
+	imageedges.AddAllImageStreamImageRefEdges(g)
21 22
 
22 23
 	markers := FindDeploymentConfigTriggerErrors(g, osgraph.DefaultNamer)
23 24
 	if e, a := 1, len(markers); e != a {
... ...
@@ -37,6 +38,7 @@ func TestMissingImageStream(t *testing.T) {
37 37
 	buildedges.AddAllInputOutputEdges(g)
38 38
 	deployedges.AddAllTriggerEdges(g)
39 39
 	imageedges.AddAllImageStreamRefEdges(g)
40
+	imageedges.AddAllImageStreamImageRefEdges(g)
40 41
 
41 42
 	markers := FindDeploymentConfigTriggerErrors(g, osgraph.DefaultNamer)
42 43
 	if e, a := 1, len(markers); e != a {
... ...
@@ -11,10 +11,12 @@ import (
11 11
 const (
12 12
 	// ReferencedImageStreamGraphEdgeKind is an edge that goes from an ImageStreamTag node back to an ImageStream
13 13
 	ReferencedImageStreamGraphEdgeKind = "ReferencedImageStreamGraphEdge"
14
+	// ReferencedImageStreamImageGraphEdgeKind is an edge that goes from an ImageStreamImage node back to an ImageStream
15
+	ReferencedImageStreamImageGraphEdgeKind = "ReferencedImageStreamImageGraphEdgeKind"
14 16
 )
15 17
 
16
-// AddImageStreamRefEdge ensures that a directed edge exists between an IST Node and the IS it references
17
-func AddImageStreamRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamTagNode) {
18
+// AddImageStreamTagRefEdge ensures that a directed edge exists between an IST Node and the IS it references
19
+func AddImageStreamTagRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamTagNode) {
18 20
 	isName, _, _ := imageapi.SplitImageStreamTag(node.Name)
19 21
 	imageStream := &imageapi.ImageStream{}
20 22
 	imageStream.Namespace = node.Namespace
... ...
@@ -24,11 +26,31 @@ func AddImageStreamRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageS
24 24
 	g.AddEdge(node, imageStreamNode, ReferencedImageStreamGraphEdgeKind)
25 25
 }
26 26
 
27
+// AddImageStreamImageRefEdge ensures that a directed edge exists between an ImageStreamImage Node and the IS it references
28
+func AddImageStreamImageRefEdge(g osgraph.MutableUniqueGraph, node *imagegraph.ImageStreamImageNode) {
29
+	dockImgRef, _ := imageapi.ParseDockerImageReference(node.Name)
30
+	imageStream := &imageapi.ImageStream{}
31
+	imageStream.Namespace = node.Namespace
32
+	imageStream.Name = dockImgRef.Name
33
+
34
+	imageStreamNode := imagegraph.FindOrCreateSyntheticImageStreamNode(g, imageStream)
35
+	g.AddEdge(node, imageStreamNode, ReferencedImageStreamImageGraphEdgeKind)
36
+}
37
+
27 38
 // AddAllImageStreamRefEdges calls AddImageStreamRefEdge for every ImageStreamTagNode in the graph
28 39
 func AddAllImageStreamRefEdges(g osgraph.MutableUniqueGraph) {
29 40
 	for _, node := range g.(graph.Graph).Nodes() {
30 41
 		if istNode, ok := node.(*imagegraph.ImageStreamTagNode); ok {
31
-			AddImageStreamRefEdge(g, istNode)
42
+			AddImageStreamTagRefEdge(g, istNode)
43
+		}
44
+	}
45
+}
46
+
47
+// AddAllImageStreamImageRefEdges calls AddImageStreamImageRefEdge for every ImageStreamImageNode in the graph
48
+func AddAllImageStreamImageRefEdges(g osgraph.MutableUniqueGraph) {
49
+	for _, node := range g.(graph.Graph).Nodes() {
50
+		if isimageNode, ok := node.(*imagegraph.ImageStreamImageNode); ok {
51
+			AddImageStreamImageRefEdge(g, isimageNode)
32 52
 		}
33 53
 	}
34 54
 }
... ...
@@ -102,6 +102,10 @@ func (n ImageStreamImageNode) String() string {
102 102
 	return string(ImageStreamImageNodeName(n.ImageStreamImage))
103 103
 }
104 104
 
105
+func (n ImageStreamImageNode) ResourceString() string {
106
+	return "isimage/" + n.Name
107
+}
108
+
105 109
 func (*ImageStreamImageNode) Kind() string {
106 110
 	return ImageStreamImageNodeKind
107 111
 }