diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index b4edd19a70d..5817e7a40fb 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -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) + } } diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index f2f5815d45f..d07390a0b5b 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -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) + } } diff --git a/pkg/api/v1beta3/conversion.go b/pkg/api/v1beta3/conversion.go index ded76bb4019..8ba2a03f062 100644 --- a/pkg/api/v1beta3/conversion.go +++ b/pkg/api/v1beta3/conversion.go @@ -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) + } } diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 4c2741839b8..ad0fa294dcc 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -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 } diff --git a/pkg/client/request.go b/pkg/client/request.go index 2b7aa61051d..dd6ba4df0e4 100644 --- a/pkg/client/request.go +++ b/pkg/client/request.go @@ -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", + }, }, } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 3969fac12d3..1ada03e0395 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -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"} @@ -584,7 +584,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 } diff --git a/pkg/master/master.go b/pkg/master/master.go index d52665e20af..1f29fbbc462 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -61,7 +61,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" @@ -388,7 +388,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) @@ -435,7 +435,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, diff --git a/pkg/registry/secret/etcd/etcd.go b/pkg/registry/secret/etcd/etcd.go new file mode 100644 index 00000000000..ae966c7e6f7 --- /dev/null +++ b/pkg/registry/secret/etcd/etcd.go @@ -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} +} diff --git a/pkg/registry/secret/etcd/etcd_test.go b/pkg/registry/secret/etcd/etcd_test.go new file mode 100644 index 00000000000..81db1c9c0e6 --- /dev/null +++ b/pkg/registry/secret/etcd/etcd_test.go @@ -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, + ) +} diff --git a/pkg/registry/secret/registry.go b/pkg/registry/secret/registry.go index 2707954b318..8a51adaf165 100644 --- a/pkg/registry/secret/registry.go +++ b/pkg/registry/secret/registry.go @@ -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 } diff --git a/pkg/registry/secret/registry_test.go b/pkg/registry/secret/registry_test.go deleted file mode 100644 index b88c4848aa4..00000000000 --- a/pkg/registry/secret/registry_test.go +++ /dev/null @@ -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)) - } - } -} diff --git a/pkg/registry/secret/rest.go b/pkg/registry/secret/rest.go deleted file mode 100644 index 7ad4decf2c8..00000000000 --- a/pkg/registry/secret/rest.go +++ /dev/null @@ -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{} -} diff --git a/pkg/registry/secret/rest_test.go b/pkg/registry/secret/rest_test.go deleted file mode 100644 index 291c29cae7c..00000000000 --- a/pkg/registry/secret/rest_test.go +++ /dev/null @@ -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)) - } -} diff --git a/pkg/registry/secret/strategy.go b/pkg/registry/secret/strategy.go new file mode 100644 index 00000000000..1f4a1f71020 --- /dev/null +++ b/pkg/registry/secret/strategy.go @@ -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), + } +}