Skip to content

Commit 3179a2a

Browse files
committed
registry: report publicDockerImageRepository to image stream if configured
1 parent e3585cc commit 3179a2a

File tree

22 files changed

+188
-75
lines changed

22 files changed

+188
-75
lines changed

pkg/cmd/server/admission/init.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type PluginInitializer struct {
2828
Informers kinternalinformers.SharedInformerFactory
2929
ClusterResourceQuotaInformer quotainformer.ClusterResourceQuotaInformer
3030
ClusterQuotaMapper clusterquotamapping.ClusterQuotaMapper
31-
DefaultRegistryFn imageapi.DefaultRegistryFunc
31+
RegistryHostnameRetriever imageapi.RegistryHostnameRetriever
3232
SecurityInformers securityinformer.SharedInformerFactory
3333
UserInformers userinformer.SharedInformerFactory
3434
}
@@ -70,7 +70,7 @@ func (i *PluginInitializer) Initialize(plugin admission.Interface) {
7070
wantsSecurityInformer.SetSecurityInformers(i.SecurityInformers)
7171
}
7272
if wantsDefaultRegistryFunc, ok := plugin.(WantsDefaultRegistryFunc); ok {
73-
wantsDefaultRegistryFunc.SetDefaultRegistryFunc(i.DefaultRegistryFn)
73+
wantsDefaultRegistryFunc.SetDefaultRegistryFunc(i.RegistryHostnameRetriever.InternalRegistryHostnameFn())
7474
}
7575
if wantsUserInformer, ok := plugin.(WantsUserInformer); ok {
7676
wantsUserInformer.SetUserInformer(i.UserInformers)

pkg/cmd/server/admission/types.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/openshift/origin/pkg/client"
1111
configapi "github.com/openshift/origin/pkg/cmd/server/api"
12-
imageapi "github.com/openshift/origin/pkg/image/apis/image"
1312
"github.com/openshift/origin/pkg/project/cache"
1413
"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
1514
quotainformer "github.com/openshift/origin/pkg/quota/generated/informers/internalversion/quota/internalversion"
@@ -79,7 +78,7 @@ type WantsSecurityInformer interface {
7978
// WantsDefaultRegistryFunc should be implemented by admission plugins that need to know the default registry
8079
// address.
8180
type WantsDefaultRegistryFunc interface {
82-
SetDefaultRegistryFunc(imageapi.DefaultRegistryFunc)
81+
SetDefaultRegistryFunc(func() (string, bool))
8382
admission.Validator
8483
}
8584

pkg/cmd/server/api/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,15 @@ type ImagePolicyConfig struct {
519519
// this policy - typically only administrators or system integrations will have those
520520
// permissions.
521521
AllowedRegistriesForImport *AllowedRegistries
522+
// InternalRegistryHostname sets the hostname for the default internal Docker
523+
// Registry. This can be overriden by using OPENSHIFT_DEFAULT_REGISTRY
524+
// environment variable.
525+
InternalRegistryHostname string
526+
// ExternalRegistryHostname sets the hostname for the default external Docker
527+
// Registry. The external hostname should be set only when the registry is
528+
// exposed externally. The value is used in 'publicDockerImageRepository'
529+
// field in ImageStreams.
530+
ExternalRegistryHostname string
522531
}
523532

524533
// AllowedRegistries represents a list of registries allowed for the image import.

pkg/cmd/server/api/v1/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,15 @@ type ImagePolicyConfig struct {
377377
// this policy - typically only administrators or system integrations will have those
378378
// permissions.
379379
AllowedRegistriesForImport *AllowedRegistries `json:"allowedRegistriesForImport,omitempty"`
380+
// InternalRegistryHostname sets the hostname for the default internal Docker
381+
// Registry. This can be overriden by using OPENSHIFT_DEFAULT_REGISTRY
382+
// environment variable.
383+
InternalRegistryHostname string `json:"internalRegistryHostname"`
384+
// ExternalRegistryHostname sets the hostname for the default external Docker
385+
// Registry. The external hostname should be set only when the registry is
386+
// exposed externally. The value is used in 'publicDockerImageRepository'
387+
// field in ImageStreams.
388+
ExternalRegistryHostname string `json:"externalRegistryHostname"`
380389
}
381390

382391
// AllowedRegistries represents a list of registries allowed for the image import.

pkg/cmd/server/origin/master.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (c *MasterConfig) newOpenshiftAPIConfig(kubeAPIServerConfig apiserver.Confi
7373
RuleResolver: c.RuleResolver,
7474
SubjectLocator: c.SubjectLocator,
7575
LimitVerifier: c.LimitVerifier,
76-
RegistryNameFn: c.RegistryNameFn,
76+
RegistryHostnameRetriever: c.RegistryHostnameRetriever,
7777
AllowedRegistriesForImport: c.Options.ImagePolicyConfig.AllowedRegistriesForImport,
7878
MaxImagesBulkImportedPerRepository: c.Options.ImagePolicyConfig.MaxImagesBulkImportedPerRepository,
7979
RouteAllocator: c.RouteAllocator(),

pkg/cmd/server/origin/master_config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,9 @@ type MasterConfig struct {
141141

142142
// ImageFor is a function that returns the appropriate image to use for a named component
143143
ImageFor func(component string) string
144-
// RegistryNameFn retrieves the name of the integrated registry, or false if no such registry
144+
// RegistryHostnameRetriever retrieves the name of the integrated registry, or false if no such registry
145145
// is available.
146-
RegistryNameFn imageapi.DefaultRegistryFunc
146+
RegistryHostnameRetriever imageapi.RegistryHostnameRetriever
147147

148148
// ExternalVersionCodec is the codec used when serializing annotations, which cannot be changed
149149
// without all clients being aware of the new version.
@@ -318,7 +318,7 @@ func BuildMasterConfig(options configapi.MasterConfig, informers InformerAccess)
318318
Informers: informers.GetInternalKubeInformers(),
319319
ClusterResourceQuotaInformer: informers.GetQuotaInformers().Quota().InternalVersion().ClusterResourceQuotas(),
320320
ClusterQuotaMapper: clusterQuotaMappingController.GetClusterQuotaMapper(),
321-
DefaultRegistryFn: imageapi.DefaultRegistryFunc(defaultRegistryFunc),
321+
RegistryHostnameRetriever: imageapi.DefaultRegistryHostnameRetriever(defaultRegistryFunc, options.ImagePolicyConfig.ExternalRegistryHostname, options.ImagePolicyConfig.InternalRegistryHostname),
322322
SecurityInformers: informers.GetSecurityInformers(),
323323
UserInformers: informers.GetUserInformers(),
324324
}
@@ -365,8 +365,8 @@ func BuildMasterConfig(options configapi.MasterConfig, informers InformerAccess)
365365
AdmissionControl: originAdmission,
366366
KubeAdmissionControl: kubeAdmission,
367367

368-
ImageFor: imageTemplate.ExpandOrDie,
369-
RegistryNameFn: imageapi.DefaultRegistryFunc(defaultRegistryFunc),
368+
ImageFor: imageTemplate.ExpandOrDie,
369+
RegistryHostnameRetriever: imageapi.DefaultRegistryHostnameRetriever(defaultRegistryFunc, "", ""),
370370

371371
// TODO: migration of versions of resources stored in annotations must be sorted out
372372
ExternalVersionCodec: kapi.Codecs.LegacyCodec(schema.GroupVersion{Group: "", Version: "v1"}),

pkg/cmd/server/origin/openshift_apiserver.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ type OpenshiftAPIConfig struct {
8383
RuleResolver rbacregistryvalidation.AuthorizationRuleResolver
8484
SubjectLocator authorizer.SubjectLocator
8585
LimitVerifier imageadmission.LimitVerifier
86-
// RegistryNameFn retrieves the name of the integrated registry, or false if no such registry
87-
// is available.
88-
RegistryNameFn imageapi.DefaultRegistryFunc
86+
// RegistryHostnameRetriever retrieves the internal and external hostname of
87+
// the integrated registry, or false if no such registry is available.
88+
RegistryHostnameRetriever imageapi.RegistryHostnameRetriever
8989
AllowedRegistriesForImport *configapi.AllowedRegistries
9090
MaxImagesBulkImportedPerRepository int
9191

@@ -144,8 +144,8 @@ func (c *OpenshiftAPIConfig) Validate() error {
144144
if c.LimitVerifier == nil {
145145
ret = append(ret, fmt.Errorf("LimitVerifier is required"))
146146
}
147-
if c.RegistryNameFn == nil {
148-
ret = append(ret, fmt.Errorf("RegistryNameFn is required"))
147+
if c.RegistryHostnameRetriever == nil {
148+
ret = append(ret, fmt.Errorf("RegistryHostnameRetriever is required"))
149149
}
150150
if c.RouteAllocator == nil {
151151
ret = append(ret, fmt.Errorf("RouteAllocator is required"))

pkg/cmd/server/origin/storage.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,12 @@ func (c OpenshiftAPIConfig) GetRestStorage() (map[schema.GroupVersion]map[string
204204
imageRegistry := image.NewRegistry(imageStorage)
205205
imageSignatureStorage := imagesignature.NewREST(c.DeprecatedOpenshiftClient.Images())
206206
imageStreamSecretsStorage := imagesecret.NewREST(c.KubeClientInternal.Core())
207-
imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage, err := imagestreametcd.NewREST(c.GenericConfig.RESTOptionsGetter, c.RegistryNameFn, subjectAccessReviewRegistry, c.LimitVerifier)
207+
imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage, err := imagestreametcd.NewREST(c.GenericConfig.RESTOptionsGetter, c.RegistryHostnameRetriever, subjectAccessReviewRegistry, c.LimitVerifier)
208208
if err != nil {
209209
return nil, fmt.Errorf("error building REST storage: %v", err)
210210
}
211211
imageStreamRegistry := imagestream.NewRegistry(imageStreamStorage, imageStreamStatusStorage, internalImageStreamStorage)
212-
imageStreamMappingStorage := imagestreammapping.NewREST(imageRegistry, imageStreamRegistry, c.RegistryNameFn)
212+
imageStreamMappingStorage := imagestreammapping.NewREST(imageRegistry, imageStreamRegistry, c.RegistryHostnameRetriever)
213213
imageStreamTagStorage := imagestreamtag.NewREST(imageRegistry, imageStreamRegistry)
214214
importerCache, err := imageimporter.NewImageStreamLayerCache(imageimporter.DefaultImageStreamLayerCacheSize)
215215
if err != nil {
@@ -231,7 +231,7 @@ func (c OpenshiftAPIConfig) GetRestStorage() (map[schema.GroupVersion]map[string
231231
insecureImportTransport,
232232
importerDockerClientFn,
233233
c.AllowedRegistriesForImport,
234-
c.RegistryNameFn,
234+
c.RegistryHostnameRetriever,
235235
c.DeprecatedOpenshiftClient.SubjectAccessReviews())
236236
imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageStreamRegistry)
237237

pkg/image/admission/imagepolicy/imagepolicy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func newImagePolicyPlugin(parsed *api.ImagePolicyConfig) (*imagePolicyPlugin, er
108108
}, nil
109109
}
110110

111-
func (a *imagePolicyPlugin) SetDefaultRegistryFunc(fn imageapi.DefaultRegistryFunc) {
111+
func (a *imagePolicyPlugin) SetDefaultRegistryFunc(fn func() (string, bool)) {
112112
a.integratedRegistryMatcher.RegistryMatcher = rules.RegistryNameMatcher(fn)
113113
}
114114

pkg/image/admission/imagepolicy/rules/rules.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ type RegistryMatcher interface {
2323
Matches(name string) bool
2424
}
2525

26-
type RegistryNameMatcher imageapi.DefaultRegistryFunc
26+
type RegistryNameMatcher func() (string, bool)
2727

2828
func (m RegistryNameMatcher) Matches(name string) bool {
29-
current, ok := imageapi.DefaultRegistryFunc(m)()
29+
current, ok := m()
3030
if !ok {
3131
return false
3232
}

pkg/image/apis/image/helper.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,45 @@ var errNoRegistryURLPathAllowed = fmt.Errorf("no path after <host>[:<port>] is a
4545
var errNoRegistryURLQueryAllowed = fmt.Errorf("no query arguments are allowed after <host>[:<port>]")
4646
var errRegistryURLHostEmpty = fmt.Errorf("no host name specified")
4747

48-
// DefaultRegistry returns the default Docker registry (host or host:port), or false if it is not available.
49-
type DefaultRegistry interface {
50-
DefaultRegistry() (string, bool)
48+
// RegistryHostnameRetriever represents an interface for retrieving the hostname
49+
// of internal and external registry.
50+
type RegistryHostnameRetriever interface {
51+
InternalRegistryHostnameFn() func() (string, bool)
52+
ExternalRegistryHostnameFn() func() (string, bool)
5153
}
5254

53-
// DefaultRegistryFunc implements DefaultRegistry for a simple function.
54-
type DefaultRegistryFunc func() (string, bool)
55+
// DefaultRegistryHostnameRetriever is a default implementation of
56+
// RegistryHostnameRetriever.
57+
// The first argument is a function that lazy-loads the value of
58+
// OPENSHIFT_DEFAULT_REGISTRY environment variable.
59+
func DefaultRegistryHostnameRetriever(defaultFn func() (string, bool), external, internal string) RegistryHostnameRetriever {
60+
return &defaultRegistryHostnameRetriever{defaultFn: defaultFn, external: external, internal: internal}
61+
}
62+
63+
type defaultRegistryHostnameRetriever struct {
64+
defaultFn func() (string, bool)
65+
internal, external string
66+
}
67+
68+
// InternalRegistryHostnameFn returns a function that can be used to lazy-load
69+
// the internal Docker Registry hostname. If the master configuration propertly
70+
// InternalRegistryHostname is set, it will prefer that over the lazy-loaded
71+
// environment variable 'OPENSHIFT_DEFAULT_REGISTRY'.
72+
func (r *defaultRegistryHostnameRetriever) InternalRegistryHostnameFn() func() (string, bool) {
73+
if len(r.internal) > 0 {
74+
return func() (string, bool) { return r.internal, true }
75+
}
76+
if r.defaultFn == nil {
77+
return func() (string, bool) { return "", false }
78+
}
79+
return r.defaultFn
80+
}
5581

56-
// DefaultRegistry implements the DefaultRegistry interface for a function.
57-
func (fn DefaultRegistryFunc) DefaultRegistry() (string, bool) {
58-
return fn()
82+
// ExternalRegistryHostnameFn returns a function that can be used to retrieve an
83+
// external/public hostname of Docker Registry. External location can be
84+
// configured in master config using 'ExternalRegistryHostname' property.
85+
func (r *defaultRegistryHostnameRetriever) ExternalRegistryHostnameFn() func() (string, bool) {
86+
return func() (string, bool) { return r.external, len(r.external) > 0 }
5987
}
6088

6189
// ParseImageStreamImageName splits a string into its name component and ID component, and returns an error

pkg/image/registry/imagestream/etcd/etcd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type REST struct {
2525
var _ rest.StandardStorage = &REST{}
2626

2727
// NewREST returns a new REST.
28-
func NewREST(optsGetter restoptions.Getter, defaultRegistry imageapi.DefaultRegistry, subjectAccessReviewRegistry subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier) (*REST, *StatusREST, *InternalREST, error) {
28+
func NewREST(optsGetter restoptions.Getter, registryHostname imageapi.RegistryHostnameRetriever, subjectAccessReviewRegistry subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier) (*REST, *StatusREST, *InternalREST, error) {
2929
store := registry.Store{
3030
Copier: kapi.Scheme,
3131
NewFunc: func() runtime.Object { return &imageapi.ImageStream{} },
@@ -39,7 +39,7 @@ func NewREST(optsGetter restoptions.Getter, defaultRegistry imageapi.DefaultRegi
3939
subjectAccessReviewRegistry: subjectAccessReviewRegistry,
4040
}
4141
// strategy must be able to load image streams across namespaces during tag verification
42-
strategy := imagestream.NewStrategy(defaultRegistry, subjectAccessReviewRegistry, limitVerifier, rest)
42+
strategy := imagestream.NewStrategy(registryHostname, subjectAccessReviewRegistry, limitVerifier, rest)
4343

4444
store.CreateStrategy = strategy
4545
store.UpdateStrategy = strategy

pkg/image/registry/imagestream/etcd/etcd_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ const (
2727
)
2828

2929
var (
30-
testDefaultRegistry = imageapi.DefaultRegistryFunc(func() (string, bool) { return "test", true })
31-
noDefaultRegistry = imageapi.DefaultRegistryFunc(func() (string, bool) { return "", false })
30+
testDefaultRegistry = func() (string, bool) { return "test", true }
31+
noDefaultRegistry = func() (string, bool) { return "", false }
3232
)
3333

3434
type fakeSubjectAccessReviewRegistry struct {
@@ -48,7 +48,8 @@ func (f *fakeSubjectAccessReviewRegistry) CreateSubjectAccessReview(ctx apireque
4848

4949
func newStorage(t *testing.T) (*REST, *StatusREST, *InternalREST, *etcdtesting.EtcdTestServer) {
5050
etcdStorage, server := registrytest.NewEtcdStorage(t, latest.Version.Group)
51-
imageStorage, statusStorage, internalStorage, err := NewREST(restoptions.NewSimpleGetter(etcdStorage), noDefaultRegistry, &fakeSubjectAccessReviewRegistry{}, &testutil.FakeImageStreamLimitVerifier{})
51+
registry := imageapi.DefaultRegistryHostnameRetriever(noDefaultRegistry, "", "")
52+
imageStorage, statusStorage, internalStorage, err := NewREST(restoptions.NewSimpleGetter(etcdStorage), registry, &fakeSubjectAccessReviewRegistry{}, &testutil.FakeImageStreamLimitVerifier{})
5253
if err != nil {
5354
t.Fatal(err)
5455
}

pkg/image/registry/imagestream/strategy.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,22 @@ type ResourceGetter interface {
3434
type Strategy struct {
3535
runtime.ObjectTyper
3636
names.NameGenerator
37-
defaultRegistry imageapi.DefaultRegistry
38-
tagVerifier *TagVerifier
39-
limitVerifier imageadmission.LimitVerifier
40-
imageStreamGetter ResourceGetter
37+
registryHostnameRetriever imageapi.RegistryHostnameRetriever
38+
tagVerifier *TagVerifier
39+
limitVerifier imageadmission.LimitVerifier
40+
imageStreamGetter ResourceGetter
4141
}
4242

4343
// NewStrategy is the default logic that applies when creating and updating
4444
// ImageStream objects via the REST API.
45-
func NewStrategy(defaultRegistry imageapi.DefaultRegistry, subjectAccessReviewClient subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier, imageStreamGetter ResourceGetter) Strategy {
45+
func NewStrategy(registryHostname imageapi.RegistryHostnameRetriever, subjectAccessReviewClient subjectaccessreview.Registry, limitVerifier imageadmission.LimitVerifier, imageStreamGetter ResourceGetter) Strategy {
4646
return Strategy{
47-
ObjectTyper: kapi.Scheme,
48-
NameGenerator: names.SimpleNameGenerator,
49-
defaultRegistry: defaultRegistry,
50-
tagVerifier: &TagVerifier{subjectAccessReviewClient},
51-
limitVerifier: limitVerifier,
52-
imageStreamGetter: imageStreamGetter,
47+
ObjectTyper: kapi.Scheme,
48+
NameGenerator: names.SimpleNameGenerator,
49+
registryHostnameRetriever: registryHostname,
50+
tagVerifier: &TagVerifier{subjectAccessReviewClient},
51+
limitVerifier: limitVerifier,
52+
imageStreamGetter: imageStreamGetter,
5353
}
5454
}
5555

@@ -117,7 +117,7 @@ func (Strategy) AllowUnconditionalUpdate() bool {
117117
// if a default registry exists, the value returned is of the form
118118
// <default registry>/<namespace>/<stream name>.
119119
func (s Strategy) dockerImageRepository(stream *imageapi.ImageStream) string {
120-
registry, ok := s.defaultRegistry.DefaultRegistry()
120+
registry, ok := s.registryHostnameRetriever.InternalRegistryHostnameFn()()
121121
if !ok {
122122
return stream.Spec.DockerImageRepository
123123
}
@@ -133,6 +133,23 @@ func (s Strategy) dockerImageRepository(stream *imageapi.ImageStream) string {
133133
return ref.String()
134134
}
135135

136+
// publicDockerImageRepository determines the public location of given image
137+
// stream. If the ExternalRegistryHostname is set in the master config, the
138+
// value of this property is used as a hostname part for the docker image
139+
// reference.
140+
func (s Strategy) publicDockerImageRepository(stream *imageapi.ImageStream) string {
141+
externalHostname, ok := s.registryHostnameRetriever.ExternalRegistryHostnameFn()()
142+
if !ok {
143+
return ""
144+
}
145+
ref := imageapi.DockerImageReference{
146+
Registry: externalHostname,
147+
Namespace: stream.Namespace,
148+
Name: stream.Name,
149+
}
150+
return ref.String()
151+
}
152+
136153
func parseFromReference(stream *imageapi.ImageStream, from *kapi.ObjectReference) (string, string, error) {
137154
splitChar := ""
138155
refType := ""
@@ -164,7 +181,7 @@ func parseFromReference(stream *imageapi.ImageStream, from *kapi.ObjectReference
164181
// tagsChanged updates stream.Status.Tags based on the old and new image stream.
165182
// if the old stream is nil, all tags are considered additions.
166183
func (s Strategy) tagsChanged(old, stream *imageapi.ImageStream) field.ErrorList {
167-
internalRegistry, hasInternalRegistry := s.defaultRegistry.DefaultRegistry()
184+
internalRegistry, hasInternalRegistry := s.registryHostnameRetriever.InternalRegistryHostnameFn()()
168185

169186
var errs field.ErrorList
170187

@@ -522,10 +539,12 @@ func (s Strategy) Decorate(obj runtime.Object) error {
522539
switch t := obj.(type) {
523540
case *imageapi.ImageStream:
524541
t.Status.DockerImageRepository = s.dockerImageRepository(t)
542+
t.Status.PublicDockerImageRepository = s.publicDockerImageRepository(t)
525543
case *imageapi.ImageStreamList:
526544
for i := range t.Items {
527545
is := &t.Items[i]
528546
is.Status.DockerImageRepository = s.dockerImageRepository(is)
547+
is.Status.PublicDockerImageRepository = s.publicDockerImageRepository(is)
529548
}
530549
default:
531550
return kerrors.NewBadRequest(fmt.Sprintf("not an ImageStream nor ImageStreamList: %v", obj))

0 commit comments

Comments
 (0)