-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide layers referenced by an image stream as layers subresource
Adds a new GET endpoint to an image stream as a subresource `layers` that returns an array of every layer referenced by the image stream and the tags and images included by the image. The subresource is fed by a store driven informer that caches and indexes only the layers. Clients get a 500 retry error if the cache has not initialized yet (the client will silently retry). Turns the registry access check for a given layer into an O(1) check instead of O(N) where N is the number of images in the image stream.
- Loading branch information
1 parent
1e6007e
commit 0480134
Showing
4 changed files
with
249 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package etcd | ||
|
||
import ( | ||
"fmt" | ||
|
||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/apimachinery/pkg/watch" | ||
apirequest "k8s.io/apiserver/pkg/endpoints/request" | ||
"k8s.io/apiserver/pkg/registry/rest" | ||
"k8s.io/client-go/tools/cache" | ||
|
||
imageapi "github.com/openshift/origin/pkg/image/apis/image" | ||
) | ||
|
||
type ImageLayerIndex interface { | ||
cache.SharedIndexInformer | ||
} | ||
|
||
type ImageStore interface { | ||
rest.Watcher | ||
rest.Lister | ||
} | ||
|
||
func NewImageLayerIndex(store ImageStore) ImageLayerIndex { | ||
ctx := apirequest.NewContext() | ||
informer := cache.NewSharedIndexInformer(&cache.ListWatch{ | ||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | ||
obj, err := store.List(ctx, &metainternalversion.ListOptions{ | ||
ResourceVersion: options.ResourceVersion, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
list, ok := obj.(*imageapi.ImageList) | ||
if !ok { | ||
return nil, fmt.Errorf("unexpected store type %T for layer index", obj) | ||
} | ||
out := &metainternalversion.List{ | ||
Items: make([]runtime.Object, len(list.Items)), | ||
} | ||
for i, image := range list.Items { | ||
out.Items[i] = &ImageLayers{ | ||
Name: image.Name, | ||
Layers: image.DockerImageLayers, | ||
} | ||
} | ||
return out, nil | ||
}, | ||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { | ||
w, err := store.Watch(ctx, &metainternalversion.ListOptions{ | ||
ResourceVersion: options.ResourceVersion, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return watch.Filter(w, func(in watch.Event) (out watch.Event, keep bool) { | ||
if in.Object == nil { | ||
return in, true | ||
} | ||
image, ok := in.Object.(*imageapi.Image) | ||
if !ok { | ||
return in, true | ||
} | ||
in.Object = &ImageLayers{ | ||
Name: image.Name, | ||
Layers: image.DockerImageLayers, | ||
} | ||
return in, true | ||
}), nil | ||
}, | ||
}, &ImageLayers{}, 0, cache.Indexers{ | ||
"layers": func(obj interface{}) ([]string, error) { | ||
entry, ok := obj.(*ImageLayers) | ||
if !ok { | ||
return nil, fmt.Errorf("unexpected cache object %T", obj) | ||
} | ||
keys := make([]string, 0, len(entry.Layers)) | ||
for _, layer := range entry.Layers { | ||
keys = append(keys, layer.Name) | ||
} | ||
return keys, nil | ||
}, | ||
}) | ||
return informer | ||
} | ||
|
||
type ImageLayers struct { | ||
Name string | ||
Layers []imageapi.ImageLayer | ||
} | ||
|
||
var _ metav1.Object = &ImageLayers{} | ||
|
||
func (l *ImageLayers) GetObjectKind() schema.ObjectKind { return &metav1.TypeMeta{} } | ||
func (l *ImageLayers) DeepCopyObject() runtime.Object { | ||
var layers []imageapi.ImageLayer | ||
if l.Layers != nil { | ||
layers = make([]imageapi.ImageLayer, len(l.Layers)) | ||
copy(layers, l.Layers) | ||
} | ||
return &ImageLayers{ | ||
Name: l.Name, | ||
Layers: layers, | ||
} | ||
} | ||
|
||
// TODO: fix client-go/cache to not need this | ||
|
||
func (l *ImageLayers) GetNamespace() string { return "" } | ||
func (l *ImageLayers) SetNamespace(namespace string) {} | ||
func (l *ImageLayers) GetName() string { return l.Name } | ||
func (l *ImageLayers) SetName(name string) {} | ||
func (l *ImageLayers) GetGenerateName() string { return "" } | ||
func (l *ImageLayers) SetGenerateName(name string) {} | ||
func (l *ImageLayers) GetUID() types.UID { return "" } | ||
func (l *ImageLayers) SetUID(uid types.UID) {} | ||
func (l *ImageLayers) GetResourceVersion() string { return "" } | ||
func (l *ImageLayers) SetResourceVersion(version string) {} | ||
func (l *ImageLayers) GetGeneration() int64 { return 0 } | ||
func (l *ImageLayers) SetGeneration(generation int64) {} | ||
func (l *ImageLayers) GetSelfLink() string { return "" } | ||
func (l *ImageLayers) SetSelfLink(selfLink string) {} | ||
func (l *ImageLayers) GetCreationTimestamp() metav1.Time { return metav1.Time{} } | ||
func (l *ImageLayers) SetCreationTimestamp(timestamp metav1.Time) {} | ||
func (l *ImageLayers) GetDeletionTimestamp() *metav1.Time { return nil } | ||
func (l *ImageLayers) SetDeletionTimestamp(timestamp *metav1.Time) {} | ||
func (l *ImageLayers) GetDeletionGracePeriodSeconds() *int64 { return nil } | ||
func (l *ImageLayers) SetDeletionGracePeriodSeconds(*int64) {} | ||
func (l *ImageLayers) GetLabels() map[string]string { return nil } | ||
func (l *ImageLayers) SetLabels(labels map[string]string) {} | ||
func (l *ImageLayers) GetAnnotations() map[string]string { return nil } | ||
func (l *ImageLayers) SetAnnotations(annotations map[string]string) {} | ||
func (l *ImageLayers) GetInitializers() *metav1.Initializers { return nil } | ||
func (l *ImageLayers) SetInitializers(initializers *metav1.Initializers) {} | ||
func (l *ImageLayers) GetFinalizers() []string { return nil } | ||
func (l *ImageLayers) SetFinalizers(finalizers []string) {} | ||
func (l *ImageLayers) GetOwnerReferences() []metav1.OwnerReference { return nil } | ||
func (l *ImageLayers) SetOwnerReferences([]metav1.OwnerReference) {} | ||
func (l *ImageLayers) GetClusterName() string { return "" } | ||
func (l *ImageLayers) SetClusterName(clusterName string) {} |