Browse code

integration-cli: Add secret/config templating tests

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2017/06/16 04:06:08
Showing 3 changed files
... ...
@@ -1,8 +1,10 @@
1 1
 package config
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"sort"
5 6
 	"testing"
7
+	"time"
6 8
 
7 9
 	"github.com/docker/docker/api/types"
8 10
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -10,6 +12,7 @@ import (
10 10
 	"github.com/docker/docker/client"
11 11
 	"github.com/docker/docker/integration/internal/swarm"
12 12
 	"github.com/docker/docker/internal/testutil"
13
+	"github.com/docker/docker/pkg/stdcopy"
13 14
 	"github.com/gotestyourself/gotestyourself/skip"
14 15
 	"github.com/stretchr/testify/assert"
15 16
 	"github.com/stretchr/testify/require"
... ...
@@ -188,3 +191,130 @@ func TestConfigsUpdate(t *testing.T) {
188 188
 	err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
189 189
 	testutil.ErrorContains(t, err, "only updates to Labels are allowed")
190 190
 }
191
+
192
+func TestTemplatedConfig(t *testing.T) {
193
+	d := swarm.NewSwarm(t, testEnv)
194
+	defer d.Stop(t)
195
+
196
+	ctx := context.Background()
197
+	client := swarm.GetClient(t, d)
198
+
199
+	referencedSecretSpec := swarmtypes.SecretSpec{
200
+		Annotations: swarmtypes.Annotations{
201
+			Name: "referencedsecret",
202
+		},
203
+		Data: []byte("this is a secret"),
204
+	}
205
+	referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
206
+	assert.NoError(t, err)
207
+
208
+	referencedConfigSpec := swarmtypes.ConfigSpec{
209
+		Annotations: swarmtypes.Annotations{
210
+			Name: "referencedconfig",
211
+		},
212
+		Data: []byte("this is a config"),
213
+	}
214
+	referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
215
+	assert.NoError(t, err)
216
+
217
+	configSpec := swarmtypes.ConfigSpec{
218
+		Annotations: swarmtypes.Annotations{
219
+			Name: "templated_config",
220
+		},
221
+		Templating: &swarmtypes.Driver{
222
+			Name: "golang",
223
+		},
224
+		Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
225
+			"{{secret \"referencedsecrettarget\"}}\n" +
226
+			"{{config \"referencedconfigtarget\"}}\n"),
227
+	}
228
+
229
+	templatedConfig, err := client.ConfigCreate(ctx, configSpec)
230
+	assert.NoError(t, err)
231
+
232
+	serviceID := swarm.CreateService(t, d,
233
+		swarm.ServiceWithConfig(
234
+			&swarmtypes.ConfigReference{
235
+				File: &swarmtypes.ConfigReferenceFileTarget{
236
+					Name: "/templated_config",
237
+					UID:  "0",
238
+					GID:  "0",
239
+					Mode: 0600,
240
+				},
241
+				ConfigID:   templatedConfig.ID,
242
+				ConfigName: "templated_config",
243
+			},
244
+		),
245
+		swarm.ServiceWithConfig(
246
+			&swarmtypes.ConfigReference{
247
+				File: &swarmtypes.ConfigReferenceFileTarget{
248
+					Name: "referencedconfigtarget",
249
+					UID:  "0",
250
+					GID:  "0",
251
+					Mode: 0600,
252
+				},
253
+				ConfigID:   referencedConfig.ID,
254
+				ConfigName: "referencedconfig",
255
+			},
256
+		),
257
+		swarm.ServiceWithSecret(
258
+			&swarmtypes.SecretReference{
259
+				File: &swarmtypes.SecretReferenceFileTarget{
260
+					Name: "referencedsecrettarget",
261
+					UID:  "0",
262
+					GID:  "0",
263
+					Mode: 0600,
264
+				},
265
+				SecretID:   referencedSecret.ID,
266
+				SecretName: "referencedsecret",
267
+			},
268
+		),
269
+		swarm.ServiceWithName("svc"),
270
+	)
271
+
272
+	var tasks []swarmtypes.Task
273
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
274
+		tasks = swarm.GetRunningTasks(t, d, serviceID)
275
+		return len(tasks) > 0
276
+	})
277
+
278
+	task := tasks[0]
279
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
280
+		if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
281
+			task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
282
+		}
283
+		return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
284
+	})
285
+
286
+	attach := swarm.ExecTask(t, d, task, types.ExecConfig{
287
+		Cmd:          []string{"/bin/cat", "/templated_config"},
288
+		AttachStdout: true,
289
+		AttachStderr: true,
290
+	})
291
+
292
+	buf := bytes.NewBuffer(nil)
293
+	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
294
+	require.NoError(t, err)
295
+
296
+	expect := "SERVICE_NAME=svc\n" +
297
+		"this is a secret\n" +
298
+		"this is a config\n"
299
+
300
+	assert.Equal(t, expect, buf.String())
301
+}
302
+
303
+func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
304
+	t.Helper()
305
+	after := time.After(timeout)
306
+	for {
307
+		select {
308
+		case <-after:
309
+			t.Fatalf("timed out waiting for condition")
310
+		default:
311
+		}
312
+		if f(t) {
313
+			return
314
+		}
315
+		time.Sleep(100 * time.Millisecond)
316
+	}
317
+}
... ...
@@ -1,10 +1,14 @@
1 1
 package swarm
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"fmt"
5 6
 	"testing"
