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  }