Skip to content

Commit 5fa08d2

Browse files
committed
introduce volume.type=image
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 482b622 commit 5fa08d2

File tree

7 files changed

+83
-40
lines changed

7 files changed

+83
-40
lines changed

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/Microsoft/go-winio v0.6.2
99
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
1010
github.com/buger/goterm v1.0.4
11-
github.com/compose-spec/compose-go/v2 v2.5.0
11+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca
1212
github.com/containerd/containerd/v2 v2.0.4
1313
github.com/containerd/platforms v1.0.0-rc.1
1414
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
@@ -206,5 +206,3 @@ require (
206206
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
207207
sigs.k8s.io/yaml v1.4.0 // indirect
208208
)
209-
210-
replace github.com/compose-spec/compose-go/v2 => github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
8383
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
8484
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
8585
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
86+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca h1:4dH4DudeDunWTYetcJxQE65osreQKvaLtFLdl9CcqME=
87+
github.com/compose-spec/compose-go/v2 v2.5.1-0.20250409070949-8e1a035095ca/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
8688
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
8789
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
8890
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
@@ -167,8 +169,6 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ
167169
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
168170
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
169171
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
170-
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535 h1:S/P6v3QxsMpkKn+2OSMPNkfSkadSjSHoMGAc/eBZgMU=
171-
github.com/glours/compose-go/v2 v2.0.0-20250403082600-80aa75f06535/go.mod h1:vPlkN0i+0LjLf9rv52lodNMUTJF5YHVfHVGLLIP67NA=
172172
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
173173
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
174174
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=

pkg/compose/build.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,17 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
318318
}
319319

320320
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
321-
var imageNames []string
321+
imageNames := utils.Set[string]{}
322322
for _, s := range project.Services {
323323
imgName := api.GetImageNameOrDefault(s, project.Name)
324-
if !utils.StringContains(imageNames, imgName) {
325-
imageNames = append(imageNames, imgName)
324+
imageNames.Add(imgName)
325+
for _, volume := range s.Volumes {
326+
if volume.Type == types.VolumeTypeImage {
327+
imageNames.Add(volume.Source)
328+
}
326329
}
327330
}
328-
imgs, err := s.getImageSummaries(ctx, imageNames)
331+
imgs, err := s.getImageSummaries(ctx, imageNames.Elements())
329332
if err != nil {
330333
return nil, err
331334
}

pkg/compose/create.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,7 +1125,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
11251125
}
11261126
}
11271127

1128-
bind, vol, tmpfs := buildMountOptions(volume)
1128+
bind, vol, tmpfs, img := buildMountOptions(volume)
11291129

11301130
if bind != nil {
11311131
volume.Type = types.VolumeTypeBind
@@ -1140,37 +1140,35 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
11401140
BindOptions: bind,
11411141
VolumeOptions: vol,
11421142
TmpfsOptions: tmpfs,
1143+
ImageOptions: img,
11431144
}, nil
11441145
}
11451146

1146-
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
1147+
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions, *mount.ImageOptions) {
1148+
if volume.Type != types.VolumeTypeBind && volume.Bind != nil {
1149+
logrus.Warnf("mount of type `%s` should not define `bind` option", volume.Type)
1150+
}
1151+
if volume.Type != types.VolumeTypeVolume && volume.Volume != nil {
1152+
logrus.Warnf("mount of type `%s` should not define `volume` option", volume.Type)
1153+
}
1154+
if volume.Type != types.VolumeTypeTmpfs && volume.Tmpfs != nil {
1155+
logrus.Warnf("mount of type `%s` should not define `tmpfs` option", volume.Type)
1156+
}
1157+
if volume.Type != types.VolumeTypeImage && volume.Image != nil {
1158+
logrus.Warnf("mount of type `%s` should not define `image` option", volume.Type)
1159+
}
1160+
11471161
switch volume.Type {
11481162
case "bind":
1149-
if volume.Volume != nil {
1150-
logrus.Warnf("mount of type `bind` should not define `volume` option")
1151-
}
1152-
if volume.Tmpfs != nil {
1153-
logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
1154-
}
1155-
return buildBindOption(volume.Bind), nil, nil
1163+
return buildBindOption(volume.Bind), nil, nil, nil
11561164
case "volume":
1157-
if volume.Bind != nil {
1158-
logrus.Warnf("mount of type `volume` should not define `bind` option")
1159-
}
1160-
if volume.Tmpfs != nil {
1161-
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
1162-
}
1163-
return nil, buildVolumeOptions(volume.Volume), nil
1165+
return nil, buildVolumeOptions(volume.Volume), nil, nil
11641166
case "tmpfs":
1165-
if volume.Bind != nil {
1166-
logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
1167-
}
1168-
if volume.Volume != nil {
1169-
logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
1170-
}
1171-
return nil, nil, buildTmpfsOptions(volume.Tmpfs)
1167+
return nil, nil, buildTmpfsOptions(volume.Tmpfs), nil
1168+
case "image":
1169+
return nil, nil, nil, buildImageOptions(volume.Image)
11721170
}
1173-
return nil, nil, nil
1171+
return nil, nil, nil, nil
11741172
}
11751173

