github.com/moby/docker@v26.1.3+incompatible/integration-cli/docker_cli_service_create_test.go (about) 1 //go:build !windows 2 3 package main 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/mount" 14 "github.com/docker/docker/api/types/swarm" 15 "github.com/docker/docker/integration-cli/checker" 16 "github.com/docker/docker/testutil" 17 "gotest.tools/v3/assert" 18 "gotest.tools/v3/poll" 19 ) 20 21 func (s *DockerSwarmSuite) TestServiceCreateMountVolume(c *testing.T) { 22 ctx := testutil.GetContext(c) 23 d := s.AddDaemon(ctx, c, true, true) 24 out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--mount", "type=volume,source=foo,target=/foo,volume-nocopy", "busybox", "top") 25 assert.NilError(c, err, out) 26 id := strings.TrimSpace(out) 27 28 var tasks []swarm.Task 29 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 30 tasks = d.GetServiceTasks(ctx, c, id) 31 return len(tasks) > 0, "" 32 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 33 34 task := tasks[0] 35 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 36 if task.NodeID == "" || task.Status.ContainerStatus == nil { 37 task = d.GetTask(ctx, c, task.ID) 38 } 39 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 40 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 41 42 // check container mount config 43 out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .HostConfig.Mounts}}", task.Status.ContainerStatus.ContainerID) 44 assert.NilError(c, err, out) 45 46 var mountConfig []mount.Mount 47 assert.Assert(c, json.Unmarshal([]byte(out), &mountConfig) == nil) 48 assert.Equal(c, len(mountConfig), 1) 49 50 assert.Equal(c, mountConfig[0].Source, "foo") 51 assert.Equal(c, mountConfig[0].Target, "/foo") 52 assert.Equal(c, mountConfig[0].Type, mount.TypeVolume) 53 assert.Assert(c, mountConfig[0].VolumeOptions != nil) 54 assert.Assert(c, mountConfig[0].VolumeOptions.NoCopy) 55 56 // check container mounts actual 57 out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .Mounts}}", task.Status.ContainerStatus.ContainerID) 58 assert.NilError(c, err, out) 59 60 var mounts []types.MountPoint 61 assert.Assert(c, json.Unmarshal([]byte(out), &mounts) == nil) 62 assert.Equal(c, len(mounts), 1) 63 64 assert.Equal(c, mounts[0].Type, mount.TypeVolume) 65 assert.Equal(c, mounts[0].Name, "foo") 66 assert.Equal(c, mounts[0].Destination, "/foo") 67 assert.Equal(c, mounts[0].RW, true) 68 } 69 70 func (s *DockerSwarmSuite) TestServiceCreateWithSecretSimple(c *testing.T) { 71 ctx := testutil.GetContext(c) 72 d := s.AddDaemon(ctx, c, true, true) 73 74 serviceName := "test-service-secret" 75 testName := "test_secret" 76 id := d.CreateSecret(c, swarm.SecretSpec{ 77 Annotations: swarm.Annotations{ 78 Name: testName, 79 }, 80 Data: []byte("TESTINGDATA"), 81 }) 82 assert.Assert(c, id != "", "secrets: %s", id) 83 84 out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", testName, "busybox", "top") 85 assert.NilError(c, err, out) 86 87 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName) 88 assert.NilError(c, err) 89 90 var refs []swarm.SecretReference 91 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 92 assert.Equal(c, len(refs), 1) 93 94 assert.Equal(c, refs[0].SecretName, testName) 95 assert.Assert(c, refs[0].File != nil) 96 assert.Equal(c, refs[0].File.Name, testName) 97 assert.Equal(c, refs[0].File.UID, "0") 98 assert.Equal(c, refs[0].File.GID, "0") 99 100 out, err = d.Cmd("service", "rm", serviceName) 101 assert.NilError(c, err, out) 102 d.DeleteSecret(c, testName) 103 } 104 105 func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTargetPaths(c *testing.T) { 106 ctx := testutil.GetContext(c) 107 d := s.AddDaemon(ctx, c, true, true) 108 109 testPaths := map[string]string{ 110 "app": "/etc/secret", 111 "test_secret": "test_secret", 112 "relative_secret": "relative/secret", 113 "escapes_in_container": "../secret", 114 } 115 116 var secretFlags []string 117 118 for testName, testTarget := range testPaths { 119 id := d.CreateSecret(c, swarm.SecretSpec{ 120 Annotations: swarm.Annotations{ 121 Name: testName, 122 }, 123 Data: []byte("TESTINGDATA " + testName + " " + testTarget), 124 }) 125 assert.Assert(c, id != "", "secrets: %s", id) 126 127 secretFlags = append(secretFlags, "--secret", fmt.Sprintf("source=%s,target=%s", testName, testTarget)) 128 } 129 130 serviceName := "svc" 131 serviceCmd := []string{"service", "create", "--detach", "--no-resolve-image", "--name", serviceName} 132 serviceCmd = append(serviceCmd, secretFlags...) 133 serviceCmd = append(serviceCmd, "busybox", "top") 134 out, err := d.Cmd(serviceCmd...) 135 assert.NilError(c, err, out) 136 137 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName) 138 assert.NilError(c, err) 139 140 var refs []swarm.SecretReference 141 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 142 assert.Equal(c, len(refs), len(testPaths)) 143 144 var tasks []swarm.Task 145 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 146 tasks = d.GetServiceTasks(ctx, c, serviceName) 147 return len(tasks) > 0, "" 148 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 149 150 task := tasks[0] 151 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 152 if task.NodeID == "" || task.Status.ContainerStatus == nil { 153 task = d.GetTask(ctx, c, task.ID) 154 } 155 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 156 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 157 158 for testName, testTarget := range testPaths { 159 path := testTarget 160 if !filepath.IsAbs(path) { 161 path = filepath.Join("/run/secrets", path) 162 } 163 out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path) 164 assert.NilError(c, err) 165 assert.Equal(c, out, "TESTINGDATA "+testName+" "+testTarget) 166 } 167 168 out, err = d.Cmd("service", "rm", serviceName) 169 assert.NilError(c, err, out) 170 } 171 172 func (s *DockerSwarmSuite) TestServiceCreateWithSecretReferencedTwice(c *testing.T) { 173 ctx := testutil.GetContext(c) 174 d := s.AddDaemon(ctx, c, true, true) 175 176 id := d.CreateSecret(c, swarm.SecretSpec{ 177 Annotations: swarm.Annotations{ 178 Name: "mysecret", 179 }, 180 Data: []byte("TESTINGDATA"), 181 }) 182 assert.Assert(c, id != "", "secrets: %s", id) 183 184 serviceName := "svc" 185 out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--secret", "source=mysecret,target=target1", "--secret", "source=mysecret,target=target2", "busybox", "top") 186 assert.NilError(c, err, out) 187 188 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Secrets }}", serviceName) 189 assert.NilError(c, err) 190 191 var refs []swarm.SecretReference 192 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 193 assert.Equal(c, len(refs), 2) 194 195 var tasks []swarm.Task 196 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 197 tasks = d.GetServiceTasks(ctx, c, serviceName) 198 return len(tasks) > 0, "" 199 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 200 201 task := tasks[0] 202 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 203 if task.NodeID == "" || task.Status.ContainerStatus == nil { 204 task = d.GetTask(ctx, c, task.ID) 205 } 206 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 207 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 208 209 for _, target := range []string{"target1", "target2"} { 210 assert.NilError(c, err, out) 211 path := filepath.Join("/run/secrets", target) 212 out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path) 213 assert.NilError(c, err) 214 assert.Equal(c, out, "TESTINGDATA") 215 } 216 217 out, err = d.Cmd("service", "rm", serviceName) 218 assert.NilError(c, err, out) 219 } 220 221 func (s *DockerSwarmSuite) TestServiceCreateWithConfigSimple(c *testing.T) { 222 ctx := testutil.GetContext(c) 223 d := s.AddDaemon(ctx, c, true, true) 224 225 serviceName := "test-service-config" 226 testName := "test_config" 227 id := d.CreateConfig(c, swarm.ConfigSpec{ 228 Annotations: swarm.Annotations{ 229 Name: testName, 230 }, 231 Data: []byte("TESTINGDATA"), 232 }) 233 assert.Assert(c, id != "", "configs: %s", id) 234 235 out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--config", testName, "busybox", "top") 236 assert.NilError(c, err, out) 237 238 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Configs }}", serviceName) 239 assert.NilError(c, err) 240 241 var refs []swarm.ConfigReference 242 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 243 assert.Equal(c, len(refs), 1) 244 245 assert.Equal(c, refs[0].ConfigName, testName) 246 assert.Assert(c, refs[0].File != nil) 247 assert.Equal(c, refs[0].File.Name, testName) 248 assert.Equal(c, refs[0].File.UID, "0") 249 assert.Equal(c, refs[0].File.GID, "0") 250 251 out, err = d.Cmd("service", "rm", serviceName) 252 assert.NilError(c, err, out) 253 d.DeleteConfig(c, testName) 254 } 255 256 func (s *DockerSwarmSuite) TestServiceCreateWithConfigSourceTargetPaths(c *testing.T) { 257 ctx := testutil.GetContext(c) 258 d := s.AddDaemon(ctx, c, true, true) 259 260 testPaths := map[string]string{ 261 "app": "/etc/config", 262 "test_config": "test_config", 263 "relative_config": "relative/config", 264 } 265 266 var configFlags []string 267 268 for testName, testTarget := range testPaths { 269 id := d.CreateConfig(c, swarm.ConfigSpec{ 270 Annotations: swarm.Annotations{ 271 Name: testName, 272 }, 273 Data: []byte("TESTINGDATA " + testName + " " + testTarget), 274 }) 275 assert.Assert(c, id != "", "configs: %s", id) 276 277 configFlags = append(configFlags, "--config", fmt.Sprintf("source=%s,target=%s", testName, testTarget)) 278 } 279 280 serviceName := "svc" 281 serviceCmd := []string{"service", "create", "--detach", "--no-resolve-image", "--name", serviceName} 282 serviceCmd = append(serviceCmd, configFlags...) 283 serviceCmd = append(serviceCmd, "busybox", "top") 284 out, err := d.Cmd(serviceCmd...) 285 assert.NilError(c, err, out) 286 287 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Configs }}", serviceName) 288 assert.NilError(c, err) 289 290 var refs []swarm.ConfigReference 291 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 292 assert.Equal(c, len(refs), len(testPaths)) 293 294 var tasks []swarm.Task 295 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 296 tasks = d.GetServiceTasks(ctx, c, serviceName) 297 return len(tasks) > 0, "" 298 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 299 300 task := tasks[0] 301 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 302 if task.NodeID == "" || task.Status.ContainerStatus == nil { 303 task = d.GetTask(ctx, c, task.ID) 304 } 305 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 306 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 307 308 for testName, testTarget := range testPaths { 309 path := testTarget 310 if !filepath.IsAbs(path) { 311 path = filepath.Join("/", path) 312 } 313 out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path) 314 assert.NilError(c, err) 315 assert.Equal(c, out, "TESTINGDATA "+testName+" "+testTarget) 316 } 317 318 out, err = d.Cmd("service", "rm", serviceName) 319 assert.NilError(c, err, out) 320 } 321 322 func (s *DockerSwarmSuite) TestServiceCreateWithConfigReferencedTwice(c *testing.T) { 323 ctx := testutil.GetContext(c) 324 d := s.AddDaemon(ctx, c, true, true) 325 326 id := d.CreateConfig(c, swarm.ConfigSpec{ 327 Annotations: swarm.Annotations{ 328 Name: "myconfig", 329 }, 330 Data: []byte("TESTINGDATA"), 331 }) 332 assert.Assert(c, id != "", "configs: %s", id) 333 334 serviceName := "svc" 335 out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", serviceName, "--config", "source=myconfig,target=target1", "--config", "source=myconfig,target=target2", "busybox", "top") 336 assert.NilError(c, err, out) 337 338 out, err = d.Cmd("service", "inspect", "--format", "{{ json .Spec.TaskTemplate.ContainerSpec.Configs }}", serviceName) 339 assert.NilError(c, err) 340 341 var refs []swarm.ConfigReference 342 assert.Assert(c, json.Unmarshal([]byte(out), &refs) == nil) 343 assert.Equal(c, len(refs), 2) 344 345 var tasks []swarm.Task 346 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 347 tasks = d.GetServiceTasks(ctx, c, serviceName) 348 return len(tasks) > 0, "" 349 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 350 351 task := tasks[0] 352 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 353 if task.NodeID == "" || task.Status.ContainerStatus == nil { 354 task = d.GetTask(ctx, c, task.ID) 355 } 356 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 357 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 358 359 for _, target := range []string{"target1", "target2"} { 360 assert.NilError(c, err, out) 361 path := filepath.Join("/", target) 362 out, err := d.Cmd("exec", task.Status.ContainerStatus.ContainerID, "cat", path) 363 assert.NilError(c, err) 364 assert.Equal(c, out, "TESTINGDATA") 365 } 366 367 out, err = d.Cmd("service", "rm", serviceName) 368 assert.NilError(c, err, out) 369 } 370 371 func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *testing.T) { 372 ctx := testutil.GetContext(c) 373 d := s.AddDaemon(ctx, c, true, true) 374 out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--mount", "type=tmpfs,target=/foo,tmpfs-size=1MB", "busybox", "sh", "-c", "mount | grep foo; exec tail -f /dev/null") 375 assert.NilError(c, err, out) 376 id := strings.TrimSpace(out) 377 378 var tasks []swarm.Task 379 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 380 tasks = d.GetServiceTasks(ctx, c, id) 381 return len(tasks) > 0, "" 382 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 383 384 task := tasks[0] 385 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 386 if task.NodeID == "" || task.Status.ContainerStatus == nil { 387 task = d.GetTask(ctx, c, task.ID) 388 } 389 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 390 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 391 392 // check container mount config 393 out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .HostConfig.Mounts}}", task.Status.ContainerStatus.ContainerID) 394 assert.NilError(c, err, out) 395 396 var mountConfig []mount.Mount 397 assert.Assert(c, json.Unmarshal([]byte(out), &mountConfig) == nil) 398 assert.Equal(c, len(mountConfig), 1) 399 400 assert.Equal(c, mountConfig[0].Source, "") 401 assert.Equal(c, mountConfig[0].Target, "/foo") 402 assert.Equal(c, mountConfig[0].Type, mount.TypeTmpfs) 403 assert.Assert(c, mountConfig[0].TmpfsOptions != nil) 404 assert.Equal(c, mountConfig[0].TmpfsOptions.SizeBytes, int64(1048576)) 405 406 // check container mounts actual 407 out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .Mounts}}", task.Status.ContainerStatus.ContainerID) 408 assert.NilError(c, err, out) 409 410 var mounts []types.MountPoint 411 assert.Assert(c, json.Unmarshal([]byte(out), &mounts) == nil) 412 assert.Equal(c, len(mounts), 1) 413 414 assert.Equal(c, mounts[0].Type, mount.TypeTmpfs) 415 assert.Equal(c, mounts[0].Name, "") 416 assert.Equal(c, mounts[0].Destination, "/foo") 417 assert.Equal(c, mounts[0].RW, true) 418 419 out, err = s.nodeCmd(c, task.NodeID, "logs", task.Status.ContainerStatus.ContainerID) 420 assert.NilError(c, err, out) 421 assert.Assert(c, strings.HasPrefix(strings.TrimSpace(out), "tmpfs on /foo type tmpfs")) 422 assert.Assert(c, strings.Contains(strings.TrimSpace(out), "size=1024k")) 423 } 424 425 func (s *DockerSwarmSuite) TestServiceCreateWithNetworkAlias(c *testing.T) { 426 ctx := testutil.GetContext(c) 427 d := s.AddDaemon(ctx, c, true, true) 428 out, err := d.Cmd("network", "create", "--scope=swarm", "test_swarm_br") 429 assert.NilError(c, err, out) 430 431 out, err = d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--network=name=test_swarm_br,alias=srv_alias", "--name=alias_tst_container", "busybox", "top") 432 assert.NilError(c, err, out) 433 id := strings.TrimSpace(out) 434 435 var tasks []swarm.Task 436 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 437 tasks = d.GetServiceTasks(ctx, c, id) 438 return len(tasks) > 0, "" 439 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 440 441 task := tasks[0] 442 poll.WaitOn(c, pollCheck(c, func(c *testing.T) (interface{}, string) { 443 if task.NodeID == "" || task.Status.ContainerStatus == nil { 444 task = d.GetTask(ctx, c, task.ID) 445 } 446 return task.NodeID != "" && task.Status.ContainerStatus != nil, "" 447 }, checker.Equals(true)), poll.WithTimeout(defaultReconciliationTimeout)) 448 449 // check container alias config 450 out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .NetworkSettings.Networks.test_swarm_br.Aliases}}", task.Status.ContainerStatus.ContainerID) 451 assert.NilError(c, err, out) 452 453 // Make sure the only alias seen is the container-id 454 var aliases []string 455 assert.Assert(c, json.Unmarshal([]byte(out), &aliases) == nil) 456 assert.Equal(c, len(aliases), 1) 457 458 assert.Assert(c, strings.Contains(task.Status.ContainerStatus.ContainerID, aliases[0])) 459 }