6 7
 
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/api/types/filters"
7 10
 	swarmtypes "github.com/docker/docker/api/types/swarm"
11
+	"github.com/docker/docker/client"
8 12
 	"github.com/docker/docker/integration-cli/daemon"
9 13
 	"github.com/docker/docker/internal/test/environment"
10 14
 	"github.com/stretchr/testify/require"
... ...
@@ -34,3 +38,121 @@ func NewSwarm(t *testing.T, testEnv *environment.Execution) *daemon.Swarm {
34 34
 	require.NoError(t, d.Init(swarmtypes.InitRequest{}))
35 35
 	return d
36 36
 }
37
+
38
+// ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers
39
+type ServiceSpecOpt func(*swarmtypes.ServiceSpec)
40
+
41
+// CreateService creates a service on the passed in swarm daemon.
42
+func CreateService(t *testing.T, d *daemon.Swarm, opts ...ServiceSpecOpt) string {
43
+	spec := defaultServiceSpec()
44
+	for _, o := range opts {
45
+		o(&spec)
46
+	}
47
+
48
+	client := GetClient(t, d)
49
+
50
+	resp, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{})
51
+	require.NoError(t, err, "error creating service")
52
+	return resp.ID
53
+}
54
+
55
+func defaultServiceSpec() swarmtypes.ServiceSpec {
56
+	var spec swarmtypes.ServiceSpec
57
+	ServiceWithImage("busybox:latest")(&spec)
58
+	ServiceWithCommand([]string{"/bin/top"})(&spec)
59
+	ServiceWithReplicas(1)(&spec)
60
+	return spec
61
+}
62
+
63
+// ServiceWithImage sets the image to use for the service
64
+func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) {
65
+	return func(spec *swarmtypes.ServiceSpec) {
66
+		ensureContainerSpec(spec)
67
+		spec.TaskTemplate.ContainerSpec.Image = image
68
+	}
69
+}
70
+
71
+// ServiceWithCommand sets the command to use for the service
72
+func ServiceWithCommand(cmd []string) ServiceSpecOpt {
73
+	return func(spec *swarmtypes.ServiceSpec) {
74
+		ensureContainerSpec(spec)
75
+		spec.TaskTemplate.ContainerSpec.Command = cmd
76
+	}
77
+}
78
+
79
+// ServiceWithConfig adds the config reference to the service
80
+func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt {
81
+	return func(spec *swarmtypes.ServiceSpec) {
82
+		ensureContainerSpec(spec)
83
+		spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef)
84
+	}
85
+}
86
+
87
+// ServiceWithSecret adds the secret reference to the service
88
+func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt {
89
+	return func(spec *swarmtypes.ServiceSpec) {
90
+		ensureContainerSpec(spec)
91
+		spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef)
92
+	}
93
+}
94
+
95
+// ServiceWithReplicas sets the replicas for the service
96
+func ServiceWithReplicas(n uint64) ServiceSpecOpt {
97
+	return func(spec *swarmtypes.ServiceSpec) {
98
+		spec.Mode = swarmtypes.ServiceMode{
99
+			Replicated: &swarmtypes.ReplicatedService{
100
+				Replicas: &n,
101
+			},
102
+		}
103
+	}
104
+}
105
+
106
+// ServiceWithName sets the name of the service
107
+func ServiceWithName(name string) ServiceSpecOpt {
108
+	return func(spec *swarmtypes.ServiceSpec) {
109
+		spec.Annotations.Name = name
110
+	}
111
+}
112
+
113
+// GetRunningTasks gets the list of running tasks for a service
114
+func GetRunningTasks(t *testing.T, d *daemon.Swarm, serviceID string) []swarmtypes.Task {
115
+	client := GetClient(t, d)
116
+
117
+	filterArgs := filters.NewArgs()
118
+	filterArgs.Add("desired-state", "running")
119
+	filterArgs.Add("service", serviceID)
120
+
121
+	options := types.TaskListOptions{
122
+		Filters: filterArgs,
123
+	}
124
+	tasks, err := client.TaskList(context.Background(), options)
125
+	require.NoError(t, err)
126
+	return tasks
127
+}
128
+
129
+// ExecTask runs the passed in exec config on the given task
130
+func ExecTask(t *testing.T, d *daemon.Swarm, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
131
+	client := GetClient(t, d)
132
+
133
+	ctx := context.Background()
134
+	resp, err := client.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, config)
135
+	require.NoError(t, err, "error creating exec")
136
+
137
+	startCheck := types.ExecStartCheck{}
138
+	attach, err := client.ContainerExecAttach(ctx, resp.ID, startCheck)
139
+	require.NoError(t, err, "error attaching to exec")
140
+	return attach
141
+}
142
+
143
+func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
144
+	if spec.TaskTemplate.ContainerSpec == nil {
145
+		spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{}
146
+	}
147
+}
148
+
149
+// GetClient creates a new client for the passed in swarm daemon.
150
+func GetClient(t *testing.T, d *daemon.Swarm) client.APIClient {
151
+	client, err := client.NewClientWithOpts(client.WithHost((d.Sock())))
152
+	require.NoError(t, err)
153
+	return client
154
+}
... ...
@@ -1,8 +1,10 @@
1 1
 package secret
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"sort"
5 6
 	"testing"
