Skip to content

Commit f4991ec

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 f4991ec

File tree

3 files changed

+139
-1
lines changed

3 files changed

+139
-1
lines changed

cmd/nerdctl/compose/compose_up_linux_test.go

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

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)