// ValidateBuildConfig tests required fields for a Build. func ValidateBuildConfig(config *buildapi.BuildConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&config.ObjectMeta, true, validation.NameIsDNSSubdomain).Prefix("metadata")...) // image change triggers that refer fromRefs := map[string]struct{}{} for i, trg := range config.Spec.Triggers { allErrs = append(allErrs, validateTrigger(&trg).PrefixIndex(i).Prefix("triggers")...) if trg.Type != buildapi.ImageChangeBuildTriggerType || trg.ImageChange == nil { continue } from := trg.ImageChange.From if from == nil { from = buildutil.GetImageStreamForStrategy(config.Spec.Strategy) } fromKey := refKey(config.Namespace, from) _, exists := fromRefs[fromKey] if exists { allErrs = append(allErrs, fielderrors.NewFieldInvalid("triggers", config.Spec.Triggers, "multiple ImageChange triggers refer to the same image stream tag")) } fromRefs[fromKey] = struct{}{} } allErrs = append(allErrs, validateBuildSpec(&config.Spec.BuildSpec).Prefix("spec")...) return allErrs }
// findImageChangeTrigger finds an image change trigger that has a from that matches the passed in ref // if no match is found but there is an image change trigger with a null from, that trigger is returned func findImageChangeTrigger(bc *buildapi.BuildConfig, ref *kapi.ObjectReference) *buildapi.ImageChangeTrigger { if ref == nil { return nil } for _, trigger := range bc.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } imageChange := trigger.ImageChange triggerRef := imageChange.From if triggerRef == nil { triggerRef = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) if triggerRef == nil || triggerRef.Kind != "ImageStreamTag" { continue } } triggerNs := triggerRef.Namespace if triggerNs == "" { triggerNs = bc.Namespace } refNs := ref.Namespace if refNs == "" { refNs = bc.Namespace } if triggerRef.Name == ref.Name && triggerNs == refNs { return imageChange } } return nil }
// AddInputEdges links the build config to its input image and source nodes. func AddInputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Spec.Source); in != nil { g.AddEdge(in, node, BuildInputEdgeKind) } inputImage := buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) if input := imageRefNode(g, inputImage, node.BuildConfig); input != nil { g.AddEdge(input, node, BuildInputImageEdgeKind) } }
// BuildConfig adds a graph node for the specific build config if it does not exist, // and will link the build config to other nodes for the images and source repositories // it depends on. func BuildConfig(g MutableUniqueGraph, config *build.BuildConfig) graph.Node { node, found := g.FindOrCreate( UniqueName(fmt.Sprintf("%d|%s/%s", BuildConfigGraphKind, config.Namespace, config.Name)), func(node Node) graph.Node { return &BuildConfigNode{ Node: node, BuildConfig: config, } }, ) if found { return node } output := config.Parameters.Output to := output.To switch { case to != nil && len(to.Name) > 0: out := ImageStreamTag(g, defaultNamespace(to.Namespace, config.Namespace), to.Name, output.Tag) g.AddEdge(node, out, BuildOutputGraphEdgeKind) case len(output.DockerImageReference) > 0: out := DockerRepository(g, output.DockerImageReference, output.Tag) g.AddEdge(node, out, BuildOutputGraphEdgeKind) } if in, ok := SourceRepository(g, config.Parameters.Source); ok { g.AddEdge(in, node, BuildInputGraphEdgeKind) } from := buildutil.GetImageStreamForStrategy(config.Parameters.Strategy) if from != nil { switch from.Kind { case "DockerImage": if ref, err := image.ParseDockerImageReference(from.Name); err == nil { tag := ref.Tag ref.Tag = "" in := DockerRepository(g, ref.String(), tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) } case "ImageStream": tag := image.DefaultImageTag in := ImageStreamTag(g, defaultNamespace(from.Namespace, config.Namespace), from.Name, tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) case "ImageStreamTag": name, tag, _ := image.SplitImageStreamTag(from.Name) in := ImageStreamTag(g, defaultNamespace(from.Namespace, config.Namespace), name, tag) g.AddEdge(in, node, BuildInputImageGraphEdgeKind) case "ImageStreamImage": glog.V(4).Infof("Ignoring ImageStreamImage reference in BuildConfig %s/%s", config.Namespace, config.Name) } } return node }
// AddTriggerEdges links the build config to its trigger input image nodes. func AddTriggerEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) { for _, trigger := range node.BuildConfig.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } from := trigger.ImageChange.From if trigger.ImageChange.From == nil { from = buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) } triggerNode := imageRefNode(g, from, node.BuildConfig) g.AddEdge(triggerNode, node, BuildTriggerImageEdgeKind) } }
// checkCircularReferences ensures there are no builds that can trigger themselves // due to an imagechangetrigger that matches the output destination of the image. // objects is a list of api objects produced by new-app. func (c *AppConfig) checkCircularReferences(objects app.Objects) error { for _, obj := range objects { if bc, ok := obj.(*buildapi.BuildConfig); ok { input := buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) if bc.Spec.Output.To != nil && input != nil && reflect.DeepEqual(input, bc.Spec.Output.To) { ns := input.Namespace if len(ns) == 0 { ns = c.OriginNamespace } return app.CircularOutputReferenceError{Reference: fmt.Sprintf("%s/%s", ns, input.Name)} } } } return nil }
// getStreams iterates over a given set of build configurations // and extracts all the image streams which trigger a // build when the image stream is updated func getStreams(configs []buildapi.BuildConfig) map[string][]string { glog.V(1).Infof("Scanning BuildConfigs") avoidDuplicates := make(map[string][]string) for _, cfg := range configs { glog.V(1).Infof("Scanning BuildConfigs %v", cfg) for _, tr := range cfg.Spec.Triggers { glog.V(1).Infof("Scanning trigger %v", tr) from := buildutil.GetImageStreamForStrategy(cfg.Spec.Strategy) glog.V(1).Infof("Strategy from= %v", from) if tr.ImageChange != nil && from != nil && from.Name != "" { glog.V(1).Infof("found ICT with from %s kind %s", from.Name, from.Kind) var name, tag string switch from.Kind { case "ImageStreamTag": bits := strings.Split(from.Name, ":") name = bits[0] tag = bits[1] default: // ImageStreamImage and DockerImage are never updated and so never // trigger builds. continue } var stream string switch from.Namespace { case "": stream = join(cfg.Namespace, name) default: stream = join(from.Namespace, name) } uniqueTag := true for _, prev := range avoidDuplicates[stream] { if prev == tag { uniqueTag = false break } } glog.V(1).Infof("checking unique tag %v %s", uniqueTag, tag) if uniqueTag { avoidDuplicates[stream] = append(avoidDuplicates[stream], tag) } } } } return avoidDuplicates }
// addBuildStrategyImageReferencesToGraph ads references from the build strategy's parent node to the image // the build strategy references. // // Edges are added to the graph from each predecessor (build or build config) // to the image specified by strategy.from, as long as the image is managed by // OpenShift. func addBuildStrategyImageReferencesToGraph(g graph.Graph, strategy buildapi.BuildStrategy, predecessor gonum.Node) { glog.V(4).Infof("Examining build strategy with type %q", strategy.Type) from := buildutil.GetImageStreamForStrategy(strategy) if from == nil { glog.V(4).Infof("Unable to determine 'from' reference - skipping") return } glog.V(4).Infof("Examining build strategy with from: %#v", from) var imageID string switch from.Kind { case "ImageStreamImage": _, id, err := imagestreamimage.ParseNameAndID(from.Name) if err != nil { glog.V(2).Infof("Error parsing ImageStreamImage name %q: %v - skipping", from.Name, err) return } imageID = id case "DockerImage": ref, err := imageapi.ParseDockerImageReference(from.Name) if err != nil { glog.V(2).Infof("Error parsing DockerImage name %q: %v - skipping", from.Name, err) return } imageID = ref.ID default: return } glog.V(4).Infof("Looking for image %q in graph", imageID) imageNode := imagegraph.FindImage(g, imageID) if imageNode == nil { glog.V(4).Infof("Unable to find image %q in graph - skipping", imageID) return } glog.V(4).Infof("Adding edge from %v to %v", predecessor, imageNode) g.AddEdge(predecessor, imageNode, ReferencedImageEdgeKind) }
// updateImageTriggers sets the LastTriggeredImageID on all the ImageChangeTriggers on the BuildConfig and // updates the From reference of the strategy if the strategy uses an ImageStream or ImageStreamTag reference func (g *BuildGenerator) updateImageTriggers(ctx kapi.Context, bc *buildapi.BuildConfig, from, triggeredBy *kapi.ObjectReference) error { var requestTrigger *buildapi.ImageChangeTrigger if from != nil { requestTrigger = findImageChangeTrigger(bc, from) } if requestTrigger != nil && triggeredBy != nil && requestTrigger.LastTriggeredImageID == triggeredBy.Name { glog.V(2).Infof("Aborting imageid triggered build for BuildConfig %s/%s with imageid %s because the BuildConfig already matches this imageid", bc.Namespace, bc.Name, triggeredBy.Name) return fmt.Errorf("build config %s/%s has already instantiated a build for imageid %s", bc.Namespace, bc.Name, triggeredBy.Name) } // Update last triggered image id for all image change triggers for _, trigger := range bc.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } // Use the requested image id for the trigger that caused the build, otherwise resolve to the latest if triggeredBy != nil && trigger.ImageChange == requestTrigger { trigger.ImageChange.LastTriggeredImageID = triggeredBy.Name continue } triggerImageRef := trigger.ImageChange.From if triggerImageRef == nil { triggerImageRef = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) } if triggerImageRef == nil { glog.Warningf("Could not get ImageStream reference for default ImageChangeTrigger on BuildConfig %s/%s", bc.Namespace, bc.Name) continue } image, err := g.resolveImageStreamReference(ctx, *triggerImageRef, bc.Namespace) if err != nil { // If the trigger is for the strategy from ref, return an error if trigger.ImageChange.From == nil { return err } // Otherwise, warn that an error occurred, but continue glog.Warningf("Could not resolve trigger reference for build config %s/%s: %#v", bc.Namespace, bc.Name, triggerImageRef) } trigger.ImageChange.LastTriggeredImageID = image } return nil }
// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on. func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode { output := node.BuildConfig.Spec.Output to := output.To switch { case to == nil: case to.Kind == "DockerImage": out := imagegraph.EnsureDockerRepositoryNode(g, to.Name, "") g.AddEdge(node, out, BuildOutputEdgeKind) case to.Kind == "ImageStreamTag": out := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(to.Namespace, node.BuildConfig.Namespace), to.Name)) g.AddEdge(node, out, BuildOutputEdgeKind) } if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Spec.Source); in != nil { g.AddEdge(in, node, BuildInputEdgeKind) } from := buildutil.GetImageStreamForStrategy(node.BuildConfig.Spec.Strategy) if from != nil { switch from.Kind { case "DockerImage": if ref, err := imageapi.ParseDockerImageReference(from.Name); err == nil { tag := ref.Tag ref.Tag = "" in := imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag) g.AddEdge(in, node, BuildInputImageEdgeKind) } case "ImageStream": in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name, imageapi.DefaultImageTag)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamTag": in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta2(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamImage": in := imagegraph.FindOrCreateSyntheticImageStreamImageNode(g, imagegraph.MakeImageStreamImageObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name)) g.AddEdge(in, node, BuildInputImageEdgeKind) } } return node }
// AddInputOutputEdges links the build config to other nodes for the images and source repositories it depends on. func AddInputOutputEdges(g osgraph.MutableUniqueGraph, node *buildgraph.BuildConfigNode) *buildgraph.BuildConfigNode { output := node.BuildConfig.Parameters.Output to := output.To switch { case to != nil && len(to.Name) > 0: out := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(to.Namespace, node.BuildConfig.Namespace), to.Name, output.Tag)) g.AddEdge(node, out, BuildOutputEdgeKind) case len(output.DockerImageReference) > 0: out := imagegraph.EnsureDockerRepositoryNode(g, output.DockerImageReference, output.Tag) g.AddEdge(node, out, BuildOutputEdgeKind) } if in := buildgraph.EnsureSourceRepositoryNode(g, node.BuildConfig.Parameters.Source); in != nil { g.AddEdge(in, node, BuildInputEdgeKind) } from := buildutil.GetImageStreamForStrategy(node.BuildConfig.Parameters.Strategy) if from != nil { switch from.Kind { case "DockerImage": if ref, err := imageapi.ParseDockerImageReference(from.Name); err == nil { tag := ref.Tag ref.Tag = "" in := imagegraph.EnsureDockerRepositoryNode(g, ref.String(), tag) g.AddEdge(in, node, BuildInputImageEdgeKind) } case "ImageStream": tag := imageapi.DefaultImageTag in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), from.Name, tag)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamTag": name, tag, _ := imageapi.SplitImageStreamTag(from.Name) in := imagegraph.FindOrCreateSyntheticImageStreamTagNode(g, imagegraph.MakeImageStreamTagObjectMeta(defaultNamespace(from.Namespace, node.BuildConfig.Namespace), name, tag)) g.AddEdge(in, node, BuildInputImageEdgeKind) case "ImageStreamImage": glog.V(4).Infof("Ignoring ImageStreamImage reference in BuildConfig %s/%s", node.BuildConfig.Namespace, node.BuildConfig.Name) } } return node }
// ValidateBuildConfig tests required fields for a Build. func ValidateBuildConfig(config *buildapi.BuildConfig) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, validation.ValidateObjectMeta(&config.ObjectMeta, true, validation.NameIsDNSSubdomain, field.NewPath("metadata"))...) // image change triggers that refer fromRefs := map[string]struct{}{} specPath := field.NewPath("spec") triggersPath := specPath.Child("triggers") for i, trg := range config.Spec.Triggers { allErrs = append(allErrs, validateTrigger(&trg, triggersPath.Index(i))...) if trg.Type != buildapi.ImageChangeBuildTriggerType || trg.ImageChange == nil { continue } from := trg.ImageChange.From if from == nil { from = buildutil.GetImageStreamForStrategy(config.Spec.Strategy) } fromKey := refKey(config.Namespace, from) _, exists := fromRefs[fromKey] if exists { allErrs = append(allErrs, field.Invalid(triggersPath, config.Spec.Triggers, "multiple ImageChange triggers refer to the same image stream tag")) } fromRefs[fromKey] = struct{}{} } allErrs = append(allErrs, validateBuildSpec(&config.Spec.BuildSpec, specPath)...) // validate ImageChangeTriggers of DockerStrategy builds strategy := config.Spec.BuildSpec.Strategy if strategy.DockerStrategy != nil && strategy.DockerStrategy.From == nil { for i, trigger := range config.Spec.Triggers { if trigger.Type == buildapi.ImageChangeBuildTriggerType && (trigger.ImageChange == nil || trigger.ImageChange.From == nil) { allErrs = append(allErrs, field.Required(triggersPath.Index(i).Child("imageChange", "from"))) } } } return allErrs }
func convert_v1_BuildConfig_To_api_BuildConfig(in *BuildConfig, out *newer.BuildConfig, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } newTriggers := []newer.BuildTriggerPolicy{} // strip off any default imagechange triggers where the buildconfig's // "from" is not an ImageStreamTag, because those triggers // will never be invoked. imageRef := buildutil.GetImageStreamForStrategy(out.Spec.Strategy) hasIST := imageRef != nil && imageRef.Kind == "ImageStreamTag" for _, trigger := range out.Spec.Triggers { if trigger.Type != newer.ImageChangeBuildTriggerType { newTriggers = append(newTriggers, trigger) continue } if (trigger.ImageChange == nil || trigger.ImageChange.From == nil) && !hasIST { continue } newTriggers = append(newTriggers, trigger) } out.Spec.Triggers = newTriggers return nil }
// generateBuildFromConfig generates a build definition based on the current imageid // from any ImageStream that is associated to the BuildConfig by From reference in // the Strategy, or uses the Image field of the Strategy. If binary is provided, override // the current build strategy with a binary artifact for this specific build. // Takes a BuildConfig to base the build on, and an optional SourceRevision to build. func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi.BuildConfig, revision *buildapi.SourceRevision, binary *buildapi.BinaryBuildSource) (*buildapi.Build, error) { serviceAccount := bc.Spec.ServiceAccount if len(serviceAccount) == 0 { serviceAccount = g.DefaultServiceAccountName } if len(serviceAccount) == 0 { serviceAccount = bootstrappolicy.BuilderServiceAccountName } // Need to copy the buildConfig here so that it doesn't share pointers with // the build object which could be (will be) modified later. obj, _ := kapi.Scheme.Copy(bc) bcCopy := obj.(*buildapi.BuildConfig) build := &buildapi.Build{ Spec: buildapi.BuildSpec{ ServiceAccount: serviceAccount, Source: bcCopy.Spec.Source, Strategy: bcCopy.Spec.Strategy, Output: bcCopy.Spec.Output, Revision: revision, Resources: bcCopy.Spec.Resources, PostCommit: bcCopy.Spec.PostCommit, CompletionDeadlineSeconds: bcCopy.Spec.CompletionDeadlineSeconds, }, ObjectMeta: kapi.ObjectMeta{ Labels: bcCopy.Labels, }, Status: buildapi.BuildStatus{ Phase: buildapi.BuildPhaseNew, Config: &kapi.ObjectReference{ Kind: "BuildConfig", Name: bc.Name, Namespace: bc.Namespace, }, }, } if binary != nil { build.Spec.Source.Git = nil build.Spec.Source.Binary = binary if build.Spec.Source.Dockerfile != nil && binary.AsFile == "Dockerfile" { build.Spec.Source.Dockerfile = nil } } build.Name = getNextBuildName(bc) if build.Annotations == nil { build.Annotations = make(map[string]string) } build.Annotations[buildapi.BuildNumberAnnotation] = strconv.Itoa(bc.Status.LastVersion) if build.Labels == nil { build.Labels = make(map[string]string) } build.Labels[buildapi.BuildConfigLabelDeprecated] = bcCopy.Name build.Labels[buildapi.BuildConfigLabel] = bcCopy.Name builderSecrets, err := g.FetchServiceAccountSecrets(bc.Namespace, serviceAccount) if err != nil { return nil, err } if build.Spec.Output.PushSecret == nil { build.Spec.Output.PushSecret = g.resolveImageSecret(ctx, builderSecrets, build.Spec.Output.To, bc.Namespace) } strategyImageChangeTrigger := getStrategyImageChangeTrigger(bc) // Resolve image source if present for i, sourceImage := range build.Spec.Source.Images { if sourceImage.PullSecret == nil { sourceImage.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &sourceImage.From, bc.Namespace) } var sourceImageSpec string // if the imagesource matches the strategy from, and we have a trigger for the strategy from, // use the imageid from the trigger rather than resolving it. if strategyFrom := buildutil.GetImageStreamForStrategy(bc.Spec.Strategy); reflect.DeepEqual(sourceImage.From, *strategyFrom) && strategyImageChangeTrigger != nil { sourceImageSpec = strategyImageChangeTrigger.LastTriggeredImageID } else { refImageChangeTrigger := getImageChangeTriggerForRef(bc, &sourceImage.From) // if there is no trigger associated with this imagesource, resolve the imagesource reference now. // otherwise use the imageid from the imagesource trigger. if refImageChangeTrigger == nil { sourceImageSpec, err = g.resolveImageStreamReference(ctx, sourceImage.From, bc.Namespace) if err != nil { return nil, err } } else { sourceImageSpec = refImageChangeTrigger.LastTriggeredImageID } } sourceImage.From.Kind = "DockerImage" sourceImage.From.Name = sourceImageSpec sourceImage.From.Namespace = "" build.Spec.Source.Images[i] = sourceImage } // If the Build is using a From reference instead of a resolved image, we need to resolve that From // reference to a valid image so we can run the build. Builds do not consume ImageStream references, // only image specs. var image string if strategyImageChangeTrigger != nil { image = strategyImageChangeTrigger.LastTriggeredImageID } switch { case build.Spec.Strategy.SourceStrategy != nil: if image == "" { image, err = g.resolveImageStreamReference(ctx, build.Spec.Strategy.SourceStrategy.From, build.Status.Config.Namespace) if err != nil { return nil, err } } build.Spec.Strategy.SourceStrategy.From = kapi.ObjectReference{ Kind: "DockerImage", Name: image, } if build.Spec.Strategy.SourceStrategy.PullSecret == nil { build.Spec.Strategy.SourceStrategy.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &build.Spec.Strategy.SourceStrategy.From, bc.Namespace) } case build.Spec.Strategy.DockerStrategy != nil && build.Spec.Strategy.DockerStrategy.From != nil: if image == "" { image, err = g.resolveImageStreamReference(ctx, *build.Spec.Strategy.DockerStrategy.From, build.Status.Config.Namespace) if err != nil { return nil, err } } build.Spec.Strategy.DockerStrategy.From = &kapi.ObjectReference{ Kind: "DockerImage", Name: image, } if build.Spec.Strategy.DockerStrategy.PullSecret == nil { build.Spec.Strategy.DockerStrategy.PullSecret = g.resolveImageSecret(ctx, builderSecrets, build.Spec.Strategy.DockerStrategy.From, bc.Namespace) } case build.Spec.Strategy.CustomStrategy != nil: if image == "" { image, err = g.resolveImageStreamReference(ctx, build.Spec.Strategy.CustomStrategy.From, build.Status.Config.Namespace) if err != nil { return nil, err } } build.Spec.Strategy.CustomStrategy.From = kapi.ObjectReference{ Kind: "DockerImage", Name: image, } if build.Spec.Strategy.CustomStrategy.PullSecret == nil { build.Spec.Strategy.CustomStrategy.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &build.Spec.Strategy.CustomStrategy.From, bc.Namespace) } updateCustomImageEnv(build.Spec.Strategy.CustomStrategy, image) } return build, nil }
func TestInstantiateWithImageTrigger(t *testing.T) { imageID := "the-image-id-12345" defaultTriggers := func() []buildapi.BuildTriggerPolicy { return []buildapi.BuildTriggerPolicy{ { Type: buildapi.GenericWebHookBuildTriggerType, }, { Type: buildapi.ImageChangeBuildTriggerType, ImageChange: &buildapi.ImageChangeTrigger{}, }, { Type: buildapi.ImageChangeBuildTriggerType, ImageChange: &buildapi.ImageChangeTrigger{ From: &kapi.ObjectReference{ Name: "image1:tag1", Kind: "ImageStreamTag", }, }, }, { Type: buildapi.ImageChangeBuildTriggerType, ImageChange: &buildapi.ImageChangeTrigger{ From: &kapi.ObjectReference{ Name: "image2:tag2", Namespace: "image2ns", Kind: "ImageStreamTag", }, }, }, } } triggersWithImageID := func() []buildapi.BuildTriggerPolicy { triggers := defaultTriggers() triggers[2].ImageChange.LastTriggeredImageID = imageID return triggers } tests := []struct { name string reqFrom *kapi.ObjectReference triggerIndex int // index of trigger that will be updated with the image id, if -1, no update expected triggers []buildapi.BuildTriggerPolicy errorExpected bool }{ { name: "default trigger", reqFrom: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "image3:tag3", }, triggerIndex: 1, triggers: defaultTriggers(), }, { name: "trigger with from", reqFrom: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "image1:tag1", }, triggerIndex: 2, triggers: defaultTriggers(), }, { name: "trigger with from and namespace", reqFrom: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "image2:tag2", Namespace: "image2ns", }, triggerIndex: 3, triggers: defaultTriggers(), }, { name: "existing image id", reqFrom: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "image1:tag1", }, triggers: triggersWithImageID(), errorExpected: true, }, } for _, tc := range tests { bc := &buildapi.BuildConfig{ Spec: buildapi.BuildConfigSpec{ BuildSpec: buildapi.BuildSpec{ Strategy: buildapi.BuildStrategy{ SourceStrategy: &buildapi.SourceBuildStrategy{ From: kapi.ObjectReference{ Name: "image3:tag3", Kind: "ImageStreamTag", }, }, }, }, Triggers: tc.triggers, }, } generator := mockBuildGeneratorForInstantiate() client := generator.Client.(Client) client.GetBuildConfigFunc = func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) { return bc, nil } client.UpdateBuildConfigFunc = func(ctx kapi.Context, buildConfig *buildapi.BuildConfig) error { bc = buildConfig return nil } generator.Client = client req := &buildapi.BuildRequest{ TriggeredByImage: &kapi.ObjectReference{ Kind: "DockerImage", Name: imageID, }, From: tc.reqFrom, } _, err := generator.Instantiate(kapi.NewDefaultContext(), req) if err != nil && !tc.errorExpected { t.Errorf("%s: unexpected error %v", tc.name, err) continue } if err == nil && tc.errorExpected { t.Errorf("%s: expected error but didn't get one", tc.name) continue } if tc.errorExpected { continue } for i := range bc.Spec.Triggers { if i == tc.triggerIndex { // Verify that the trigger got updated if bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID != imageID { t.Errorf("%s: expeccted trigger at index %d to contain imageID %s", tc.name, i, imageID) } continue } // Ensure that other triggers are updated with the latest docker image ref if bc.Spec.Triggers[i].Type == buildapi.ImageChangeBuildTriggerType { from := bc.Spec.Triggers[i].ImageChange.From if from == nil { from = buildutil.GetImageStreamForStrategy(bc.Spec.Strategy) } if bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID != ("ref@" + from.Name) { t.Errorf("%s: expected LastTriggeredImageID for trigger at %d to be %s. Got: %s", tc.name, i, "ref@"+from.Name, bc.Spec.Triggers[i].ImageChange.LastTriggeredImageID) } } } } }
// findStreamDeps accepts an image stream and a list of build // configurations and returns the dependency tree of the specified // image stream func findStreamDeps(stream, tag string, buildConfigList []buildapi.BuildConfig) (*Node, error) { root := &Node{ FullName: stream, Tags: []string{tag}, } namespace, name, err := split(stream) if err != nil { return nil, err } // Search all build configurations in order to find the image // streams depending on the specified image stream var childNamespace, childName, childTag string for _, cfg := range buildConfigList { for _, tr := range cfg.Spec.Triggers { from := buildutil.GetImageStreamForStrategy(cfg.Spec.Strategy) if from == nil || from.Kind != "ImageStreamTag" || tr.ImageChange == nil { continue } if cfg.Spec.Output.To == nil || len(cfg.Spec.Output.To.Name) == 0 { // build has no output image, so the chain ends here. continue } // Setup zeroed fields to their default values if from.Namespace == "" { from.Namespace = cfg.Namespace } fromTag := strings.Split(from.Name, ":")[1] parentStream := namespace + "/" + name + ":" + tag if buildutil.NameFromImageStream("", from, fromTag) == parentStream { if cfg.Spec.Output.To.Kind == "ImageStreamTag" { bits := strings.Split(cfg.Spec.Output.To.Name, ":") if len(bits) != 2 { return nil, fmt.Errorf("Invalid ImageStreamTag %s/%s does not contain a :tag", namespace, cfg.Spec.Output.To.Name) } childName = bits[0] childTag = bits[1] if cfg.Spec.Output.To.Namespace != "" { childNamespace = cfg.Spec.Output.To.Namespace } else { childNamespace = cfg.Namespace } } else { ref, err := imageapi.ParseDockerImageReference(cfg.Spec.Output.To.Name) if err != nil { return nil, err } childName = ref.Name childTag = ref.Tag childNamespace = cfg.Namespace } childStream := join(childNamespace, childName) // Build all children and their dependency trees recursively child, err := findStreamDeps(childStream, childTag, buildConfigList) if err != nil { return nil, err } // Add edge between root and child cfgFullName := join(cfg.Namespace, cfg.Name) root.Edges = append(root.Edges, NewEdge(cfgFullName, child.FullName)) // If the child depends on root via more than one tag, we have to make sure // that only one single instance of the child will make it into root.Children cont := false for _, stream := range root.Children { if stream.FullName == child.FullName { // Just pass the tag along and discard the current child stream.Tags = append(stream.Tags, child.Tags...) cont = true break } } if cont { // Do not append this child in root.Children. It's already in there continue } root.Children = append(root.Children, child) } } } return root, nil }
// HandleImageRepo processes the next ImageStream event. func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageStream) error { glog.V(4).Infof("Build image change controller detected ImageStream change %s", repo.Status.DockerImageRepository) // Loop through all build configurations and record if there was an error // instead of breaking the loop. The error will be returned in the end, so the // retry controller can retry. Any BuildConfigs that were processed successfully // should have had their LastTriggeredImageID updated, so the retry should result // in a no-op for them. hasError := false // TODO: this is inefficient for _, bc := range c.BuildConfigStore.List() { config := bc.(*buildapi.BuildConfig) var ( from *kapi.ObjectReference shouldBuild = false triggeredImage = "" ) // For every ImageChange trigger find the latest tagged image from the image repository and // invoke a build using that image id. A new build is triggered only if the latest tagged image id or pull spec // differs from the last triggered build recorded on the build config for that trigger for _, trigger := range config.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } if trigger.ImageChange.From != nil { from = trigger.ImageChange.From } else { from = buildutil.GetImageStreamForStrategy(config.Spec.Strategy) } if from == nil || from.Kind != "ImageStreamTag" { continue } fromStreamName, tag, ok := imageapi.SplitImageStreamTag(from.Name) if !ok { glog.Errorf("Invalid image stream tag: %s in build config %s/%s", from.Name, config.Name, config.Namespace) continue } fromNamespace := from.Namespace if len(fromNamespace) == 0 { fromNamespace = config.Namespace } // only trigger a build if this image repo matches the name and namespace of the ref in the build trigger // also do not trigger if the imagerepo does not have a valid DockerImageRepository value for us to pull // the image from if len(repo.Status.DockerImageRepository) == 0 || fromStreamName != repo.Name || fromNamespace != repo.Namespace { continue } // This split is safe because ImageStreamTag names always have the form // name:tag. latest := imageapi.LatestTaggedImage(repo, tag) if latest == nil { glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", repo.Namespace, repo.Name, tag) continue } glog.V(4).Infof("Found ImageStream %s/%s with tag %s", repo.Namespace, repo.Name, tag) // (must be different) to trigger a build last := trigger.ImageChange.LastTriggeredImageID next := latest.DockerImageReference if len(last) == 0 || (len(next) > 0 && next != last) { triggeredImage = next shouldBuild = true // it doesn't really make sense to have multiple image change triggers any more, // so just exit the loop now break } } if shouldBuild { glog.V(4).Infof("Running build for BuildConfig %s/%s", config.Namespace, config.Name) // instantiate new build request := &buildapi.BuildRequest{ ObjectMeta: kapi.ObjectMeta{ Name: config.Name, Namespace: config.Namespace, }, TriggeredByImage: &kapi.ObjectReference{ Kind: "DockerImage", Name: triggeredImage, }, From: from, } if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil { if kerrors.IsConflict(err) { util.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", config.Namespace, config.Name, err)) } else { util.HandleError(fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", config.Namespace, config.Name, err)) } hasError = true continue } } } if hasError { return fmt.Errorf("an error occurred processing 1 or more build configurations; the image change trigger for image stream %s will be retried", repo.Status.DockerImageRepository) } return nil }