Skip to content

Commit 76c26b0

Browse files
committed
fix: support tmpfs long syntax in compose volumes
In the current implementation, nerdctl compose command ignores tmpfs configurations specified in the long syntax within the volumes section of compose.yml [1]. [1] https://docs.docker.com/reference/compose-file/services/#long-syntax-6 > - `type`: The mount type. Either `volume`, `bind`, `tmpfs`, `image`, `npipe`, or `cluster` > - `target`: The path in the container where the volume is mounted. > - `read_only`: Flag to set the volume as read-only. > - `tmpfs`: Configures additional tmpfs options: > - `size`: The size for the tmpfs mount in bytes (either numeric or as bytes unit). > - `mode`: The file mode for the tmpfs mount as Unix permission bits as an octal number. Introduced in Docker Compose version [2.14.0](https://docs.docker.com/compose/releases/release-notes/#2260). This behavior has been reported in issue#4556. Therefore, this commit modifies so that when tmpfs is specified using the long syntax in the volumes section, tmpfs is created within the container. Signed-off-by: Hayato Kiwata <[email protected]>
1 parent 457984d commit 76c26b0

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

cmd/nerdctl/compose/compose_up_linux_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030

3131
"github.com/containerd/log"
3232
"github.com/containerd/nerdctl/mod/tigron/expect"
33+
"github.com/containerd/nerdctl/mod/tigron/require"
3334
"github.com/containerd/nerdctl/mod/tigron/test"
3435

3536
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
@@ -671,3 +672,75 @@ services:
671672
base.Cmd("images").AssertOutNotContains(testutil.CommonImage)
672673
base.ComposeCmd("-f", comp.YAMLFullPath(), "up").AssertExitCode(1)
673674
}
675+
676+
func TestComposeTmpfsVolume(t *testing.T) {
677+
testCase := nerdtest.Setup()
678+
679+
testCase.Require = require.Not(require.Windows)
680+
681+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
682+
containerName := data.Identifier("tmpfs")
683+
composeYAML := fmt.Sprintf(`
684+
services:
685+
tmpfs:
686+
container_name: %s
687+
image: %s
688+
command: sleep infinity
689+
volumes:
690+
- type: tmpfs
691+
target: /target-rw
692+
tmpfs:
693+
size: 64m
694+
- type: tmpfs
695+
target: /target-ro
696+
read_only: true
697+
tmpfs:
698+
size: 64m
699+
mode: 0o1770
700+
`, containerName, testutil.CommonImage)
701+
702+
composeYAMLPath := data.Temp().Save(composeYAML, "compose.yaml")
703+
704+
helpers.Ensure("compose", "-f", composeYAMLPath, "up", "-d")
705+
nerdtest.EnsureContainerStarted(helpers, containerName)
706+
707+
data.Labels().Set("composeYAML", composeYAMLPath)
708+
data.Labels().Set("containerName", containerName)
709+
}
710+
711+
testCase.SubTests = []*test.Case{
712+
{
713+
Description: "rw tmpfs mount",
714+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
715+
return helpers.Command("exec", data.Labels().Get("containerName"), "grep", "/target-rw", "/proc/mounts")
716+
},
717+
Expected: test.Expects(0, nil,
718+
expect.All(
719+
expect.Contains("/target-rw"),
720+
expect.Contains("rw"),
721+
expect.Contains("size=65536k"),
722+
),
723+
),
724+
},
725+
{
726+
Description: "ro tmpfs mount with mode",
727+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
728+
return helpers.Command("exec", data.Labels().Get("containerName"), "grep", "/target-ro", "/proc/mounts")
729+
},
730+
Expected: test.Expects(0, nil,
731+
expect.All(
732+
expect.Contains("/target-ro"),
733+
expect.Contains("ro"),
734+
expect.Contains("size=65536k"),
735+
expect.Contains("mode=1770"),
736+
),
737+
),
738+
},
739+
}
740+
741+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
742+
helpers.Anyhow("compose", "-f", data.Labels().Get("composeYAML"), "down")
743+
}
744+
745+
testCase.Run(t)
746+
}

pkg/composer/serviceparser/serviceparser.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,14 @@ func newContainer(project *types.Project, parsed *Service, i int) (*Container, e
699699
if err != nil {
700700
return nil, err
701701
}
702-
c.RunArgs = append(c.RunArgs, "-v="+vStr)
702+
703+
switch v.Type {
704+
case "tmpfs":
705+
c.RunArgs = append(c.RunArgs, "--tmpfs="+vStr)
706+
default:
707+
c.RunArgs = append(c.RunArgs, "-v="+vStr)
708+
}
709+
703710
c.Mkdir = mkdir
704711
}
705712

@@ -778,6 +785,7 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
778785
"ReadOnly",
779786
"Bind",
780787
"Volume",
788+
"Tmpfs",
781789
); len(unknown) > 0 {
782790
log.L.Warnf("Ignoring: volume: %+v", unknown)
783791
}
@@ -800,6 +808,29 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
800808
return "", nil, fmt.Errorf("volume target must be an absolute path, got %q", c.Target)
801809
}
802810

811+
if c.Type == "tmpfs" {
812+
var opts []string
813+
814+
if c.ReadOnly {
815+
opts = append(opts, "ro")
816+
}
817+
if c.Tmpfs != nil {
818+
if c.Tmpfs.Size != 0 {
819+
opts = append(opts, fmt.Sprintf("size=%d", c.Tmpfs.Size))
820+
}
821+
if c.Tmpfs.Mode != 0 {
822+
opts = append(opts, fmt.Sprintf("mode=%o", c.Tmpfs.Mode))
823+
}
824+
}
825+
826+
s := c.Target
827+
if len(opts) > 0 {
828+
s = fmt.Sprintf("%s:%s", s, strings.Join(opts, ","))
829+
}
830+
831+
return s, mkdir, nil
832+
}
833+
803834
if c.Source == "" {
804835
// anonymous volume
805836
s := c.Target

pkg/composer/serviceparser/serviceparser_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,43 @@ services:
439439
}
440440
}
441441

442+
func TestTmpfsVolumeLongSyntax(t *testing.T) {
443+
t.Parallel()
444+
445+
if runtime.GOOS == "windows" {
446+
t.Skip("test is not compatible with windows")
447+
}
448+
449+
const dockerComposeYAML = `
450+
services:
451+
foo:
452+
image: nginx:alpine
453+
volumes:
454+
- type: tmpfs
455+
target: /target
456+
read_only: true
457+
tmpfs:
458+
size: 2G
459+
mode: 0o1770
460+
`
461+
comp := testutil.NewComposeDir(t, dockerComposeYAML)
462+
defer comp.CleanUp()
463+
464+
project, err := testutil.LoadProject(comp.YAMLFullPath(), comp.ProjectName(), nil)
465+
assert.NilError(t, err)
466+
467+
fooSvc, err := project.GetService("foo")
468+
assert.NilError(t, err)
469+
470+
foo, err := Parse(project, fooSvc)
471+
assert.NilError(t, err)
472+
473+
t.Logf("foo: %+v", foo)
474+
for _, c := range foo.Containers {
475+
assert.Assert(t, in(c.RunArgs, "--tmpfs=/target:ro,size=2147483648,mode=1770"))
476+
}
477+
}
478+
442479
func TestParseNetworkMode(t *testing.T) {
443480
t.Parallel()
444481
const dockerComposeYAML = `

0 commit comments

Comments
 (0)