Convert Secret registry to use update/create strategy, allow filtering by Type

This commit is contained in:
Jordan Liggitt 2015-04-27 23:50:56 -04:00
parent c2a78483b4
commit ac67fff1cf
14 changed files with 391 additions and 524 deletions

View File

@ -1648,4 +1648,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta1", "Secret",
func(label, value string) (string, string, error) {
switch label {
case "type":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -1564,4 +1564,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta2", "Secret",
func(label, value string) (string, string, error) {
switch label {
case "type":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -2807,4 +2807,17 @@ func init() {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
err = newer.Scheme.AddFieldLabelConversionFunc("v1beta3", "Secret",
func(label, value string) (string, string, error) {
switch label {
case "type":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}

View File

@ -1242,6 +1242,29 @@ func ValidateSecret(secret *api.Secret) errs.ValidationErrorList {
allErrs = append(allErrs, errs.NewFieldForbidden("data", "Maximum secret size exceeded"))
}
switch secret.Type {
case api.SecretTypeOpaque, "":
// no-op
default:
// no-op
}
return allErrs
}
// ValidateSecretUpdate tests if required fields in the Secret are set.
func ValidateSecretUpdate(oldSecret, newSecret *api.Secret) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldSecret.ObjectMeta, &newSecret.ObjectMeta).Prefix("metadata")...)
if len(newSecret.Type) == 0 {
newSecret.Type = oldSecret.Type
}
if newSecret.Type != oldSecret.Type {
allErrs = append(allErrs, errs.NewFieldInvalid("type", newSecret.Type, "field is immutable"))
}
allErrs = append(allErrs, ValidateSecret(newSecret)...)
return allErrs
}

View File

@ -20,6 +20,16 @@ import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/metrics"
@ -31,15 +41,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
"github.com/golang/glog"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
)
// specialParams lists parameters that are handled specially and which users of Request
@ -253,6 +254,7 @@ const (
NodeUnschedulable = "spec.unschedulable"
ObjectNameField = "metadata.name"
PodHost = "spec.host"
SecretType = "type"
)
type clientFieldNameToAPIVersionFieldName map[string]string
@ -305,6 +307,9 @@ var fieldMappings = versionToResourceToFieldMapping{
"pods": clientFieldNameToAPIVersionFieldName{
PodHost: "DesiredState.Host",
},
"secrets": clientFieldNameToAPIVersionFieldName{
SecretType: "type",
},
},
"v1beta2": resourceTypeToFieldMapping{
"nodes": clientFieldNameToAPIVersionFieldName{
@ -318,6 +323,9 @@ var fieldMappings = versionToResourceToFieldMapping{
"pods": clientFieldNameToAPIVersionFieldName{
PodHost: "DesiredState.Host",
},
"secrets": clientFieldNameToAPIVersionFieldName{
SecretType: "type",
},
},
"v1beta3": resourceTypeToFieldMapping{
"nodes": clientFieldNameToAPIVersionFieldName{
@ -331,6 +339,9 @@ var fieldMappings = versionToResourceToFieldMapping{
"pods": clientFieldNameToAPIVersionFieldName{
PodHost: "spec.host",
},
"secrets": clientFieldNameToAPIVersionFieldName{
SecretType: "type",
},
},
}

View File

@ -255,7 +255,7 @@ var eventColumns = []string{"FIRSTSEEN", "LASTSEEN", "COUNT", "NAME", "KIND", "S
var limitRangeColumns = []string{"NAME"}
var resourceQuotaColumns = []string{"NAME"}
var namespaceColumns = []string{"NAME", "LABELS", "STATUS"}
var secretColumns = []string{"NAME", "DATA"}
var secretColumns = []string{"NAME", "TYPE", "DATA"}
var persistentVolumeColumns = []string{"NAME", "LABELS", "CAPACITY", "ACCESSMODES", "STATUS", "CLAIM"}
var persistentVolumeClaimColumns = []string{"NAME", "LABELS", "STATUS", "VOLUME"}
var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
@ -583,7 +583,7 @@ func printNamespaceList(list *api.NamespaceList, w io.Writer) error {
}
func printSecret(item *api.Secret, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%v\n", item.Name, len(item.Data))
_, err := fmt.Fprintf(w, "%s\t%s\t%v\n", item.Name, item.Type, len(item.Data))
return err
}

View File

@ -60,7 +60,7 @@ import (
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
podtemplateetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate/etcd"
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
secretetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/ui"
@ -381,7 +381,7 @@ func (m *Master) init(c *Config) {
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
resourceQuotaStorage, resourceQuotaStatusStorage := resourcequotaetcd.NewStorage(c.EtcdHelper)
secretRegistry := secret.NewEtcdRegistry(c.EtcdHelper)
secretStorage := secretetcd.NewStorage(c.EtcdHelper)
persistentVolumeStorage, persistentVolumeStatusStorage := pvetcd.NewStorage(c.EtcdHelper)
persistentVolumeClaimStorage, persistentVolumeClaimStatusStorage := pvcetcd.NewStorage(c.EtcdHelper)
@ -428,7 +428,7 @@ func (m *Master) init(c *Config) {
"namespaces": namespaceStorage,
"namespaces/status": namespaceStatusStorage,
"namespaces/finalize": namespaceFinalizeStorage,
"secrets": secret.NewStorage(secretRegistry),
"secrets": secretStorage,
"persistentVolumes": persistentVolumeStorage,
"persistentVolumes/status": persistentVolumeStatusStorage,
"persistentVolumeClaims": persistentVolumeClaimStorage,

View File

@ -0,0 +1,64 @@
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// REST implements a RESTStorage for secrets against etcd
type REST struct {
*etcdgeneric.Etcd
}
// NewStorage returns a registry which will store Secret in the given helper
func NewStorage(h tools.EtcdHelper) *REST {
prefix := "/secrets"
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Secret{} },
NewListFunc: func() runtime.Object { return &api.SecretList{} },
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
KeyFunc: func(ctx api.Context, id string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*api.Secret).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return secret.Matcher(label, field)
},
EndpointName: "secrets",
Helper: h,
}
store.CreateStrategy = secret.Strategy
store.UpdateStrategy = secret.Strategy
return &REST{store}
}

View File

@ -0,0 +1,93 @@
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
)
func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
helper := tools.NewEtcdHelper(fakeEtcdClient, testapi.Codec(), etcdtest.PathPrefix())
return fakeEtcdClient, helper
}
func validNewSecret(name string) *api.Secret {
return &api.Secret{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Data: map[string][]byte{
"test": []byte("data"),
},
}
}
func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
secret := validNewSecret("foo")
secret.Name = ""
secret.GenerateName = "foo-"
test.TestCreate(
// valid
secret,
// invalid
&api.Secret{},
&api.Secret{
ObjectMeta: api.ObjectMeta{Name: "name"},
Data: map[string][]byte{"name with spaces": []byte("")},
},
&api.Secret{
ObjectMeta: api.ObjectMeta{Name: "name"},
Data: map[string][]byte{".dotfile": []byte("")},
},
)
}
func TestUpdate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewStorage(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
key := etcdtest.AddPrefix("secrets/default/foo")
fakeEtcdClient.ExpectNotFoundGet(key)
fakeEtcdClient.ChangeIndex = 2
secret := validNewSecret("foo")
existing := validNewSecret("exists")
obj, err := storage.Create(api.NewDefaultContext(), existing)
if err != nil {
t.Fatalf("unable to create object: %v", err)
}
older := obj.(*api.Secret)
older.ResourceVersion = "1"
test.TestUpdate(
secret,
existing,
older,
)
}

View File

@ -18,32 +18,70 @@ package secret
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// registry implements custom changes to generic.Etcd.
type registry struct {
*etcdgeneric.Etcd
// Registry is an interface implemented by things that know how to store Secret objects.
type Registry interface {
// ListSecrets obtains a list of Secrets having labels which match selector.
ListSecrets(ctx api.Context, selector labels.Selector) (*api.SecretList, error)
// Watch for new/changed/deleted secrets
WatchSecrets(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
// Get a specific Secret
GetSecret(ctx api.Context, name string) (*api.Secret, error)
// Create a Secret based on a specification.
CreateSecret(ctx api.Context, Secret *api.Secret) (*api.Secret, error)
// Update an existing Secret
UpdateSecret(ctx api.Context, Secret *api.Secret) (*api.Secret, error)
// Delete an existing Secret
DeleteSecret(ctx api.Context, name string) error
}
// NewEtcdRegistry returns a registry which will store Secret in the given helper
func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry {
prefix := "/secrets"
return registry{
Etcd: &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.Secret{} },
NewListFunc: func() runtime.Object { return &api.SecretList{} },
EndpointName: "secrets",
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
KeyFunc: func(ctx api.Context, id string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id)
},
Helper: h,
},
}
// storage puts strong typing around storage calls
type storage struct {
rest.StandardStorage
}
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
// types will panic.
func NewRegistry(s rest.StandardStorage) Registry {
return &storage{s}
}
func (s *storage) ListSecrets(ctx api.Context, label labels.Selector) (*api.SecretList, error) {
obj, err := s.List(ctx, label, fields.Everything())
if err != nil {
return nil, err
}
return obj.(*api.SecretList), nil
}
func (s *storage) WatchSecrets(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return s.Watch(ctx, label, field, resourceVersion)
}
func (s *storage) GetSecret(ctx api.Context, name string) (*api.Secret, error) {
obj, err := s.Get(ctx, name)
if err != nil {
return nil, err
}
return obj.(*api.Secret), nil
}
func (s *storage) CreateSecret(ctx api.Context, secret *api.Secret) (*api.Secret, error) {
obj, err := s.Create(ctx, secret)
return obj.(*api.Secret), err
}
func (s *storage) UpdateSecret(ctx api.Context, secret *api.Secret) (*api.Secret, error) {
obj, _, err := s.Update(ctx, secret)
return obj.(*api.Secret), err
}
func (s *storage) DeleteSecret(ctx api.Context, name string) error {
_, err := s.Delete(ctx, name, nil)
return err
}

View File

@ -1,110 +0,0 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secret
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/coreos/go-etcd/etcd"
)
func NewTestSecretEtcdRegistry(t *testing.T) (*tools.FakeEtcdClient, generic.Registry) {
f := tools.NewFakeEtcdClient(t)
f.TestIndex = true
h := tools.NewEtcdHelper(f, testapi.Codec(), etcdtest.PathPrefix())
return f, NewEtcdRegistry(h)
}
func TestSecretCreate(t *testing.T) {
secret := &api.Secret{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "foo",
},
Data: map[string][]byte{
"data-1": []byte("value-1"),
},
}
nodeWithSecret := tools.EtcdResponseWithError{
R: &etcd.Response{
Node: &etcd.Node{
Value: runtime.EncodeOrDie(testapi.Codec(), secret),
ModifiedIndex: 1,
CreatedIndex: 1,
},
},
E: nil,
}
emptyNode := tools.EtcdResponseWithError{
R: &etcd.Response{},
E: tools.EtcdErrorNotFound,
}
ctx := api.NewDefaultContext()
key := "foo"
path, err := etcdgeneric.NamespaceKeyFunc(ctx, "/secrets", key)
path = etcdtest.AddPrefix(path)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
table := map[string]struct {
existing tools.EtcdResponseWithError
expect tools.EtcdResponseWithError
toCreate runtime.Object
errOK func(error) bool
}{
"normal": {
existing: emptyNode,
expect: nodeWithSecret,
toCreate: secret,
errOK: func(err error) bool { return err == nil },
},
"preExisting": {
existing: nodeWithSecret,
expect: nodeWithSecret,
toCreate: secret,
errOK: errors.IsAlreadyExists,
},
}
for name, item := range table {
fakeClient, registry := NewTestSecretEtcdRegistry(t)
fakeClient.Data[path] = item.existing
err := registry.CreateWithName(ctx, key, item.toCreate)
if !item.errOK(err) {
t.Errorf("%v: unexpected error: %v", name, err)
}
if e, a := item.expect, fakeClient.Data[path]; !reflect.DeepEqual(e, a) {
t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a))
}
}
}

