mirror of
https://github.com/docker/compose.git
synced 2026-08-04 14:47:40 +00:00
introduce volume.type=image
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
846161d447
commit
01e83defc2
7 changed files with 99 additions and 40 deletions
|
|
@ -318,14 +318,17 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
|
|||
}
|
||||
|
||||
func (s *composeService) getLocalImagesDigests(ctx context.Context, project *types.Project) (map[string]api.ImageSummary, error) {
|
||||
var imageNames []string
|
||||
imageNames := utils.Set[string]{}
|
||||
for _, s := range project.Services {
|
||||
imgName := api.GetImageNameOrDefault(s, project.Name)
|
||||
if !utils.StringContains(imageNames, imgName) {
|
||||
imageNames = append(imageNames, imgName)
|
||||
imageNames.Add(imgName)
|
||||
for _, volume := range s.Volumes {
|
||||
if volume.Type == types.VolumeTypeImage {
|
||||
imageNames.Add(volume.Source)
|
||||
}
|
||||
}
|
||||
}
|
||||
imgs, err := s.getImageSummaries(ctx, imageNames)
|
||||
imgs, err := s.getImageSummaries(ctx, imageNames.Elements())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -871,6 +871,15 @@ MOUNTS:
|
|||
}
|
||||
}
|
||||
}
|
||||
if m.Type == mount.TypeImage {
|
||||
version, err := s.RuntimeVersion(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if versions.LessThan(version, "1.48") {
|
||||
return nil, nil, fmt.Errorf("volume with type=image require Docker Engine v28 or later")
|
||||
}
|
||||
}
|
||||
mounts = append(mounts, m)
|
||||
}
|
||||
return binds, mounts, nil
|
||||
|
|
@ -1125,7 +1134,7 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
|||
}
|
||||
}
|
||||
|
||||
bind, vol, tmpfs := buildMountOptions(volume)
|
||||
bind, vol, tmpfs, img := buildMountOptions(volume)
|
||||
|
||||
if bind != nil {
|
||||
volume.Type = types.VolumeTypeBind
|
||||
|
|
@ -1140,37 +1149,35 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
|||
BindOptions: bind,
|
||||
VolumeOptions: vol,
|
||||
TmpfsOptions: tmpfs,
|
||||
ImageOptions: img,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
|
||||
func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions, *mount.ImageOptions) {
|
||||
if volume.Type != types.VolumeTypeBind && volume.Bind != nil {
|
||||
logrus.Warnf("mount of type `%s` should not define `bind` option", volume.Type)
|
||||
}
|
||||
if volume.Type != types.VolumeTypeVolume && volume.Volume != nil {
|
||||
logrus.Warnf("mount of type `%s` should not define `volume` option", volume.Type)
|
||||
}
|
||||
if volume.Type != types.VolumeTypeTmpfs && volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `%s` should not define `tmpfs` option", volume.Type)
|
||||
}
|
||||
if volume.Type != types.VolumeTypeImage && volume.Image != nil {
|
||||
logrus.Warnf("mount of type `%s` should not define `image` option", volume.Type)
|
||||
}
|
||||
|
||||
switch volume.Type {
|
||||
case "bind":
|
||||
if volume.Volume != nil {
|
||||
logrus.Warnf("mount of type `bind` should not define `volume` option")
|
||||
}
|
||||
if volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `bind` should not define `tmpfs` option")
|
||||
}
|
||||
return buildBindOption(volume.Bind), nil, nil
|
||||
return buildBindOption(volume.Bind), nil, nil, nil
|
||||
case "volume":
|
||||
if volume.Bind != nil {
|
||||
logrus.Warnf("mount of type `volume` should not define `bind` option")
|
||||
}
|
||||
if volume.Tmpfs != nil {
|
||||
logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
|
||||
}
|
||||
return nil, buildVolumeOptions(volume.Volume), nil
|
||||
return nil, buildVolumeOptions(volume.Volume), nil, nil
|
||||
case "tmpfs":
|
||||
if volume.Bind != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
|
||||
}
|
||||
if volume.Volume != nil {
|
||||
logrus.Warnf("mount of type `tmpfs` should not define `volume` option")
|
||||
}
|
||||
return nil, nil, buildTmpfsOptions(volume.Tmpfs)
|
||||
return nil, nil, buildTmpfsOptions(volume.Tmpfs), nil
|
||||
case "image":
|
||||
return nil, nil, nil, buildImageOptions(volume.Image)
|
||||
}
|
||||
return nil, nil, nil
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
|
||||
|
|
@ -1199,7 +1206,7 @@ func buildVolumeOptions(vol *types.ServiceVolumeVolume) *mount.VolumeOptions {
|
|||
return &mount.VolumeOptions{
|
||||
NoCopy: vol.NoCopy,
|
||||
Subpath: vol.Subpath,
|
||||
// Labels: , // FIXME missing from model ?
|
||||
Labels: vol.Labels,
|
||||
// DriverConfig: , // FIXME missing from model ?
|
||||
}
|
||||
}
|
||||
|
|
@ -1214,6 +1221,15 @@ func buildTmpfsOptions(tmpfs *types.ServiceVolumeTmpfs) *mount.TmpfsOptions {
|
|||
}
|
||||
}
|
||||
|
||||
func buildImageOptions(image *types.ServiceVolumeImage) *mount.ImageOptions {
|
||||
if image == nil {
|
||||
return nil
|
||||
}
|
||||
return &mount.ImageOptions{
|
||||
Subpath: image.SubPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *composeService) ensureNetwork(ctx context.Context, project *types.Project, name string, n *types.NetworkConfig) (string, error) {
|
||||
if n.External {
|
||||
return s.resolveExternalNetwork(ctx, n)
|
||||
|
|
|
|||
|
|
@ -290,15 +290,28 @@ func encodedAuth(ref reference.Named, configFile driver.Auth) (string, error) {
|
|||
}
|
||||
|
||||
func (s *composeService) pullRequiredImages(ctx context.Context, project *types.Project, images map[string]api.ImageSummary, quietPull bool) error {
|
||||
var needPull []types.ServiceConfig
|
||||
for _, service := range project.Services {
|
||||
needPull := map[string]types.ServiceConfig{}
|
||||
for name, service := range project.Services {
|
||||
pull, err := mustPull(service, images)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pull {
|
||||
needPull = append(needPull, service)
|
||||
needPull[name] = service
|
||||
}
|
||||
for i, vol := range service.Volumes {
|
||||
if vol.Type == types.VolumeTypeImage {
|
||||
if _, ok := images[vol.Source]; !ok {
|
||||
// Hack: create a fake ServiceConfig so we pull missing volume image
|
||||
n := fmt.Sprintf("%s:volume %d", name, i)
|
||||
needPull[n] = types.ServiceConfig{
|
||||
Name: n,
|
||||
Image: vol.Source,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if len(needPull) == 0 {
|
||||
return nil
|
||||
|
|
@ -308,11 +321,11 @@ func (s *composeService) pullRequiredImages(ctx context.Context, project *types.
|
|||
w := progress.ContextWriter(ctx)
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(s.maxConcurrency)
|
||||
pulledImages := make([]api.ImageSummary, len(needPull))
|
||||
for i, service := range needPull {
|
||||
pulledImages := map[string]api.ImageSummary{}
|
||||
for name, service := range needPull {
|
||||
eg.Go(func() error {
|
||||
id, err := s.pullServiceImage(ctx, service, s.configFile(), w, quietPull, project.Environment["DOCKER_DEFAULT_PLATFORM"])
|
||||
pulledImages[i] = api.ImageSummary{
|
||||
pulledImages[name] = api.ImageSummary{
|
||||
ID: id,
|
||||
Repository: service.Image,
|
||||
LastTagTime: time.Now(),
|
||||
|
|
|
|||
10
pkg/e2e/fixtures/volumes/compose.yaml
Normal file
10
pkg/e2e/fixtures/volumes/compose.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
services:
|
||||
with_image:
|
||||
image: alpine
|
||||
command: "ls -al /mnt/image"
|
||||
volumes:
|
||||
- type: image
|
||||
source: nginx:alpine
|
||||
target: /mnt/image
|
||||
image:
|
||||
subpath: usr/share/nginx/html/
|
||||
|
|
@ -174,3 +174,22 @@ func TestUpRecreateVolumes_IgnoreBinds(t *testing.T) {
|
|||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/recreate-volumes/bind.yaml", "--project-name", projectName, "up", "-d")
|
||||
assert.Check(t, !strings.Contains(res.Combined(), "Recreated"))
|
||||
}
|
||||
|
||||
func TestImageVolume(t *testing.T) {
|
||||
c := NewCLI(t)
|
||||
const projectName = "compose-e2e-image-volume"
|
||||
t.Cleanup(func() {
|
||||
c.cleanupWithDown(t, projectName)
|
||||
})
|
||||
|
||||
version := c.RunDockerCmd(t, "version", "-f", "{{.Server.Version}}")
|
||||
major, _, found := strings.Cut(version.Combined(), ".")
|
||||
assert.Assert(t, found)
|
||||
if major == "26" || major == "27" {
|
||||
t.Skip("Skipping test due to docker version < 28")
|
||||
}
|
||||
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/volumes/compose.yaml", "--project-name", projectName, "up", "with_image")
|
||||
out := res.Combined()
|
||||
assert.Check(t, strings.Contains(out, "index.html"))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue