func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, sets.String, error) { g := osgraph.New() loaders := []GraphLoader{ &serviceLoader{namespace: namespace, lister: d.K}, &serviceAccountLoader{namespace: namespace, lister: d.K}, &secretLoader{namespace: namespace, lister: d.K}, &rcLoader{namespace: namespace, lister: d.K}, &podLoader{namespace: namespace, lister: d.K}, // TODO check swagger for feature enablement and selectively add bcLoader and buildLoader // then remove errors.TolerateNotFoundError method. &bcLoader{namespace: namespace, lister: d.C}, &buildLoader{namespace: namespace, lister: d.C}, &isLoader{namespace: namespace, lister: d.C}, &dcLoader{namespace: namespace, lister: d.C}, &routeLoader{namespace: namespace, lister: d.C}, } loadingFuncs := []func() error{} for _, loader := range loaders { loadingFuncs = append(loadingFuncs, loader.Load) } forbiddenResources := sets.String{} if errs := parallel.Run(loadingFuncs...); len(errs) > 0 { actualErrors := []error{} for _, err := range errs { if kapierrors.IsForbidden(err) { forbiddenErr := err.(*kapierrors.StatusError) if (forbiddenErr.Status().Details != nil) && (len(forbiddenErr.Status().Details.Kind) > 0) { forbiddenResources.Insert(forbiddenErr.Status().Details.Kind) } continue } actualErrors = append(actualErrors, err) } if len(actualErrors) > 0 { return g, forbiddenResources, utilerrors.NewAggregate(actualErrors) } } for _, loader := range loaders { loader.AddToGraph(g) } kubeedges.AddAllExposedPodTemplateSpecEdges(g) kubeedges.AddAllExposedPodEdges(g) kubeedges.AddAllManagedByRCPodEdges(g) kubeedges.AddAllRequestedServiceAccountEdges(g) kubeedges.AddAllMountableSecretEdges(g) kubeedges.AddAllMountedSecretEdges(g) buildedges.AddAllInputOutputEdges(g) buildedges.AddAllBuildEdges(g) deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) imageedges.AddAllImageStreamRefEdges(g) routeedges.AddAllRouteEdges(g) return g, forbiddenResources, nil }
func TestBareDCGroup(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/bare-dc.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } kubeedges.AddAllExposedPodTemplateSpecEdges(g) buildedges.AddAllInputOutputEdges(g) deployedges.AddAllTriggerEdges(g) coveredNodes := IntSet{} serviceGroups, coveredByServiceGroups := AllServiceGroups(g, coveredNodes) coveredNodes.Insert(coveredByServiceGroups.List()...) bareDCPipelines, coveredByDCs := AllDeploymentConfigPipelines(g, coveredNodes) coveredNodes.Insert(coveredByDCs.List()...) bareBCPipelines, coveredByBCs := AllImagePipelinesFromBuildConfig(g, coveredNodes) coveredNodes.Insert(coveredByBCs.List()...) if e, a := 0, len(serviceGroups); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := 1, len(bareDCPipelines); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := 0, len(bareBCPipelines); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := 1, len(bareDCPipelines[0].Images); e != a { t.Errorf("expected %v, got %v", e, a) } }
func TestMissingReadinessProbe(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build-2.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) deployedges.AddAllTriggerEdges(g) imageedges.AddAllImageStreamRefEdges(g) markers := FindDeploymentConfigReadinessWarnings(g, osgraph.DefaultNamer, "command probe") if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } if got, expected := markers[0].Key, MissingReadinessProbeWarning; got != expected { t.Fatalf("expected marker key %q, got %q", expected, got) } }
func TestMissingImageStreamTag(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/missing-istag.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) deployedges.AddAllTriggerEdges(g) imageedges.AddAllImageStreamRefEdges(g) markers := FindDeploymentConfigTriggerErrors(g, osgraph.DefaultNamer) if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } if got, expected := markers[0].Key, MissingImageStreamTagWarning; got != expected { t.Fatalf("expected marker key %q, got %q", expected, got) } }
func TestSyntheticImageStreamTag(t *testing.T) { g, _, err := osgraphtest.BuildGraph("../../../api/graph/test/unpushable-build.yaml") if err != nil { t.Fatalf("unexpected error: %v", err) } buildedges.AddAllInputOutputEdges(g) deployedges.AddAllTriggerEdges(g) imageedges.AddAllImageStreamRefEdges(g) markers := FindDeploymentConfigTriggerErrors(g) if e, a := 1, len(markers); e != a { t.Fatalf("expected %v, got %v", e, a) } if got, expected := markers[0].Key, ImageStreamTagNotAvailableInfo; got != expected { t.Fatalf("expected marker key %q, got %q", expected, got) } }
func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, error) { g := osgraph.New() loaders := []GraphLoader{ &serviceLoader{namespace: namespace, lister: d.K}, &serviceAccountLoader{namespace: namespace, lister: d.K}, &secretLoader{namespace: namespace, lister: d.K}, &rcLoader{namespace: namespace, lister: d.K}, &podLoader{namespace: namespace, lister: d.K}, &bcLoader{namespace: namespace, lister: d.C}, &buildLoader{namespace: namespace, lister: d.C}, &isLoader{namespace: namespace, lister: d.C}, &dcLoader{namespace: namespace, lister: d.C}, } loadingFuncs := []func() error{} for _, loader := range loaders { loadingFuncs = append(loadingFuncs, loader.Load) } if errs := parallel.Run(loadingFuncs...); len(errs) > 0 { return g, utilerrors.NewAggregate(errs) } for _, loader := range loaders { loader.AddToGraph(g) } kubeedges.AddAllExposedPodTemplateSpecEdges(g) kubeedges.AddAllExposedPodEdges(g) kubeedges.AddAllManagedByRCPodEdges(g) kubeedges.AddAllRequestedServiceAccountEdges(g) kubeedges.AddAllMountableSecretEdges(g) kubeedges.AddAllMountedSecretEdges(g) buildedges.AddAllInputOutputEdges(g) buildedges.AddAllBuildEdges(g) deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) imageedges.AddAllImageStreamRefEdges(g) return g, nil }
func (d *ProjectStatusDescriber) MakeGraph(namespace string) (osgraph.Graph, error) { g := osgraph.New() loadingFuncs := []GraphLoadingFunc{loadServices, loadBuildConfigs, loadImageStreams, loadDeploymentConfigs, loadBuilds, loadReplicationControllers} listingWaitGroup := sync.WaitGroup{} graphLock := sync.Mutex{} errorChannel := make(chan error, len(loadingFuncs)) for _, loadingFunc := range loadingFuncs { listingWaitGroup.Add(1) go func(loadingFunc GraphLoadingFunc) { defer listingWaitGroup.Done() if err := loadingFunc(g, graphLock, namespace, d.K, d.C); err != nil { errorChannel <- err } }(loadingFunc) } listingWaitGroup.Wait() close(errorChannel) // if we had an error. Aggregate them and return them errlist := []error{} for err := range errorChannel { errlist = append(errlist, err) } if len(errlist) > 0 { return g, utilerrors.NewAggregate(errlist) } kubeedges.AddAllExposedPodTemplateSpecEdges(g) buildedges.AddAllInputOutputEdges(g) buildedges.AddAllBuildEdges(g) deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) imageedges.AddAllImageStreamRefEdges(g) return g, nil }
func TestGraph(t *testing.T) { g := osgraph.New() now := time.Now() builds := []buildapi.Build{ { ObjectMeta: kapi.ObjectMeta{ Name: "build1-1-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-10 * time.Second)), }, Status: buildapi.BuildStatusFailed, }, { ObjectMeta: kapi.ObjectMeta{ Name: "build1-2-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-5 * time.Second)), }, Status: buildapi.BuildStatusComplete, }, { ObjectMeta: kapi.ObjectMeta{ Name: "build1-3-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-15 * time.Second)), }, Status: buildapi.BuildStatusPending, }, } bc1Node := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build1"}, Triggers: []buildapi.BuildTriggerPolicy{ { ImageChange: &buildapi.ImageChangeTrigger{}, }, }, Parameters: buildapi.BuildParameters{ Strategy: buildapi.BuildStrategy{ Type: buildapi.SourceBuildStrategyType, SourceStrategy: &buildapi.SourceBuildStrategy{ From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:base-image"}, }, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{Name: "other"}, Tag: "tag1", }, }, }) buildedges.JoinBuilds(bc1Node, builds) bcTestNode := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "test"}, Parameters: buildapi.BuildParameters{ Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{Name: "other"}, Tag: "base-image", }, }, }) buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build2"}, Parameters: buildapi.BuildParameters{ Output: buildapi.BuildOutput{ DockerImageReference: "mycustom/repo/image", Tag: "tag2", }, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc-is-ignored"}, Spec: kapi.ServiceSpec{ Selector: nil, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc1"}, Spec: kapi.ServiceSpec{ Selector: map[string]string{ "deploymentconfig": "deploy1", }, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc2"}, Spec: kapi.ServiceSpec{ Selector: map[string]string{ "deploymentconfig": "deploy1", "env": "prod", }, }, }) deploygraph.EnsureDeploymentConfigNode(g, &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "other", Name: "deploy1"}, Triggers: []deployapi.DeploymentTriggerPolicy{ { ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{ From: kapi.ObjectReference{Namespace: "default", Name: "other"}, ContainerNames: []string{"1", "2"}, Tag: "tag1", }, }, }, Template: deployapi.DeploymentTemplate{ ControllerTemplate: kapi.ReplicationControllerSpec{ Template: &kapi.PodTemplateSpec{ ObjectMeta: kapi.ObjectMeta{ Labels: map[string]string{ "deploymentconfig": "deploy1", "env": "prod", }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "1", Image: "mycustom/repo/image", }, { Name: "2", Image: "mycustom/repo/image2", }, { Name: "3", Image: "mycustom/repo/image3", }, }, }, }, }, }, }) deploygraph.EnsureDeploymentConfigNode(g, &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "deploy2"}, Template: deployapi.DeploymentTemplate{ ControllerTemplate: kapi.ReplicationControllerSpec{ Template: &kapi.PodTemplateSpec{ ObjectMeta: kapi.ObjectMeta{ Labels: map[string]string{ "deploymentconfig": "deploy2", "env": "dev", }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "1", Image: "someother/image:v1", }, }, }, }, }, }, }) kubeedges.AddAllExposedPodTemplateSpecEdges(g) buildedges.AddAllInputOutputEdges(g) deployedges.AddAllTriggerEdges(g) t.Log(g) ir, dc, bc, other := 0, 0, 0, 0 for _, node := range g.NodeList() { switch g.Object(node).(type) { case *deployapi.DeploymentConfig: if g.Kind(node) != deploygraph.DeploymentConfigNodeKind { t.Fatalf("unexpected kind: %v", g.Kind(node)) } dc++ case *buildapi.BuildConfig: if g.Kind(node) != buildgraph.BuildConfigNodeKind { t.Fatalf("unexpected kind: %v", g.Kind(node)) } bc++ case *imageapi.ImageStream: // TODO resolve this check for 2 kinds, since both have the same object type if g.Kind(node) != imagegraph.ImageStreamNodeKind && g.Kind(node) != imagegraph.ImageStreamTagNodeKind { t.Fatalf("unexpected kind: %v", g.Kind(node)) } ir++ default: other++ } } if dc != 2 || bc != 3 || ir != 3 || other != 12 { t.Errorf("unexpected nodes: %d %d %d %d", dc, bc, ir, other) } for _, edge := range g.EdgeList() { if g.EdgeKind(edge) == osgraph.UnknownEdgeKind { t.Errorf("edge reported unknown kind: %#v", edge) } } // imagestreamtag default/other:base-image istID := 0 for _, node := range g.NodeList() { if g.Name(node) == "<imagestreamtag default/other:base-image>" { istID = node.ID() break } } edge := g.EdgeBetween(concrete.Node(bcTestNode.ID()), concrete.Node(istID)) if edge == nil { t.Fatalf("failed to find edge between %d and %d", bcTestNode.ID(), istID) } if len(g.SubgraphWithNodes([]graph.Node{edge.Head(), edge.Tail()}, osgraph.ExistingDirectEdge).EdgeList()) != 1 { t.Fatalf("expected one edge") } if len(g.SubgraphWithNodes([]graph.Node{edge.Tail(), edge.Head()}, osgraph.ExistingDirectEdge).EdgeList()) != 1 { t.Fatalf("expected one edge") } if e := g.EdgeBetween(concrete.Node(bcTestNode.ID()), concrete.Node(istID)); e == nil { t.Errorf("expected edge for %d-%d", bcTestNode.ID(), istID) } if e := g.EdgeBetween(concrete.Node(istID), concrete.Node(bcTestNode.ID())); e == nil { t.Errorf("expected edge for %d-%d", bcTestNode.ID(), istID) } pipelines, covered := DeploymentPipelines(g) if len(pipelines) != 2 { t.Fatalf("unexpected pipelines: %#v", pipelines) } if len(covered) != 7 { t.Fatalf("unexpected covered nodes: %#v", covered) } for from, images := range pipelines { t.Logf("from %s", from.Name) for _, path := range images { t.Logf(" %v", path) } } serviceGroups := ServiceAndDeploymentGroups(g) if len(serviceGroups) != 5 { t.Errorf("unexpected service groups: %#v", serviceGroups) } if len(serviceGroups[3].Builds) != 1 { t.Fatalf("unexpected final group: %#v", serviceGroups[2]) } for _, group := range serviceGroups { dcs := len(group.Deployments) svcs := len(group.Services) for _, svc := range group.Services { t.Logf("service %s", svc.Service.Name) } indent := "" if svcs > 0 { indent = " " } for _, deployment := range group.Deployments { t.Logf("%sdeployment %s", indent, deployment.Deployment.Name) for _, image := range deployment.Images { t.Logf("%s image %s", indent, image.Image.ImageSpec()) if image.Build != nil { if image.Build.LastSuccessfulBuild != nil { t.Logf("%s built at %s", indent, image.Build.LastSuccessfulBuild.CreationTimestamp) } else if image.Build.LastUnsuccessfulBuild != nil { t.Logf("%s build %s at %s", indent, image.Build.LastUnsuccessfulBuild.Status, image.Build.LastSuccessfulBuild.CreationTimestamp) } for _, b := range image.Build.ActiveBuilds { t.Logf("%s build %s %s", indent, b.Name, b.Status) } } } } if dcs != 0 || svcs != 0 { continue } for _, build := range group.Builds { if build.Image != nil { if build.Build != nil { t.Logf("%s <- build %s (%d)", build.Image.ImageSpec(), build.Build.Name, build.Image.ID()) } else { t.Logf("%s (%d)", build.Image.ImageSpec(), build.Image.ID()) } } else { t.Logf("build %s (%d)", build.Build.Name, build.Build.ID()) t.Errorf("expected build %d to have an image edge", build.Build.ID()) } } } }
func TestGraph(t *testing.T) { g := osgraph.New() now := time.Now() builds := []buildapi.Build{ { ObjectMeta: kapi.ObjectMeta{ Name: "build1-1-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-10 * time.Second)), }, Status: buildapi.BuildStatus{ Phase: buildapi.BuildPhaseFailed, }, }, { ObjectMeta: kapi.ObjectMeta{ Name: "build1-2-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-5 * time.Second)), }, Status: buildapi.BuildStatus{ Phase: buildapi.BuildPhaseComplete, }, }, { ObjectMeta: kapi.ObjectMeta{ Name: "build1-3-abc", Labels: map[string]string{buildapi.BuildConfigLabel: "build1"}, CreationTimestamp: util.NewTime(now.Add(-15 * time.Second)), }, Status: buildapi.BuildStatus{ Phase: buildapi.BuildPhasePending, }, }, } for i := range builds { buildgraph.EnsureBuildNode(g, &builds[i]) } buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build1"}, Spec: buildapi.BuildConfigSpec{ Triggers: []buildapi.BuildTriggerPolicy{ { ImageChange: &buildapi.ImageChangeTrigger{}, }, }, BuildSpec: buildapi.BuildSpec{ Strategy: buildapi.BuildStrategy{ Type: buildapi.SourceBuildStrategyType, SourceStrategy: &buildapi.SourceBuildStrategy{ From: kapi.ObjectReference{Kind: "ImageStreamTag", Name: "test:base-image"}, }, }, Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{Kind: "ImageStreamTag", Name: "other:tag1"}, }, }, }, }) bcTestNode := buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "test"}, Spec: buildapi.BuildConfigSpec{ BuildSpec: buildapi.BuildSpec{ Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{Kind: "ImageStreamTag", Name: "other:base-image"}, }, }, }, }) buildgraph.EnsureBuildConfigNode(g, &buildapi.BuildConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "build2"}, Spec: buildapi.BuildConfigSpec{ BuildSpec: buildapi.BuildSpec{ Output: buildapi.BuildOutput{ To: &kapi.ObjectReference{Kind: "DockerImage", Name: "mycustom/repo/image:tag2"}, }, }, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc-is-ignored"}, Spec: kapi.ServiceSpec{ Selector: nil, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc1"}, Spec: kapi.ServiceSpec{ Selector: map[string]string{ "deploymentconfig": "deploy1", }, }, }) kubegraph.EnsureServiceNode(g, &kapi.Service{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "svc2"}, Spec: kapi.ServiceSpec{ Selector: map[string]string{ "deploymentconfig": "deploy1", "env": "prod", }, }, }) deploygraph.EnsureDeploymentConfigNode(g, &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "other", Name: "deploy1"}, Triggers: []deployapi.DeploymentTriggerPolicy{ { ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{ From: kapi.ObjectReference{Namespace: "default", Name: "other"}, ContainerNames: []string{"1", "2"}, Tag: "tag1", }, }, }, Template: deployapi.DeploymentTemplate{ ControllerTemplate: kapi.ReplicationControllerSpec{ Template: &kapi.PodTemplateSpec{ ObjectMeta: kapi.ObjectMeta{ Labels: map[string]string{ "deploymentconfig": "deploy1", "env": "prod", }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "1", Image: "mycustom/repo/image", }, { Name: "2", Image: "mycustom/repo/image2", }, { Name: "3", Image: "mycustom/repo/image3", }, }, }, }, }, }, }) deploygraph.EnsureDeploymentConfigNode(g, &deployapi.DeploymentConfig{ ObjectMeta: kapi.ObjectMeta{Namespace: "default", Name: "deploy2"}, Template: deployapi.DeploymentTemplate{ ControllerTemplate: kapi.ReplicationControllerSpec{ Template: &kapi.PodTemplateSpec{ ObjectMeta: kapi.ObjectMeta{ Labels: map[string]string{ "deploymentconfig": "deploy2", "env": "dev", }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "1", Image: "someother/image:v1", }, }, }, }, }, }, }) kubeedges.AddAllExposedPodTemplateSpecEdges(g) buildedges.AddAllInputOutputEdges(g) buildedges.AddAllBuildEdges(g) deployedges.AddAllTriggerEdges(g) deployedges.AddAllDeploymentEdges(g) t.Log(g) for _, edge := range g.Edges() { if g.EdgeKinds(edge).Has(osgraph.UnknownEdgeKind) { t.Errorf("edge reported unknown kind: %#v", edge) } } // imagestreamtag default/other:base-image istID := 0 for _, node := range g.Nodes() { if g.Name(node) == "ImageStreamTag|default/other:base-image" { istID = node.ID() break } } edge := g.Edge(concrete.Node(bcTestNode.ID()), concrete.Node(istID)) if edge == nil { t.Fatalf("failed to find edge between %d and %d", bcTestNode.ID(), istID) } if len(g.SubgraphWithNodes([]graph.Node{edge.From(), edge.To()}, osgraph.ExistingDirectEdge).Edges()) != 1 { t.Fatalf("expected one edge") } if len(g.SubgraphWithNodes([]graph.Node{edge.To(), edge.From()}, osgraph.ExistingDirectEdge).Edges()) != 1 { t.Fatalf("expected one edge") } if e := g.Edge(concrete.Node(bcTestNode.ID()), concrete.Node(istID)); e == nil { t.Errorf("expected edge for %d-%d", bcTestNode.ID(), istID) } coveredNodes := IntSet{} serviceGroups, coveredByServiceGroups := AllServiceGroups(g, coveredNodes) coveredNodes.Insert(coveredByServiceGroups.List()...) bareDCPipelines, coveredByDCs := AllDeploymentConfigPipelines(g, coveredNodes) coveredNodes.Insert(coveredByDCs.List()...) if len(bareDCPipelines) != 1 { t.Fatalf("unexpected pipelines: %#v", bareDCPipelines) } if len(coveredNodes) != 10 { t.Fatalf("unexpected covered nodes: %#v", coveredNodes) } for _, bareDCPipeline := range bareDCPipelines { t.Logf("from %s", bareDCPipeline.Deployment.Name) for _, path := range bareDCPipeline.Images { t.Logf(" %v", path) } } if len(serviceGroups) != 3 { t.Errorf("unexpected service groups: %#v", serviceGroups) } for _, serviceGroup := range serviceGroups { t.Logf("service %s", serviceGroup.Service.Name) indent := " " for _, deployment := range serviceGroup.DeploymentConfigPipelines { t.Logf("%sdeployment %s", indent, deployment.Deployment.Name) for _, image := range deployment.Images { t.Logf("%s image %s", indent, image.Image.ImageSpec()) if image.Build != nil { if image.LastSuccessfulBuild != nil { t.Logf("%s built at %s", indent, image.LastSuccessfulBuild.Build.CreationTimestamp) } else if image.LastUnsuccessfulBuild != nil { t.Logf("%s build %s at %s", indent, image.LastUnsuccessfulBuild.Build.Status, image.LastUnsuccessfulBuild.Build.CreationTimestamp) } for _, b := range image.ActiveBuilds { t.Logf("%s build %s %s", indent, b.Build.Name, b.Build.Status) } } } } } }