mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #4515 from pmorie/secrets_wip
Secret volume plugin iteration 1
This commit is contained in:
commit
af67829eca
@ -230,7 +230,10 @@ type GitRepo struct {
|
||||
// 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 {
|
||||
// Reference to a Secret
|
||||
Target ObjectReference `json:"target"`
|
||||
@ -1318,15 +1321,22 @@ type ResourceQuotaList struct {
|
||||
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 {
|
||||
TypeMeta `json:",inline"`
|
||||
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"`
|
||||
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
|
||||
|
||||
const (
|
||||
@ -1339,5 +1349,3 @@ type SecretList struct {
|
||||
|
||||
Items []Secret `json:"items"`
|
||||
}
|
||||
|
||||
const MaxSecretSize = 1 * 1024 * 1024
|
||||
|
@ -1100,13 +1100,21 @@ type ResourceQuotaList struct {
|
||||
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 {
|
||||
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"`
|
||||
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
|
||||
|
||||
const (
|
||||
|
@ -1103,14 +1103,21 @@ type ResourceQuotaList struct {
|
||||
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 {
|
||||
TypeMeta `json:",inline"`
|
||||
|
||||
// Data contains the secret data. Each key must be a valid DNS_SUBDOMAIN.
|
||||
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
|
||||
|
||||
const (
|
||||
|
@ -1243,16 +1243,22 @@ type ResourceQuotaList struct {
|
||||
Items []ResourceQuota `json:"items"`
|
||||
}
|
||||
|
||||
// Secret holds mappings between paths and secret data
|
||||
// TODO: shouldn't "Secret" be a plural?
|
||||
// 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 {
|
||||
TypeMeta `json:",inline"`
|
||||
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"`
|
||||
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
|
||||
|
||||
const (
|
||||
|
@ -853,9 +853,14 @@ func ValidateSecret(secret *api.Secret) errs.ValidationErrorList {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if totalSize > api.MaxSecretSize {
|
||||
allErrs = append(allErrs, errs.NewFieldForbidden("data", "Maximum secret size exceeded"))
|
||||
}
|
||||
|
@ -2497,7 +2497,7 @@ func TestValidateSecret(t *testing.T) {
|
||||
return api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||
Data: map[string][]byte{
|
||||
"foo": []byte("bar"),
|
||||
"data-1": []byte("bar"),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -2508,6 +2508,7 @@ func TestValidateSecret(t *testing.T) {
|
||||
emptyNs = validSecret()
|
||||
invalidNs = validSecret()
|
||||
overMaxSize = validSecret()
|
||||
invalidKey = validSecret()
|
||||
)
|
||||
|
||||
emptyName.Name = ""
|
||||
@ -2517,6 +2518,7 @@ func TestValidateSecret(t *testing.T) {
|
||||
overMaxSize.Data = map[string][]byte{
|
||||
"over": make([]byte, api.MaxSecretSize+1),
|
||||
}
|
||||
invalidKey.Data["a..b"] = []byte("whoops")
|
||||
|
||||
tests := map[string]struct {
|
||||
secret api.Secret
|
||||
@ -2528,6 +2530,7 @@ func TestValidateSecret(t *testing.T) {
|
||||
"empty namespace": {emptyNs, false},
|
||||
"invalid namespace": {invalidNs, false},
|
||||
"over max size": {overMaxSize, false},
|
||||
"invalid key": {invalidKey, false},
|
||||
}
|
||||
|
||||
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/git_repo"
|
||||
"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.
|
||||
@ -39,6 +40,7 @@ func ProbeVolumePlugins() []volume.Plugin {
|
||||
allPlugins = append(allPlugins, gce_pd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||
|
||||
return allPlugins
|
||||
}
|
||||
|
@ -179,6 +179,8 @@ func (s *KubeletServer) Run(_ []string) error {
|
||||
glog.Warningf("No API client: %v", err)
|
||||
}
|
||||
|
||||
glog.Infof("Using root directory: %v", s.RootDirectory)
|
||||
|
||||
credentialprovider.SetPreferredDockercfgPath(s.RootDirectory)
|
||||
|
||||
kcfg := KubeletConfig{
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
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")
|
||||
if err != nil {
|
||||
@ -46,7 +46,7 @@ func TestCanSupport(t *testing.T) {
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
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")
|
||||
if err != nil {
|
||||
@ -100,7 +100,7 @@ func TestPlugin(t *testing.T) {
|
||||
|
||||
func TestPluginBackCompat(t *testing.T) {
|
||||
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")
|
||||
if err != nil {
|
||||
@ -125,7 +125,7 @@ func TestPluginBackCompat(t *testing.T) {
|
||||
|
||||
func TestPluginLegacy(t *testing.T) {
|
||||
plugMgr := volume.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("empty")
|
||||
if err != nil {
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
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")
|
||||
if err != nil {
|
||||
@ -80,7 +80,7 @@ func (fake *fakeMounter) List() ([]mount.MountPoint, error) {
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
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")
|
||||
if err != nil {
|
||||
@ -146,7 +146,7 @@ func TestPlugin(t *testing.T) {
|
||||
|
||||
func TestPluginLegacy(t *testing.T) {
|
||||
plugMgr := volume.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake"})
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"/tmp/fake", nil})
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("gce-pd")
|
||||
if err != nil {
|
||||
|
@ -35,7 +35,7 @@ func newTestHost(t *testing.T) volume.Host {
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp rootdir: %v", err)
|
||||
}
|
||||
return &volume.FakeHost{tempDir}
|
||||
return &volume.FakeHost{tempDir, nil}
|
||||
}
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
plugMgr := volume.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake"})
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake", nil})
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
||||
if err != nil {
|
||||
@ -45,7 +45,7 @@ func TestCanSupport(t *testing.T) {
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
plugMgr := volume.PluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake"})
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), &volume.FakeHost{"fake", nil})
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
||||
if err != nil {
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"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
|
||||
// directory might not actually exist on disk yet.
|
||||
GetPodPluginDir(podUID types.UID, pluginName string) string
|
||||
|
||||
// GetKubeClient returns a client interface
|
||||
GetKubeClient() client.Interface
|
||||
}
|
||||
|
||||
// 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"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
// FakeHost is useful for testing volume plugins.
|
||||
type FakeHost struct {
|
||||
RootDir string
|
||||
RootDir string
|
||||
KubeClient client.Interface
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *FakeHost) GetKubeClient() client.Interface {
|
||||
return f.KubeClient
|
||||
}
|
||||
|
||||
// FakePlugin is useful for for testing. It tries to be a fully compliant
|
||||
// plugin, but all it does is make empty directories.
|
||||
// Use as:
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"path"
|
||||
|
||||
"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"
|
||||
"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)
|
||||
}
|
||||
|
||||
func (vh *volumeHost) GetKubeClient() client.Interface {
|
||||
return vh.kubelet.kubeClient
|
||||
}
|
||||
|
||||
func (kl *Kubelet) newVolumeBuilderFromPlugins(spec *api.Volume, podUID types.UID) volume.Builder {
|
||||
plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec)
|
||||
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