// 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") buildFrom := buildutil.GetInputReference(config.Spec.Strategy) for i, trg := range config.Spec.Triggers { allErrs = append(allErrs, validateTrigger(&trg, buildFrom, triggersPath.Index(i))...) if trg.Type != buildapi.ImageChangeBuildTriggerType || trg.ImageChange == nil { continue } from := trg.ImageChange.From if from == nil { from = buildFrom } 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)...) 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.GetInputReference(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 }
// ImageStreamReferenceIndexFunc is a default index function that indexes based on image stream references. func ImageStreamReferenceIndexFunc(obj interface{}) ([]string, error) { switch t := obj.(type) { case *buildapi.BuildConfig: var keys []string for _, trigger := range t.Spec.Triggers { if trigger.Type != buildapi.ImageChangeBuildTriggerType { continue } from := trigger.ImageChange.From // We're indexing on the imagestream referenced by the imagechangetrigger, // however buildconfigs allow one "default" imagechangetrigger in which // the ICT does not explicitly indicate the image it is triggering on, // instead it triggers on the image being used as the builder/base image // as referenced in the build strategy, so if this is an ICT w/ no // explicit image reference, use the image referenced by the strategy // because this is the default ICT. if from == nil { from = buildutil.GetInputReference(t.Spec.Strategy) if from == nil || from.Kind != "ImageStreamTag" { continue } } name, _, _ := imageapi.SplitImageStreamTag(from.Name) namespace := from.Namespace // if the imagestream reference has no namespace, use the // namespace of the buildconfig. if len(namespace) == 0 { namespace = t.Namespace } keys = append(keys, namespace+"/"+name) } if len(keys) == 0 { // Return an empty key for configs that don't hold object references. keys = append(keys, "") } return keys, nil case *deployapi.DeploymentConfig: var keys []string for _, trigger := range t.Spec.Triggers { if trigger.Type != deployapi.DeploymentTriggerOnImageChange { continue } params := trigger.ImageChangeParams name, _, _ := imageapi.SplitImageStreamTag(params.From.Name) keys = append(keys, params.From.Namespace+"/"+name) } if len(keys) == 0 { // Return an empty key for configs that don't hold object references. keys = append(keys, "") } return keys, nil } return nil, fmt.Errorf("image stream reference index not implemented for %#v", obj) }
// 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.GetInputReference(node.BuildConfig.Spec.Strategy) if input := imageRefNode(g, inputImage, node.BuildConfig); input != nil { g.AddEdge(input, node, BuildInputImageEdgeKind) } }
// 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 i, obj := range objects { if glog.V(5) { json, _ := json.MarshalIndent(obj, "", "\t") glog.Infof("\n\nCycle check input object %v:\n%v\n", i, string(json)) } if bc, ok := obj.(*buildapi.BuildConfig); ok { input := buildutil.GetInputReference(bc.Spec.Strategy) output := bc.Spec.Output.To if output == nil || input == nil { return nil } dockerInput, err := c.followRefToDockerImage(input, nil, objects) if err != nil { glog.Warningf("Unable to check for circular build input: %v", err) return nil } glog.V(5).Infof("Post follow input:\n%#v\n", dockerInput) dockerOutput, err := c.followRefToDockerImage(output, nil, objects) if err != nil { glog.Warningf("Unable to check for circular build output: %v", err) return nil } glog.V(5).Infof("Post follow:\n%#v\n", dockerOutput) if dockerInput != nil && dockerOutput != nil { if reflect.DeepEqual(dockerInput, dockerOutput) { return app.CircularOutputReferenceError{Reference: fmt.Sprintf("%s", dockerInput.Name)} } } // If it is not possible to follow input and output out to DockerImages, // it is likely they are referencing newly created ImageStreams. Just // make sure they are not the same image stream. inCopy := *input outCopy := *output for _, ref := range []*kapi.ObjectReference{&inCopy, &outCopy} { // Some code paths add namespace and others don't. Make things // consistent. if len(ref.Namespace) == 0 { ref.Namespace = c.OriginNamespace } } if reflect.DeepEqual(inCopy, outCopy) { return app.CircularOutputReferenceError{Reference: fmt.Sprintf("%s/%s", inCopy.Namespace, inCopy.Name)} } } } return nil }
// strategyTrigger returns a synthetic ImageChangeTrigger that represents the image stream tag the build strategy // points to, or nil if no such strategy trigger is possible (if the build doesn't point to an ImageStreamTag). func strategyTrigger(config *buildapi.BuildConfig) *ImageChangeTrigger { if from := buildutil.GetInputReference(config.Spec.Strategy); from != nil { if from.Kind == "ImageStreamTag" { // normalize the strategy object reference from.Namespace = defaultNamespace(from.Namespace, config.Namespace) return &ImageChangeTrigger{From: from.Name, Namespace: from.Namespace} } } return nil }
// 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.GetInputReference(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.GetInputReference(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 }
// 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.GetInputReference(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 }
// 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) { from := buildutil.GetInputReference(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) }
// setBuildSourceImage set BuildSource Image item for new build func (g *BuildGenerator) setBuildSourceImage(ctx kapi.Context, builderSecrets []kapi.Secret, bcCopy *buildapi.BuildConfig, Source *buildapi.BuildSource) error { var err error strategyImageChangeTrigger := getStrategyImageChangeTrigger(bcCopy) for i, sourceImage := range Source.Images { if sourceImage.PullSecret == nil { sourceImage.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &sourceImage.From, bcCopy.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.GetInputReference(bcCopy.Spec.Strategy); strategyFrom != nil && reflect.DeepEqual(sourceImage.From, *strategyFrom) && strategyImageChangeTrigger != nil { sourceImageSpec = strategyImageChangeTrigger.LastTriggeredImageID } else { refImageChangeTrigger := getImageChangeTriggerForRef(bcCopy, &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, bcCopy.Namespace) if err != nil { return err } } else { sourceImageSpec = refImageChangeTrigger.LastTriggeredImageID } } sourceImage.From.Kind = "DockerImage" sourceImage.From.Name = sourceImageSpec sourceImage.From.Namespace = "" Source.Images[i] = sourceImage } return nil }
func Convert_v1_BuildConfig_To_api_BuildConfig(in *BuildConfig, out *newer.BuildConfig, s conversion.Scope) error { if err := autoConvert_v1_BuildConfig_To_api_BuildConfig(in, out, s); 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.GetInputReference(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 }
// HandleImageStream processes the next ImageStream event. func (c *ImageChangeController) HandleImageStream(stream *imageapi.ImageStream) error { glog.V(4).Infof("Build image change controller detected ImageStream change %s", stream.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 bcs, err := c.BuildConfigIndex.GetConfigsForImageStreamTrigger(stream.Namespace, stream.Name) if err != nil { return err } for _, config := range bcs { var ( from *kapi.ObjectReference shouldBuild = false triggeredImage = "" latest *imageapi.TagEvent ) // For every ImageChange trigger find the latest tagged image from the image stream 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.GetInputReference(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 stream matches the name and namespace of the stream 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(stream.Status.DockerImageRepository) == 0 || fromStreamName != stream.Name || fromNamespace != stream.Namespace { continue } // This split is safe because ImageStreamTag names always have the form // name:tag. latest = imageapi.LatestTaggedImage(stream, tag) if latest == nil { glog.V(4).Infof("unable to find tagged image: no image recorded for %s/%s:%s", stream.Namespace, stream.Name, tag) continue } glog.V(4).Infof("Found ImageStream %s/%s with tag %s", stream.Namespace, stream.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) buildCauses := []buildapi.BuildTriggerCause{} // instantiate new build request := &buildapi.BuildRequest{ ObjectMeta: kapi.ObjectMeta{ Name: config.Name, Namespace: config.Namespace, }, TriggeredBy: append(buildCauses, buildapi.BuildTriggerCause{ Message: "Image change", ImageChangeBuild: &buildapi.ImageChangeCause{ ImageID: latest.DockerImageReference, FromRef: from, }, }, ), TriggeredByImage: &kapi.ObjectReference{ Kind: "DockerImage", Name: triggeredImage, }, From: from, } if _, err := c.BuildConfigInstantiator.Instantiate(config.Namespace, request); err != nil { if kerrors.IsConflict(err) { utilruntime.HandleError(fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", config.Namespace, config.Name, err)) } else { utilruntime.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", stream.Status.DockerImageRepository) } 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{ CommonSpec: buildapi.CommonSpec{ 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 } } else { // must explicitly set this because we copied the source values from the buildconfig. build.Spec.Source.Binary = nil } build.Name = getNextBuildName(bc) if build.Annotations == nil { build.Annotations = make(map[string]string) } build.Annotations[buildapi.BuildNumberAnnotation] = strconv.FormatInt(bc.Status.LastVersion, 10) build.Annotations[buildapi.BuildConfigAnnotation] = bcCopy.Name if build.Labels == nil { build.Labels = make(map[string]string) } build.Labels[buildapi.BuildConfigLabelDeprecated] = buildapi.LabelValue(bcCopy.Name) build.Labels[buildapi.BuildConfigLabel] = buildapi.LabelValue(bcCopy.Name) build.Labels[buildapi.BuildRunPolicyLabel] = string(bc.Spec.RunPolicy) 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.GetInputReference(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.RuntimeImage != nil { runtimeImageName, err := g.resolveImageStreamReference(ctx, *build.Spec.Strategy.SourceStrategy.RuntimeImage, build.Status.Config.Namespace) if err != nil { return nil, err } build.Spec.Strategy.SourceStrategy.RuntimeImage = &kapi.ObjectReference{ Kind: "DockerImage", Name: runtimeImageName, } } if build.Spec.Strategy.SourceStrategy.PullSecret == nil { // we have 3 different variations: // 1) builder and runtime images use the same secret => use builder image secret // 2) builder and runtime images use different secrets => use builder image secret // 3) builder doesn't need a secret but runtime image requires it => use runtime image secret // The case when both of the images don't use secret (equals to nil) is covered by the first variant. pullSecret := g.resolveImageSecret(ctx, builderSecrets, &build.Spec.Strategy.SourceStrategy.From, bc.Namespace) if pullSecret == nil { pullSecret = g.resolveImageSecret(ctx, builderSecrets, build.Spec.Strategy.SourceStrategy.RuntimeImage, bc.Namespace) } build.Spec.Strategy.SourceStrategy.PullSecret = pullSecret } 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.GetInputReference(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) } } } } }