View File

@ -1,163 +0,0 @@
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secret
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// REST provides the RESTStorage access patterns to work with Secret objects.
type REST struct {
registry generic.Registry
}
// NewStorage returns a new REST. You must use a registry created by
// NewEtcdRegistry unless you're testing.
func NewStorage(registry generic.Registry) *REST {
return &REST{
registry: registry,
}
}
// Create a Secret object
func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
secret, ok := obj.(*api.Secret)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
if !api.ValidNamespace(ctx, &secret.ObjectMeta) {
return nil, errors.NewConflict("secret", secret.Namespace, fmt.Errorf("Secret.Namespace does not match the provided context"))
}
if len(secret.Name) == 0 {
secret.Name = string(util.NewUUID())
}
if errs := validation.ValidateSecret(secret); len(errs) > 0 {
return nil, errors.NewInvalid("secret", secret.Name, errs)
}
api.FillObjectMetaSystemFields(ctx, &secret.ObjectMeta)
err := rs.registry.CreateWithName(ctx, secret.Name, secret)
if err != nil {
return nil, err
}
return rs.registry.Get(ctx, secret.Name)
}
// Update updates a Secret object.
func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
secret, ok := obj.(*api.Secret)
if !ok {
return nil, false, fmt.Errorf("not a secret: %#v", obj)
}
if !api.ValidNamespace(ctx, &secret.ObjectMeta) {
return nil, false, errors.NewConflict("secret", secret.Namespace, fmt.Errorf("Secret.Namespace does not match the provided context"))
}
oldObj, err := rs.registry.Get(ctx, secret.Name)
if err != nil {
return nil, false, err
}
editSecret := oldObj.(*api.Secret)
// set the editable fields on the existing object
editSecret.Labels = secret.Labels
editSecret.ResourceVersion = secret.ResourceVersion
editSecret.Annotations = secret.Annotations
editSecret.Data = secret.Data
editSecret.Type = secret.Type
if errs := validation.ValidateSecret(editSecret); len(errs) > 0 {
return nil, false, errors.NewInvalid("secret", editSecret.Name, errs)
}
err = rs.registry.UpdateWithName(ctx, editSecret.Name, editSecret)
if err != nil {
return nil, false, err
}
out, err := rs.registry.Get(ctx, editSecret.Name)
return out, false, err
}
// Delete deletes the Secret with the specified name
func (rs *REST) Delete(ctx api.Context, name string) (runtime.Object, error) {
obj, err := rs.registry.Get(ctx, name)
if err != nil {
return nil, err
}
_, ok := obj.(*api.Secret)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return rs.registry.Delete(ctx, name, nil)
}
// Get gets a Secret with the specified name
func (rs *REST) Get(ctx api.Context, name string) (runtime.Object, error) {
obj, err := rs.registry.Get(ctx, name)
if err != nil {
return nil, err
}
secret, ok := obj.(*api.Secret)
if !ok {
return nil, fmt.Errorf("invalid object type")
}
return secret, err
}
func (rs *REST) getAttrs(obj runtime.Object) (objLabels labels.Set, objFields fields.Set, err error) {
secret, ok := obj.(*api.Secret)
if !ok {
return nil, nil, fmt.Errorf("invalid object type")
}
return labels.Set{}, fields.Set{
"type": string(secret.Type),
}, nil
}
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs})
}
func (rs *REST) Watch(ctx api.Context, label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion)
}
// New returns a new api.Secret
func (*REST) New() runtime.Object {
return &api.Secret{}
}
func (*REST) NewList() runtime.Object {
return &api.SecretList{}
}

