make the resource prefix in etcd configurable for cohabitation

This commit is contained in:
deads2k 2016-07-25 14:04:00 -04:00
parent 27bb99d41e
commit aa3db4d995
44 changed files with 99 additions and 49 deletions

View File

@ -38,6 +38,7 @@ import (
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/apiserver/authenticator"
"k8s.io/kubernetes/pkg/capabilities"
@ -185,7 +186,7 @@ func Run(s *options.APIServer) error {
if err != nil {
glog.Fatalf("Unable to get serviceaccounts storage: %v", err)
}
serviceAccountGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storage)
serviceAccountGetter = serviceaccountcontroller.NewGetterFromStorageInterface(storage, storageFactory.ResourcePrefix(api.Resource("serviceaccounts")), storageFactory.ResourcePrefix(api.Resource("secrets")))
}
authenticator, err := authenticator.New(authenticator.AuthenticatorConfig{
@ -222,11 +223,11 @@ func Run(s *options.APIServer) error {
if modeEnabled(apiserver.ModeRBAC) {
mustGetRESTOptions := func(resource string) generic.RESTOptions {
s, err := storageFactory.New(api.Resource(resource))
s, err := storageFactory.New(rbac.Resource(resource))
if err != nil {
glog.Fatalf("Unable to get %s storage: %v", resource, err)
}
return generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage}
return generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage, ResourcePrefix: storageFactory.ResourcePrefix(rbac.Resource(resource))}
}
// For initial bootstrapping go directly to etcd to avoid privillege escalation check.

View File

@ -168,5 +168,6 @@ func createRESTOptionsOrDie(s *genericoptions.ServerRunOptions, g *genericapiser
Storage: storage,
Decorator: g.StorageDecorator(),
DeleteCollectionWorkers: s.DeleteCollectionWorkers,
ResourcePrefix: f.ResourcePrefix(resource),
}
}

View File

@ -69,9 +69,9 @@ func (r *registryGetter) GetSecret(namespace, name string) (*api.Secret, error)
// NewGetterFromStorageInterface returns a ServiceAccountTokenGetter that
// uses the specified storage to retrieve service accounts and secrets.
func NewGetterFromStorageInterface(s storage.Interface) serviceaccount.ServiceAccountTokenGetter {
func NewGetterFromStorageInterface(s storage.Interface, saPrefix, secretPrefix string) serviceaccount.ServiceAccountTokenGetter {
return NewGetterFromRegistries(
serviceaccountregistry.NewRegistry(serviceaccountetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage})),
secret.NewRegistry(secretetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage})),
serviceaccountregistry.NewRegistry(serviceaccountetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage, ResourcePrefix: saPrefix})),
secret.NewRegistry(secretetcd.NewREST(generic.RESTOptions{Storage: s, Decorator: generic.UndecoratedStorage, ResourcePrefix: secretPrefix})),
)
}

View File

