Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 84 additions & 59 deletions cmd/nerdctl/compose/compose_run_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ import (
"gotest.tools/v3/assert"

"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"
"github.com/containerd/nerdctl/mod/tigron/tig"

"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
"github.com/containerd/nerdctl/v2/pkg/testutil/nettestutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/portlock"
"github.com/containerd/nerdctl/v2/pkg/testutil/testregistry"
)

func composeRunCleanup() test.Butler {
Expand Down Expand Up @@ -583,32 +583,31 @@ services:
}

func TestComposePushAndPullWithCosignVerify(t *testing.T) {
testutil.RequireExecutable(t, "cosign")
testutil.DockerIncompatible(t)
testutil.RequiresBuild(t)
testutil.RegisterBuildCacheCleanup(t)
t.Parallel()

base := testutil.NewBase(t)
base.Env = append(base.Env, "COSIGN_PASSWORD=1")

keyPair := helpers.NewCosignKeyPair(t, "cosign-key-pair", "1")
reg := testregistry.NewWithNoAuth(base, 0, false)
t.Cleanup(func() {
keyPair.Cleanup()
reg.Cleanup(nil)
})

tID := testutil.Identifier(t)
testImageRefPrefix := fmt.Sprintf("127.0.0.1:%d/%s/", reg.Port, tID)

var (
imageSvc0 = testImageRefPrefix + "composebuild_svc0"
imageSvc1 = testImageRefPrefix + "composebuild_svc1"
imageSvc2 = testImageRefPrefix + "composebuild_svc2"
const sttyPartialOutput = "speed 38400 baud"

testCase := nerdtest.Setup()

testCase.Require = require.All(
require.Binary("cosign"),
require.Not(nerdtest.Docker),
nerdtest.Build,
nerdtest.Registry,
)

dockerComposeYAML := fmt.Sprintf(`
testCase.Env["COSIGN_PASSWORD"] = "1"

dockerfile := fmt.Sprintf("FROM %s", testutil.CommonImage)

var reg *registry.Server
var composeYAML string

testCase.Setup = func(data test.Data, helpers test.Helpers) {
pri, pub := nerdtest.GenerateCosignKeyPair(data, helpers, "1")
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
reg.Setup(data, helpers)

prefix := fmt.Sprintf("127.0.0.1:%d/%s/", reg.Port, data.Identifier())
composeYAML = fmt.Sprintf(`
services:
svc0:
build: .
Expand All @@ -635,37 +634,63 @@ services:
x-nerdctl-sign: none
entrypoint:
- stty
`, imageSvc0, keyPair.PublicKey, keyPair.PrivateKey,
imageSvc1, keyPair.PrivateKey, imageSvc2)

dockerfile := fmt.Sprintf(`FROM %s`, testutil.CommonImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()
comp.WriteFile("Dockerfile", dockerfile)

projectName := comp.ProjectName()
t.Logf("projectName=%q", projectName)
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down", "-v").Run()

// 1. build both services/images
base.ComposeCmd("-f", comp.YAMLFullPath(), "build").AssertOK()
// 2. compose push with cosign for svc0/svc1, (and none for svc2)
base.ComposeCmd("-f", comp.YAMLFullPath(), "push").AssertOK()
// 3. compose pull with cosign
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc0").AssertOK() // key match
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc1").AssertFail() // key mismatch
base.ComposeCmd("-f", comp.YAMLFullPath(), "pull", "svc2").AssertOK() // verify passed
// 4. compose run
const sttyPartialOutput = "speed 38400 baud"
// unbuffer(1) emulates tty, which is required by `nerdctl run -t`.
// unbuffer(1) can be installed with `apt-get install expect`.
unbuffer := []string{"unbuffer"}
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc0").AssertOutContains(sttyPartialOutput) // key match
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc1").AssertFail() // key mismatch
base.ComposeCmdWithHelper(unbuffer, "-f", comp.YAMLFullPath(), "run", "svc2").AssertOutContains(sttyPartialOutput) // verify passed
// 5. compose up
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc0").AssertOK() // key match
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc1").AssertFail() // key mismatch
base.ComposeCmd("-f", comp.YAMLFullPath(), "up", "svc2").AssertOK() // verify passed
`, prefix+"composebuild_svc0", pub, pri, prefix+"composebuild_svc1", pri, prefix+"composebuild_svc2")

data.Temp().Save(composeYAML, "compose.yaml")
data.Temp().Save(dockerfile, "Dockerfile")

composePath := data.Temp().Path("compose.yaml")
// Build both services/images and push, signing svc0/svc1 with cosign (svc2 unsigned).
helpers.Ensure("compose", "-f", composePath, "build")
helpers.Ensure("compose", "-f", composePath, "push")
}

testCase.Cleanup = func(data test.Data, helpers test.Helpers) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks! I will switch the subtest cleanup to composeRunCleanup() once #5116 lands. It handles the compose run one-off containers and the lock ordering more cleanly than my inline down. Will update this PR when #5116 is in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, #5116 landed so I rebased and switched both the subtest and top-level cleanups to composeRunCleanup(). Thanks!

composeRunCleanup()(data, helpers)
if reg != nil {
reg.Cleanup(data, helpers)
}
}

// Each subtest re-materializes the compose project (the signed images live in the
// shared registry set up above) and exercises one verify scenario:
// svc0 verifies against the matching key, svc1 against a mismatching key (must fail),
// svc2 is not verified.
subTest := func(description, op, svc string, tty bool, expected test.Manager) *test.Case {
return &test.Case{
Description: description,
Setup: func(data test.Data, helpers test.Helpers) {
data.Temp().Save(composeYAML, "compose.yaml")
data.Temp().Save(dockerfile, "Dockerfile")
},
Cleanup: composeRunCleanup(),
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
cmd := helpers.Command("compose", "-f", data.Temp().Path("compose.yaml"), op, svc)
if tty {
// stty (the entrypoint) requires a tty, which `run -t` provides.
cmd.WithPseudoTTY()
}
return cmd
},
Expected: expected,
}
}

success := test.Expects(expect.ExitCodeSuccess, nil, nil)
fail := test.Expects(expect.ExitCodeGenericFail, nil, nil)
successWithOutput := test.Expects(expect.ExitCodeSuccess, nil, expect.Contains(sttyPartialOutput))

testCase.SubTests = []*test.Case{
subTest("compose pull svc0 (key match)", "pull", "svc0", false, success),
subTest("compose pull svc1 (key mismatch)", "pull", "svc1", false, fail),
subTest("compose pull svc2 (verify none)", "pull", "svc2", false, success),
subTest("compose run svc0 (key match)", "run", "svc0", true, successWithOutput),
subTest("compose run svc1 (key mismatch)", "run", "svc1", true, fail),
subTest("compose run svc2 (verify none)", "run", "svc2", true, successWithOutput),
subTest("compose up svc0 (key match)", "up", "svc0", false, success),
subTest("compose up svc1 (key mismatch)", "up", "svc1", false, fail),
subTest("compose up svc2 (verify none)", "up", "svc2", false, success),
}

testCase.Run(t)
}
Loading