Skip to content

Commit dde538f

Browse files
author
Michal Minář
committed
Import from repository doesn't panic
- Check the import status before a use of image object. - Also to set tag names on import status objects in order to produce nice output (containing tag names that failed to import). Signed-off-by: Michal Minar <[email protected]>
1 parent 8fb838f commit dde538f

File tree

4 files changed

+151
-15
lines changed

4 files changed

+151
-15
lines changed

pkg/image/importer/importer.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func importImages(ctx gocontext.Context, retriever RepositoryRetriever, isi *api
173173
}
174174
for _, index := range tags[j] {
175175
if tag.Err != nil {
176-
setImageImportStatus(isi, index, tag.Err)
176+
setImageImportStatus(isi, index, tag.Name, tag.Err)
177177
continue
178178
}
179179
copied := *tag.Image
@@ -194,7 +194,7 @@ func importImages(ctx gocontext.Context, retriever RepositoryRetriever, isi *api
194194
}
195195
for _, index := range ids[j] {
196196
if digest.Err != nil {
197-
setImageImportStatus(isi, index, digest.Err)
197+
setImageImportStatus(isi, index, "", digest.Err)
198198
continue
199199
}
200200
image := &isi.Status.Images[index]
@@ -267,6 +267,7 @@ func importFromRepository(ctx gocontext.Context, retriever RepositoryRetriever,
267267
status.Status.Status = unversioned.StatusSuccess
268268
status.Images = make([]api.ImageImportStatus, len(repo.Tags))
269269
for i, tag := range repo.Tags {
270+
status.Images[i].Tag = tag.Name
270271
if tag.Err != nil {
271272
failures++
272273
status.Images[i].Status = imageImportStatus(tag.Err, "", "repository")
@@ -277,7 +278,6 @@ func importFromRepository(ctx gocontext.Context, retriever RepositoryRetriever,
277278
copied := *tag.Image
278279
ref.Tag, ref.ID = tag.Name, copied.Name
279280
copied.DockerImageReference = ref.MostSpecific().Exact()
280-
status.Images[i].Tag = tag.Name
281281
status.Images[i].Image = &copied
282282
}
283283
if failures > 0 {
@@ -598,7 +598,8 @@ func imageImportStatus(err error, kind, position string) unversioned.Status {
598598
}
599599
}
600600

601-
func setImageImportStatus(images *api.ImageStreamImport, i int, err error) {
601+
func setImageImportStatus(images *api.ImageStreamImport, i int, tag string, err error) {
602+
images.Status.Images[i].Tag = tag
602603
images.Status.Images[i].Status = imageImportStatus(err, "", "")
603604
}
604605

pkg/image/importer/importer_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ func TestImport(t *testing.T) {
149149
if status := isi.Status.Images[3].Status; status.Status != "" {
150150
t.Errorf("unexpected status: %#v", isi.Status.Images[3].Status)
151151
}
152+
expectedTags := []string{"latest", "", "", ""}
153+
for i, image := range isi.Status.Images {
154+
if image.Tag != expectedTags[i] {
155+
t.Errorf("unexpected tag of status %d (%s != %s)", i, image.Tag, expectedTags[i])
156+
}
157+
}
152158
},
153159
},
154160
{
@@ -186,6 +192,7 @@ func TestImport(t *testing.T) {
186192
if len(isi.Status.Images) != 2 {
187193
t.Errorf("unexpected number of images: %#v", isi.Status.Repository.Images)
188194
}
195+
expectedTags := []string{"", "tag"}
189196
for i, image := range isi.Status.Images {
190197
if image.Status.Status != unversioned.StatusSuccess {
191198
t.Errorf("unexpected status %d: %#v", i, image.Status)
@@ -198,6 +205,9 @@ func TestImport(t *testing.T) {
198205
if image.Image.DockerImageReference != "test@sha256:958608f8ecc1dc62c93b6c610f3a834dae4220c9642e6e8b4e0f2b3ad7cbd238" {
199206
t.Errorf("unexpected ref %d: %#v", i, image.Image.DockerImageReference)
200207
}
208+
if image.Tag != expectedTags[i] {
209+
t.Errorf("unexpected tag of status %d (%s != %s)", i, image.Tag, expectedTags[i])
210+
}
201211
}
202212
},
203213
},
@@ -222,10 +232,14 @@ func TestImport(t *testing.T) {
222232
if len(isi.Status.Repository.Images) != 5 {
223233
t.Errorf("unexpected number of images: %#v", isi.Status.Repository.Images)
224234
}
235+
expectedTags := []string{"3", "v2", "v1", "3.1", "abc"}
225236
for i, image := range isi.Status.Repository.Images {
226237
if image.Status.Status != unversioned.StatusFailure || image.Status.Message != "Internal error occurred: no such tag" {
227238
t.Errorf("unexpected status %d: %#v", i, isi.Status.Repository.Images)
228239
}
240+
if image.Tag != expectedTags[i] {
241+
t.Errorf("unexpected tag of status %d (%s != %s)", i, image.Tag, expectedTags[i])
242+
}
229243
}
230244
},
231245
},

pkg/image/registry/imagestreamimport/rest.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
177177

178178
if spec := isi.Spec.Repository; spec != nil {
179179
for i, status := range isi.Status.Repository.Images {
180+
if checkImportFailure(status, stream, status.Tag, nextGeneration, now) {
181+
continue
182+
}
183+
180184
image := status.Image
181185
ref, err := api.ParseDockerImageReference(image.DockerImageReference)
182186
if err != nil {
@@ -196,10 +200,6 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
196200
// we've imported a set of tags, ensure spec tag will point to this for later imports
197201
from.ID, from.Tag = "", tag
198202

199-
if checkImportFailure(status, stream, tag, nextGeneration, now) {
200-
continue
201-
}
202-
203203
if updated, ok := r.importSuccessful(ctx, image, stream, tag, from.Exact(), nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok {
204204
isi.Status.Repository.Images[i].Image = updated
205205
}
@@ -278,6 +278,17 @@ func checkImportFailure(status api.ImageImportStatus, stream *api.ImageStream, t
278278

279279
LastTransitionTime: now,
280280
}
281+
282+
if tag == "" {
283+
if len(status.Tag) > 0 {
284+
tag = status.Tag
285+
} else if status.Image != nil {
286+
if ref, err := api.ParseDockerImageReference(status.Image.DockerImageReference); err == nil {
287+
tag = ref.Tag
288+
}
289+
}
290+
}
291+
281292
if !api.HasTagCondition(stream, tag, condition) {
282293
api.SetTagConditions(stream, tag, condition)
283294
if tagRef, ok := stream.Spec.Tags[tag]; ok {

test/integration/imageimporter_test.go

Lines changed: 117 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"testing"
1414
"time"
1515

16+
"github.com/docker/distribution/registry/api/errcode"
1617
gocontext "golang.org/x/net/context"
1718

1819
kapi "k8s.io/kubernetes/pkg/api"
@@ -135,16 +136,18 @@ func TestImageStreamImport(t *testing.T) {
135136
}
136137
}
137138

138-
func mockRegistryHandler(t *testing.T, count *int) http.Handler {
139+
func mockRegistryHandler(t *testing.T, requireAuth bool, count *int) http.Handler {
139140
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
140141
(*count)++
141142
t.Logf("%d got %s %s", *count, r.Method, r.URL.Path)
142143

143144
w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
144-
if len(r.Header.Get("Authorization")) == 0 {
145-
w.Header().Set("WWW-Authenticate", "BASIC")
146-
w.WriteHeader(http.StatusUnauthorized)
147-
return
145+
if requireAuth {
146+
if len(r.Header.Get("Authorization")) == 0 {
147+
w.Header().Set("WWW-Authenticate", "BASIC")
148+
w.WriteHeader(http.StatusUnauthorized)
149+
return
150+
}
148151
}
149152

150153
switch r.URL.Path {
@@ -154,6 +157,12 @@ func mockRegistryHandler(t *testing.T, count *int) http.Handler {
154157
w.Write([]byte(phpManifest))
155158
case "/v2/test/image2/manifests/" + etcdDigest:
156159
w.Write([]byte(etcdManifest))
160+
case "/v2/test/image3/tags/list":
161+
w.Write([]byte("{\"name\": \"test/image3\", \"tags\": [\"latest\", \"v1\", \"v2\"]}"))
162+
case "/v2/test/image3/manifests/latest", "/v2/test/image3/manifests/v2", "/v2/test/image3/manifests/" + forbiddenDigest:
163+
errcode.ServeJSON(w, errcode.ErrorCodeUnknown)
164+
case "/v2/test/image3/manifests/v1", "/v2/test/image3/manifests/" + etcdDigest:
165+
w.Write([]byte(etcdManifest))
157166
default:
158167
t.Fatalf("unexpected request %s: %#v", r.URL.Path, r)
159168
}
@@ -164,7 +173,7 @@ func TestImageStreamImportAuthenticated(t *testing.T) {
164173
testutil.RequireEtcd(t)
165174
// start regular HTTP servers
166175
count := 0
167-
server := httptest.NewServer(mockRegistryHandler(t, &count))
176+
server := httptest.NewServer(mockRegistryHandler(t, true, &count))
168177
server2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
169178
w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
170179
if len(r.Header.Get("Authorization")) == 0 {
@@ -177,7 +186,7 @@ func TestImageStreamImportAuthenticated(t *testing.T) {
177186

178187
// start a TLS server
179188
count2 := 0
180-
server3 := httptest.NewTLSServer(mockRegistryHandler(t, &count2))
189+
server3 := httptest.NewTLSServer(mockRegistryHandler(t, true, &count2))
181190

182191
url1, _ := url.Parse(server.URL)
183192
url2, _ := url.Parse(server2.URL)
@@ -324,6 +333,105 @@ func TestImageStreamImportAuthenticated(t *testing.T) {
324333
}
325334
}
326335

336+
// Verifies that individual errors for particular tags are handled properly when pulling all tags from a
337+
// repository.
338+
func TestImageStreamImportTagsFromRepository(t *testing.T) {
339+
testutil.RequireEtcd(t)
340+
// start regular HTTP servers
341+
count := 0
342+
server := httptest.NewServer(mockRegistryHandler(t, false, &count))
343+
344+
url, _ := url.Parse(server.URL)
345+
346+
// start a master
347+
_, clusterAdminKubeConfig, err := testserver.StartTestMaster()
348+
if err != nil {
349+
t.Fatalf("unexpected error: %v", err)
350+
}
351+
/*
352+
_, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig)
353+
if err != nil {
354+
t.Fatalf("unexpected error: %v", err)
355+
}
356+
*/
357+
c, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig)
358+
if err != nil {
359+
t.Fatalf("unexpected error: %v", err)
360+
}
361+
err = testutil.CreateNamespace(clusterAdminKubeConfig, testutil.Namespace())
362+
if err != nil {
363+
t.Fatalf("unexpected error: %v", err)
364+
}
365+
366+
importSpec := &api.ImageStreamImport{
367+
ObjectMeta: kapi.ObjectMeta{Name: "test"},
368+
Spec: api.ImageStreamImportSpec{
369+
Import: true,
370+
Repository: &api.RepositoryImportSpec{
371+
From: kapi.ObjectReference{Kind: "DockerImage", Name: url.Host + "/test/image3"},
372+
ImportPolicy: api.TagImportPolicy{Insecure: true},
373+
IncludeManifest: true,
374+
},
375+
},
376+
}
377+
378+
// import expecting regular image to pass
379+
isi, err := c.ImageStreams(testutil.Namespace()).Import(importSpec)
380+
if err != nil {
381+
t.Fatal(err)
382+
}
383+
if len(isi.Status.Images) != 0 {
384+
t.Errorf("imported unexpected number of images (%d != 0)", len(isi.Status.Images))
385+
}
386+
if isi.Status.Repository == nil {
387+
t.Fatalf("exported non-nil repository status")
388+
}
389+
if len(isi.Status.Repository.Images) != 3 {
390+
t.Fatalf("imported unexpected number of tags (%d != 3)", len(isi.Status.Repository.Images))
391+
}
392+
for i, image := range isi.Status.Repository.Images {
393+
switch i {
394+
case 2:
395+
if image.Status.Status != unversioned.StatusSuccess {
396+
t.Errorf("import of image %d did not succeed: %#v", i, image.Status)
397+
}
398+
if image.Tag != "v1" {
399+
t.Errorf("unexpected tag at position %d (%s != v1)", i, image.Tag)
400+
}
401+
if image.Image == nil {
402+
t.Fatalf("expected image to be set")
403+
}
404+
if image.Image.DockerImageReference != url.Host+"/test/image3@"+etcdDigest {
405+
t.Errorf("unexpected DockerImageReference (%s != %s)", image.Image.DockerImageReference, url.Host+"/test/image3@"+etcdDigest)
406+
}
407+
if image.Image.Name != etcdDigest {
408+
t.Errorf("expected etcd digest as a name of the image (%s != %s)", image.Image.Name, etcdDigest)
409+
}
410+
default:
411+
if image.Status.Status != unversioned.StatusFailure || image.Status.Reason != unversioned.StatusReasonInternalError {
412+
t.Fatalf("import of image %d did not report internal server error: %#v", i, image.Status)
413+
}
414+
expectedTags := []string{"latest", "v2"}[i]
415+
if image.Tag != expectedTags {
416+
t.Errorf("unexpected tag at position %d (%s != %s)", i, image.Tag, expectedTags[i])
417+
}
418+
}
419+
}
420+
421+
is, err := c.ImageStreams(testutil.Namespace()).Get("test")
422+
if err != nil {
423+
t.Fatal(err)
424+
}
425+
tagEvent := api.LatestTaggedImage(is, "v1")
426+
if tagEvent == nil {
427+
t.Fatalf("no image tagged for v1: %#v", is)
428+
}
429+
430+
if tagEvent == nil || tagEvent.Image != etcdDigest || tagEvent.DockerImageReference != url.Host+"/test/image3@"+etcdDigest {
431+
t.Fatalf("expected the etcd image to be tagged: %#v", tagEvent)
432+
}
433+
}
434+
327435
// Verifies that the import scheduler fetches an image repeatedly (every 1s as per the default
328436
// test controller interval), updates the image stream only when there are changes, and if an
329437
// error occurs writes the error only once (instead of every interval)
@@ -861,3 +969,5 @@ const phpManifest = `{
861969
}
862970
]
863971
}`
972+
973+
const forbiddenDigest = `sha256:f374c0d9b59e6fdf9f8922d59e946b05fbeabaed70b0639d7b6b524f3299e87b`

0 commit comments

Comments
 (0)