mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 14:37:00 +00:00
Convert Secret registry to use update/create strategy, allow filtering by Type
This commit is contained in:
parent
c2a78483b4
commit
ac67fff1cf
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
64
pkg/registry/secret/etcd/etcd.go
Normal file
64
pkg/registry/secret/etcd/etcd.go
Normal 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}
|
||||
}
|
93
pkg/registry/secret/etcd/etcd_test.go
Normal file
93
pkg/registry/secret/etcd/etcd_test.go
Normal 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,
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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{}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
85
pkg/registry/secret/strategy.go
Normal file
85
pkg/registry/secret/strategy.go
Normal 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),
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user