Release v1.2.0 branch
Clayton Coleman authored on 2016/05/24 08:04:31... | ... |
@@ -70,7 +70,7 @@ func (autoscalerStrategy) AllowCreateOnUpdate() bool { |
70 | 70 |
// PrepareForUpdate clears fields that are not allowed to be set by end users on update. |
71 | 71 |
func (autoscalerStrategy) PrepareForUpdate(obj, old runtime.Object) { |
72 | 72 |
newHPA := obj.(*extensions.HorizontalPodAutoscaler) |
73 |
- oldHPA := obj.(*extensions.HorizontalPodAutoscaler) |
|
73 |
+ oldHPA := old.(*extensions.HorizontalPodAutoscaler) |
|
74 | 74 |
// Update is not allowed to set status |
75 | 75 |
newHPA.Status = oldHPA.Status |
76 | 76 |
} |
... | ... |
@@ -64,7 +64,7 @@ func (persistentvolumeStrategy) AllowCreateOnUpdate() bool { |
64 | 64 |
// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a PV |
65 | 65 |
func (persistentvolumeStrategy) PrepareForUpdate(obj, old runtime.Object) { |
66 | 66 |
newPv := obj.(*api.PersistentVolume) |
67 |
- oldPv := obj.(*api.PersistentVolume) |
|
67 |
+ oldPv := old.(*api.PersistentVolume) |
|
68 | 68 |
newPv.Status = oldPv.Status |
69 | 69 |
} |
70 | 70 |
|
... | ... |
@@ -86,7 +86,7 @@ var StatusStrategy = persistentvolumeStatusStrategy{Strategy} |
86 | 86 |
// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status |
87 | 87 |
func (persistentvolumeStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { |
88 | 88 |
newPv := obj.(*api.PersistentVolume) |
89 |
- oldPv := obj.(*api.PersistentVolume) |
|
89 |
+ oldPv := old.(*api.PersistentVolume) |
|
90 | 90 |
newPv.Spec = oldPv.Spec |
91 | 91 |
} |
92 | 92 |
|
... | ... |
@@ -64,7 +64,7 @@ func (persistentvolumeclaimStrategy) AllowCreateOnUpdate() bool { |
64 | 64 |
// PrepareForUpdate sets the Status field which is not allowed to be set by end users on update |
65 | 65 |
func (persistentvolumeclaimStrategy) PrepareForUpdate(obj, old runtime.Object) { |
66 | 66 |
newPvc := obj.(*api.PersistentVolumeClaim) |
67 |
- oldPvc := obj.(*api.PersistentVolumeClaim) |
|
67 |
+ oldPvc := old.(*api.PersistentVolumeClaim) |
|
68 | 68 |
newPvc.Status = oldPvc.Status |
69 | 69 |
} |
70 | 70 |
|
... | ... |
@@ -86,7 +86,7 @@ var StatusStrategy = persistentvolumeclaimStatusStrategy{Strategy} |
86 | 86 |
// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status |
87 | 87 |
func (persistentvolumeclaimStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { |
88 | 88 |
newPv := obj.(*api.PersistentVolumeClaim) |
89 |
- oldPv := obj.(*api.PersistentVolumeClaim) |
|
89 |
+ oldPv := old.(*api.PersistentVolumeClaim) |
|
90 | 90 |
newPv.Spec = oldPv.Spec |
91 | 91 |
} |
92 | 92 |
|
... | ... |
@@ -18,6 +18,7 @@ package storage |
18 | 18 |
|
19 | 19 |
import ( |
20 | 20 |
"fmt" |
21 |
+ "net/http" |
|
21 | 22 |
"reflect" |
22 | 23 |
"strconv" |
23 | 24 |
"strings" |
... | ... |
@@ -25,8 +26,10 @@ import ( |
25 | 25 |
"time" |
26 | 26 |
|
27 | 27 |
"k8s.io/kubernetes/pkg/api" |
28 |
+ "k8s.io/kubernetes/pkg/api/errors" |
|
28 | 29 |
"k8s.io/kubernetes/pkg/api/meta" |
29 | 30 |
"k8s.io/kubernetes/pkg/api/rest" |
31 |
+ "k8s.io/kubernetes/pkg/api/unversioned" |
|
30 | 32 |
"k8s.io/kubernetes/pkg/client/cache" |
31 | 33 |
"k8s.io/kubernetes/pkg/conversion" |
32 | 34 |
"k8s.io/kubernetes/pkg/runtime" |
... | ... |
@@ -264,7 +267,10 @@ func (c *Cacher) Watch(ctx context.Context, key string, resourceVersion string, |
264 | 264 |
defer c.watchCache.RUnlock() |
265 | 265 |
initEvents, err := c.watchCache.GetAllEventsSinceThreadUnsafe(watchRV) |
266 | 266 |
if err != nil { |
267 |
- return nil, err |
|
267 |
+ // To match the uncached watch implementation, once we have passed authn/authz/admission, |
|
268 |
+ // and successfully parsed a resource version, other errors must fail with a watch event of type ERROR, |
|
269 |
+ // rather than a directly returned error. |
|
270 |
+ return newErrWatcher(err), nil |
|
268 | 271 |
} |
269 | 272 |
|
270 | 273 |
c.Lock() |
... | ... |
@@ -460,6 +466,46 @@ func (lw *cacherListerWatcher) Watch(options api.ListOptions) (watch.Interface, |
460 | 460 |
return lw.storage.WatchList(context.TODO(), lw.resourcePrefix, options.ResourceVersion, Everything) |
461 | 461 |
} |
462 | 462 |
|
463 |
+// cacherWatch implements watch.Interface to return a single error |
|
464 |
+type errWatcher struct { |
|
465 |
+ result chan watch.Event |
|
466 |
+} |
|
467 |
+ |
|
468 |
+func newErrWatcher(err error) *errWatcher { |
|
469 |
+ // Create an error event |
|
470 |
+ errEvent := watch.Event{Type: watch.Error} |
|
471 |
+ switch err := err.(type) { |
|
472 |
+ case runtime.Object: |
|
473 |
+ errEvent.Object = err |
|
474 |
+ case *errors.StatusError: |
|
475 |
+ errEvent.Object = &err.ErrStatus |
|
476 |
+ default: |
|
477 |
+ errEvent.Object = &unversioned.Status{ |
|
478 |
+ Status: unversioned.StatusFailure, |
|
479 |
+ Message: err.Error(), |
|
480 |
+ Reason: unversioned.StatusReasonInternalError, |
|
481 |
+ Code: http.StatusInternalServerError, |
|
482 |
+ } |
|
483 |
+ } |
|
484 |
+ |
|
485 |
+ // Create a watcher with room for a single event, populate it, and close the channel |
|
486 |
+ watcher := &errWatcher{result: make(chan watch.Event, 1)} |
|
487 |
+ watcher.result <- errEvent |
|
488 |
+ close(watcher.result) |
|
489 |
+ |
|
490 |
+ return watcher |
|
491 |
+} |
|
492 |
+ |
|
493 |
+// Implements watch.Interface. |
|
494 |
+func (c *errWatcher) ResultChan() <-chan watch.Event { |
|
495 |
+ return c.result |
|
496 |
+} |
|
497 |
+ |
|
498 |
+// Implements watch.Interface. |
|
499 |
+func (c *errWatcher) Stop() { |
|
500 |
+ // no-op |
|
501 |
+} |
|
502 |
+ |
|
463 | 503 |
// cacherWatch implements watch.Interface |
464 | 504 |
type cacheWatcher struct { |
465 | 505 |
sync.Mutex |
... | ... |
@@ -24,6 +24,7 @@ import ( |
24 | 24 |
"time" |
25 | 25 |
|
26 | 26 |
"k8s.io/kubernetes/pkg/api" |
27 |
+ "k8s.io/kubernetes/pkg/api/errors" |
|
27 | 28 |
"k8s.io/kubernetes/pkg/api/meta" |
28 | 29 |
"k8s.io/kubernetes/pkg/api/testapi" |
29 | 30 |
apitesting "k8s.io/kubernetes/pkg/api/testing" |
... | ... |
@@ -225,11 +226,15 @@ func TestWatch(t *testing.T) { |
225 | 225 |
verifyWatchEvent(t, watcher, watch.Added, podFoo) |
226 | 226 |
verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) |
227 | 227 |
|
228 |
- // Check whether we get too-old error. |
|
229 |
- _, err = cacher.Watch(context.TODO(), "pods/ns/foo", "1", storage.Everything) |
|
230 |
- if err == nil { |
|
231 |
- t.Errorf("Expected 'error too old' error") |
|
228 |
+ // Check whether we get too-old error via the watch channel |
|
229 |
+ tooOldWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "1", storage.Everything) |
|
230 |
+ if err != nil { |
|
231 |
+ t.Fatalf("Expected no direct error, got %v", err) |
|
232 | 232 |
} |
233 |
+ defer tooOldWatcher.Stop() |
|
234 |
+ // Ensure we get a "Gone" error |
|
235 |
+ expectedGoneError := errors.NewGone("").(*errors.StatusError).ErrStatus |
|
236 |
+ verifyWatchEvent(t, tooOldWatcher, watch.Error, &expectedGoneError) |
|
233 | 237 |
|
234 | 238 |
initialWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, storage.Everything) |
235 | 239 |
if err != nil { |
... | ... |
@@ -173,7 +173,7 @@ func importImages(ctx gocontext.Context, retriever RepositoryRetriever, isi *api |
173 | 173 |
} |
174 | 174 |
for _, index := range tags[j] { |
175 | 175 |
if tag.Err != nil { |
176 |
- setImageImportStatus(isi, index, tag.Err) |
|
176 |
+ setImageImportStatus(isi, index, tag.Name, tag.Err) |
|
177 | 177 |
continue |
178 | 178 |
} |
179 | 179 |
copied := *tag.Image |
... | ... |
@@ -194,7 +194,7 @@ func importImages(ctx gocontext.Context, retriever RepositoryRetriever, isi *api |
194 | 194 |
} |
195 | 195 |
for _, index := range ids[j] { |
196 | 196 |
if digest.Err != nil { |
197 |
- setImageImportStatus(isi, index, digest.Err) |
|
197 |
+ setImageImportStatus(isi, index, "", digest.Err) |
|
198 | 198 |
continue |
199 | 199 |
} |
200 | 200 |
image := &isi.Status.Images[index] |
... | ... |
@@ -267,6 +267,7 @@ func importFromRepository(ctx gocontext.Context, retriever RepositoryRetriever, |
267 | 267 |
status.Status.Status = unversioned.StatusSuccess |
268 | 268 |
status.Images = make([]api.ImageImportStatus, len(repo.Tags)) |
269 | 269 |
for i, tag := range repo.Tags { |
270 |
+ status.Images[i].Tag = tag.Name |
|
270 | 271 |
if tag.Err != nil { |
271 | 272 |
failures++ |
272 | 273 |
status.Images[i].Status = imageImportStatus(tag.Err, "", "repository") |
... | ... |
@@ -277,7 +278,6 @@ func importFromRepository(ctx gocontext.Context, retriever RepositoryRetriever, |
277 | 277 |
copied := *tag.Image |
278 | 278 |
ref.Tag, ref.ID = tag.Name, copied.Name |
279 | 279 |
copied.DockerImageReference = ref.MostSpecific().Exact() |
280 |
- status.Images[i].Tag = tag.Name |
|
281 | 280 |
status.Images[i].Image = &copied |
282 | 281 |
} |
283 | 282 |
if failures > 0 { |
... | ... |
@@ -598,7 +598,8 @@ func imageImportStatus(err error, kind, position string) unversioned.Status { |
598 | 598 |
} |
599 | 599 |
} |
600 | 600 |
|
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 |
|
602 | 603 |
images.Status.Images[i].Status = imageImportStatus(err, "", "") |
603 | 604 |
} |
604 | 605 |
|
... | ... |
@@ -149,6 +149,12 @@ func TestImport(t *testing.T) { |
149 | 149 |
if status := isi.Status.Images[3].Status; status.Status != "" { |
150 | 150 |
t.Errorf("unexpected status: %#v", isi.Status.Images[3].Status) |
151 | 151 |
} |
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 |
+ } |
|
152 | 158 |
}, |
153 | 159 |
}, |
154 | 160 |
{ |
... | ... |
@@ -186,6 +192,7 @@ func TestImport(t *testing.T) { |
186 | 186 |
if len(isi.Status.Images) != 2 { |
187 | 187 |
t.Errorf("unexpected number of images: %#v", isi.Status.Repository.Images) |
188 | 188 |
} |
189 |
+ expectedTags := []string{"", "tag"} |
|
189 | 190 |
for i, image := range isi.Status.Images { |
190 | 191 |
if image.Status.Status != unversioned.StatusSuccess { |
191 | 192 |
t.Errorf("unexpected status %d: %#v", i, image.Status) |
... | ... |
@@ -198,6 +205,9 @@ func TestImport(t *testing.T) { |
198 | 198 |
if image.Image.DockerImageReference != "test@sha256:958608f8ecc1dc62c93b6c610f3a834dae4220c9642e6e8b4e0f2b3ad7cbd238" { |
199 | 199 |
t.Errorf("unexpected ref %d: %#v", i, image.Image.DockerImageReference) |
200 | 200 |
} |
201 |
+ if image.Tag != expectedTags[i] { |
|
202 |
+ t.Errorf("unexpected tag of status %d (%s != %s)", i, image.Tag, expectedTags[i]) |
|
203 |
+ } |
|
201 | 204 |
} |
202 | 205 |
}, |
203 | 206 |
}, |
... | ... |
@@ -222,10 +232,14 @@ func TestImport(t *testing.T) { |
222 | 222 |
if len(isi.Status.Repository.Images) != 5 { |
223 | 223 |
t.Errorf("unexpected number of images: %#v", isi.Status.Repository.Images) |
224 | 224 |
} |
225 |
+ expectedTags := []string{"3", "v2", "v1", "3.1", "abc"} |
|
225 | 226 |
for i, image := range isi.Status.Repository.Images { |
226 | 227 |
if image.Status.Status != unversioned.StatusFailure || image.Status.Message != "Internal error occurred: no such tag" { |
227 | 228 |
t.Errorf("unexpected status %d: %#v", i, isi.Status.Repository.Images) |
228 | 229 |
} |
230 |
+ if image.Tag != expectedTags[i] { |
|
231 |
+ t.Errorf("unexpected tag of status %d (%s != %s)", i, image.Tag, expectedTags[i]) |
|
232 |
+ } |
|
229 | 233 |
} |
230 | 234 |
}, |
231 | 235 |
}, |
... | ... |
@@ -177,6 +177,10 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err |
177 | 177 |
|
178 | 178 |
if spec := isi.Spec.Repository; spec != nil { |
179 | 179 |
for i, status := range isi.Status.Repository.Images { |
180 |
+ if checkImportFailure(status, stream, status.Tag, nextGeneration, now) { |
|
181 |
+ continue |
|
182 |
+ } |
|
183 |
+ |
|
180 | 184 |
image := status.Image |
181 | 185 |
ref, err := api.ParseDockerImageReference(image.DockerImageReference) |
182 | 186 |
if err != nil { |
... | ... |
@@ -196,10 +200,6 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err |
196 | 196 |
// we've imported a set of tags, ensure spec tag will point to this for later imports |
197 | 197 |
from.ID, from.Tag = "", tag |
198 | 198 |
|
199 |
- if checkImportFailure(status, stream, tag, nextGeneration, now) { |
|
200 |
- continue |
|
201 |
- } |
|
202 |
- |
|
203 | 199 |
if updated, ok := r.importSuccessful(ctx, image, stream, tag, from.Exact(), nextGeneration, now, spec.ImportPolicy, importedImages, updatedImages); ok { |
204 | 200 |
isi.Status.Repository.Images[i].Image = updated |
205 | 201 |
} |
... | ... |
@@ -278,6 +278,17 @@ func checkImportFailure(status api.ImageImportStatus, stream *api.ImageStream, t |
278 | 278 |
|
279 | 279 |
LastTransitionTime: now, |
280 | 280 |
} |
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 |
+ |
|
281 | 292 |
if !api.HasTagCondition(stream, tag, condition) { |
282 | 293 |
api.SetTagConditions(stream, tag, condition) |
283 | 294 |
if tagRef, ok := stream.Spec.Tags[tag]; ok { |
... | ... |
@@ -13,6 +13,7 @@ import ( |
13 | 13 |
"testing" |
14 | 14 |
"time" |
15 | 15 |
|
16 |
+ "github.com/docker/distribution/registry/api/errcode" |
|
16 | 17 |
gocontext "golang.org/x/net/context" |
17 | 18 |
|
18 | 19 |
kapi "k8s.io/kubernetes/pkg/api" |
... | ... |
@@ -135,16 +136,26 @@ func TestImageStreamImport(t *testing.T) { |
135 | 135 |
} |
136 | 136 |
} |
137 | 137 |
|
138 |
-func mockRegistryHandler(t *testing.T, count *int) http.Handler { |
|
138 |
+// mockRegistryHandler returns a registry mock handler with several repositories. requireAuth causes handler |
|
139 |
+// to return unauthorized and request basic authentication header if not given. count is increased each |
|
140 |
+// time the handler is invoked. There are three repositories: |
|
141 |
+// - test/image with phpManifest |
|
142 |
+// - test/image2 with etcdManifest |
|
143 |
+// - test/image3 with tags: v1, v2 and latest |
|
144 |
+// - the first points to etcdManifest |
|
145 |
+// - the others cause handler to return unknown error |
|
146 |
+func mockRegistryHandler(t *testing.T, requireAuth bool, count *int) http.Handler { |
|
139 | 147 |
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
140 | 148 |
(*count)++ |
141 | 149 |
t.Logf("%d got %s %s", *count, r.Method, r.URL.Path) |
142 | 150 |
|
143 | 151 |
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 |
|
152 |
+ if requireAuth { |
|
153 |
+ if len(r.Header.Get("Authorization")) == 0 { |
|
154 |
+ w.Header().Set("WWW-Authenticate", "BASIC") |
|
155 |
+ w.WriteHeader(http.StatusUnauthorized) |
|
156 |
+ return |
|
157 |
+ } |
|
148 | 158 |
} |
149 | 159 |
|
150 | 160 |
switch r.URL.Path { |
... | ... |
@@ -154,6 +165,12 @@ func mockRegistryHandler(t *testing.T, count *int) http.Handler { |
154 | 154 |
w.Write([]byte(phpManifest)) |
155 | 155 |
case "/v2/test/image2/manifests/" + etcdDigest: |
156 | 156 |
w.Write([]byte(etcdManifest)) |
157 |
+ case "/v2/test/image3/tags/list": |
|
158 |
+ w.Write([]byte("{\"name\": \"test/image3\", \"tags\": [\"latest\", \"v1\", \"v2\"]}")) |
|
159 |
+ case "/v2/test/image3/manifests/latest", "/v2/test/image3/manifests/v2", "/v2/test/image3/manifests/" + danglingDigest: |
|
160 |
+ errcode.ServeJSON(w, errcode.ErrorCodeUnknown) |
|
161 |
+ case "/v2/test/image3/manifests/v1", "/v2/test/image3/manifests/" + etcdDigest: |
|
162 |
+ w.Write([]byte(etcdManifest)) |
|
157 | 163 |
default: |
158 | 164 |
t.Fatalf("unexpected request %s: %#v", r.URL.Path, r) |
159 | 165 |
} |
... | ... |
@@ -164,7 +181,7 @@ func TestImageStreamImportAuthenticated(t *testing.T) { |
164 | 164 |
testutil.RequireEtcd(t) |
165 | 165 |
// start regular HTTP servers |
166 | 166 |
count := 0 |
167 |
- server := httptest.NewServer(mockRegistryHandler(t, &count)) |
|
167 |
+ server := httptest.NewServer(mockRegistryHandler(t, true, &count)) |
|
168 | 168 |
server2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
169 | 169 |
w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") |
170 | 170 |
if len(r.Header.Get("Authorization")) == 0 { |
... | ... |
@@ -177,7 +194,7 @@ func TestImageStreamImportAuthenticated(t *testing.T) { |
177 | 177 |
|
178 | 178 |
// start a TLS server |
179 | 179 |
count2 := 0 |
180 |
- server3 := httptest.NewTLSServer(mockRegistryHandler(t, &count2)) |
|
180 |
+ server3 := httptest.NewTLSServer(mockRegistryHandler(t, true, &count2)) |
|
181 | 181 |
|
182 | 182 |
url1, _ := url.Parse(server.URL) |
183 | 183 |
url2, _ := url.Parse(server2.URL) |
... | ... |
@@ -324,6 +341,105 @@ func TestImageStreamImportAuthenticated(t *testing.T) { |
324 | 324 |
} |
325 | 325 |
} |
326 | 326 |
|
327 |
+// Verifies that individual errors for particular tags are handled properly when pulling all tags from a |
|
328 |
+// repository. |
|
329 |
+func TestImageStreamImportTagsFromRepository(t *testing.T) { |
|
330 |
+ testutil.RequireEtcd(t) |
|
331 |
+ // start regular HTTP servers |
|
332 |
+ count := 0 |
|
333 |
+ server := httptest.NewServer(mockRegistryHandler(t, false, &count)) |
|
334 |
+ |
|
335 |
+ url, _ := url.Parse(server.URL) |
|
336 |
+ |
|
337 |
+ // start a master |
|
338 |
+ _, clusterAdminKubeConfig, err := testserver.StartTestMaster() |
|
339 |
+ if err != nil { |
|
340 |
+ t.Fatalf("unexpected error: %v", err) |
|
341 |
+ } |
|
342 |
+ /* |
|
343 |
+ _, err := testutil.GetClusterAdminKubeClient(clusterAdminKubeConfig) |
|
344 |
+ if err != nil { |
|
345 |
+ t.Fatalf("unexpected error: %v", err) |
|
346 |
+ } |
|
347 |
+ */ |
|
348 |
+ c, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) |
|
349 |
+ if err != nil { |
|
350 |
+ t.Fatalf("unexpected error: %v", err) |
|
351 |
+ } |
|
352 |
+ err = testutil.CreateNamespace(clusterAdminKubeConfig, testutil.Namespace()) |
|
353 |
+ if err != nil { |
|
354 |
+ t.Fatalf("unexpected error: %v", err) |
|
355 |
+ } |
|
356 |
+ |
|
357 |
+ importSpec := &api.ImageStreamImport{ |
|
358 |
+ ObjectMeta: kapi.ObjectMeta{Name: "test"}, |
|
359 |
+ Spec: api.ImageStreamImportSpec{ |
|
360 |
+ Import: true, |
|
361 |
+ Repository: &api.RepositoryImportSpec{ |
|
362 |
+ From: kapi.ObjectReference{Kind: "DockerImage", Name: url.Host + "/test/image3"}, |
|
363 |
+ ImportPolicy: api.TagImportPolicy{Insecure: true}, |
|
364 |
+ IncludeManifest: true, |
|
365 |
+ }, |
|
366 |
+ }, |
|
367 |
+ } |
|
368 |
+ |
|
369 |
+ // import expecting regular image to pass |
|
370 |
+ isi, err := c.ImageStreams(testutil.Namespace()).Import(importSpec) |
|
371 |
+ if err != nil { |
|
372 |
+ t.Fatal(err) |
|
373 |
+ } |
|
374 |
+ if len(isi.Status.Images) != 0 { |
|
375 |
+ t.Errorf("imported unexpected number of images (%d != 0)", len(isi.Status.Images)) |
|
376 |
+ } |
|
377 |
+ if isi.Status.Repository == nil { |
|
378 |
+ t.Fatalf("exported non-nil repository status") |
|
379 |
+ } |
|
380 |
+ if len(isi.Status.Repository.Images) != 3 { |
|
381 |
+ t.Fatalf("imported unexpected number of tags (%d != 3)", len(isi.Status.Repository.Images)) |
|
382 |
+ } |
|
383 |
+ for i, image := range isi.Status.Repository.Images { |
|
384 |
+ switch i { |
|
385 |
+ case 2: |
|
386 |
+ if image.Status.Status != unversioned.StatusSuccess { |
|
387 |
+ t.Errorf("import of image %d did not succeed: %#v", i, image.Status) |
|
388 |
+ } |
|
389 |
+ if image.Tag != "v1" { |
|
390 |
+ t.Errorf("unexpected tag at position %d (%s != v1)", i, image.Tag) |
|
391 |
+ } |
|
392 |
+ if image.Image == nil { |
|
393 |
+ t.Fatalf("expected image to be set") |
|
394 |
+ } |
|
395 |
+ if image.Image.DockerImageReference != url.Host+"/test/image3@"+etcdDigest { |
|
396 |
+ t.Errorf("unexpected DockerImageReference (%s != %s)", image.Image.DockerImageReference, url.Host+"/test/image3@"+etcdDigest) |
|
397 |
+ } |
|
398 |
+ if image.Image.Name != etcdDigest { |
|
399 |
+ t.Errorf("expected etcd digest as a name of the image (%s != %s)", image.Image.Name, etcdDigest) |
|
400 |
+ } |
|
401 |
+ default: |
|
402 |
+ if image.Status.Status != unversioned.StatusFailure || image.Status.Reason != unversioned.StatusReasonInternalError { |
|
403 |
+ t.Fatalf("import of image %d did not report internal server error: %#v", i, image.Status) |
|
404 |
+ } |
|
405 |
+ expectedTags := []string{"latest", "v2"}[i] |
|
406 |
+ if image.Tag != expectedTags { |
|
407 |
+ t.Errorf("unexpected tag at position %d (%s != %s)", i, image.Tag, expectedTags[i]) |
|
408 |
+ } |
|
409 |
+ } |
|
410 |
+ } |
|
411 |
+ |
|
412 |
+ is, err := c.ImageStreams(testutil.Namespace()).Get("test") |
|
413 |
+ if err != nil { |
|
414 |
+ t.Fatal(err) |
|
415 |
+ } |
|
416 |
+ tagEvent := api.LatestTaggedImage(is, "v1") |
|
417 |
+ if tagEvent == nil { |
|
418 |
+ t.Fatalf("no image tagged for v1: %#v", is) |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ if tagEvent == nil || tagEvent.Image != etcdDigest || tagEvent.DockerImageReference != url.Host+"/test/image3@"+etcdDigest { |
|
422 |
+ t.Fatalf("expected the etcd image to be tagged: %#v", tagEvent) |
|
423 |
+ } |
|
424 |
+} |
|
425 |
+ |
|
327 | 426 |
// Verifies that the import scheduler fetches an image repeatedly (every 1s as per the default |
328 | 427 |
// test controller interval), updates the image stream only when there are changes, and if an |
329 | 428 |
// error occurs writes the error only once (instead of every interval) |
... | ... |
@@ -861,3 +977,5 @@ const phpManifest = `{ |
861 | 861 |
} |
862 | 862 |
] |
863 | 863 |
}` |
864 |
+ |
|
865 |
+const danglingDigest = `sha256:f374c0d9b59e6fdf9f8922d59e946b05fbeabaed70b0639d7b6b524f3299e87b` |