Example #1
0
// findPendingTagMarkers is the guts behind FindPendingTags .... break out some of the content and reduce some indentation
func findPendingTagMarkers(istNode *imagegraph.ImageStreamTagNode, g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	buildFound := false
	bcNodes := buildedges.BuildConfigsForTag(g, graph.Node(istNode))
	for _, bcNode := range bcNodes {
		latestBuild := buildedges.GetLatestBuild(g, bcNode)

		// A build config points to the non existent tag but no current build exists.
		if latestBuild == nil {
			continue
		}
		buildFound = true

		// A build config points to the non existent tag but something is going on with
		// the latest build.
		// TODO: Handle other build phases.
		switch latestBuild.Build.Status.Phase {
		case buildapi.BuildPhaseCancelled:
			// TODO: Add a warning here.
		case buildapi.BuildPhaseError:
			// TODO: Add a warning here.
		case buildapi.BuildPhaseComplete:
			// We should never hit this. The output of our build is missing but the build is complete.
			// Most probably the user has messed up?
		case buildapi.BuildPhaseFailed:
			// Since the tag hasn't been populated yet, we assume there hasn't been a successful
			// build so far.
			markers = append(markers, osgraph.Marker{
				Node:         graph.Node(latestBuild),
				RelatedNodes: []graph.Node{graph.Node(istNode), graph.Node(bcNode)},

				Severity:   osgraph.ErrorSeverity,
				Key:        LatestBuildFailedErr,
				Message:    fmt.Sprintf("%s has failed.", f.ResourceName(latestBuild)),
				Suggestion: osgraph.Suggestion(fmt.Sprintf("Inspect the build failure with 'oc logs -f bc/%s'", bcNode.BuildConfig.GetName())),
			})
		default:
			// Do nothing when latest build is new, pending, or running.
		}

	}

	// if no current builds exist for any of the build configs, append marker for that
	// but ignore ISTs which have no build configs
	if !buildFound && len(bcNodes) > 0 {
		markers = append(markers, osgraph.Marker{
			Node:         graph.Node(istNode),
			RelatedNodes: bcNodesToRelatedNodes(bcNodes),

			Severity:   osgraph.WarningSeverity,
			Key:        TagNotAvailableWarning,
			Message:    fmt.Sprintf("%s needs to be imported or created by a build.", f.ResourceName(istNode)),
			Suggestion: osgraph.Suggestion(multiBCStartBuildSuggestion(bcNodes)),
		})
	}
	return markers
}
Example #2
0
// FindPendingTags inspects all imageStreamTags that serve as outputs to builds.
//
// Precedence of failures:
// 1. A build config points to the non existent tag but no current build exists.
// 2. A build config points to the non existent tag but the latest build has failed.
func FindPendingTags(g osgraph.Graph) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastIstNode := range g.NodesByKind(imagegraph.ImageStreamTagNodeKind) {
		istNode := uncastIstNode.(*imagegraph.ImageStreamTagNode)
		if bcNode, points := buildPointsToTag(g, uncastIstNode); points && !istNode.Found() {
			latestBuild := latestBuild(g, bcNode)

			// A build config points to the non existent tag but no current build exists.
			if latestBuild == nil {
				markers = append(markers, osgraph.Marker{
					Node:         graph.Node(bcNode),
					RelatedNodes: []graph.Node{uncastIstNode},

					Severity:   osgraph.WarningSeverity,
					Key:        TagNotAvailableWarning,
					Message:    fmt.Sprintf("%s needs to be imported or created by a build.", istNode.ResourceString()),
					Suggestion: osgraph.Suggestion(fmt.Sprintf("oc start-build %s", bcNode.ResourceString())),
				})
				continue
			}

			// A build config points to the non existent tag but something is going on with
			// the latest build.
			// TODO: Handle other build phases.
			switch latestBuild.Build.Status.Phase {
			case buildapi.BuildPhaseCancelled:
				// TODO: Add a warning here.
			case buildapi.BuildPhaseError:
				// TODO: Add a warning here.
			case buildapi.BuildPhaseComplete:
				// We should never hit this. The output of our build is missing but the build is complete.
				// Most probably the user has messed up?
			case buildapi.BuildPhaseFailed:
				// Since the tag hasn't been populated yet, we assume there hasn't been a successful
				// build so far.
				markers = append(markers, osgraph.Marker{
					Node:         graph.Node(latestBuild),
					RelatedNodes: []graph.Node{uncastIstNode, graph.Node(bcNode)},

					Severity:   osgraph.ErrorSeverity,
					Key:        LatestBuildFailedErr,
					Message:    fmt.Sprintf("%s has failed.", latestBuild.ResourceString()),
					Suggestion: osgraph.Suggestion(fmt.Sprintf("Inspect the build failure with 'oc logs %s'", latestBuild.ResourceString())),
				})
			default:
				// Do nothing when latest build is new, pending, or running.
			}
		}
	}

	return markers
}
Example #3
0
// getImageStreamTagSuggestion will return the appropriate marker Suggestion for when a BuildConfig is missing its input ImageStreamTag;  in particular,
// it will determine whether or not another BuildConfig can produce the aforementioned ImageStreamTag
func getImageStreamTagSuggestion(g osgraph.Graph, f osgraph.Namer, tagNode *imagegraph.ImageStreamTagNode) osgraph.Suggestion {
	bcs := []string{}
	for _, bcNode := range g.PredecessorNodesByEdgeKind(tagNode, buildedges.BuildOutputEdgeKind) {
		bcs = append(bcs, f.ResourceName(bcNode))
	}
	if len(bcs) == 1 {
		return osgraph.Suggestion(fmt.Sprintf("oc start-build %s", bcs[0]))
	}
	if len(bcs) > 0 {
		return osgraph.Suggestion(fmt.Sprintf("`oc start-build` with one of these: %s.", strings.Join(bcs[:], ",")))
	}
	return osgraph.Suggestion(fmt.Sprintf("%s needs to be imported.", f.ResourceName(tagNode)))
}
Example #4
0
// FindDeploymentConfigReadinessWarnings inspects deploymentconfigs and reports those that
// don't have readiness probes set up.
func FindDeploymentConfigReadinessWarnings(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker {
	markers := []osgraph.Marker{}

Node:
	for _, uncastDcNode := range g.NodesByKind(deploygraph.DeploymentConfigNodeKind) {
		dcNode := uncastDcNode.(*deploygraph.DeploymentConfigNode)
		if t := dcNode.DeploymentConfig.Spec.Template; t != nil && len(t.Spec.Containers) > 0 {
			for _, container := range t.Spec.Containers {
				if container.ReadinessProbe != nil {
					continue Node
				}
			}
			// All of the containers in the deployment config lack a readiness probe
			markers = append(markers, osgraph.Marker{
				Node:     uncastDcNode,
				Severity: osgraph.WarningSeverity,
				Key:      MissingReadinessProbeWarning,
				Message: fmt.Sprintf("%s has no readiness probe to verify pods are ready to accept traffic or ensure deployment is successful.",
					f.ResourceName(dcNode)),
				Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --readiness ...", setProbeCommand, f.ResourceName(dcNode))),
			})
			continue Node
		}
	}

	return markers
}
Example #5
0
// FindMissingLivenessProbes inspects all PodSpecs for missing liveness probes and generates a list of non-duplicate markers
func FindMissingLivenessProbes(g osgraph.Graph, f osgraph.Namer, setProbeCommand string) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodSpecNode := range g.NodesByKind(kubegraph.PodSpecNodeKind) {
		podSpecNode := uncastPodSpecNode.(*kubegraph.PodSpecNode)
		if hasLivenessProbe(podSpecNode) {
			continue
		}

		topLevelNode := osgraph.GetTopLevelContainerNode(g, podSpecNode)

		// skip any podSpec nodes that are managed by other nodes.
		// Liveness probes should only be applied to a controlling
		// podSpec node, and not to any of its children.
		if hasControllerRefEdge(g, topLevelNode) {
			continue
		}

		topLevelString := f.ResourceName(topLevelNode)
		markers = append(markers, osgraph.Marker{
			Node:         podSpecNode,
			RelatedNodes: []graph.Node{topLevelNode},

			Severity: osgraph.InfoSeverity,
			Key:      MissingLivenessProbeWarning,
			Message: fmt.Sprintf("%s has no liveness probe to verify pods are still running.",
				topLevelString),
			Suggestion: osgraph.Suggestion(fmt.Sprintf("%s %s --liveness ...", setProbeCommand, topLevelString)),
		})
	}

	return markers
}
Example #6
0
// getImageStreamImageSuggestion will return the appropriate marker Suggestion for when a BuildConfig is missing its input ImageStreamImage
func getImageStreamImageSuggestion(imageID string, imageStream *imageapi.ImageStream) osgraph.Suggestion {
	// check the images stream to see if any import images are in flight or have failed
	annotation, ok := imageStream.Annotations[imageapi.DockerImageRepositoryCheckAnnotation]
	if !ok {
		return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID))
	}

	if checkTime, err := time.Parse(time.RFC3339, annotation); err == nil {
		// this time based annotation is set by pkg/image/controller/controller.go whenever import/tag operations are performed; unless
		// in the midst of an import/tag operation, it stays set and serves as a timestamp for when the last operation occurred;
		// so we will check if the image stream has been updated "recently";
		// 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"
		compareTime := checkTime.Add(5 * time.Minute)
		currentTime, _ := time.Parse(time.RFC3339, unversioned.Now().UTC().Format(time.RFC3339))
		if compareTime.Before(currentTime) {
			return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID))
		}

		return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` with hexadecimal ID %s possibly in progress", imageStream.GetName(), imageID))

	}
	return osgraph.Suggestion(fmt.Sprintf("Possible error occurred with `oc import-image %s --from=` with hexadecimal ID %s; inspect images stream annotations", imageStream.GetName(), imageID))
}
Example #7
0
// FindUnpushableBuildConfigs checks all build configs that will output to an IST backed by an ImageStream and checks to make sure their builds can push.
func FindUnpushableBuildConfigs(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	// note, unlike with Inputs, ImageStreamImage is not a valid type for build output

bc:
	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
		for _, istNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
			for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(istNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
				imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)

				if !imageStreamNode.IsFound {
					markers = append(markers, osgraph.Marker{
						Node:         bcNode,
						RelatedNodes: []graph.Node{istNode},

						Severity: osgraph.ErrorSeverity,
						Key:      MissingOutputImageStreamErr,
						Message: fmt.Sprintf("%s is pushing to %s, but the image stream for that tag does not exist.",
							f.ResourceName(bcNode), f.ResourceName(istNode)),
					})

					continue
				}

				if len(imageStreamNode.Status.DockerImageRepository) == 0 {
					markers = append(markers, osgraph.Marker{
						Node:         bcNode,
						RelatedNodes: []graph.Node{istNode},

						Severity: osgraph.ErrorSeverity,
						Key:      MissingRequiredRegistryErr,
						Message: fmt.Sprintf("%s is pushing to %s, but the administrator has not configured the integrated Docker registry.",
							f.ResourceName(bcNode), f.ResourceName(istNode)),
						Suggestion: osgraph.Suggestion("oc adm registry -h"),
					})

					continue bc
				}
			}
		}
	}

	return markers
}
Example #8
0
func FindMissingTLSTerminationType(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) {
		routeNode := uncastRouteNode.(*routegraph.RouteNode)

		if routeNode.Spec.TLS != nil && len(routeNode.Spec.TLS.Termination) == 0 {
			markers = append(markers, osgraph.Marker{
				Node: routeNode,

				Severity:   osgraph.ErrorSeverity,
				Key:        MissingTLSTerminationTypeErr,
				Message:    fmt.Sprintf("%s has a TLS configuration but no termination type specified.", f.ResourceName(routeNode)),
				Suggestion: osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"tls\":{\"termination\":\"<type>\"}}}' (replace <type> with a valid termination type: edge, passthrough, reencrypt)", f.ResourceName(routeNode)))})
		}
	}

	return markers
}
Example #9
0
// FindHPASpecsMissingCPUTargets scans the graph in search of HorizontalPodAutoscalers that are missing a CPU utilization target.
// As of right now, the only metric that HPAs can use to scale pods is the CPU utilization, so if a HPA is missing this target it
// is effectively useless.
func FindHPASpecsMissingCPUTargets(graph osgraph.Graph, namer osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastNode := range graph.NodesByKind(kubenodes.HorizontalPodAutoscalerNodeKind) {
		node := uncastNode.(*kubenodes.HorizontalPodAutoscalerNode)

		if node.HorizontalPodAutoscaler.Spec.CPUUtilization == nil {
			markers = append(markers, osgraph.Marker{
				Node:       node,
				Severity:   osgraph.ErrorSeverity,
				Key:        HPAMissingCPUTargetError,
				Message:    fmt.Sprintf("%s is missing a CPU utilization target", namer.ResourceName(node)),
				Suggestion: osgraph.Suggestion(fmt.Sprintf(`oc patch %s -p '{"spec":{"cpuUtilization":{"targetPercentage": 80}}}'`, namer.ResourceName(node))),
			})
		}
	}

	return markers
}
Example #10
0
func FindPathBasedPassthroughRoutes(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) {
		routeNode := uncastRouteNode.(*routegraph.RouteNode)

		if len(routeNode.Spec.Path) > 0 && routeNode.Spec.TLS != nil && routeNode.Spec.TLS.Termination == routeapi.TLSTerminationPassthrough {
			markers = append(markers, osgraph.Marker{
				Node: routeNode,

				Severity:   osgraph.ErrorSeverity,
				Key:        PathBasedPassthroughErr,
				Message:    fmt.Sprintf("%s is path-based and uses passthrough termination, which is an invalid combination.", f.ResourceName(routeNode)),
				Suggestion: osgraph.Suggestion(fmt.Sprintf("1. use spec.tls.termination=edge or 2. use spec.tls.termination=reencrypt and specify spec.tls.destinationCACertificate or 3. remove spec.path")),
			})
		}
	}

	return markers
}
Example #11
0
// FindMissingRouter creates markers for all routes in case there is no running router.
func FindMissingRouter(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastRouteNode := range g.NodesByKind(routegraph.RouteNodeKind) {
		routeNode := uncastRouteNode.(*routegraph.RouteNode)

		if len(routeNode.Route.Status.Ingress) == 0 {
			markers = append(markers, osgraph.Marker{
				Node: routeNode,

				Severity:   osgraph.ErrorSeverity,
				Key:        MissingRequiredRouterErr,
				Message:    fmt.Sprintf("%s is routing traffic to svc/%s, but either the administrator has not installed a router or the router is not selecting this route.", f.ResourceName(routeNode), routeNode.Spec.To.Name),
				Suggestion: osgraph.Suggestion("oc adm router -h"),
			})
		}
	}

	return markers
}
Example #12
0
// FindRestartingPods inspects all Pods to see if they've restarted more than the threshold. logsCommandName is the name of
// the command that should be invoked to see pod logs. securityPolicyCommandPattern is a format string accepting two replacement
// variables for fmt.Sprintf - 1, the namespace of the current pod, 2 the service account of the pod.
func FindRestartingPods(g osgraph.Graph, f osgraph.Namer, logsCommandName, securityPolicyCommandPattern string) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, uncastPodNode := range g.NodesByKind(kubegraph.PodNodeKind) {
		podNode := uncastPodNode.(*kubegraph.PodNode)
		pod, ok := podNode.Object().(*kapi.Pod)
		if !ok {
			continue
		}

		for _, containerStatus := range pod.Status.ContainerStatuses {
			switch {
			case containerCrashLoopBackOff(containerStatus):
				var suggestion string
				switch {
				case containerIsNonRoot(pod, containerStatus.Name):
					suggestion = heredoc.Docf(`
						The container is starting and exiting repeatedly. This usually means the container is unable
						to start, misconfigured, or limited by security restrictions. Check the container logs with

						  %s %s -c %s

						Current security policy prevents your containers from being run as the root user. Some images
						may fail expecting to be able to change ownership or permissions on directories. Your admin
						can grant you access to run containers that need to run as the root user with this command:

						  %s
						`, logsCommandName, pod.Name, containerStatus.Name, fmt.Sprintf(securityPolicyCommandPattern, pod.Namespace, pod.Spec.ServiceAccountName))
				default:
					suggestion = heredoc.Docf(`
						The container is starting and exiting repeatedly. This usually means the container is unable
						to start, misconfigured, or limited by security restrictions. Check the container logs with

						  %s %s -c %s
						`, logsCommandName, pod.Name, containerStatus.Name)
				}
				markers = append(markers, osgraph.Marker{
					Node: podNode,

					Severity: osgraph.ErrorSeverity,
					Key:      CrashLoopingPodError,
					Message: fmt.Sprintf("container %q in %s is crash-looping", containerStatus.Name,
						f.ResourceName(podNode)),
					Suggestion: osgraph.Suggestion(suggestion),
				})
			case containerRestartedRecently(containerStatus, nowFn()):
				markers = append(markers, osgraph.Marker{
					Node: podNode,

					Severity: osgraph.WarningSeverity,
					Key:      RestartingPodWarning,
					Message: fmt.Sprintf("container %q in %s has restarted within the last 10 minutes", containerStatus.Name,
						f.ResourceName(podNode)),
				})
			case containerRestartedFrequently(containerStatus):
				markers = append(markers, osgraph.Marker{
					Node: podNode,

					Severity: osgraph.WarningSeverity,
					Key:      RestartingPodWarning,
					Message: fmt.Sprintf("container %q in %s has restarted %d times", containerStatus.Name,
						f.ResourceName(podNode), containerStatus.RestartCount),
				})
			}
		}
	}

	return markers
}
Example #13
0
func routePortMarker(g osgraph.Graph, f osgraph.Namer, routeNode *routegraph.RouteNode) *osgraph.Marker {
	for _, uncastServiceNode := range g.SuccessorNodesByEdgeKind(routeNode, routeedges.ExposedThroughRouteEdgeKind) {
		svcNode := uncastServiceNode.(*kubegraph.ServiceNode)

		if !svcNode.Found() {
			return &osgraph.Marker{
				Node:         routeNode,
				RelatedNodes: []graph.Node{svcNode},

				Severity: osgraph.WarningSeverity,
				Key:      MissingServiceWarning,
				Message: fmt.Sprintf("%s is supposed to route traffic to %s but %s doesn't exist.",
					f.ResourceName(routeNode), f.ResourceName(svcNode), f.ResourceName(svcNode)),
				// TODO: Suggest 'oc create service' once that's a thing.
				// See https://github.com/kubernetes/kubernetes/pull/19509
			}
		}

		if len(svcNode.Spec.Ports) > 1 && (routeNode.Spec.Port == nil || len(routeNode.Spec.Port.TargetPort.String()) == 0) {
			return &osgraph.Marker{
				Node:         routeNode,
				RelatedNodes: []graph.Node{svcNode},

				Severity: osgraph.WarningSeverity,
				Key:      MissingRoutePortWarning,
				Message: fmt.Sprintf("%s doesn't have a port specified and is routing traffic to %s which uses multiple ports.",
					f.ResourceName(routeNode), f.ResourceName(svcNode)),
			}
		}

		if routeNode.Spec.Port == nil {
			// If no port is specified, we don't need to analyze any further.
			return nil
		}

		routePortString := routeNode.Spec.Port.TargetPort.String()
		if routePort, err := strconv.Atoi(routePortString); err == nil {
			for _, port := range svcNode.Spec.Ports {
				if port.TargetPort.IntValue() == routePort {
					return nil
				}
			}

			// route has a numeric port, service has no port with that number as a targetPort.
			marker := &osgraph.Marker{
				Node:         routeNode,
				RelatedNodes: []graph.Node{svcNode},

				Severity: osgraph.WarningSeverity,
				Key:      WrongRoutePortWarning,
				Message: fmt.Sprintf("%s has a port specified (%d) but %s has no such targetPort.",
					f.ResourceName(routeNode), routePort, f.ResourceName(svcNode)),
			}
			if len(svcNode.Spec.Ports) == 1 {
				marker.Suggestion = osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"port\":{\"targetPort\": %d}}}'", f.ResourceName(routeNode), svcNode.Spec.Ports[0].TargetPort.IntValue()))
			}

			return marker
		}

		for _, port := range svcNode.Spec.Ports {
			if port.Name == routePortString {
				return nil
			}
		}

		// route has a named port, service has no port with that name.
		marker := &osgraph.Marker{
			Node:         routeNode,
			RelatedNodes: []graph.Node{svcNode},

			Severity: osgraph.WarningSeverity,
			Key:      WrongRoutePortWarning,
			Message: fmt.Sprintf("%s has a named port specified (%q) but %s has no such named port.",
				f.ResourceName(routeNode), routePortString, f.ResourceName(svcNode)),
		}
		if len(svcNode.Spec.Ports) == 1 {
			marker.Suggestion = osgraph.Suggestion(fmt.Sprintf("oc patch %s -p '{\"spec\":{\"port\":{\"targetPort\": %d}}}'", f.ResourceName(routeNode), svcNode.Spec.Ports[0].TargetPort.IntValue()))
		}

		return marker
	}
	return nil
}
Example #14
0
// FindMissingInputImageStreams checks all build configs and confirms that their From element exists
//
// Precedence of failures:
// 1. A build config's input points to an image stream that does not exist
// 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
// 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
func FindMissingInputImageStreams(g osgraph.Graph, f osgraph.Namer) []osgraph.Marker {
	markers := []osgraph.Marker{}

	for _, bcNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
		for _, bcInputNode := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputImageEdgeKind) {
			switch bcInputNode.(type) {
			case *imagegraph.ImageStreamTagNode:

				for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
					imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)

					// note, BuildConfig.Spec.BuildSpec.Strategy.[Docker|Source|Custom]Stragegy.From Input of ImageStream has been converted to ImageStreamTag on the vX to api conversion
					// prior to our reaching this point in the code; so there is not need to check for that type vs. ImageStreamTag or ImageStreamImage;

					tagNode, _ := bcInputNode.(*imagegraph.ImageStreamTagNode)
					imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
					if _, ok := imageStream.Status.Tags[tagNode.ImageTag()]; !ok {

						markers = append(markers, osgraph.Marker{
							Node: bcNode,
							RelatedNodes: []graph.Node{bcInputNode,
								imageStreamNode},
							Severity:   osgraph.WarningSeverity,
							Key:        MissingImageStreamTagWarning,
							Message:    fmt.Sprintf("%s builds from %s, but the image stream tag does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
							Suggestion: osgraph.Suggestion(fmt.Sprintf("examine analysis of build config outputs from this command and see if they build %s", f.ResourceName(bcInputNode))),
						})

					}

				}

			case *imagegraph.ImageStreamImageNode:

				for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(bcInputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
					imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)

					imageNode, _ := bcInputNode.(*imagegraph.ImageStreamImageNode)
					imageStream := imageStreamNode.Object().(*imageapi.ImageStream)
					found, imageID, suggestion := validImageStreamImage(imageNode, imageStream)
					if !found {

						markers = append(markers, osgraph.Marker{
							Node: bcNode,
							RelatedNodes: []graph.Node{bcInputNode,
								imageStreamNode},
							Severity:   osgraph.WarningSeverity,
							Key:        MissingImageStreamImageWarning,
							Message:    fmt.Sprintf("%s builds from %s, but the image stream image does not exist.", f.ResourceName(bcNode), f.ResourceName(bcInputNode)),
							Suggestion: osgraph.Suggestion(fmt.Sprintf(suggestion, imageID, f.ResourceName(imageStreamNode))),
						})

					}

				}

			}

		}
	}
	return markers
}