mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Add secret volume plugin and e2e test
This commit is contained in:
parent
afefa85b26
commit
a42ff94c8f
@ -230,7 +230,10 @@ type GitRepo struct {
|
|||||||
// TODO: Consider credentials here.
|
// TODO: Consider credentials here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapts a Secret into a VolumeSource
|
// Adapts a Secret into a VolumeSource.
|
||||||
|
//
|
||||||
|
// The contents of the target Secret's Data field will be presented in a volume
|
||||||
|
// as files using the keys in the Data field as the file names.
|
||||||
type SecretSource struct {
|
type SecretSource struct {
|
||||||
// Reference to a Secret
|
// Reference to a Secret
|
||||||
Target ObjectReference `json:"target"`
|
Target ObjectReference `json:"target"`
|
||||||
@ -1318,15 +1321,22 @@ type ResourceQuotaList struct {
|
|||||||
Items []ResourceQuota `json:"items"`
|
Items []ResourceQuota `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret holds secret data of a certain type
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
|
// the Data field must be less than MaxSecretSize bytes.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
ObjectMeta `json:"metadata,omitempty"`
|
ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Data contains the secret data. Each key must be a valid DNS_SUBDOMAIN.
|
||||||
|
// The serialized form of the secret data is a base64 encoded string.
|
||||||
Data map[string][]byte `json:"data,omitempty"`
|
Data map[string][]byte `json:"data,omitempty"`
|
||||||
Type SecretType `json:"type,omitempty"`
|
|
||||||
|
// Used to facilitate programatic handling of secret data.
|
||||||
|
Type SecretType `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MaxSecretSize = 1 * 1024 * 1024
|
||||||
|
|
||||||
type SecretType string
|
type SecretType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -1339,5 +1349,3 @@ type SecretList struct {
|
|||||||
|
|
||||||
Items []Secret `json:"items"`
|
Items []Secret `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxSecretSize = 1 * 1024 * 1024
|
|
||||||
|
@ -1100,13 +1100,21 @@ type ResourceQuotaList struct {
|
|||||||
Items []ResourceQuota `json:"items"`
|
Items []ResourceQuota `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
|
// the Data field must be less than MaxSecretSize bytes.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Data contains the secret data. Each key must be a valid DNS_SUBDOMAIN.
|
||||||
|
// The serialized form of the secret data is a base64 encoded string.
|
||||||
Data map[string][]byte `json:"data,omitempty"`
|
Data map[string][]byte `json:"data,omitempty"`
|
||||||
Type SecretType `json:"type,omitempty"`
|
|
||||||
|
// Used to facilitate programatic handling of secret data.
|
||||||
|
Type SecretType `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MaxSecretSize = 1 * 1024 * 1024
|
||||||
|
|
||||||
type SecretType string
|
type SecretType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1103,14 +1103,21 @@ type ResourceQuotaList struct {
|
|||||||
Items []ResourceQuota `json:"items"`
|
Items []ResourceQuota `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret holds secret data of a certain type
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
|
// the Data field must be less than MaxSecretSize bytes.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Data contains the secret data. Each key must be a valid DNS_SUBDOMAIN.
|
||||||
Data map[string][]byte `json:"data,omitempty"`
|
Data map[string][]byte `json:"data,omitempty"`
|
||||||
Type SecretType `json:"type,omitempty"`
|
|
||||||
|
// Used to facilitate programatic handling of secret data.
|
||||||
|
// The serialized form of the secret data is a base64 encoded string.
|
||||||
|
Type SecretType `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MaxSecretSize = 1 * 1024 * 1024
|
||||||
|
|
||||||
type SecretType string
|
type SecretType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1243,16 +1243,22 @@ type ResourceQuotaList struct {
|
|||||||
Items []ResourceQuota `json:"items"`
|
Items []ResourceQuota `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret holds mappings between paths and secret data
|
// Secret holds secret data of a certain type. The total bytes of the values in
|
||||||
// TODO: shouldn't "Secret" be a plural?
|
// the Data field must be less than MaxSecretSize bytes.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
TypeMeta `json:",inline"`
|
TypeMeta `json:",inline"`
|
||||||
ObjectMeta `json:"metadata,omitempty"`
|
ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Data contains the secret data. Each key must be a valid DNS_SUBDOMAIN.
|
||||||
|
// The serialized form of the secret data is a base64 encoded string.
|
||||||
Data map[string][]byte `json:"data,omitempty"`
|
Data map[string][]byte `json:"data,omitempty"`
|
||||||
Type SecretType `json:"type,omitempty"`
|
|
||||||
|
// Used to facilitate programatic handling of secret data.
|
||||||
|
Type SecretType `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MaxSecretSize = 1 * 1024 * 1024
|
||||||
|
|
||||||
type SecretType string
|
type SecretType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -853,9 +853,14 @@ func ValidateSecret(secret *api.Secret) errs.ValidationErrorList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
totalSize := 0
|
totalSize := 0
|
||||||
for _, value := range secret.Data {
|
for key, value := range secret.Data {
|
||||||
|
if !util.IsDNSSubdomain(key) {
|
||||||
|
allErrs = append(allErrs, errs.NewFieldInvalid(fmt.Sprintf("data[%v]", key), key, cIdentifierErrorMsg))
|
||||||
|
}
|
||||||
|
|
||||||
totalSize += len(value)
|
totalSize += len(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalSize > api.MaxSecretSize {
|
if totalSize > api.MaxSecretSize {
|
||||||
allErrs = append(allErrs, errs.NewFieldForbidden("data", "Maximum secret size exceeded"))
|
allErrs = append(allErrs, errs.NewFieldForbidden("data", "Maximum secret size exceeded"))
|
||||||
}
|
}
|
||||||
|
@ -2497,7 +2497,7 @@ func TestValidateSecret(t *testing.T) {
|
|||||||
return api.Secret{
|
return api.Secret{
|
||||||
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
|
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
"foo": []byte("bar"),
|
"data-1": []byte("bar"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2508,6 +2508,7 @@ func TestValidateSecret(t *testing.T) {
|
|||||||
emptyNs = validSecret()
|
emptyNs = validSecret()
|
||||||
invalidNs = validSecret()
|
invalidNs = validSecret()
|
||||||
overMaxSize = validSecret()
|
overMaxSize = validSecret()
|
||||||
|
invalidKey = validSecret()
|
||||||
)
|
)
|
||||||
|
|
||||||
emptyName.Name = ""
|
emptyName.Name = ""
|
||||||
@ -2517,6 +2518,7 @@ func TestValidateSecret(t *testing.T) {
|
|||||||
overMaxSize.Data = map[string][]byte{
|
overMaxSize.Data = map[string][]byte{
|
||||||
"over": make([]byte, api.MaxSecretSize+1),
|
"over": make([]byte, api.MaxSecretSize+1),
|
||||||
}
|
}
|
||||||
|
invalidKey.Data["a..b"] = []byte("whoops")
|
||||||
|
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
secret api.Secret
|
secret api.Secret
|
||||||
@ -2528,6 +2530,7 @@ func TestValidateSecret(t *testing.T) {
|
|||||||
"empty namespace": {emptyNs, false},
|
"empty namespace": {emptyNs, false},
|
||||||
"invalid namespace": {invalidNs, false},
|
"invalid namespace": {invalidNs, false},
|
||||||
"over max size": {overMaxSize, false},
|
"over max size": {overMaxSize, false},
|
||||||
|
"invalid key": {invalidKey, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tc := range tests {
|
for name, tc := range tests {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/gce_pd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/gce_pd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/git_repo"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/git_repo"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/host_path"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/host_path"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume/secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProbeVolumePlugins collects all volume plugins into an easy to use list.
|
// ProbeVolumePlugins collects all volume plugins into an easy to use list.
|
||||||
@ -39,6 +40,7 @@ func ProbeVolumePlugins() []volume.Plugin {
|
|||||||
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||||
|
|
||||||
return allPlugins
|
return allPlugins
|
||||||
}
|
}
|
||||||
|
@ -179,6 +179,8 @@ func (s *KubeletServer) Run(_ []string) error {
|
|||||||
glog.Warningf("No API client: %v", err)
|
glog.Warningf("No API client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glog.Infof("Using root directory: %v", s.RootDirectory)
|
||||||
|
|
||||||
credentialprovider.SetPreferredDockercfgPath(s.RootDirectory)
|
credentialprovider.SetPreferredDockercfgPath(s.RootDirectory)
|
||||||
|
|
||||||
kcfg := KubeletConfig{
|
kcfg := KubeletConfig{
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
func TestCanSupport(t *testing.T) {
|
func TestCanSupport(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -46,7 +46,7 @@ func TestCanSupport(t *testing.T) {
|
|||||||
|
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +100,7 @@ func TestPlugin(t *testing.T) {
|
|||||||
|
|
||||||
func TestPluginBackCompat(t *testing.T) {
|
func TestPluginBackCompat(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/empty-dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,7 +125,7 @@ func TestPluginBackCompat(t *testing.T) {
|
|||||||
|
|
||||||
func TestPluginLegacy(t *testing.T) {
|
func TestPluginLegacy(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("empty")
|
plug, err := plugMgr.FindPluginByName("empty")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
func TestCanSupport(t *testing.T) {
|
func TestCanSupport(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -80,7 +80,7 @@ func (fake *fakeMounter) List() ([]mount.MountPoint, error) {
|
|||||||
|
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/gce-pd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,7 +146,7 @@ func TestPlugin(t *testing.T) {
|
|||||||
|
|
||||||
func TestPluginLegacy(t *testing.T) {
|
func TestPluginLegacy(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("gce-pd")
|
plug, err := plugMgr.FindPluginByName("gce-pd")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,7 +35,7 @@ func newTestHost(t *testing.T) volume.Host {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't make a temp rootdir: %v", err)
|
t.Fatalf("can't make a temp rootdir: %v", err)
|
||||||
}
|
}
|
||||||
return &volume.FakeHost{tempDir}
|
return &volume.FakeHost{tempDir, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCanSupport(t *testing.T) {
|
func TestCanSupport(t *testing.T) {
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
func TestCanSupport(t *testing.T) {
|
func TestCanSupport(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -45,7 +45,7 @@ func TestCanSupport(t *testing.T) {
|
|||||||
|
|
||||||
func TestPlugin(t *testing.T) {
|
func TestPlugin(t *testing.T) {
|
||||||
plugMgr := volume.PluginMgr{}
|
plugMgr := volume.PluginMgr{}
|
||||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake"})
|
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake", nil})
|
||||||
|
|
||||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
|
||||||
@ -76,6 +77,9 @@ type Host interface {
|
|||||||
// does not exist, the result of this call might not exist. This
|
// does not exist, the result of this call might not exist. This
|
||||||
// directory might not actually exist on disk yet.
|
// directory might not actually exist on disk yet.
|
||||||
GetPodPluginDir(podUID types.UID, pluginName string) string
|
GetPodPluginDir(podUID types.UID, pluginName string) string
|
||||||
|
|
||||||
|
// GetKubeClient returns a client interface
|
||||||
|
GetKubeClient() client.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginMgr tracks registered plugins.
|
// PluginMgr tracks registered plugins.
|
||||||
|
133
pkg/kubelet/volume/secret/secret.go
Normal file
133
pkg/kubelet/volume/secret/secret.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProbeVolumePlugin is the entry point for plugin detection in a package.
|
||||||
|
func ProbeVolumePlugins() []volume.Plugin {
|
||||||
|
return []volume.Plugin{&secretPlugin{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
secretPluginName = "kubernetes.io/secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
// secretPlugin implements the VolumePlugin interface.
|
||||||
|
type secretPlugin struct {
|
||||||
|
host volume.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) Init(host volume.Host) {
|
||||||
|
plugin.host = host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) Name() string {
|
||||||
|
return secretPluginName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) CanSupport(spec *api.Volume) bool {
|
||||||
|
if spec.Source.Secret != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) NewBuilder(spec *api.Volume, podUID types.UID) (volume.Builder, error) {
|
||||||
|
return plugin.newBuilderInternal(spec, podUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) newBuilderInternal(spec *api.Volume, podUID types.UID) (volume.Builder, error) {
|
||||||
|
return &secretVolume{spec.Name, podUID, plugin, &spec.Source.Secret.Target}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||||
|
return plugin.newCleanerInternal(volName, podUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plugin *secretPlugin) newCleanerInternal(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||||
|
return &secretVolume{volName, podUID, plugin, nil}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretVolume handles retrieving secrets from the API server
|
||||||
|
// and placing them into the volume on the host.
|
||||||
|
type secretVolume struct {
|
||||||
|
volName string
|
||||||
|
podUID types.UID
|
||||||
|
plugin *secretPlugin
|
||||||
|
secretRef *api.ObjectReference
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *secretVolume) SetUp() error {
|
||||||
|
// TODO: explore tmpfs for secret volumes
|
||||||
|
hostPath := sv.GetPath()
|
||||||
|
glog.V(3).Infof("Setting up volume %v for pod %v at %v", sv.volName, sv.podUID, hostPath)
|
||||||
|
err := os.MkdirAll(hostPath, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient := sv.plugin.host.GetKubeClient()
|
||||||
|
if kubeClient == nil {
|
||||||
|
return fmt.Errorf("Cannot setup secret volume %v because kube client is not configured", sv)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := kubeClient.Secrets(sv.secretRef.Namespace).Get(sv.secretRef.Name)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Couldn't get secret %v/%v", sv.secretRef.Namespace, sv.secretRef.Name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data := range secret.Data {
|
||||||
|
hostFilePath := path.Join(hostPath, name)
|
||||||
|
err := ioutil.WriteFile(hostFilePath, data, 0777)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Error writing secret data to host path: %v, %v", hostFilePath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *secretVolume) GetPath() string {
|
||||||
|
return sv.plugin.host.GetPodVolumeDir(sv.podUID, volume.EscapePluginName(secretPluginName), sv.volName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *secretVolume) TearDown() error {
|
||||||
|
glog.V(3).Infof("Tearing down volume %v for pod %v at %v", sv.volName, sv.podUID, sv.GetPath())
|
||||||
|
tmpDir, err := volume.RenameDirectory(sv.GetPath(), sv.volName+".deleting~")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
160
pkg/kubelet/volume/secret/secret_test.go
Normal file
160
pkg/kubelet/volume/secret/secret_test.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestHost(t *testing.T, fakeKubeClient client.Interface) volume.Host {
|
||||||
|
tempDir, err := ioutil.TempDir("/tmp", "secret_volume_test.")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't make a temp rootdir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &volume.FakeHost{tempDir, fakeKubeClient}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanSupport(t *testing.T) {
|
||||||
|
pluginMgr := volume.PluginMgr{}
|
||||||
|
pluginMgr.InitPlugins(ProbeVolumePlugins(), newTestHost(t, nil))
|
||||||
|
|
||||||
|
plugin, err := pluginMgr.FindPluginByName(secretPluginName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Can't find the plugin by name")
|
||||||
|
}
|
||||||
|
if plugin.Name() != secretPluginName {
|
||||||
|
t.Errorf("Wrong name: %s", plugin.Name())
|
||||||
|
}
|
||||||
|
if !plugin.CanSupport(&api.Volume{Source: api.VolumeSource{Secret: &api.SecretSource{Target: api.ObjectReference{}}}}) {
|
||||||
|
t.Errorf("Expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlugin(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testPodUID = "test_pod_uid"
|
||||||
|
testVolumeName = "test_volume_name"
|
||||||
|
testNamespace = "test_secret_namespace"
|
||||||
|
testName = "test_secret_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
volumeSpec := &api.Volume{
|
||||||
|
Name: testVolumeName,
|
||||||
|
Source: api.VolumeSource{
|
||||||
|
Secret: &api.SecretSource{
|
||||||
|
Target: api.ObjectReference{
|
||||||
|
Namespace: testNamespace,
|
||||||
|
Name: testName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := api.Secret{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Namespace: testNamespace,
|
||||||
|
Name: testName,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"data-1": []byte("value-1"),
|
||||||
|
"data-2": []byte("value-2"),
|
||||||
|
"data-3": []byte("value-3"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &client.Fake{
|
||||||
|
Secret: secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginMgr := volume.PluginMgr{}
|
||||||
|
pluginMgr.InitPlugins(ProbeVolumePlugins(), newTestHost(t, client))
|
||||||
|
|
||||||
|
plugin, err := pluginMgr.FindPluginByName(secretPluginName)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Can't find the plugin by name")
|
||||||
|
}
|
||||||
|
|
||||||
|
builder, err := plugin.NewBuilder(volumeSpec, types.UID(testPodUID))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to make a new Builder: %v", err)
|
||||||
|
}
|
||||||
|
if builder == nil {
|
||||||
|
t.Errorf("Got a nil Builder: %v")
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePath := builder.GetPath()
|
||||||
|
if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~secret/test_volume_name")) {
|
||||||
|
t.Errorf("Got unexpected path: %s", volumePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = builder.SetUp()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to setup volume: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(volumePath); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
|
||||||
|
} else {
|
||||||
|
t.Errorf("SetUp() failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range secret.Data {
|
||||||
|
secretDataHostPath := path.Join(volumePath, key)
|
||||||
|
if _, err := os.Stat(secretDataHostPath); err != nil {
|
||||||
|
t.Fatalf("SetUp() failed, couldn't find secret data on disk: %v", secretDataHostPath)
|
||||||
|
} else {
|
||||||
|
actualSecretBytes, err := ioutil.ReadFile(secretDataHostPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Couldn't read secret data from: %v", secretDataHostPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualSecretValue := string(actualSecretBytes)
|
||||||
|
if string(value) != actualSecretValue {
|
||||||
|
t.Errorf("Unexpected value; expected %q, got %q", value, actualSecretValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaner, err := plugin.NewCleaner(testVolumeName, types.UID(testPodUID))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to make a new Cleaner: %v", err)
|
||||||
|
}
|
||||||
|
if cleaner == nil {
|
||||||
|
t.Errorf("Got a nil Cleaner: %v")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cleaner.TearDown(); err != nil {
|
||||||
|
t.Errorf("Expected success, got: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(volumePath); err == nil {
|
||||||
|
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
t.Errorf("SetUp() failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
@ -21,12 +21,14 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakeHost is useful for testing volume plugins.
|
// FakeHost is useful for testing volume plugins.
|
||||||
type FakeHost struct {
|
type FakeHost struct {
|
||||||
RootDir string
|
RootDir string
|
||||||
|
KubeClient client.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeHost) GetPluginDir(podUID string) string {
|
func (f *FakeHost) GetPluginDir(podUID string) string {
|
||||||
@ -41,6 +43,10 @@ func (f *FakeHost) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
|||||||
return path.Join(f.RootDir, "pods", string(podUID), "plugins", pluginName)
|
return path.Join(f.RootDir, "pods", string(podUID), "plugins", pluginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FakeHost) GetKubeClient() client.Interface {
|
||||||
|
return f.KubeClient
|
||||||
|
}
|
||||||
|
|
||||||
// FakePlugin is useful for for testing. It tries to be a fully compliant
|
// FakePlugin is useful for for testing. It tries to be a fully compliant
|
||||||
// plugin, but all it does is make empty directories.
|
// plugin, but all it does is make empty directories.
|
||||||
// Use as:
|
// Use as:
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/volume"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
@ -48,6 +49,10 @@ func (vh *volumeHost) GetPodPluginDir(podUID types.UID, pluginName string) strin
|
|||||||
return vh.kubelet.getPodPluginDir(podUID, pluginName)
|
return vh.kubelet.getPodPluginDir(podUID, pluginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vh *volumeHost) GetKubeClient() client.Interface {
|
||||||
|
return vh.kubelet.kubeClient
|
||||||
|
}
|
||||||
|
|
||||||
func (kl *Kubelet) newVolumeBuilderFromPlugins(spec *api.Volume, podUID types.UID) volume.Builder {
|
func (kl *Kubelet) newVolumeBuilderFromPlugins(spec *api.Volume, podUID types.UID) volume.Builder {
|
||||||
plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec)
|
plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
166
test/e2e/secrets.go
Normal file
166
test/e2e/secrets.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
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 e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Secrets", func() {
|
||||||
|
var c *client.Client
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
c, err = loadClient()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be consumable from pods", func() {
|
||||||
|
ns := api.NamespaceDefault
|
||||||
|
name := "secret-test-" + string(util.NewUUID())
|
||||||
|
volumeName := "secret-volume"
|
||||||
|
volumeMountPath := "/etc/secret-volume"
|
||||||
|
|
||||||
|
secret := &api.Secret{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"data-1": []byte("value-1\n"),
|
||||||
|
"data-2": []byte("value-2\n"),
|
||||||
|
"data-3": []byte("value-3\n"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, err := c.Secrets(ns).Create(secret)
|
||||||
|
By(fmt.Sprintf("Creating secret with name %s", secret.Name))
|
||||||
|
if err != nil {
|
||||||
|
Fail(fmt.Sprintf("unable to create test secret %s: %v", secret.Name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up secret
|
||||||
|
defer func() {
|
||||||
|
By("Cleaning up the secret")
|
||||||
|
if err = c.Secrets(ns).Delete(secret.Name); err != nil {
|
||||||
|
Fail(fmt.Sprintf("unable to delete secret %v: %v", secret.Name, err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
By(fmt.Sprintf("Creating a pod to consume secret %v", secret.Name))
|
||||||
|
// Make a client pod that verifies that it has the service environment variables.
|
||||||
|
clientName := "client-secrets-" + string(util.NewUUID())
|
||||||
|
clientPod := &api.Pod{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: clientName,
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
Source: api.VolumeSource{
|
||||||
|
Secret: &api.SecretSource{
|
||||||
|
Target: api.ObjectReference{
|
||||||
|
Kind: "Secret",
|
||||||
|
Namespace: ns,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []api.Container{
|
||||||
|
{
|
||||||
|
Name: "catcont",
|
||||||
|
Image: "busybox",
|
||||||
|
Command: []string{"sh", "-c", "cat /etc/secret-volume/data-1; sleep 600"},
|
||||||
|
VolumeMounts: []api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
MountPath: volumeMountPath,
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: api.RestartPolicy{
|
||||||
|
Never: &api.RestartPolicyNever{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = c.Pods(ns).Create(clientPod)
|
||||||
|
if err != nil {
|
||||||
|
Fail(fmt.Sprintf("Failed to create pod: %v", err))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
c.Pods(ns).Delete(clientPod.Name)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for client pod to complete.
|
||||||
|
expectNoError(waitForPodRunning(c, clientPod.Name, 60*time.Second))
|
||||||
|
|
||||||
|
// Grab its logs. Get host first.
|
||||||
|
clientPodStatus, err := c.Pods(ns).Get(clientPod.Name)
|
||||||
|
if err != nil {
|
||||||
|
Fail(fmt.Sprintf("Failed to get clientPod to know host: %v", err))
|
||||||
|
}
|
||||||
|
By(fmt.Sprintf("Trying to get logs from host %s pod %s container %s: %v",
|
||||||
|
clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, err))
|
||||||
|
var logs []byte
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Sometimes the actual containers take a second to get started, try to get logs for 60s
|
||||||
|
for time.Now().Sub(start) < (60 * time.Second) {
|
||||||
|
logs, err = c.Get().
|
||||||
|
Prefix("proxy").
|
||||||
|
Resource("minions").
|
||||||
|
Name(clientPodStatus.Status.Host).
|
||||||
|
Suffix("containerLogs", ns, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name).
|
||||||
|
Do().
|
||||||
|
Raw()
|
||||||
|
fmt.Sprintf("clientPod logs:%v\n", string(logs))
|
||||||
|
By(fmt.Sprintf("clientPod logs:%v\n", string(logs)))
|
||||||
|
if strings.Contains(string(logs), "Internal Error") {
|
||||||
|
By(fmt.Sprintf("Failed to get logs from host %s pod %s container %s: %v",
|
||||||
|
clientPodStatus.Status.Host, clientPodStatus.Name, clientPodStatus.Spec.Containers[0].Name, string(logs)))
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
toFind := []string{
|
||||||
|
"value-1",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range toFind {
|
||||||
|
Expect(string(logs)).To(ContainSubstring(m), "%q in secret data", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We could try a wget the service from the client pod. But services.sh e2e test covers that pretty well.
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user