@ -19,6 +19,7 @@ package genericapiserver
import (
"fmt"
"mime"
"strings"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
@ -37,6 +38,12 @@ type StorageFactory interface {
// New finds the storage destination for the given group and resource. It will
// return an error if the group has no storage destination configured.
New(groupResource unversioned.GroupResource) (storage.Interface, error)
// ResourcePrefix returns the overridden resource prefix for the GroupResource
// This allows for cohabitation of resources with different native types and provides
// centralized control over the shape of etcd directories
ResourcePrefix(groupResource unversioned.GroupResource) string
// Backends gets all backends for all registered storage destinations.
// Used for getting all instances for health validations.
Backends() []string
@ -78,8 +85,12 @@ type groupResourceOverrides struct {
// etcdLocation contains the list of "special" locations that are used for particular GroupResources
// These are merged on top of the StorageConfig when requesting the storage.Interface for a given GroupResource
etcdLocation []string
// etcdPrefix contains the list of "special" prefixes for a GroupResource. Resource=* means for the entire group
// etcdPrefix is the base location for a GroupResource.
etcdPrefix string
// etcdResourcePrefix is the location to use to store a particular type under the `etcdPrefix` location
// If empty, the default mapping is used. If the default mapping doesn't contain an entry, it will use
// the ToLowered name of the resource, not including the group.
etcdResourcePrefix string
// mediaType is the desired serializer to choose. If empty, the default is chosen.
mediaType string
// serializer contains the list of "special" serializers for a GroupResource. Resource=* means for the entire group
@ -123,6 +134,13 @@ func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource unversioned.GroupRes
s.Overrides[groupResource] = overrides
}
// SetResourceEtcdPrefix sets the prefix for a resource, but not the base-dir. You'll end up in `etcdPrefix/resourceEtcdPrefix`.
func (s *DefaultStorageFactory) SetResourceEtcdPrefix(groupResource unversioned.GroupResource, prefix string) {
overrides := s.Overrides[groupResource]
overrides.etcdResourcePrefix = prefix
s.Overrides[groupResource] = overrides
}
func (s *DefaultStorageFactory) SetSerializer(groupResource unversioned.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
overrides := s.Overrides[groupResource]
overrides.mediaType = mediaType
@ -268,3 +286,30 @@ func NewStorageCodec(storageMediaType string, ns runtime.StorageSerializer, stor
}
return runtime.NewCodec(encoder, decoder), nil
}
var specialDefaultResourcePrefixes = map[unversioned.GroupResource]string{
unversioned.GroupResource{Group: "", Resource: "replicationControllers"}: "controllers",
unversioned.GroupResource{Group: "", Resource: "replicationcontrollers"}: "controllers",
unversioned.GroupResource{Group: "", Resource: "endpoints"}: "services/endpoints",
unversioned.GroupResource{Group: "", Resource: "nodes"}: "minions",
unversioned.GroupResource{Group: "", Resource: "services"}: "services/specs",
}
func (s *DefaultStorageFactory) ResourcePrefix(groupResource unversioned.GroupResource) string {
chosenStorageResource := s.getStorageGroupResource(groupResource)
groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
exactResourceOverride := s.Overrides[chosenStorageResource]
etcdResourcePrefix := specialDefaultResourcePrefixes[chosenStorageResource]
if len(groupOverride.etcdResourcePrefix) > 0 {
etcdResourcePrefix = groupOverride.etcdResourcePrefix
}
if len(exactResourceOverride.etcdResourcePrefix) > 0 {
etcdResourcePrefix = exactResourceOverride.etcdResourcePrefix
}
if len(etcdResourcePrefix) == 0 {
etcdResourcePrefix = strings.ToLower(chosenStorageResource.Resource)
}
return etcdResourcePrefix
}

View File

@ -731,6 +731,7 @@ func (m *Master) GetRESTOptionsOrDie(c *Config, resource unversioned.GroupResour
Storage: storage,
Decorator: m.StorageDecorator(),
DeleteCollectionWorkers: m.deleteCollectionWorkers,
ResourcePrefix: c.StorageFactory.ResourcePrefix(resource),
}
}

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against ClusterRole objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/clusterroles"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &rbac.ClusterRoleList{} }
storageInterface := opts.Decorator(

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against ClusterRoleBinding objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/clusterrolebindings"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &rbac.ClusterRoleBindingList{} }
storageInterface := opts.Decorator(

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work with ConfigMap objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/configmaps"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.ConfigMapList{} }
storageInterface := opts.Decorator(

View File

@ -59,7 +59,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/controllers"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.ReplicationControllerList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against DaemonSets.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/daemonsets"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensions.DaemonSetList{} }
storageInterface := opts.Decorator(

View File

@ -59,7 +59,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against deployments.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *RollbackREST) {
prefix := "/deployments"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensions.DeploymentList{} }
storageInterface := opts.Decorator(

View File

@ -41,7 +41,7 @@ const defaultReplicas = 100
func newStorage(t *testing.T) (*DeploymentStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName)
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "deployments"}
deploymentStorage := NewStorage(restOptions)
return &deploymentStorage, server
}

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against endpoints.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/services/endpoints"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.EndpointsList{} }
storageInterface := opts.Decorator(

View File

@ -30,7 +30,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against events.
func NewREST(opts generic.RESTOptions, ttl uint64) *REST {
prefix := "/events"
prefix := "/" + opts.ResourcePrefix
// We explicitly do NOT do any decoration here - switching on Cacher
// for events will lead to too high memory consumption.

View File

@ -32,7 +32,7 @@ import (
func newStorage(t *testing.T) (*ScaleREST, *etcdtesting.EtcdTestServer, storage.Interface) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "")
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "controllers"}
return NewStorage(restOptions).Scale, server, etcdStorage
}

View File

@ -25,4 +25,6 @@ type RESTOptions struct {
Storage pkgstorage.Interface
Decorator StorageDecorator
DeleteCollectionWorkers int
ResourcePrefix string
}

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/horizontalpodautoscalers"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &autoscaling.HorizontalPodAutoscalerList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/ingress"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensions.IngressList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against Jobs.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/jobs"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &batch.JobList{} }
storageInterface := opts.Decorator(

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against horizontal pod autoscalers.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/limitranges"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.LimitRangeList{} }
storageInterface := opts.Decorator(

View File

@ -50,7 +50,7 @@ type FinalizeREST struct {
// NewREST returns a RESTStorage object that will work against namespaces.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *FinalizeREST) {
prefix := "/namespaces"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.NamespaceList{} }
storageInterface := opts.Decorator(

View File

@ -31,7 +31,7 @@ import (
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "")
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "namespaces"}
namespaceStorage, _, _ := NewREST(restOptions)
return namespaceStorage, server
}

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against network policies.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/networkpolicies"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensionsapi.NetworkPolicyList{} }
storageInterface := opts.Decorator(

View File

@ -66,7 +66,7 @@ func (r *StatusREST) Update(ctx api.Context, name string, objInfo rest.UpdatedOb
// NewREST returns a RESTStorage object that will work against nodes.
func NewStorage(opts generic.RESTOptions, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper) NodeStorage {
prefix := "/minions"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.NodeList{} }
storageInterface := opts.Decorator(

View File

@ -33,7 +33,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against persistent volumes.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/persistentvolumes"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.PersistentVolumeList{} }
storageInterface := opts.Decorator(

View File

@ -33,7 +33,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against persistent volume claims.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/persistentvolumeclaims"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.PersistentVolumeClaimList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/petsets"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &appsapi.PetSetList{} }
storageInterface := opts.Decorator(

View File

@ -33,7 +33,7 @@ import (
func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName)
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "petsets"}
petSetStorage, statusStorage := NewREST(restOptions)
return petSetStorage, statusStorage, server
}

View File

@ -57,7 +57,7 @@ type REST struct {
// NewStorage returns a RESTStorage object that will work against pods.
func NewStorage(opts generic.RESTOptions, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper) PodStorage {
prefix := "/pods"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.PodList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against pod disruption budgets.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/poddisruptionbudgets"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &policyapi.PodDisruptionBudgetList{} }
storageInterface := opts.Decorator(

View File

@ -34,7 +34,7 @@ import (
func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, policy.GroupName)
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "poddisruptionbudgets"}
podDisruptionBudgetStorage, statusStorage := NewREST(restOptions)
return podDisruptionBudgetStorage, statusStorage, server
}

View File

@ -31,16 +31,16 @@ type REST struct {
*registry.Store
}
const Prefix = "/podsecuritypolicies"
// NewREST returns a RESTStorage object that will work against PodSecurityPolicy objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensions.PodSecurityPolicyList{} }
storageInterface := opts.Decorator(
opts.Storage,
100,
&extensions.PodSecurityPolicy{},
Prefix,
prefix,
podsecuritypolicy.Strategy,
newListFunc,
storage.NoTriggerPublisher,
@ -50,10 +50,10 @@ func NewREST(opts generic.RESTOptions) *REST {
NewFunc: func() runtime.Object { return &extensions.PodSecurityPolicy{} },
NewListFunc: newListFunc,
KeyRootFunc: func(ctx api.Context) string {
return Prefix
return prefix
},
KeyFunc: func(ctx api.Context, name string) (string, error) {
return registry.NoNamespaceKeyFunc(ctx, Prefix, name)
return registry.NoNamespaceKeyFunc(ctx, prefix, name)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*extensions.PodSecurityPolicy).Name, nil

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against pod templates.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/podtemplates"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.PodTemplateList{} }
storageInterface := opts.Decorator(

View File

@ -58,7 +58,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against ReplicaSet.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/replicasets"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &extensions.ReplicaSetList{} }
storageInterface := opts.Decorator(

View File

@ -38,7 +38,7 @@ const defaultReplicas = 100
func newStorage(t *testing.T) (*ReplicaSetStorage, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions")
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "replicasets"}
replicaSetStorage := NewStorage(restOptions)
return &replicaSetStorage, server
}

View File

@ -33,7 +33,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against resource quotas.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/resourcequotas"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.ResourceQuotaList{} }
storageInterface := opts.Decorator(

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against Role objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/roles"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &rbac.RoleList{} }
storageInterface := opts.Decorator(

View File

@ -34,7 +34,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against RoleBinding objects.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/rolebindings"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &rbac.RoleBindingList{} }
storageInterface := opts.Decorator(

View File

@ -35,7 +35,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against ScheduledJobs.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/scheduledjobs"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &batch.ScheduledJobList{} }
storageInterface := opts.Decorator(

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against secrets.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/secrets"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.SecretList{} }
storageInterface := opts.Decorator(

View File

@ -33,7 +33,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against services.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/services/specs"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.ServiceList{} }
storageInterface := opts.Decorator(

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a RESTStorage object that will work against service accounts.
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/serviceaccounts"
prefix := "/" + opts.ResourcePrefix
newListFunc := func() runtime.Object { return &api.ServiceAccountList{} }
storageInterface := opts.Decorator(

View File

@ -32,7 +32,7 @@ type REST struct {
// NewREST returns a registry which will store ThirdPartyResource in the given helper
func NewREST(opts generic.RESTOptions) *REST {
prefix := "/thirdpartyresources"
prefix := "/" + opts.ResourcePrefix
// We explicitly do NOT do any decoration here yet.
storageInterface := opts.Storage

View File

@ -81,7 +81,7 @@ func newRBACAuthorizer(t *testing.T, superUser string, config *master.Config) au
if err != nil {
t.Fatalf("failed to get storage: %v", err)
}
return generic.RESTOptions{Storage: storageInterface, Decorator: generic.UndecoratedStorage}
return generic.RESTOptions{Storage: storageInterface, Decorator: generic.UndecoratedStorage, ResourcePrefix: resource}
}
roleRegistry := role.NewRegistry(roleetcd.NewREST(newRESTOptions("roles")))