Merge pull request #4515 from pmorie/secrets_wip

Secret volume plugin iteration 1
This commit is contained in:
Eric Tune 2015-02-18 15:57:31 -08:00
commit af67829eca
18 changed files with 539 additions and 24 deletions

View File

@ -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

View File

@ -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 (

View File

@ -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 (

View File

@ -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 (

View File

@ -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"))
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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{

View File

@ -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 {

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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.

View 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
}

View 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)
}
}

View File

@ -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:

View File

@ -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
View 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.
})
})