mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add secret volume plugin and e2e test
This commit is contained in:
		@@ -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"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Used to facilitate programatic handling of secret data.
 | 
				
			||||||
	Type SecretType `json:"type,omitempty"`
 | 
						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"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Used to facilitate programatic handling of secret data.
 | 
				
			||||||
	Type SecretType `json:"type,omitempty"`
 | 
						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"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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"`
 | 
						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"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Used to facilitate programatic handling of secret data.
 | 
				
			||||||
	Type SecretType `json:"type,omitempty"`
 | 
						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.
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Reference in New Issue
	
	Block a user