View File

@ -1,213 +0,0 @@
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secret
import (
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
type testRegistry struct {
*registrytest.GenericRegistry
}
func NewTestREST() (testRegistry, *REST) {
reg := testRegistry{registrytest.NewGeneric(nil)}
return reg, NewStorage(reg)
}
func testSecret(name string) *api.Secret {
return &api.Secret{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: "default",
},
Data: map[string][]byte{
"data-1": []byte("value-1"),
},
Type: api.SecretTypeOpaque,
}
}
func TestRESTCreate(t *testing.T) {
table := []struct {
ctx api.Context
secret *api.Secret
valid bool
}{
{
ctx: api.NewDefaultContext(),
secret: testSecret("foo"),
valid: true,
}, {
ctx: api.NewContext(),
secret: testSecret("bar"),
valid: false,
}, {
ctx: api.WithNamespace(api.NewContext(), "nondefault"),
secret: testSecret("bazzzz"),
valid: false,
},
}
for _, item := range table {
_, storage := NewTestREST()
c, err := storage.Create(item.ctx, item.secret)
if !item.valid {
if err == nil {
ctxNS := api.NamespaceValue(item.ctx)
t.Errorf("%v: Unexpected non-error: (%v, %v)", item.secret.Name, ctxNS, item.secret.Namespace)
}
continue
}
if err != nil {
t.Errorf("%v: Unexpected error: %v", item.secret.Name, err)
continue
}
if !api.HasObjectMetaSystemFieldValues(&item.secret.ObjectMeta) {
t.Errorf("storage did not populate object meta field values")
}
if e, a := item.secret, c; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
// Ensure we implement the interface
_ = rest.Watcher(storage)
}
}
func TestRESTUpdate(t *testing.T) {
ctx := api.NewDefaultContext()
registry, rest := NewTestREST()
registry.CreateWithName(ctx, "foo", testSecret("foo"))
modifiedSecret := testSecret("foo")
modifiedSecret.Data = map[string][]byte{
"data-2": []byte("value-2"),
}
updatedObj, created, err := rest.Update(ctx, modifiedSecret)
if err != nil {
t.Fatalf("Expected no error: %v", err)
}
if updatedObj == nil {
t.Errorf("Expected non-nil object")
}
if created {
t.Errorf("expected not created")
}
updatedSecret := updatedObj.(*api.Secret)
if updatedSecret.Name != "foo" {
t.Errorf("Expected foo, but got %v", updatedSecret.Name)
}
}
func TestRESTDelete(t *testing.T) {
_, rest := NewTestREST()
secretA := testSecret("foo")
_, err := rest.Create(api.NewDefaultContext(), secretA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
c, err := rest.Delete(api.NewDefaultContext(), secretA.Name)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if stat := c.(*api.Status); stat.Status != api.StatusSuccess {
t.Errorf("unexpected status: %v", stat)
}
}
func TestRESTGet(t *testing.T) {
_, rest := NewTestREST()
secretA := testSecret("foo")
_, err := rest.Create(api.NewDefaultContext(), secretA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
got, err := rest.Get(api.NewDefaultContext(), secretA.Name)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := secretA, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTgetAttrs(t *testing.T) {
_, rest := NewTestREST()
secretA := testSecret("foo")
label, field, err := rest.getAttrs(secretA)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if e, a := label, (labels.Set{}); !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
expect := fields.Set{
"type": string(api.SecretTypeOpaque),
}
if e, a := expect, field; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTList(t *testing.T) {
reg, rest := NewTestREST()
var (
secretA = testSecret("a")
secretB = testSecret("b")
secretC = testSecret("c")
)
reg.ObjectList = &api.SecretList{
Items: []api.Secret{*secretA, *secretB, *secretC},
}
got, err := rest.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
expect := &api.SecretList{
Items: []api.Secret{*secretA, *secretB, *secretC},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}
func TestRESTWatch(t *testing.T) {
secretA := testSecret("a")
reg, rest := NewTestREST()
wi, err := rest.Watch(api.NewContext(), labels.Everything(), fields.Everything(), "0")
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
go func() {
reg.Broadcaster.Action(watch.Added, secretA)
}()
got := <-wi.ResultChan()
if e, a := secretA, got.Object; !reflect.DeepEqual(e, a) {
t.Errorf("diff: %s", util.ObjectDiff(e, a))
}
}

View File

@ -0,0 +1,85 @@
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secret
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)
// strategy implements behavior for Secret objects
type strategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating Secret
// objects via the REST API.
var Strategy = strategy{api.Scheme, api.SimpleNameGenerator}
var _ = rest.RESTCreateStrategy(Strategy)
var _ = rest.RESTUpdateStrategy(Strategy)
func (strategy) NamespaceScoped() bool {
return true
}
func (strategy) PrepareForCreate(obj runtime.Object) {
}
func (strategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateSecret(obj.(*api.Secret))
}
func (strategy) AllowCreateOnUpdate() bool {
return false
}
func (strategy) PrepareForUpdate(obj, old runtime.Object) {
}
func (strategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
return validation.ValidateSecretUpdate(old.(*api.Secret), obj.(*api.Secret))
}
// Matcher returns a generic matcher for a given label and field selector.
func Matcher(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
sa, ok := obj.(*api.Secret)
if !ok {
return false, fmt.Errorf("not a secret")
}
fields := SelectableFields(sa)
return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil
})
}
// SelectableFields returns a label set that can be used for filter selection
func SelectableFields(obj *api.Secret) labels.Set {
return labels.Set{
"type": string(obj.Type),
}
}