Quellcode durchsuchen

integration-cli: Add secret/config templating tests

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann vor 8 Jahren
Ursprung
Commit
cdd2e6efdb

+ 130 - 0
integration/config/config_test.go

@@ -1,8 +1,10 @@
 package config
 
 import (
+	"bytes"
 	"sort"
 	"testing"
+	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
@@ -10,6 +12,7 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/integration/internal/swarm"
 	"github.com/docker/docker/internal/testutil"
+	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/gotestyourself/gotestyourself/skip"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -188,3 +191,130 @@ func TestConfigsUpdate(t *testing.T) {
 	err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
 	testutil.ErrorContains(t, err, "only updates to Labels are allowed")
 }
+
+func TestTemplatedConfig(t *testing.T) {
+	d := swarm.NewSwarm(t, testEnv)
+	defer d.Stop(t)
+
+	ctx := context.Background()
+	client := swarm.GetClient(t, d)
+
+	referencedSecretSpec := swarmtypes.SecretSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "referencedsecret",
+		},
+		Data: []byte("this is a secret"),
+	}
+	referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
+	assert.NoError(t, err)
+
+	referencedConfigSpec := swarmtypes.ConfigSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "referencedconfig",
+		},
+		Data: []byte("this is a config"),
+	}
+	referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
+	assert.NoError(t, err)
+
+	configSpec := swarmtypes.ConfigSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "templated_config",
+		},
+		Templating: &swarmtypes.Driver{
+			Name: "golang",
+		},
+		Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
+			"{{secret \"referencedsecrettarget\"}}\n" +
+			"{{config \"referencedconfigtarget\"}}\n"),
+	}
+
+	templatedConfig, err := client.ConfigCreate(ctx, configSpec)
+	assert.NoError(t, err)
+
+	serviceID := swarm.CreateService(t, d,
+		swarm.ServiceWithConfig(
+			&swarmtypes.ConfigReference{
+				File: &swarmtypes.ConfigReferenceFileTarget{
+					Name: "/templated_config",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				ConfigID:   templatedConfig.ID,
+				ConfigName: "templated_config",
+			},
+		),
+		swarm.ServiceWithConfig(
+			&swarmtypes.ConfigReference{
+				File: &swarmtypes.ConfigReferenceFileTarget{
+					Name: "referencedconfigtarget",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				ConfigID:   referencedConfig.ID,
+				ConfigName: "referencedconfig",
+			},
+		),
+		swarm.ServiceWithSecret(
+			&swarmtypes.SecretReference{
+				File: &swarmtypes.SecretReferenceFileTarget{
+					Name: "referencedsecrettarget",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				SecretID:   referencedSecret.ID,
+				SecretName: "referencedsecret",
+			},
+		),
+		swarm.ServiceWithName("svc"),
+	)
+
+	var tasks []swarmtypes.Task
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
+		tasks = swarm.GetRunningTasks(t, d, serviceID)
+		return len(tasks) > 0
+	})
+
+	task := tasks[0]
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
+		if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
+			task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
+		}
+		return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
+	})
+
+	attach := swarm.ExecTask(t, d, task, types.ExecConfig{
+		Cmd:          []string{"/bin/cat", "/templated_config"},
+		AttachStdout: true,
+		AttachStderr: true,
+	})
+
+	buf := bytes.NewBuffer(nil)
+	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
+	require.NoError(t, err)
+
+	expect := "SERVICE_NAME=svc\n" +
+		"this is a secret\n" +
+		"this is a config\n"
+
+	assert.Equal(t, expect, buf.String())
+}
+
+func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
+	t.Helper()
+	after := time.After(timeout)
+	for {
+		select {
+		case <-after:
+			t.Fatalf("timed out waiting for condition")
+		default:
+		}
+		if f(t) {
+			return
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+}

+ 122 - 0
integration/internal/swarm/service.go

@@ -1,10 +1,14 @@
 package swarm
 
 import (
+	"context"
 	"fmt"
 	"testing"
 
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/filters"
 	swarmtypes "github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/client"
 	"github.com/docker/docker/integration-cli/daemon"
 	"github.com/docker/docker/internal/test/environment"
 	"github.com/stretchr/testify/require"
@@ -34,3 +38,121 @@ func NewSwarm(t *testing.T, testEnv *environment.Execution) *daemon.Swarm {
 	require.NoError(t, d.Init(swarmtypes.InitRequest{}))
 	return d
 }
+
+// ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers
+type ServiceSpecOpt func(*swarmtypes.ServiceSpec)
+
+// CreateService creates a service on the passed in swarm daemon.
+func CreateService(t *testing.T, d *daemon.Swarm, opts ...ServiceSpecOpt) string {
+	spec := defaultServiceSpec()
+	for _, o := range opts {
+		o(&spec)
+	}
+
+	client := GetClient(t, d)
+
+	resp, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{})
+	require.NoError(t, err, "error creating service")
+	return resp.ID
+}
+
+func defaultServiceSpec() swarmtypes.ServiceSpec {
+	var spec swarmtypes.ServiceSpec
+	ServiceWithImage("busybox:latest")(&spec)
+	ServiceWithCommand([]string{"/bin/top"})(&spec)
+	ServiceWithReplicas(1)(&spec)
+	return spec
+}
+
+// ServiceWithImage sets the image to use for the service
+func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensureContainerSpec(spec)
+		spec.TaskTemplate.ContainerSpec.Image = image
+	}
+}
+
+// ServiceWithCommand sets the command to use for the service
+func ServiceWithCommand(cmd []string) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensureContainerSpec(spec)
+		spec.TaskTemplate.ContainerSpec.Command = cmd
+	}
+}
+
+// ServiceWithConfig adds the config reference to the service
+func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensureContainerSpec(spec)
+		spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef)
+	}
+}
+
+// ServiceWithSecret adds the secret reference to the service
+func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		ensureContainerSpec(spec)
+		spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef)
+	}
+}
+
+// ServiceWithReplicas sets the replicas for the service
+func ServiceWithReplicas(n uint64) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		spec.Mode = swarmtypes.ServiceMode{
+			Replicated: &swarmtypes.ReplicatedService{
+				Replicas: &n,
+			},
+		}
+	}
+}
+
+// ServiceWithName sets the name of the service
+func ServiceWithName(name string) ServiceSpecOpt {
+	return func(spec *swarmtypes.ServiceSpec) {
+		spec.Annotations.Name = name
+	}
+}
+
+// GetRunningTasks gets the list of running tasks for a service
+func GetRunningTasks(t *testing.T, d *daemon.Swarm, serviceID string) []swarmtypes.Task {
+	client := GetClient(t, d)
+
+	filterArgs := filters.NewArgs()
+	filterArgs.Add("desired-state", "running")
+	filterArgs.Add("service", serviceID)
+
+	options := types.TaskListOptions{
+		Filters: filterArgs,
+	}
+	tasks, err := client.TaskList(context.Background(), options)
+	require.NoError(t, err)
+	return tasks
+}
+
+// ExecTask runs the passed in exec config on the given task
+func ExecTask(t *testing.T, d *daemon.Swarm, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
+	client := GetClient(t, d)
+
+	ctx := context.Background()
+	resp, err := client.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, config)
+	require.NoError(t, err, "error creating exec")
+
+	startCheck := types.ExecStartCheck{}
+	attach, err := client.ContainerExecAttach(ctx, resp.ID, startCheck)
+	require.NoError(t, err, "error attaching to exec")
+	return attach
+}
+
+func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
+	if spec.TaskTemplate.ContainerSpec == nil {
+		spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{}
+	}
+}
+
+// GetClient creates a new client for the passed in swarm daemon.
+func GetClient(t *testing.T, d *daemon.Swarm) client.APIClient {
+	client, err := client.NewClientWithOpts(client.WithHost((d.Sock())))
+	require.NoError(t, err)
+	return client
+}

