mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
integration-cli: Add secret/config templating tests
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
c5df7235f6
commit
cdd2e6efdb
3 changed files with 382 additions and 0 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue