// TestProcess_changeForUnregisteredTag ensures that an image update for which // there is a matching trigger results in a no-op due to the tag specified on // the trigger not matching the tags defined on the image stream. func TestProcess_changeForUnregisteredTag(t *testing.T) { config := deploytest.OkDeploymentConfig(0) stream := deploytest.OkStreamForConfig(config) // The image has been resolved at least once before. config.Spec.Triggers[0].ImageChangeParams.From.Name = imageapi.JoinImageStreamTag(stream.Name, "unrelatedtag") fake := &testclient.Fake{} fake.AddReactor("get", "imagestreams", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { return true, stream, nil }) image := config.Spec.Template.Spec.Containers[0].Image // verify no-op; should be the same for force=true and force=false if err := processTriggers(config, fake, false); err != nil { t.Fatalf("unexpected error: %v", err) } if image != config.Spec.Template.Spec.Containers[0].Image { t.Fatalf("unexpected image update: %#v", config.Spec.Template.Spec.Containers[0].Image) } if err := processTriggers(config, fake, true); err != nil { t.Fatalf("unexpected error when forced: %v", err) } if image != config.Spec.Template.Spec.Containers[0].Image { t.Fatalf("unexpected image update when forced: %#v", config.Spec.Template.Spec.Containers[0].Image) } }
// MakeImageStreamTagObjectMeta returns an ImageStreamTag that has enough information to join the graph, but it is not // based on a full IST object. This can be used to properly initialize the graph without having to retrieve all ISTs func MakeImageStreamTagObjectMeta(namespace, name, tag string) *imageapi.ImageStreamTag { return &imageapi.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{ Namespace: namespace, Name: imageapi.JoinImageStreamTag(name, tag), }, } }
// imageFor retrieves the most recent image for a tag in a given imageStreem. func (r *REST) imageFor(ctx kapi.Context, tag string, imageStream *api.ImageStream) (*api.Image, error) { event := api.LatestTaggedImage(imageStream, tag) if event == nil || len(event.Image) == 0 { return nil, kapierrors.NewNotFound("imageStreamTag", api.JoinImageStreamTag(imageStream.Name, tag)) } return r.imageRegistry.GetImage(ctx, event.Image) }
func convert_api_BuildOutput_To_v1_BuildOutput(in *newer.BuildOutput, out *BuildOutput, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.To != nil && (len(in.To.Kind) == 0 || in.To.Kind == "ImageStream") { out.To.Kind = "ImageStreamTag" out.To.Name = imageapi.JoinImageStreamTag(in.To.Name, in.Tag) return nil } if len(in.DockerImageReference) != 0 { out.To = &kapi_v1.ObjectReference{ Kind: "DockerImage", Name: imageapi.JoinImageStreamTag(in.DockerImageReference, in.Tag), } } return nil }
func (c *FakeImageStreamTags) Get(name, tag string) (*imageapi.ImageStreamTag, error) { obj, err := c.Fake.Invokes(ktestclient.NewGetAction("imagestreamtags", c.Namespace, imageapi.JoinImageStreamTag(name, tag)), &imageapi.ImageStreamTag{}) if obj == nil { return nil, err } return obj.(*imageapi.ImageStreamTag), err }
func Convert_v1_BuildOutput_To_api_BuildOutput(in *BuildOutput, out *newer.BuildOutput, s conversion.Scope) error { if err := autoConvert_v1_BuildOutput_To_api_BuildOutput(in, out, s); err != nil { return err } if in.To != nil && (in.To.Kind == "ImageStream" || len(in.To.Kind) == 0) { out.To.Kind = "ImageStreamTag" out.To.Name = imageapi.JoinImageStreamTag(in.To.Name, "") } return nil }
func convert_api_CustomBuildStrategy_To_v1_CustomBuildStrategy(in *newer.CustomBuildStrategy, out *CustomBuildStrategy, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.From != nil && in.From.Kind == "ImageStream" { out.From.Kind = "ImageStreamTag" out.From.Name = imageapi.JoinImageStreamTag(in.From.Name, "") } return nil }
func convert_v1beta3_BuildOutput_To_api_BuildOutput(in *BuildOutput, out *newer.BuildOutput, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.To != nil && (in.To.Kind == "ImageStream" || len(in.To.Kind) == 0) { out.To.Kind = "ImageStreamTag" out.To.Name = imageapi.JoinImageStreamTag(in.To.Name, "") } return nil }
func OkImageChangeDetails() *deployapi.DeploymentDetails { return &deployapi.DeploymentDetails{ Causes: []deployapi.DeploymentCause{{ Type: deployapi.DeploymentTriggerOnImageChange, ImageTrigger: &deployapi.DeploymentCauseImageTrigger{ From: kapi.ObjectReference{ Name: imageapi.JoinImageStreamTag(ImageStreamName, imageapi.DefaultImageTag), Kind: "ImageStreamTag", }}}}} }
func convert_v1beta3_CustomBuildStrategy_To_api_CustomBuildStrategy(in *CustomBuildStrategy, out *newer.CustomBuildStrategy, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } switch in.From.Kind { case "ImageStream": out.From.Kind = "ImageStreamTag" out.From.Name = imageapi.JoinImageStreamTag(in.From.Name, "") } return nil }
func Convert_v1_CustomBuildStrategy_To_api_CustomBuildStrategy(in *CustomBuildStrategy, out *newer.CustomBuildStrategy, s conversion.Scope) error { if err := autoConvert_v1_CustomBuildStrategy_To_api_CustomBuildStrategy(in, out, s); err != nil { return err } switch in.From.Kind { case "ImageStream": out.From.Kind = "ImageStreamTag" out.From.Name = imageapi.JoinImageStreamTag(in.From.Name, "") } return nil }
// ObjectReference returns an object reference from the image reference func (r *ImageRef) ObjectReference() kapi.ObjectReference { switch { case r.Stream != nil: return kapi.ObjectReference{ Kind: "ImageStreamTag", Name: imageapi.JoinImageStreamTag(r.Stream.Name, r.Tag), Namespace: r.Stream.Namespace, } case r.AsImageStream: return kapi.ObjectReference{ Kind: "ImageStreamTag", Name: imageapi.JoinImageStreamTag(r.Name, r.Tag), } default: return kapi.ObjectReference{ Kind: "DockerImage", Name: r.String(), } } }
func newISTag(tag string, imageStream *api.ImageStream, image *api.Image) (*api.ImageStreamTag, error) { istagName := api.JoinImageStreamTag(imageStream.Name, tag) event := api.LatestTaggedImage(imageStream, tag) if event == nil || len(event.Image) == 0 { return nil, kapierrors.NewNotFound("imageStreamTag", istagName) } ist := &api.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{ Namespace: imageStream.Namespace, Name: istagName, CreationTimestamp: event.Created, Annotations: map[string]string{}, ResourceVersion: imageStream.ResourceVersion, }, } // if the imageStream has Spec.Tags[tag].Annotations[k] = v, copy it to the image's annotations // and add them to the istag's annotations if imageStream.Spec.Tags != nil { if tagRef, ok := imageStream.Spec.Tags[tag]; ok { if image != nil && image.Annotations == nil { image.Annotations = make(map[string]string) } for k, v := range tagRef.Annotations { ist.Annotations[k] = v if image != nil { image.Annotations[k] = v } } } } if image != nil { imageWithMetadata, err := api.ImageWithMetadata(*image) if err != nil { return nil, err } ist.Image = *imageWithMetadata } else { ist.Image = api.Image{} ist.Image.Name = event.Image } // Replace the DockerImageReference with the value from event, which contains // real value from status. This should fix the problem for v1 registries, // where mutliple tags point to a single id and only the first image's metadata // is saved. This in turn will always return the pull spec from the first // imported image, which might be different than the requested tag. ist.Image.DockerImageReference = event.DockerImageReference return ist, nil }
// ObjectReference returns an object reference to this ref (as it would exist during generation) func (r *ImageRef) ObjectReference() kapi.ObjectReference { switch { case r.Stream != nil: return kapi.ObjectReference{ Kind: "ImageStreamTag", Name: imageapi.JoinImageStreamTag(r.Stream.Name, r.Reference.Tag), Namespace: r.Stream.Namespace, } case r.AsImageStream: name, _ := r.SuggestName() return kapi.ObjectReference{ Kind: "ImageStreamTag", Name: imageapi.JoinImageStreamTag(name, r.InternalTag()), } default: return kapi.ObjectReference{ Kind: "DockerImage", Name: r.PullSpec(), } } }
// EnsureAllImageStreamTagNodes creates all the ImageStreamTagNodes that are guaranteed to be present based on the ImageStream. // This is different than inferring the presence of an object, since the IST is an object derived from a join between the ImageStream // and the Image it references. func EnsureAllImageStreamTagNodes(g osgraph.MutableUniqueGraph, is *imageapi.ImageStream) []*ImageStreamTagNode { ret := []*ImageStreamTagNode{} for tag := range is.Status.Tags { ist := &imageapi.ImageStreamTag{} ist.Namespace = is.Namespace ist.Name = imageapi.JoinImageStreamTag(is.Name, tag) istNode := EnsureImageStreamTagNode(g, ist) ret = append(ret, istNode) } return ret }
func OkImageChangeTrigger() deployapi.DeploymentTriggerPolicy { return deployapi.DeploymentTriggerPolicy{ Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{ "container1", }, From: kapi.ObjectReference{ Kind: "ImageStreamTag", Name: imageapi.JoinImageStreamTag(ImageStreamName, imageapi.DefaultImageTag), }, }, } }
func Convert_api_DeploymentTriggerImageChangeParams_To_v1beta3_DeploymentTriggerImageChangeParams(in *newer.DeploymentTriggerImageChangeParams, out *DeploymentTriggerImageChangeParams, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } switch in.From.Kind { case "ImageStreamTag": case "ImageStream", "ImageRepository": out.From.Kind = "ImageStreamTag" if !strings.Contains(out.From.Name, ":") { out.From.Name = imageapi.JoinImageStreamTag(out.From.Name, imageapi.DefaultImageTag) } default: // Will be handled by validation } return nil }
// GetImageReferenceForObjectReference returns corresponding image reference for the given object // reference representing either an image stream image or image stream tag or docker image. func GetImageReferenceForObjectReference(namespace string, objRef *kapi.ObjectReference) (string, error) { switch objRef.Kind { case "ImageStreamImage", "DockerImage": res, err := imageapi.ParseDockerImageReference(objRef.Name) if err != nil { return "", err } if objRef.Kind == "ImageStreamImage" { if res.Namespace == "" { res.Namespace = objRef.Namespace } if res.Namespace == "" { res.Namespace = namespace } if len(res.ID) == 0 { return "", fmt.Errorf("missing id in ImageStreamImage reference %q", objRef.Name) } } else { // objRef.Kind == "DockerImage" res = res.DockerClientDefaults() } // docker image reference return res.DaemonMinimal().Exact(), nil case "ImageStreamTag": isName, tag, err := imageapi.ParseImageStreamTagName(objRef.Name) if err != nil { return "", err } ns := namespace if len(objRef.Namespace) > 0 { ns = objRef.Namespace } // <namespace>/<isname>:<tag> return cache.MetaNamespaceKeyFunc(&kapi.ObjectMeta{ Namespace: ns, Name: imageapi.JoinImageStreamTag(isName, tag), }) } return "", fmt.Errorf("unsupported object reference kind %s", objRef.Kind) }
func convert_v1_CustomBuildStrategy_To_api_CustomBuildStrategy(in *CustomBuildStrategy, out *newer.CustomBuildStrategy, s conversion.Scope) error { if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } if in.From != nil { switch in.From.Kind { case "ImageStream": out.From.Kind = "ImageStreamTag" out.From.Name = imageapi.JoinImageStreamTag(in.From.Name, "") case "ImageStreamTag": _, _, ok := imageapi.SplitImageStreamTag(in.From.Name) if !ok { return fmt.Errorf("ImageStreamTag object references must be in the form <name>:<tag>: %s", in.From.Name) } } } return nil }
// BuildOutput returns the BuildOutput of an image reference func (r *ImageRef) BuildOutput() (*buildapi.BuildOutput, error) { if r == nil { return &buildapi.BuildOutput{}, nil } imageRepo, err := r.ImageStream() if err != nil { return nil, err } kind := "ImageStreamTag" if !r.AsImageStream { kind = "DockerImage" } return &buildapi.BuildOutput{ To: &kapi.ObjectReference{ Kind: kind, Name: imageapi.JoinImageStreamTag(imageRepo.Name, r.Tag), }, }, nil }
func Convert_v1_DeploymentTriggerImageChangeParams_To_api_DeploymentTriggerImageChangeParams(in *DeploymentTriggerImageChangeParams, out *newer.DeploymentTriggerImageChangeParams, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*DeploymentTriggerImageChangeParams))(in) } if err := s.DefaultConvert(in, out, conversion.IgnoreMissingFields); err != nil { return err } switch in.From.Kind { case "ImageStreamTag": case "ImageStream", "ImageRepository": out.From.Kind = "ImageStreamTag" if !strings.Contains(out.From.Name, ":") { out.From.Name = imageapi.JoinImageStreamTag(out.From.Name, imageapi.DefaultImageTag) } default: // Will be handled by validation } return nil }
// TestHandle_matchScenarios comprehensively tests trigger definitions against // image stream updates to ensure that the image change triggers match (or don't // match) properly. func TestHandle_matchScenarios(t *testing.T) { tests := []struct { name string param *deployapi.DeploymentTriggerImageChangeParams matches bool }{ { name: "automatic=true, initial trigger", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, matches: true, }, { name: "automatic=false, initial trigger", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: false, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, matches: true, }, { name: "(no-op) automatic=false, already triggered", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: false, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: testapi.DockerImageReference, }, matches: false, }, { name: "(no-op) automatic=true, image is already deployed", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: testapi.DockerImageReference, }, matches: false, }, { name: "(no-op) trigger doesn't match the stream", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("other-stream", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, matches: false, }, } for _, test := range tests { updated := false fake := &testclient.Fake{} fake.AddReactor("update", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { if !test.matches { t.Fatal("unexpected deploymentconfig update") } updated = true return true, nil, nil }) controller := NewImageChangeController(dcInformer, streamInformer, fake) config := testapi.OkDeploymentConfig(1) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{{Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: test.param}} controller.dcLister.Add(config) t.Logf("running test %q", test.name) stream := makeStream(testapi.ImageStreamName, imageapi.DefaultImageTag, testapi.DockerImageReference, testapi.ImageID) if err := controller.Handle(stream); err != nil { t.Fatalf("unexpected error: %v", err) } // assert updates occurred if test.matches && !updated { t.Fatal("expected an update") } } }
// newISTag initializes an image stream tag from an image stream and image. The allowEmptyEvent will create a tag even // in the event that the status tag does does not exist yet (no image has successfully been tagged) or the image is nil. func newISTag(tag string, imageStream *api.ImageStream, image *api.Image, allowEmptyEvent bool) (*api.ImageStreamTag, error) { istagName := api.JoinImageStreamTag(imageStream.Name, tag) event := api.LatestTaggedImage(imageStream, tag) if event == nil || len(event.Image) == 0 { if !allowEmptyEvent { return nil, kapierrors.NewNotFound(api.Resource("imagestreamtags"), istagName) } event = &api.TagEvent{ Created: imageStream.CreationTimestamp, } } ist := &api.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{ Namespace: imageStream.Namespace, Name: istagName, CreationTimestamp: event.Created, Annotations: map[string]string{}, ResourceVersion: imageStream.ResourceVersion, }, Generation: event.Generation, Conditions: imageStream.Status.Tags[tag].Conditions, } if imageStream.Spec.Tags != nil { if tagRef, ok := imageStream.Spec.Tags[tag]; ok { // copy the spec tag ist.Tag = &tagRef if from := ist.Tag.From; from != nil { copied := *from ist.Tag.From = &copied } if gen := ist.Tag.Generation; gen != nil { copied := *gen ist.Tag.Generation = &copied } // if the imageStream has Spec.Tags[tag].Annotations[k] = v, copy it to the image's annotations // and add them to the istag's annotations if image != nil && image.Annotations == nil { image.Annotations = make(map[string]string) } for k, v := range tagRef.Annotations { ist.Annotations[k] = v if image != nil { image.Annotations[k] = v } } } } if image != nil { if err := api.ImageWithMetadata(image); err != nil { return nil, err } image.DockerImageManifest = "" ist.Image = *image } else { ist.Image = api.Image{} ist.Image.Name = event.Image } // Replace the DockerImageReference with the value from event, which contains // real value from status. This should fix the problem for v1 registries, // where mutliple tags point to a single id and only the first image's metadata // is saved. This in turn will always return the pull spec from the first // imported image, which might be different than the requested tag. ist.Image.DockerImageReference = event.DockerImageReference return ist, nil }
// Generate returns a potential future DeploymentConfig based on the DeploymentConfig specified // by namespace and name. Returns a RESTful error. func (g *DeploymentConfigGenerator) Generate(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { config, err := g.Client.GetDeploymentConfig(ctx, name) if err != nil { return nil, err } // Update the containers with new images based on defined triggers configChanged := false errs := field.ErrorList{} causes := []deployapi.DeploymentCause{} for i, trigger := range config.Spec.Triggers { params := trigger.ImageChangeParams // Only process image change triggers if trigger.Type != deployapi.DeploymentTriggerOnImageChange { continue } name, tag, ok := imageapi.SplitImageStreamTag(params.From.Name) if !ok { f := field.NewPath("triggers").Index(i).Child("imageChange", "from") errs = append(errs, field.Invalid(f, name, err.Error())) continue } // Find the image repo referred to by the trigger params imageStream, err := g.findImageStream(config, params) if err != nil { f := field.NewPath("triggers").Index(i).Child("imageChange", "from") errs = append(errs, field.Invalid(f, name, err.Error())) continue } // Find the latest tag event for the trigger tag latestEvent := imageapi.LatestTaggedImage(imageStream, tag) if latestEvent == nil { f := field.NewPath("triggers").Index(i).Child("imageChange", "tag") errs = append(errs, field.Invalid(f, tag, fmt.Sprintf("no image recorded for %s/%s:%s", imageStream.Namespace, imageStream.Name, tag))) continue } // Update containers template := config.Spec.Template names := sets.NewString(params.ContainerNames...) containerChanged := false for i := range template.Spec.Containers { container := &template.Spec.Containers[i] if !names.Has(container.Name) { continue } if len(latestEvent.DockerImageReference) > 0 && container.Image != latestEvent.DockerImageReference { // Update the image container.Image = latestEvent.DockerImageReference // Log the last triggered image ID params.LastTriggeredImage = latestEvent.DockerImageReference containerChanged = true } } // If any container was updated, create a cause for the change if containerChanged { configChanged = true causes = append(causes, deployapi.DeploymentCause{ Type: deployapi.DeploymentTriggerOnImageChange, ImageTrigger: &deployapi.DeploymentCauseImageTrigger{ From: kapi.ObjectReference{ Name: imageapi.JoinImageStreamTag(imageStream.Name, tag), Kind: "ImageStreamTag", }, }, }) } } if len(errs) > 0 { return nil, errors.NewInvalid(deployapi.Kind("DeploymentConfig"), config.Name, errs) } // Bump the version if we updated containers or if this is an initial // deployment if configChanged || config.Status.LatestVersion == 0 { config.Status.Details = &deployapi.DeploymentDetails{ Causes: causes, } config.Status.LatestVersion++ } return config, nil }
// TestProcess_matchScenarios comprehensively tests trigger definitions against // image stream updates to ensure that the image change triggers match (or don't // match) properly. func TestProcess_matchScenarios(t *testing.T) { tests := []struct { name string param *deployapi.DeploymentTriggerImageChangeParams notFound bool expected bool }{ { name: "automatic=true, initial trigger, explicit namespace", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, expected: true, }, { name: "automatic=true, initial trigger, implicit namespace", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, expected: true, }, { name: "automatic=false, initial trigger", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: false, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, expected: false, }, { name: "(no-op) automatic=false, already triggered", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: false, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: deploytest.DockerImageReference, }, expected: false, }, { name: "(no-op) automatic=true, image is already deployed", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)}, LastTriggeredImage: deploytest.DockerImageReference, }, expected: false, }, { name: "(no-op) trigger doesn't match the stream", param: &deployapi.DeploymentTriggerImageChangeParams{ Automatic: true, ContainerNames: []string{"container1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("other-stream", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, notFound: true, expected: false, }, } for i := range tests { test := tests[i] t.Logf("running test %q", test.name) fake := &testclient.Fake{} fake.AddReactor("get", "imagestreams", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { if test.notFound { name := action.(ktestclient.GetAction).GetName() return true, nil, errors.NewNotFound(imageapi.Resource("ImageStream"), name) } stream := fakeStream(deploytest.ImageStreamName, imageapi.DefaultImageTag, deploytest.DockerImageReference, deploytest.ImageID) return true, stream, nil }) config := deploytest.OkDeploymentConfig(1) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{ { Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: test.param, }, } image := config.Spec.Template.Spec.Containers[0].Image err := processTriggers(config, fake, false) if err != nil { t.Errorf("unexpected error: %v", err) continue } if test.expected && config.Spec.Template.Spec.Containers[0].Image == image { t.Errorf("%s: expected an image update but got none", test.name) } else if !test.expected && config.Spec.Template.Spec.Containers[0].Image != image { t.Errorf("%s: didn't expect an image update but got %s", test.name, image) } } }
func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item runtime.Object, seed int64) runtime.Object { f := apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)) f.Funcs( // Roles and RoleBindings maps are never nil func(j *authorizationapi.Policy, c fuzz.Continue) { c.FuzzNoCustom(j) if j.Roles != nil { j.Roles = make(map[string]*authorizationapi.Role) } for k, v := range j.Roles { if v == nil { delete(j.Roles, k) } } }, func(j *authorizationapi.PolicyBinding, c fuzz.Continue) { c.FuzzNoCustom(j) if j.RoleBindings == nil { j.RoleBindings = make(map[string]*authorizationapi.RoleBinding) } for k, v := range j.RoleBindings { if v == nil { delete(j.RoleBindings, k) } } }, func(j *authorizationapi.ClusterPolicy, c fuzz.Continue) { c.FuzzNoCustom(j) if j.Roles == nil { j.Roles = make(map[string]*authorizationapi.ClusterRole) } for k, v := range j.Roles { if v == nil { delete(j.Roles, k) } } }, func(j *authorizationapi.ClusterPolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.ClusterRoleBinding) }, func(j *authorizationapi.RoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if len(uservalidation.ValidateUserName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if len(uservalidation.ValidateGroupName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if len(validation.ValidateNamespaceName(j.Subjects[i].Namespace, false)) != 0 { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if len(validation.ValidateServiceAccountName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *authorizationapi.PolicyRule, c fuzz.Continue) { c.FuzzNoCustom(j) // if no groups are found, then we assume "". This matches defaulting if len(j.APIGroups) == 0 { j.APIGroups = []string{""} } switch c.Intn(3) { case 0: j.AttributeRestrictions = &authorizationapi.IsPersonalSubjectAccessReview{} case 1: j.AttributeRestrictions = &runtime.Unknown{TypeMeta: runtime.TypeMeta{Kind: "Type", APIVersion: "other"}, ContentType: "application/json", Raw: []byte(`{"apiVersion":"other","kind":"Type"}`)} default: j.AttributeRestrictions = nil } }, func(j *authorizationapi.ClusterRoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if len(uservalidation.ValidateUserName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if len(uservalidation.ValidateGroupName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if len(validation.ValidateNamespaceName(j.Subjects[i].Namespace, false)) != 0 { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if len(validation.ValidateServiceAccountName(j.Subjects[i].Name, false)) != 0 { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *template.Template, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.Parameters) // TODO: replace with structured type definition j.Objects = []runtime.Object{} }, func(j *image.Image, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.DockerImageMetadata) c.Fuzz(&j.Signatures) j.DockerImageMetadata.APIVersion = "" j.DockerImageMetadata.Kind = "" j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)] j.DockerImageReference = c.RandString() }, func(j *image.ImageSignature, c fuzz.Continue) { c.FuzzNoCustom(j) j.Conditions = nil j.ImageIdentity = "" j.SignedClaims = nil j.Created = nil j.IssuedBy = nil j.IssuedTo = nil }, func(j *image.ImageStreamMapping, c fuzz.Continue) { c.FuzzNoCustom(j) j.DockerImageRepository = "" }, func(j *image.ImageImportSpec, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To == nil { // To is defaulted to be not nil j.To = &kapi.LocalObjectReference{} } }, func(j *image.ImageStreamImage, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamImage, in order to round trip // successfully, the ImageStreamImage's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.ImageStreamSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } if j.Tags == nil { j.Tags = make(map[string]image.TagReference) } for k, v := range j.Tags { v.Name = k j.Tags[k] = v } }, func(j *image.ImageStreamStatus, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } }, func(j *image.ImageStreamTag, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamTag, in order to round trip // successfully, the ImageStreamTag's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.TagReference, c fuzz.Continue) { c.FuzzNoCustom(j) if j.From != nil { specs := []string{"", "ImageStreamTag", "ImageStreamImage"} j.From.Kind = specs[c.Intn(len(specs))] } }, func(j *build.BuildConfigSpec, c fuzz.Continue) { c.FuzzNoCustom(j) j.RunPolicy = build.BuildRunPolicySerial }, func(j *build.SourceBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.CustomBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.DockerBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.BuildOutput, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To != nil && (len(j.To.Kind) == 0 || j.To.Kind == "ImageStream") { j.To.Kind = "ImageStreamTag" } if j.To != nil && strings.Contains(j.To.Name, ":") { j.To.Name = strings.Replace(j.To.Name, ":", "-", -1) } }, func(j *route.RouteTargetReference, c fuzz.Continue) { c.FuzzNoCustom(j) j.Kind = "Service" j.Weight = new(int32) *j.Weight = 100 }, func(j *route.TLSConfig, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.Termination) == 0 && len(j.DestinationCACertificate) == 0 { j.Termination = route.TLSTerminationEdge } }, func(j *deploy.DeploymentConfig, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Triggers = []deploy.DeploymentTriggerPolicy{{Type: deploy.DeploymentTriggerOnConfigChange}} if j.Spec.Template != nil && len(j.Spec.Template.Spec.Containers) == 1 { containerName := j.Spec.Template.Spec.Containers[0].Name if p := j.Spec.Strategy.RecreateParams; p != nil { defaultHookContainerName(p.Pre, containerName) defaultHookContainerName(p.Mid, containerName) defaultHookContainerName(p.Post, containerName) } if p := j.Spec.Strategy.RollingParams; p != nil { defaultHookContainerName(p.Pre, containerName) defaultHookContainerName(p.Post, containerName) } } }, func(j *deploy.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.RecreateParams, j.RollingParams, j.CustomParams = nil, nil, nil strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] switch j.Type { case deploy.DeploymentStrategyTypeRecreate: params := &deploy.RecreateDeploymentStrategyParams{} c.Fuzz(params) if params.TimeoutSeconds == nil { s := int64(120) params.TimeoutSeconds = &s } j.RecreateParams = params case deploy.DeploymentStrategyTypeRolling: params := &deploy.RollingDeploymentStrategyParams{} randInt64 := func() *int64 { p := int64(c.RandUint64()) return &p } params.TimeoutSeconds = randInt64() params.IntervalSeconds = randInt64() params.UpdatePeriodSeconds = randInt64() policyTypes := []deploy.LifecycleHookFailurePolicy{ deploy.LifecycleHookFailurePolicyRetry, deploy.LifecycleHookFailurePolicyAbort, deploy.LifecycleHookFailurePolicyIgnore, } if c.RandBool() { params.Pre = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.Post = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.MaxUnavailable = intstr.FromInt(int(c.RandUint64())) params.MaxSurge = intstr.FromInt(int(c.RandUint64())) } else { params.MaxSurge = intstr.FromString(fmt.Sprintf("%d%%", c.RandUint64())) params.MaxUnavailable = intstr.FromString(fmt.Sprintf("%d%%", c.RandUint64())) } j.RollingParams = params } }, func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"", "a/b", "a/b/c", "a:5000/b/c", "a/b", "a/b"} tags := []string{"stuff", "other"} j.From.Name = specs[c.Intn(len(specs))] if len(j.From.Name) > 0 { j.From.Name = image.JoinImageStreamTag(j.From.Name, tags[c.Intn(len(tags))]) } }, func(j *deploy.DeploymentTriggerImageChangeParams, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"a/b", "a/b/c", "a:5000/b/c", "a/b:latest", "a/b@test"} j.From.Kind = "DockerImage" j.From.Name = specs[c.Intn(len(specs))] }, // TODO: uncomment when round tripping for init containers is available (the annotation is // not supported on security context review for now) func(j *securityapi.PodSecurityPolicyReview, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Template.Spec.InitContainers = nil for i := range j.Status.AllowedServiceAccounts { j.Status.AllowedServiceAccounts[i].Template.Spec.InitContainers = nil } }, func(j *securityapi.PodSecurityPolicySelfSubjectReview, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Template.Spec.InitContainers = nil j.Status.Template.Spec.InitContainers = nil }, func(j *securityapi.PodSecurityPolicySubjectReview, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Template.Spec.InitContainers = nil j.Status.Template.Spec.InitContainers = nil }, func(j *oauthapi.OAuthAuthorizeToken, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.CodeChallenge) > 0 && len(j.CodeChallengeMethod) == 0 { j.CodeChallengeMethod = "plain" } }, func(j *oauthapi.OAuthClientAuthorization, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.Scopes) == 0 { j.Scopes = append(j.Scopes, "user:full") } }, func(j *route.RouteSpec, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.WildcardPolicy) == 0 { j.WildcardPolicy = route.WildcardPolicyNone } }, func(j *route.RouteIngress, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.WildcardPolicy) == 0 { j.WildcardPolicy = route.WildcardPolicyNone } }, func(j *runtime.Object, c fuzz.Continue) { // runtime.EmbeddedObject causes a panic inside of fuzz because runtime.Object isn't handled. }, func(t *time.Time, c fuzz.Continue) { // This is necessary because the standard fuzzed time.Time object is // completely nil, but when JSON unmarshals dates it fills in the // unexported loc field with the time.UTC object, resulting in // reflect.DeepEqual returning false in the round trip tests. We solve it // by using a date that will be identical to the one JSON unmarshals. *t = time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC) }, func(u64 *uint64, c fuzz.Continue) { // TODO: uint64's are NOT handled right. *u64 = c.RandUint64() >> 8 }, ) f.Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, item) } j.SetKind("") j.SetAPIVersion("") return item }
func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object { f := apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)) f.Funcs( // Roles and RoleBindings maps are never nil func(j *authorizationapi.Policy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.Role) }, func(j *authorizationapi.PolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.RoleBinding) }, func(j *authorizationapi.ClusterPolicy, c fuzz.Continue) { j.Roles = make(map[string]*authorizationapi.ClusterRole) }, func(j *authorizationapi.ClusterPolicyBinding, c fuzz.Continue) { j.RoleBindings = make(map[string]*authorizationapi.ClusterRoleBinding) }, func(j *authorizationapi.RoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *authorizationapi.ClusterRoleBinding, c fuzz.Continue) { c.FuzzNoCustom(j) for i := range j.Subjects { kinds := []string{authorizationapi.UserKind, authorizationapi.SystemUserKind, authorizationapi.GroupKind, authorizationapi.SystemGroupKind, authorizationapi.ServiceAccountKind} j.Subjects[i].Kind = kinds[c.Intn(len(kinds))] switch j.Subjects[i].Kind { case authorizationapi.UserKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateUserName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validusername%d", i) } case authorizationapi.GroupKind: j.Subjects[i].Namespace = "" if valid, _ := uservalidation.ValidateGroupName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("validgroupname%d", i) } case authorizationapi.ServiceAccountKind: if valid, _ := validation.ValidateNamespaceName(j.Subjects[i].Namespace, false); !valid { j.Subjects[i].Namespace = fmt.Sprintf("sanamespacehere%d", i) } if valid, _ := validation.ValidateServiceAccountName(j.Subjects[i].Name, false); !valid { j.Subjects[i].Name = fmt.Sprintf("sanamehere%d", i) } case authorizationapi.SystemUserKind, authorizationapi.SystemGroupKind: j.Subjects[i].Namespace = "" j.Subjects[i].Name = ":" + j.Subjects[i].Name } j.Subjects[i].UID = types.UID("") j.Subjects[i].APIVersion = "" j.Subjects[i].ResourceVersion = "" j.Subjects[i].FieldPath = "" } }, func(j *template.Template, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.Parameters) // TODO: replace with structured type definition j.Objects = []runtime.Object{} }, func(j *image.Image, c fuzz.Continue) { c.Fuzz(&j.ObjectMeta) c.Fuzz(&j.DockerImageMetadata) j.DockerImageMetadata.APIVersion = "" j.DockerImageMetadata.Kind = "" j.DockerImageMetadataVersion = []string{"pre012", "1.0"}[c.Rand.Intn(2)] j.DockerImageReference = c.RandString() }, func(j *image.ImageStreamMapping, c fuzz.Continue) { c.FuzzNoCustom(j) j.DockerImageRepository = "" }, func(j *image.ImageImportSpec, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To == nil { // To is defaulted to be not nil j.To = &api.LocalObjectReference{} } }, func(j *image.ImageStreamImage, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamImage, in order to round trip // successfully, the ImageStreamImage's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.ImageStreamSpec, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } if j.Tags == nil { j.Tags = make(map[string]image.TagReference) } }, func(j *image.ImageStreamStatus, c fuzz.Continue) { c.FuzzNoCustom(j) // if the generated fuzz value has a tag or image id, strip it if strings.ContainsAny(j.DockerImageRepository, ":@") { j.DockerImageRepository = "" } }, func(j *image.ImageStreamTag, c fuzz.Continue) { c.Fuzz(&j.Image) // because we de-embedded Image from ImageStreamTag, in order to round trip // successfully, the ImageStreamTag's ObjectMeta must match the Image's. j.ObjectMeta = j.Image.ObjectMeta }, func(j *image.TagReference, c fuzz.Continue) { c.FuzzNoCustom(j) if j.From != nil { specs := []string{"", "ImageStreamTag", "ImageStreamImage"} j.From.Kind = specs[c.Intn(len(specs))] } }, func(j *build.SourceBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.CustomBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.DockerBuildStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) j.From.Kind = "ImageStreamTag" j.From.Name = "image:tag" j.From.APIVersion = "" j.From.ResourceVersion = "" j.From.FieldPath = "" }, func(j *build.BuildOutput, c fuzz.Continue) { c.FuzzNoCustom(j) if j.To != nil && (len(j.To.Kind) == 0 || j.To.Kind == "ImageStream") { j.To.Kind = "ImageStreamTag" } if j.To != nil && strings.Contains(j.To.Name, ":") { j.To.Name = strings.Replace(j.To.Name, ":", "-", -1) } }, func(j *route.RouteSpec, c fuzz.Continue) { c.FuzzNoCustom(j) j.To = api.ObjectReference{ Kind: "Service", Name: j.To.Name, } }, func(j *route.TLSConfig, c fuzz.Continue) { c.FuzzNoCustom(j) if len(j.Termination) == 0 && len(j.DestinationCACertificate) == 0 { j.Termination = route.TLSTerminationEdge } }, func(j *deploy.DeploymentConfig, c fuzz.Continue) { c.FuzzNoCustom(j) j.Spec.Triggers = []deploy.DeploymentTriggerPolicy{{Type: deploy.DeploymentTriggerOnConfigChange}} if forVersion == "v1beta3" { // v1beta3 does not contain the PodSecurityContext type. For this API version, only fuzz // the host namespace fields. The fields set to nil here are the other fields of the // PodSecurityContext that will not roundtrip correctly from internal->v1beta3->internal. j.Spec.Template.Spec.SecurityContext.SELinuxOptions = nil j.Spec.Template.Spec.SecurityContext.RunAsUser = nil j.Spec.Template.Spec.SecurityContext.RunAsNonRoot = nil j.Spec.Template.Spec.SecurityContext.SupplementalGroups = nil j.Spec.Template.Spec.SecurityContext.FSGroup = nil } }, func(j *deploy.DeploymentStrategy, c fuzz.Continue) { c.FuzzNoCustom(j) strategyTypes := []deploy.DeploymentStrategyType{deploy.DeploymentStrategyTypeRecreate, deploy.DeploymentStrategyTypeRolling, deploy.DeploymentStrategyTypeCustom} j.Type = strategyTypes[c.Rand.Intn(len(strategyTypes))] switch j.Type { case deploy.DeploymentStrategyTypeRolling: params := &deploy.RollingDeploymentStrategyParams{} randInt64 := func() *int64 { p := int64(c.RandUint64()) return &p } params.TimeoutSeconds = randInt64() params.IntervalSeconds = randInt64() params.UpdatePeriodSeconds = randInt64() policyTypes := []deploy.LifecycleHookFailurePolicy{ deploy.LifecycleHookFailurePolicyRetry, deploy.LifecycleHookFailurePolicyAbort, deploy.LifecycleHookFailurePolicyIgnore, } if c.RandBool() { params.Pre = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.Post = &deploy.LifecycleHook{ FailurePolicy: policyTypes[c.Rand.Intn(len(policyTypes))], ExecNewPod: &deploy.ExecNewPodHook{ ContainerName: c.RandString(), }, } } if c.RandBool() { params.MaxUnavailable = util.NewIntOrStringFromInt(int(c.RandUint64())) params.MaxSurge = util.NewIntOrStringFromInt(int(c.RandUint64())) } else { params.MaxSurge = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) params.MaxUnavailable = util.NewIntOrStringFromString(fmt.Sprintf("%d%%", c.RandUint64())) } j.RollingParams = params default: j.RollingParams = nil } }, func(j *deploy.DeploymentCauseImageTrigger, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"", "a/b", "a/b/c", "a:5000/b/c", "a/b", "a/b"} tags := []string{"stuff", "other"} j.From.Name = specs[c.Intn(len(specs))] if len(j.From.Name) > 0 { j.From.Name = image.JoinImageStreamTag(j.From.Name, tags[c.Intn(len(tags))]) } }, func(j *deploy.DeploymentTriggerImageChangeParams, c fuzz.Continue) { c.FuzzNoCustom(j) specs := []string{"a/b", "a/b/c", "a:5000/b/c", "a/b:latest", "a/b@test"} j.From.Kind = "DockerImage" j.From.Name = specs[c.Intn(len(specs))] }, func(j *runtime.EmbeddedObject, c fuzz.Continue) { // runtime.EmbeddedObject causes a panic inside of fuzz because runtime.Object isn't handled. }, func(t *time.Time, c fuzz.Continue) { // This is necessary because the standard fuzzed time.Time object is // completely nil, but when JSON unmarshals dates it fills in the // unexported loc field with the time.UTC object, resulting in // reflect.DeepEqual returning false in the round trip tests. We solve it // by using a date that will be identical to the one JSON unmarshals. *t = time.Date(2000, 1, 1, 1, 1, 1, 0, time.UTC) }, func(u64 *uint64, c fuzz.Continue) { // TODO: uint64's are NOT handled right. *u64 = c.RandUint64() >> 8 }, ) f.Fuzz(item) j, err := meta.TypeAccessor(item) if err != nil { t.Fatalf("Unexpected error %v for %#v", err, item) } j.SetKind("") j.SetAPIVersion("") return item }
// TestHandle_matchScenarios comprehensively tests trigger definitions against // image repo updates to ensure that the image change triggers match (or don't // match) properly. func TestHandle_matchScenarios(t *testing.T) { params := map[string]*deployapi.DeploymentTriggerImageChangeParams{ "params.1": { Automatic: true, ContainerNames: []string{"container-1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("repoA", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, "params.2": { Automatic: true, ContainerNames: []string{"container-1"}, From: kapi.ObjectReference{Name: imageapi.JoinImageStreamTag("repoA", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, "params.3": { Automatic: false, ContainerNames: []string{"container-1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("repoA", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, "params.4": { Automatic: true, ContainerNames: []string{"container-1"}, From: kapi.ObjectReference{Name: imageapi.JoinImageStreamTag("repoA", imageapi.DefaultImageTag)}, LastTriggeredImage: "registry:8080/openshift/test-image@sha256:00000000000000000000000000000001", }, "params.5": { Automatic: true, ContainerNames: []string{"container-1"}, From: kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("repoC", imageapi.DefaultImageTag)}, LastTriggeredImage: "", }, } tagHistoryFor := func(tag, dir, image string) map[string]imageapi.TagEventList { return map[string]imageapi.TagEventList{ tag: { Items: []imageapi.TagEvent{ { DockerImageReference: dir, Image: image, }, }, }, } } updates := map[string]*imageapi.ImageStream{ "update.1": { ObjectMeta: kapi.ObjectMeta{Name: "repoA", Namespace: kapi.NamespaceDefault}, Status: imageapi.ImageStreamStatus{ Tags: tagHistoryFor( imageapi.DefaultImageTag, "registry:8080/openshift/test-image@sha256:00000000000000000000000000000001", "00000000000000000000000000000001", ), }, }, } scenarios := []struct { param string repo string matches bool }{ // Update from empty last image ID to a new one with explicit namespaces {"params.1", "update.1", true}, // Update from empty last image ID to a new one with implicit namespaces {"params.2", "update.1", true}, // Update from empty last image ID to a new one, but not marked automatic {"params.3", "update.1", false}, // Updated image ID is equal to the last triggered ID {"params.4", "update.1", false}, // Trigger repo reference doesn't match {"params.5", "update.1", false}, } for _, s := range scenarios { config := deployapitest.OkDeploymentConfig(0) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{ { Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: params[s.param], }, } updated := false generated := false controller := &ImageChangeController{ deploymentConfigClient: &deploymentConfigClientImpl{ updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { if !s.matches { t.Fatalf("unexpected DeploymentConfig update for scenario: %v", s) } updated = true return config, nil }, generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { if !s.matches { t.Fatalf("unexpected generator call for scenario: %v", s) } generated = true // simulate a generation newConfig := deployapitest.OkDeploymentConfig(config.Status.LatestVersion + 1) newConfig.Namespace = config.Namespace newConfig.Spec.Triggers = config.Spec.Triggers return newConfig, nil }, listDeploymentConfigsFunc: func() ([]*deployapi.DeploymentConfig, error) { return []*deployapi.DeploymentConfig{config}, nil }, }, } t.Logf("running scenario: %v", s) err := controller.Handle(updates[s.repo]) if err != nil { t.Fatalf("unexpected error for scenario %v: %v", s, err) } // assert updates/generations occurred if s.matches && !updated { t.Fatalf("expected update for scenario: %v", s) } if s.matches && !generated { t.Fatalf("expected generation for scenario: %v", s) } } }
// Delete deletes the specified tag from the image stream. func (c *imageStreamTags) Delete(name, tag string) error { return c.r.Delete().Namespace(c.ns).Resource("imageStreamTags").Name(api.JoinImageStreamTag(name, tag)).Do().Error() }
// Get finds the specified image by name of an image stream and tag. func (c *imageStreamTags) Get(name, tag string) (result *api.ImageStreamTag, err error) { result = &api.ImageStreamTag{} err = c.r.Get().Namespace(c.ns).Resource("imageStreamTags").Name(api.JoinImageStreamTag(name, tag)).Do().Into(result) return }