7
+	"time"
6 8
 
7 9
 	"github.com/docker/docker/api/types"
8 10
 	"github.com/docker/docker/api/types/filters"
... ...
@@ -10,6 +12,7 @@ import (
10 10
 	"github.com/docker/docker/client"
11 11
 	"github.com/docker/docker/integration/internal/swarm"
12 12
 	"github.com/docker/docker/internal/testutil"
13
+	"github.com/docker/docker/pkg/stdcopy"
13 14
 	"github.com/gotestyourself/gotestyourself/skip"
14 15
 	"github.com/stretchr/testify/assert"
15 16
 	"github.com/stretchr/testify/require"
... ...
@@ -232,3 +235,130 @@ func TestSecretsUpdate(t *testing.T) {
232 232
 	err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec)
233 233
 	testutil.ErrorContains(t, err, "only updates to Labels are allowed")
234 234
 }
235
+
236
+func TestTemplatedSecret(t *testing.T) {
237
+	d := swarm.NewSwarm(t, testEnv)
238
+	defer d.Stop(t)
239
+
240
+	ctx := context.Background()
241
+	client := swarm.GetClient(t, d)
242
+
243
+	referencedSecretSpec := swarmtypes.SecretSpec{
244
+		Annotations: swarmtypes.Annotations{
245
+			Name: "referencedsecret",
246
+		},
247
+		Data: []byte("this is a secret"),
248
+	}
249
+	referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
250
+	assert.NoError(t, err)
251
+
252
+	referencedConfigSpec := swarmtypes.ConfigSpec{
253
+		Annotations: swarmtypes.Annotations{
254
+			Name: "referencedconfig",
255
+		},
256
+		Data: []byte("this is a config"),
257
+	}
258
+	referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
259
+	assert.NoError(t, err)
260
+
261
+	secretSpec := swarmtypes.SecretSpec{
262
+		Annotations: swarmtypes.Annotations{
263
+			Name: "templated_secret",
264
+		},
265
+		Templating: &swarmtypes.Driver{
266
+			Name: "golang",
267
+		},
268
+		Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
269
+			"{{secret \"referencedsecrettarget\"}}\n" +
270
+			"{{config \"referencedconfigtarget\"}}\n"),
271
+	}
272
+
273
+	templatedSecret, err := client.SecretCreate(ctx, secretSpec)
274
+	assert.NoError(t, err)
275
+
276
+	serviceID := swarm.CreateService(t, d,
277
+		swarm.ServiceWithSecret(
278
+			&swarmtypes.SecretReference{
279
+				File: &swarmtypes.SecretReferenceFileTarget{
280
+					Name: "templated_secret",
281
+					UID:  "0",
282
+					GID:  "0",
283
+					Mode: 0600,
284
+				},
285
+				SecretID:   templatedSecret.ID,
286
+				SecretName: "templated_secret",
287
+			},
288
+		),
289
+		swarm.ServiceWithConfig(
290
+			&swarmtypes.ConfigReference{
291
+				File: &swarmtypes.ConfigReferenceFileTarget{
292
+					Name: "referencedconfigtarget",
293
+					UID:  "0",
294
+					GID:  "0",
295
+					Mode: 0600,
296
+				},
297
+				ConfigID:   referencedConfig.ID,
298
+				ConfigName: "referencedconfig",
299
+			},
300
+		),
301
+		swarm.ServiceWithSecret(
302
+			&swarmtypes.SecretReference{
303
+				File: &swarmtypes.SecretReferenceFileTarget{
304
+					Name: "referencedsecrettarget",
305
+					UID:  "0",
306
+					GID:  "0",
307
+					Mode: 0600,
308
+				},
309
+				SecretID:   referencedSecret.ID,
310
+				SecretName: "referencedsecret",
311
+			},
312
+		),
313
+		swarm.ServiceWithName("svc"),
314
+	)
315
+
316
+	var tasks []swarmtypes.Task
317
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
318
+		tasks = swarm.GetRunningTasks(t, d, serviceID)
319
+		return len(tasks) > 0
320
+	})
321
+
322
+	task := tasks[0]
323
+	waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
324
+		if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
325
+			task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
326
+		}
327
+		return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
328
+	})
329
+
330
+	attach := swarm.ExecTask(t, d, task, types.ExecConfig{
331
+		Cmd:          []string{"/bin/cat", "/run/secrets/templated_secret"},
332
+		AttachStdout: true,
333
+		AttachStderr: true,
334
+	})
335
+
336
+	buf := bytes.NewBuffer(nil)
337
+	_, err = stdcopy.StdCopy(buf, buf, attach.Reader)
338
+	require.NoError(t, err)
339
+
340
+	expect := "SERVICE_NAME=svc\n" +
341
+		"this is a secret\n" +
342
+		"this is a config\n"
343
+
344
+	assert.Equal(t, expect, buf.String())
345
+}
346
+
347
+func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
348
+	t.Helper()
349
+	after := time.After(timeout)
350
+	for {
351
+		select {
352
+		case <-after:
353
+			t.Fatalf("timed out waiting for condition")
354
+		default:
355
+		}
356
+		if f(t) {
357
+			return
358
+		}
359
+		time.Sleep(100 * time.Millisecond)
360
+	}
361
+}