11761174
func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
@@ -1199,7 +1197,7 @@ func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
11991197
return &mount.VolumeOptions{
12001198
NoCopy: vol.NoCopy,
12011199
Subpath: vol.Subpath,
1202-
// Labels: , // FIXME missing from model ?
1200+
Labels: vol.Labels,
12031201
// DriverConfig: , // FIXME missing from model ?
12041202
}
12051203
}
@@ -1214,6 +1212,15 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
12141212
}
12151213
}
12161214

1215+
func buildImageOptions(image *types.ServiceVolumeImage) *mount.ImageOptions {
1216+
if image == nil {
1217+
return nil
1218+
}
1219+
return &mount.ImageOptions{
1220+
Subpath: image.SubPath,
1221+
}
1222+
}
1223+
12171224
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
12181225
if n.External {
12191226
return s.resolveExternalNetwork(ctx, n)

pkg/compose/pull.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,28 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
290290
}
291291

292292
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]api.ImageSummary, quietPull bool) error {
293-
var needPull []types.ServiceConfig
294-
for _, service := range project.Services {
293+
needPull := map[string]types.ServiceConfig{}
294+
for name, service := range project.Services {
295295
pull, err := mustPull(service, images)
296296
if err != nil {
297297
return err
298298
}
299299
if pull {
300-
needPull = append(needPull, service)
300+
needPull[name] = service
301301
}
302+
for i, vol := range service.Volumes {
303+
if vol.Type == types.VolumeTypeImage {
304+
if _, ok := images[vol.Source]; !ok {
305+
// Hack: create a fake ServiceConfig so we pull missing volume image
306+
n := fmt.Sprintf("%s:volume %d", name, i)
307+
needPull[n] = types.ServiceConfig{
308+
Name: n,
309+
Image: vol.Source,
310+
}
311+
}
312+
}
313+
}
314+
302315
}
303316
if len(needPull) == 0 {
304317
return nil
@@ -308,11 +321,11 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
308321
w := progress.ContextWriter(ctx)
309322
eg, ctx := errgroup.WithContext(ctx)
310323
eg.SetLimit(s.maxConcurrency)
311-
pulledImages := make([]api.ImageSummary, len(needPull))
312-
for i, service := range needPull {
324+
pulledImages := map[string]api.ImageSummary{}
325+
for name, service := range needPull {
313326
eg.Go(func() error {
314327
id, err := s.pullServiceImage(ctx, service, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
315-
pulledImages[i] = api.ImageSummary{
328+
pulledImages[name] = api.ImageSummary{
316329
ID: id,
317330
Repository: service.Image,
318331
LastTagTime: time.Now(),
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
with_image:
3+
image: alpine
4+
command: "ls -al /mnt/image"
5+
volumes:
6+
- type: image
7+
source: nginx:alpine
8+
target: /mnt/image
9+
image:
10+
subpath: usr/share/nginx/html/

pkg/e2e/volumes_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,15 @@ func TestUpRecreateVolumes_IgnoreBinds(t *testing.T) {
174174
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/bind.yaml", "--project-name", projectName, "up", "-d")
175175
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
176176
}
177+
178+
func TestImageVolume(t *testing.T) {
179+
c := NewCLI(t)
180+
const projectName = "compose-e2e-image-volume"
181+
t.Cleanup(func() {
182+
c.cleanupWithDown(t, projectName)
183+
})
184+
185+
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/volumes/compose.yaml", "--project-name", projectName, "up", "with_image")
186+
out := res.Combined()
187+
assert.Check(t, strings.Contains(out, "index.html"))
188+
}

0 commit comments

Comments
 (0)