+ 130 - 0
integration/secret/secret_test.go

@@ -1,8 +1,10 @@
 package secret
 
 import (
+	"bytes"
 	"sort"
 	"testing"
+	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
@@ -10,6 +12,7 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/integration/internal/swarm"
 	"github.com/docker/docker/internal/testutil"
+	"github.com/docker/docker/pkg/stdcopy"
 	"github.com/gotestyourself/gotestyourself/skip"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -232,3 +235,130 @@ func TestSecretsUpdate(t *testing.T) {
 	err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec)
 	testutil.ErrorContains(t, err, "only updates to Labels are allowed")
 }
+
+func TestTemplatedSecret(t *testing.T) {
+	d := swarm.NewSwarm(t, testEnv)
+	defer d.Stop(t)
+
+	ctx := context.Background()
+	client := swarm.GetClient(t, d)
+
+	referencedSecretSpec := swarmtypes.SecretSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "referencedsecret",
+		},
+		Data: []byte("this is a secret"),
+	}
+	referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
+	assert.NoError(t, err)
+
+	referencedConfigSpec := swarmtypes.ConfigSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "referencedconfig",
+		},
+		Data: []byte("this is a config"),
+	}
+	referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
+	assert.NoError(t, err)
+
+	secretSpec := swarmtypes.SecretSpec{
+		Annotations: swarmtypes.Annotations{
+			Name: "templated_secret",
+		},
+		Templating: &swarmtypes.Driver{
+			Name: "golang",
+		},
+		Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
+			"{{secret \"referencedsecrettarget\"}}\n" +
+			"{{config \"referencedconfigtarget\"}}\n"),
+	}
+
+	templatedSecret, err := client.SecretCreate(ctx, secretSpec)
+	assert.NoError(t, err)
+
+	serviceID := swarm.CreateService(t, d,
+		swarm.ServiceWithSecret(
+			&swarmtypes.SecretReference{
+				File: &swarmtypes.SecretReferenceFileTarget{
+					Name: "templated_secret",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				SecretID:   templatedSecret.ID,
+				SecretName: "templated_secret",
+			},
+		),
+		swarm.ServiceWithConfig(
+			&swarmtypes.ConfigReference{
+				File: &swarmtypes.ConfigReferenceFileTarget{
+					Name: "referencedconfigtarget",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				ConfigID:   referencedConfig.ID,
+				ConfigName: "referencedconfig",
+			},
+		),
+		swarm.ServiceWithSecret(
+			&swarmtypes.SecretReference{
+				File: &swarmtypes.SecretReferenceFileTarget{
+					Name: "referencedsecrettarget",
+					UID:  "0",
+					GID:  "0",
+					Mode: 0600,
+				},
+				SecretID:   referencedSecret.ID,
+				SecretName: "referencedsecret",
+			},
+		),
+		swarm.ServiceWithName("svc"),
+	)
+
+	var tasks []swarmtypes.Task
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
+		tasks = swarm.GetRunningTasks(t, d, serviceID)
+		return len(tasks) > 0
+	})
+
+	task := tasks[0]
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
+		if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
+			task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
+		}
+		return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
+	})
+
+	attach := swarm.ExecTask(t, d, task, types.ExecConfig{
+		Cmd:          []string{"/bin/cat", "/run/secrets/templated_secret"},
+		AttachStdout: true,
+		AttachStderr: true,
+	})
+
+	buf := bytes.NewBuffer(nil)
+	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
+	require.NoError(t, err)
+
+	expect := "SERVICE_NAME=svc\n" +
+		"this is a secret\n" +
+		"this is a config\n"
+
+	assert.Equal(t, expect, buf.String())
+}
+
+func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
+	t.Helper()
+	after := time.After(timeout)
+	for {
+		select {
+		case <-after:
+			t.Fatalf("timed out waiting for condition")
+		default:
+		}
+		if f(t) {
+			return
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+}