package graphview
import (
"sort"
"github.com/gonum/graph"
osgraph "github.com/openshift/origin/pkg/api/graph"
buildedges "github.com/openshift/origin/pkg/build/graph"
buildgraph "github.com/openshift/origin/pkg/build/graph/nodes"
imageedges "github.com/openshift/origin/pkg/image/graph"
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
)
// ImagePipeline represents a build, its output, and any inputs. The input
// to a build may be another ImagePipeline.
type ImagePipeline struct {
Image ImageTagLocation
DestinationResolved bool
ScheduledImport bool
Build *buildgraph.BuildConfigNode
LastSuccessfulBuild *buildgraph.BuildNode
LastUnsuccessfulBuild *buildgraph.BuildNode
ActiveBuilds []*buildgraph.BuildNode
// If set, the base image used by the build
BaseImage ImageTagLocation
// if set, the build config names that produces the base image
BaseBuilds []string
// If set, the source repository that inputs to the build
Source SourceLocation
}
// ImageTagLocation identifies the source or destination of an image. Represents
// both a tag in a Docker image repository, as well as a tag in an OpenShift image stream.
type ImageTagLocation interface {
ID() int
ImageSpec() string
ImageTag() string
}
// SourceLocation identifies a repository that is an input to a build.
type SourceLocation interface {
ID() int
}
func AllImagePipelinesFromBuildConfig(g osgraph.Graph, excludeNodeIDs IntSet) ([]ImagePipeline, IntSet) {
covered := IntSet{}
pipelines := []ImagePipeline{}
for _, uncastNode := range g.NodesByKind(buildgraph.BuildConfigNodeKind) {
if excludeNodeIDs.Has(uncastNode.ID()) {
continue
}
pipeline, covers := NewImagePipelineFromBuildConfigNode(g, uncastNode.(*buildgraph.BuildConfigNode))
covered.Insert(covers.List()...)
pipelines = append(pipelines, pipeline)
}
sort.Sort(SortedImagePipelines(pipelines))
outputImageToBCMap := make(map[string][]string)
for _, pipeline := range pipelines {
// note, bc does not have to have an output image
if pipeline.Image != nil {
bcs, ok := outputImageToBCMap[pipeline.Image.ImageSpec()]
if !ok {
bcs = []string{}
}
bcs = append(bcs, pipeline.Build.BuildConfig.Name)
outputImageToBCMap[pipeline.Image.ImageSpec()] = bcs
}
}
if len(outputImageToBCMap) > 0 {
for i, pipeline := range pipelines {
// note, bc does not have to have an input strategy image
if pipeline.BaseImage != nil {
baseBCs, ok := outputImageToBCMap[pipeline.BaseImage.ImageSpec()]
if ok && len(baseBCs) > 0 {
pipelines[i].BaseBuilds = baseBCs
}
}
}
}
return pipelines, covered
}
// NewImagePipeline attempts to locate a build flow from the provided node. If no such
// build flow can be located, false is returned.
func NewImagePipelineFromBuildConfigNode(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (ImagePipeline, IntSet) {
covered := IntSet{}
covered.Insert(bcNode.ID())
flow := ImagePipeline{}
base, src, coveredInputs, scheduled, _ := findBuildInputs(g, bcNode)
covered.Insert(coveredInputs.List()...)
flow.BaseImage = base
flow.Source = src
flow.Build = bcNode
flow.ScheduledImport = scheduled
flow.LastSuccessfulBuild, flow.LastUnsuccessfulBuild, flow.ActiveBuilds = buildedges.RelevantBuilds(g, flow.Build)
flow.Image = findBuildOutput(g, bcNode)
// we should have at most one
for _, buildOutputNode := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
// this will handle the imagestream tag case
for _, input := range g.SuccessorNodesByEdgeKind(buildOutputNode, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
// this will handle the imagestream image case
for _, input := range g.SuccessorNodesByEdgeKind(buildOutputNode, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
// TODO handle the DockerImage case
}
return flow, covered
}
// NewImagePipelineFromImageTagLocation returns the ImagePipeline and all the nodes contributing to it
func NewImagePipelineFromImageTagLocation(g osgraph.Graph, node graph.Node, imageTagLocation ImageTagLocation) (ImagePipeline, IntSet) {
covered := IntSet{}
covered.Insert(node.ID())
flow := ImagePipeline{}
flow.Image = imageTagLocation
for _, input := range g.PredecessorNodesByEdgeKind(node, buildedges.BuildOutputEdgeKind) {
covered.Insert(input.ID())
build := input.(*buildgraph.BuildConfigNode)
if flow.Build != nil {
// report this as an error (unexpected duplicate input build)
}
if build.BuildConfig == nil {
// report this as as a missing build / broken link
break
}
base, src, coveredInputs, scheduled, _ := findBuildInputs(g, build)
covered.Insert(coveredInputs.List()...)
flow.BaseImage = base
flow.Source = src
flow.Build = build
flow.ScheduledImport = scheduled
flow.LastSuccessfulBuild, flow.LastUnsuccessfulBuild, flow.ActiveBuilds = buildedges.RelevantBuilds(g, flow.Build)
}
for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamGraphEdgeKind) {
covered.Insert(input.ID())
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
for _, input := range g.SuccessorNodesByEdgeKind(node, imageedges.ReferencedImageStreamImageGraphEdgeKind) {
covered.Insert(input.ID())
imageStreamNode := input.(*imagegraph.ImageStreamNode)
flow.DestinationResolved = (len(imageStreamNode.Status.DockerImageRepository) != 0)
}
return flow, covered
}
func findBuildInputs(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (base ImageTagLocation, source SourceLocation, covered IntSet, scheduled bool, err error) {
covered = IntSet{}
// find inputs to the build
for _, input := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputEdgeKind) {
if source != nil {
// report this as an error (unexpected duplicate source)
}
covered.Insert(input.ID())
source = input.(SourceLocation)
}
for _, input := range g.PredecessorNodesByEdgeKind(bcNode, buildedges.BuildInputImageEdgeKind) {
if base != nil {
// report this as an error (unexpected duplicate input build)
}
covered.Insert(input.ID())
base = input.(ImageTagLocation)
scheduled = imageStreamTagScheduled(g, input, base)
}
return
}
func findBuildOutput(g osgraph.Graph, bcNode *buildgraph.BuildConfigNode) (result ImageTagLocation) {
for _, output := range g.SuccessorNodesByEdgeKind(bcNode, buildedges.BuildOutputEdgeKind) {
result = output.(ImageTagLocation)
return
}
return
}
func imageStreamTagScheduled(g osgraph.Graph, input graph.Node, base ImageTagLocation) (scheduled bool) {
for _, uncastImageStreamNode := range g.SuccessorNodesByEdgeKind(input, imageedges.ReferencedImageStreamGraphEdgeKind) {
imageStreamNode := uncastImageStreamNode.(*imagegraph.ImageStreamNode)
if imageStreamNode.ImageStream != nil {
if tag, ok := imageStreamNode.ImageStream.Spec.Tags[base.ImageTag()]; ok {
scheduled = tag.ImportPolicy.Scheduled
return
}
}
}
return
}
type SortedImagePipelines []ImagePipeline
func (m SortedImagePipelines) Len() int { return len(m) }
func (m SortedImagePipelines) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m SortedImagePipelines) Less(i, j int) bool {
return CompareImagePipeline(&m[i], &m[j])
}
func CompareImagePipeline(a, b *ImagePipeline) bool {
switch {
case a.Build != nil && b.Build != nil && a.Build.BuildConfig != nil && b.Build.BuildConfig != nil:
return CompareObjectMeta(&a.Build.BuildConfig.ObjectMeta, &b.Build.BuildConfig.ObjectMeta)
case a.Build != nil && a.Build.BuildConfig != nil:
return true
case b.Build != nil && b.Build.BuildConfig != nil:
return false
}
if a.Image == nil || b.Image == nil {
return true
}
return a.Image.ImageSpec() < b.Image.ImageSpec()
}