github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/compose_run_linux_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "io" 22 "strings" 23 "testing" 24 "time" 25 26 "github.com/containerd/log" 27 "github.com/containerd/nerdctl/pkg/testutil" 28 "github.com/containerd/nerdctl/pkg/testutil/nettestutil" 29 "github.com/containerd/nerdctl/pkg/testutil/testregistry" 30 "gotest.tools/v3/assert" 31 ) 32 33 func TestComposeRun(t *testing.T) { 34 base := testutil.NewBase(t) 35 // specify the name of container in order to remove 36 // TODO: when `compose rm` is implemented, replace it. 37 containerName := testutil.Identifier(t) 38 39 dockerComposeYAML := fmt.Sprintf(` 40 version: '3.1' 41 services: 42 alpine: 43 image: %s 44 entrypoint: 45 - stty 46 `, testutil.AlpineImage) 47 48 comp := testutil.NewComposeDir(t, dockerComposeYAML) 49 defer comp.CleanUp() 50 projectName := comp.ProjectName() 51 t.Logf("projectName=%q", projectName) 52 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 53 54 defer base.Cmd("rm", "-f", "-v", containerName).Run() 55 const sttyPartialOutput = "speed 38400 baud" 56 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 57 // unbuffer(1) can be installed with `apt-get install expect`. 58 unbuffer := []string{"unbuffer"} 59 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 60 "run", "--name", containerName, "alpine").AssertOutContains(sttyPartialOutput) 61 } 62 63 func TestComposeRunWithRM(t *testing.T) { 64 base := testutil.NewBase(t) 65 // specify the name of container in order to remove 66 // TODO: when `compose rm` is implemented, replace it. 67 containerName := testutil.Identifier(t) 68 69 dockerComposeYAML := fmt.Sprintf(` 70 version: '3.1' 71 services: 72 alpine: 73 image: %s 74 entrypoint: 75 - stty 76 `, testutil.AlpineImage) 77 78 comp := testutil.NewComposeDir(t, dockerComposeYAML) 79 defer comp.CleanUp() 80 projectName := comp.ProjectName() 81 t.Logf("projectName=%q", projectName) 82 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 83 84 defer base.Cmd("rm", "-f", "-v", containerName).Run() 85 const sttyPartialOutput = "speed 38400 baud" 86 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 87 // unbuffer(1) can be installed with `apt-get install expect`. 88 unbuffer := []string{"unbuffer"} 89 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 90 "run", "--name", containerName, "--rm", "alpine").AssertOutContains(sttyPartialOutput) 91 92 psCmd := base.Cmd("ps", "-a", "--format=\"{{.Names}}\"") 93 result := psCmd.Run() 94 stdoutContent := result.Stdout() + result.Stderr() 95 assert.Assert(psCmd.Base.T, result.ExitCode == 0, stdoutContent) 96 if strings.Contains(stdoutContent, containerName) { 97 log.L.Errorf("test failed, the container %s is not removed", stdoutContent) 98 t.Fail() 99 return 100 } 101 } 102 103 func TestComposeRunWithServicePorts(t *testing.T) { 104 base := testutil.NewBase(t) 105 // specify the name of container in order to remove 106 // TODO: when `compose rm` is implemented, replace it. 107 containerName := testutil.Identifier(t) 108 109 dockerComposeYAML := fmt.Sprintf(` 110 version: '3.1' 111 services: 112 web: 113 image: %s 114 ports: 115 - 8080:80 116 `, testutil.NginxAlpineImage) 117 118 comp := testutil.NewComposeDir(t, dockerComposeYAML) 119 defer comp.CleanUp() 120 projectName := comp.ProjectName() 121 t.Logf("projectName=%q", projectName) 122 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 123 124 defer base.Cmd("rm", "-f", "-v", containerName).Run() 125 go func() { 126 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 127 // unbuffer(1) can be installed with `apt-get install expect`. 128 unbuffer := []string{"unbuffer"} 129 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 130 "run", "--service-ports", "--name", containerName, "web").Run() 131 }() 132 133 checkNginx := func() error { 134 resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 10, false) 135 if err != nil { 136 return err 137 } 138 respBody, err := io.ReadAll(resp.Body) 139 if err != nil { 140 return err 141 } 142 if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { 143 t.Logf("respBody=%q", respBody) 144 return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) 145 } 146 return nil 147 } 148 var nginxWorking bool 149 for i := 0; i < 30; i++ { 150 t.Logf("(retry %d)", i) 151 err := checkNginx() 152 if err == nil { 153 nginxWorking = true 154 break 155 } 156 t.Log(err) 157 time.Sleep(3 * time.Second) 158 } 159 if !nginxWorking { 160 t.Fatal("nginx is not working") 161 } 162 t.Log("nginx seems functional") 163 } 164 165 func TestComposeRunWithPublish(t *testing.T) { 166 base := testutil.NewBase(t) 167 // specify the name of container in order to remove 168 // TODO: when `compose rm` is implemented, replace it. 169 containerName := testutil.Identifier(t) 170 171 dockerComposeYAML := fmt.Sprintf(` 172 version: '3.1' 173 services: 174 web: 175 image: %s 176 `, testutil.NginxAlpineImage) 177 178 comp := testutil.NewComposeDir(t, dockerComposeYAML) 179 defer comp.CleanUp() 180 projectName := comp.ProjectName() 181 t.Logf("projectName=%q", projectName) 182 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 183 184 defer base.Cmd("rm", "-f", "-v", containerName).Run() 185 go func() { 186 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 187 // unbuffer(1) can be installed with `apt-get install expect`. 188 unbuffer := []string{"unbuffer"} 189 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 190 "run", "--publish", "8080:80", "--name", containerName, "web").Run() 191 }() 192 193 checkNginx := func() error { 194 resp, err := nettestutil.HTTPGet("http://127.0.0.1:8080", 10, false) 195 if err != nil { 196 return err 197 } 198 respBody, err := io.ReadAll(resp.Body) 199 if err != nil { 200 return err 201 } 202 if !strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet) { 203 t.Logf("respBody=%q", respBody) 204 return fmt.Errorf("respBody does not contain %q", testutil.NginxAlpineIndexHTMLSnippet) 205 } 206 return nil 207 } 208 var nginxWorking bool 209 for i := 0; i < 30; i++ { 210 t.Logf("(retry %d)", i) 211 err := checkNginx() 212 if err == nil { 213 nginxWorking = true 214 break 215 } 216 t.Log(err) 217 time.Sleep(3 * time.Second) 218 } 219 if !nginxWorking { 220 t.Fatal("nginx is not working") 221 } 222 t.Log("nginx seems functional") 223 } 224 225 func TestComposeRunWithEnv(t *testing.T) { 226 base := testutil.NewBase(t) 227 // specify the name of container in order to remove 228 // TODO: when `compose rm` is implemented, replace it. 229 containerName := testutil.Identifier(t) 230 231 dockerComposeYAML := fmt.Sprintf(` 232 version: '3.1' 233 services: 234 alpine: 235 image: %s 236 entrypoint: 237 - sh 238 - -c 239 - "echo $$FOO" 240 `, testutil.AlpineImage) 241 242 comp := testutil.NewComposeDir(t, dockerComposeYAML) 243 defer comp.CleanUp() 244 projectName := comp.ProjectName() 245 t.Logf("projectName=%q", projectName) 246 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 247 248 defer base.Cmd("rm", "-f", "-v", containerName).Run() 249 const partialOutput = "bar" 250 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 251 // unbuffer(1) can be installed with `apt-get install expect`. 252 unbuffer := []string{"unbuffer"} 253 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 254 "run", "-e", "FOO=bar", "--name", containerName, "alpine").AssertOutContains(partialOutput) 255 } 256 257 func TestComposeRunWithUser(t *testing.T) { 258 base := testutil.NewBase(t) 259 // specify the name of container in order to remove 260 // TODO: when `compose rm` is implemented, replace it. 261 containerName := testutil.Identifier(t) 262 263 dockerComposeYAML := fmt.Sprintf(` 264 version: '3.1' 265 services: 266 alpine: 267 image: %s 268 entrypoint: 269 - id 270 - -u 271 `, testutil.AlpineImage) 272 273 comp := testutil.NewComposeDir(t, dockerComposeYAML) 274 defer comp.CleanUp() 275 projectName := comp.ProjectName() 276 t.Logf("projectName=%q", projectName) 277 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 278 279 defer base.Cmd("rm", "-f", "-v", containerName).Run() 280 const partialOutput = "5000" 281 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 282 // unbuffer(1) can be installed with `apt-get install expect`. 283 unbuffer := []string{"unbuffer"} 284 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 285 "run", "--user", "5000", "--name", containerName, "alpine").AssertOutContains(partialOutput) 286 } 287 288 func TestComposeRunWithLabel(t *testing.T) { 289 base := testutil.NewBase(t) 290 containerName := testutil.Identifier(t) 291 292 dockerComposeYAML := fmt.Sprintf(` 293 version: '3.1' 294 services: 295 alpine: 296 image: %s 297 entrypoint: 298 - echo 299 - "dummy log" 300 labels: 301 - "foo=bar" 302 `, testutil.AlpineImage) 303 304 comp := testutil.NewComposeDir(t, dockerComposeYAML) 305 defer comp.CleanUp() 306 projectName := comp.ProjectName() 307 t.Logf("projectName=%q", projectName) 308 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 309 310 defer base.Cmd("rm", "-f", "-v", containerName).Run() 311 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 312 // unbuffer(1) can be installed with `apt-get install expect`. 313 unbuffer := []string{"unbuffer"} 314 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 315 "run", "--label", "foo=rab", "--label", "x=y", "--name", containerName, "alpine").AssertOK() 316 317 container := base.InspectContainer(containerName) 318 if container.Config == nil { 319 log.L.Errorf("test failed, cannot fetch container config") 320 t.Fail() 321 } 322 assert.Equal(t, container.Config.Labels["foo"], "rab") 323 assert.Equal(t, container.Config.Labels["x"], "y") 324 } 325 326 func TestComposeRunWithArgs(t *testing.T) { 327 base := testutil.NewBase(t) 328 containerName := testutil.Identifier(t) 329 330 dockerComposeYAML := fmt.Sprintf(` 331 version: '3.1' 332 services: 333 alpine: 334 image: %s 335 entrypoint: 336 - echo 337 `, testutil.AlpineImage) 338 339 comp := testutil.NewComposeDir(t, dockerComposeYAML) 340 defer comp.CleanUp() 341 projectName := comp.ProjectName() 342 t.Logf("projectName=%q", projectName) 343 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 344 345 defer base.Cmd("rm", "-f", "-v", containerName).Run() 346 const partialOutput = "hello world" 347 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 348 // unbuffer(1) can be installed with `apt-get install expect`. 349 unbuffer := []string{"unbuffer"} 350 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 351 "run", "--name", containerName, "alpine", partialOutput).AssertOutContains(partialOutput) 352 } 353 354 func TestComposeRunWithEntrypoint(t *testing.T) { 355 base := testutil.NewBase(t) 356 // specify the name of container in order to remove 357 // TODO: when `compose rm` is implemented, replace it. 358 containerName := testutil.Identifier(t) 359 360 dockerComposeYAML := fmt.Sprintf(` 361 version: '3.1' 362 services: 363 alpine: 364 image: %s 365 entrypoint: 366 - stty # should be changed 367 `, testutil.AlpineImage) 368 369 comp := testutil.NewComposeDir(t, dockerComposeYAML) 370 defer comp.CleanUp() 371 projectName := comp.ProjectName() 372 t.Logf("projectName=%q", projectName) 373 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 374 375 defer base.Cmd("rm", "-f", "-v", containerName).Run() 376 const partialOutput = "hello world" 377 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 378 // unbuffer(1) can be installed with `apt-get install expect`. 379 unbuffer := []string{"unbuffer"} 380 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 381 "run", "--entrypoint", "echo", "--name", containerName, "alpine", partialOutput).AssertOutContains(partialOutput) 382 } 383 384 func TestComposeRunWithVolume(t *testing.T) { 385 base := testutil.NewBase(t) 386 containerName := testutil.Identifier(t) 387 388 dockerComposeYAML := fmt.Sprintf(` 389 version: '3.1' 390 services: 391 alpine: 392 image: %s 393 entrypoint: 394 - stty # no meaning, just put any command 395 `, testutil.AlpineImage) 396 397 comp := testutil.NewComposeDir(t, dockerComposeYAML) 398 defer comp.CleanUp() 399 projectName := comp.ProjectName() 400 t.Logf("projectName=%q", projectName) 401 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 402 403 // The directory is automatically removed by Cleanup 404 tmpDir := t.TempDir() 405 destinationDir := "/data" 406 volumeFlagStr := fmt.Sprintf("%s:%s", tmpDir, destinationDir) 407 408 defer base.Cmd("rm", "-f", "-v", containerName).Run() 409 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 410 // unbuffer(1) can be installed with `apt-get install expect`. 411 unbuffer := []string{"unbuffer"} 412 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), 413 "run", "--volume", volumeFlagStr, "--name", containerName, "alpine").AssertOK() 414 415 container := base.InspectContainer(containerName) 416 errMsg := fmt.Sprintf("test failed, cannot find volume: %v", container.Mounts) 417 assert.Assert(t, container.Mounts != nil, errMsg) 418 assert.Assert(t, len(container.Mounts) == 1, errMsg) 419 assert.Assert(t, container.Mounts[0].Source == tmpDir, errMsg) 420 assert.Assert(t, container.Mounts[0].Destination == destinationDir, errMsg) 421 } 422 423 func TestComposePushAndPullWithCosignVerify(t *testing.T) { 424 testutil.RequireExecutable(t, "cosign") 425 testutil.DockerIncompatible(t) 426 testutil.RequiresBuild(t) 427 base := testutil.NewBase(t) 428 defer base.Cmd("builder", "prune").Run() 429 430 // set up cosign and local registry 431 t.Setenv("COSIGN_PASSWORD", "1") 432 keyPair := newCosignKeyPair(t, "cosign-key-pair") 433 defer keyPair.cleanup() 434 435 reg := testregistry.NewPlainHTTP(base, 5000) 436 defer reg.Cleanup() 437 localhostIP := "127.0.0.1" 438 t.Logf("localhost IP=%q", localhostIP) 439 testImageRefPrefix := fmt.Sprintf("%s:%d/", 440 localhostIP, reg.ListenPort) 441 t.Logf("testImageRefPrefix=%q", testImageRefPrefix) 442 443 var ( 444 imageSvc0 = testImageRefPrefix + "composebuild_svc0" 445 imageSvc1 = testImageRefPrefix + "composebuild_svc1" 446 imageSvc2 = testImageRefPrefix + "composebuild_svc2" 447 ) 448 449 dockerComposeYAML := fmt.Sprintf(` 450 services: 451 svc0: 452 build: . 453 image: %s 454 x-nerdctl-verify: cosign 455 x-nerdctl-cosign-public-key: %s 456 x-nerdctl-sign: cosign 457 x-nerdctl-cosign-private-key: %s 458 entrypoint: 459 - stty 460 svc1: 461 build: . 462 image: %s 463 x-nerdctl-verify: cosign 464 x-nerdctl-cosign-public-key: dummy_pub_key 465 x-nerdctl-sign: cosign 466 x-nerdctl-cosign-private-key: %s 467 entrypoint: 468 - stty 469 svc2: 470 build: . 471 image: %s 472 x-nerdctl-verify: none 473 x-nerdctl-sign: none 474 entrypoint: 475 - stty 476 `, imageSvc0, keyPair.publicKey, keyPair.privateKey, 477 imageSvc1, keyPair.privateKey, imageSvc2) 478 479 dockerfile := fmt.Sprintf(`FROM %s`, testutil.AlpineImage) 480 481 comp := testutil.NewComposeDir(t, dockerComposeYAML) 482 defer comp.CleanUp() 483 comp.WriteFile("Dockerfile", dockerfile) 484 485 projectName := comp.ProjectName() 486 t.Logf("projectName=%q", projectName) 487 defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run() 488 489 // 1. build both services/images 490 base.ComposeCmd("-f", comp.YAMLFullPath(), "build").AssertOK() 491 // 2. compose push with cosign for svc0/svc1, (and none for svc2) 492 base.ComposeCmd("-f", comp.YAMLFullPath(), "push").AssertOK() 493 // 3. compose pull with cosign 494 base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc0").AssertOK() // key match 495 base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc1").AssertFail() // key mismatch 496 base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc2").AssertOK() // verify passed 497 // 4. compose run 498 const sttyPartialOutput = "speed 38400 baud" 499 // unbuffer(1) emulates tty, which is required by `nerdctl run -t`. 500 // unbuffer(1) can be installed with `apt-get install expect`. 501 unbuffer := []string{"unbuffer"} 502 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc0").AssertOutContains(sttyPartialOutput) // key match 503 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc1").AssertFail() // key mismatch 504 base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc2").AssertOutContains(sttyPartialOutput) // verify passed 505 // 5. compose up 506 base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc0").AssertOK() // key match 507 base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc1").AssertFail() // key mismatch 508 base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc2").AssertOK() // verify passed 509 }