func agedStream(registry, namespace, name string, ageInMinutes int64, tags map[string]imageapi.TagEventList) imageapi.ImageStream { stream := imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: namespace, Name: name, }, Status: imageapi.ImageStreamStatus{ DockerImageRepository: fmt.Sprintf("%s/%s/%s", registry, namespace, name), Tags: tags, }, } if ageInMinutes >= 0 { stream.CreationTimestamp = unversioned.NewTime(unversioned.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute)) } return stream }
// dockerImageRepository determines the docker image stream for stream. // If stream.DockerImageRepository is set, that value is returned. Otherwise, // if a default registry exists, the value returned is of the form // <default registry>/<namespace>/<stream name>. func (s Strategy) dockerImageRepository(stream *api.ImageStream) string { registry, ok := s.defaultRegistry.DefaultRegistry() if !ok { return stream.Spec.DockerImageRepository } if len(stream.Namespace) == 0 { stream.Namespace = kapi.NamespaceDefault } ref := api.DockerImageReference{ Registry: registry, Namespace: stream.Namespace, Name: stream.Name, } return ref.String() }
// done marks the stream as being processed due to an error or failure condition. func (c *ImportController) done(stream *api.ImageStream, reason string) error { if len(reason) == 0 { reason = unversioned.Now().UTC().Format(time.RFC3339) } else if len(reason) > 300 { // cut down the reason up to 300 characters max. reason = reason[:300] } if stream.Annotations == nil { stream.Annotations = make(map[string]string) } stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = reason if _, err := c.streams.ImageStreams(stream.Namespace).Update(stream); err != nil { return err } return nil }
// done marks the stream as being processed due to an error or failure condition func (c *ImportController) done(stream *api.ImageStream, reason string, retry int) error { if len(reason) == 0 { reason = util.Now().UTC().Format(time.RFC3339) } if stream.Annotations == nil { stream.Annotations = make(map[string]string) } stream.Annotations[api.DockerImageRepositoryCheckAnnotation] = reason if _, err := c.streams.ImageStreams(stream.Namespace).Update(stream); err != nil && !errors.IsNotFound(err) { if errors.IsConflict(err) && retry > 0 { if stream, err := c.streams.ImageStreams(stream.Namespace).Get(stream.Name); err == nil { return c.done(stream, reason, retry-1) } } return err } return nil }
func TestImageStreamAdmissionEvaluatorUsage(t *testing.T) { for _, tc := range []struct { name string oldSpec *imageapi.ImageStreamSpec oldStatus *imageapi.ImageStreamStatus newSpec *imageapi.ImageStreamSpec newStatus *imageapi.ImageStreamStatus expectedImages int64 }{ { name: "empty image stream", oldStatus: nil, newStatus: &imageapi.ImageStreamStatus{}, expectedImages: 0, }, { name: "new image stream with one image", oldStatus: nil, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, expectedImages: 1, }, { name: "no change", oldSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", miscImageDigest), }, }, }, }, oldStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", miscImageDigest), }, }, }, }, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, // misc image is already present in common is expectedImages: 1, }, { name: "adding two new tags", oldStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, "foo": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", childImageWith2LayersDigest), Image: childImageWith2LayersDigest, }, }, }, "bar": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", baseImageWith2LayersDigest), Image: baseImageWith2LayersDigest, }, }, }, }, }, expectedImages: 3, }, { name: "adding an item and deleting the other tag", oldStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "foo": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", miscImageDigest), Image: miscImageDigest, }, }, }, "bar": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", baseImageWith2LayersDigest), Image: baseImageWith2LayersDigest, }, }, }, }, }, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "foo": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", childImageWith3LayersDigest), Image: childImageWith3LayersDigest, }, { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/sharedlayer@%s", miscImageDigest), Image: miscImageDigest, }, }, }, }, }, // misc image is already present in common is expectedImages: 1, }, { name: "adding a tag to spec", oldStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "new": { Name: "new", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", baseImageWith2LayersDigest), }, }, }, }, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith1LayerDigest), Image: baseImageWith1LayerDigest, }, }, }, }, }, expectedImages: 2, }, { name: "adding a tag to status already present in spec", oldSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "latest": { Name: "new", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", childImageWith2LayersDigest), }, }, }, }, newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "latest": { Name: "new", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", childImageWith2LayersDigest), }, }, }, }, newStatus: &imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "latest": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/is@%s", baseImageWith2LayersDigest), Image: childImageWith2LayersDigest, }, }, }, }, }, expectedImages: 1, }, { name: "refer to image in another namespace already present", newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "misc": { Name: "misc", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Namespace: "shared", Name: fmt.Sprintf("is@%s", miscImageDigest), }, }, }, }, expectedImages: 0, }, { name: "refer to imagestreamimage in the same namespace", newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "commonisi": { Name: "commonisi", From: &kapi.ObjectReference{ Kind: "ImageStreamImage", Name: fmt.Sprintf("common@%s", miscImageDigest), }, }, }, }, expectedImages: 0, }, { name: "refer to imagestreamtag in the same namespace", newSpec: &imageapi.ImageStreamSpec{ Tags: map[string]imageapi.TagReference{ "commonist": { Name: "commonist", From: &kapi.ObjectReference{ Kind: "ImageStreamTag", Name: "common:misc", }, }, }, }, expectedImages: 0, }, } { var newIS, oldIS *imageapi.ImageStream if tc.oldStatus != nil || tc.oldSpec != nil { oldIS = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, } if tc.oldSpec != nil { oldIS.Spec = *tc.oldSpec } if tc.oldStatus != nil { oldIS.Status = *tc.oldStatus } } if tc.newStatus != nil || tc.newSpec != nil { newIS = &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, } if tc.newSpec != nil { newIS.Spec = *tc.newSpec } if tc.newStatus != nil { newIS.Status = *tc.newStatus } } commonIS := imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "common", }, Status: imageapi.ImageStreamStatus{ Tags: map[string]imageapi.TagEventList{ "misc": { Items: []imageapi.TagEvent{ { DockerImageReference: fmt.Sprintf("172.30.12.34:5000/test/common@%s", miscImageDigest), Image: miscImageDigest, }, }, }, }, }, } iss := []imageapi.ImageStream{commonIS} if oldIS != nil { iss = append(iss, *oldIS) } fakeClient := &testclient.Fake{} fakeClient.AddReactor("get", "imagestreams", getFakeImageStreamGetHandler(t, iss...)) fakeClient.AddReactor("list", "imagestreams", getFakeImageStreamListHandler(t, iss...)) fakeClient.AddReactor("get", "images", getFakeImageGetHandler(t, "test")) evaluator := NewImageStreamAdmissionEvaluator(fakeClient) usage := evaluator.Usage(newIS) if len(usage) != len(expectedResources) { t.Errorf("[%s]: got unexpected number of computed resources: %d != %d", tc.name, len(usage), len(expectedResources)) } expectedUsage := kapi.ResourceList{ imageapi.ResourceImages: *resource.NewQuantity(tc.expectedImages, resource.DecimalSI), } masked := kquota.Mask(usage, expectedResources) if len(masked) != len(expectedUsage) { for k := range usage { if _, exists := masked[k]; !exists { t.Errorf("[%s]: got unexpected resource %q from Usage() method", tc.name, k) } } for k := range expectedUsage { if _, exists := masked[k]; !exists { t.Errorf("[%s]: expected resource %q not computed", tc.name, k) } } } for rname, expectedValue := range expectedUsage { if v, exists := masked[rname]; exists { if v.Cmp(expectedValue) != 0 { t.Errorf("[%s]: got unexpected usage for %q: %s != %s", tc.name, rname, v.String(), expectedValue.String()) } } } } }
// getImageStreamImageSuggestion will return the appropriate marker Suggestion for when a BuildConfig is missing its input ImageStreamImage func getImageStreamImageSuggestion(imageID string, imageStream *imageapi.ImageStream) osgraph.Suggestion { // check the images stream to see if any import images are in flight or have failed annotation, ok := imageStream.Annotations[imageapi.DockerImageRepositoryCheckAnnotation] if !ok { return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID)) } if checkTime, err := time.Parse(time.RFC3339, annotation); err == nil { // this time based annotation is set by pkg/image/controller/controller.go whenever import/tag operations are performed; unless // in the midst of an import/tag operation, it stays set and serves as a timestamp for when the last operation occurred; // so we will check if the image stream has been updated "recently"; // in case it is a slow link to the remote repo, see if if the check annotation occured within the last 5 minutes; if so, consider that as potentially "in progress" compareTime := checkTime.Add(5 * time.Minute) currentTime, _ := time.Parse(time.RFC3339, unversioned.Now().UTC().Format(time.RFC3339)) if compareTime.Before(currentTime) { return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` where `--from` specifies an image with hexadecimal ID %s", imageStream.GetName(), imageID)) } return osgraph.Suggestion(fmt.Sprintf("`oc import-image %s --from=` with hexadecimal ID %s possibly in progress", imageStream.GetName(), imageID)) } return osgraph.Suggestion(fmt.Sprintf("Possible error occurred with `oc import-image %s --from=` with hexadecimal ID %s; inspect images stream annotations", imageStream.GetName(), imageID)) }