diff --git a/loader/loader_test.go b/loader/loader_test.go index bde742fe..6b07c37a 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -3778,3 +3778,24 @@ services: Options: map[string]string{"bar": "zot"}, }) } + +func TestImageVolume(t *testing.T) { + p, err := loadYAML(` +name: imageVolume +services: + test: + volumes: + - type: image + source: app/image + target: /mnt/image + image: + subpath: /foo +`) + assert.NilError(t, err) + assert.DeepEqual(t, p.Services["test"].Volumes[0], types.ServiceVolumeConfig{ + Type: "image", + Source: "app/image", + Target: "/mnt/image", + Image: &types.ServiceVolumeImage{SubPath: "/foo"}, + }) +} diff --git a/override/merge.go b/override/merge.go index 8baade95..2ddd786b 100644 --- a/override/merge.go +++ b/override/merge.go @@ -61,6 +61,7 @@ func init() { mergeSpecials["services.*.extra_hosts"] = mergeExtraHosts mergeSpecials["services.*.healthcheck.test"] = override mergeSpecials["services.*.labels"] = mergeToSequence + mergeSpecials["services.*.volumes.*.volume.labels"] = mergeToSequence mergeSpecials["services.*.logging"] = mergeLogging mergeSpecials["services.*.networks"] = mergeNetworks mergeSpecials["services.*.sysctls"] = mergeToSequence diff --git a/schema/compose-spec.json b/schema/compose-spec.json index d77b09a7..ee1137fb 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -418,7 +418,9 @@ "type": "object", "required": ["type"], "properties": { - "type": {"type": "string"}, + "type": {"type": "string", + "enum": ["bind", "volume", "tmpfs", "cluster", "image"] + }, "source": {"type": "string"}, "target": {"type": "string"}, "read_only": {"type": ["boolean", "string"]}, @@ -437,6 +439,7 @@ "volume": { "type": "object", "properties": { + "labels": {"$ref": "#/definitions/list_or_dict"}, "nocopy": {"type": ["boolean", "string"]}, "subpath": {"type": "string"} }, @@ -456,6 +459,14 @@ }, "additionalProperties": false, "patternProperties": {"^x-": {}} + }, + "image": { + "type": "object", + "properties": { + "subpath": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, "additionalProperties": false, diff --git a/types/derived.gen.go b/types/derived.gen.go index e540c53f..2d60daee 100644 --- a/types/derived.gen.go +++ b/types/derived.gen.go @@ -1964,6 +1964,12 @@ func deriveDeepCopy_44(dst, src *ServiceVolumeConfig) { dst.Tmpfs = new(ServiceVolumeTmpfs) deriveDeepCopy_54(dst.Tmpfs, src.Tmpfs) } + if src.Image == nil { + dst.Image = nil + } else { + dst.Image = new(ServiceVolumeImage) + deriveDeepCopy_55(dst.Image, src.Image) + } if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) src.Extensions.DeepCopy(dst.Extensions) @@ -2028,7 +2034,7 @@ func deriveDeepCopy_46(dst, src *IPAMConfig) { } else { dst.Config = make([]*IPAMPool, len(src.Config)) } - deriveDeepCopy_55(dst.Config, src.Config) + deriveDeepCopy_56(dst.Config, src.Config) } if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) @@ -2155,7 +2161,7 @@ func deriveDeepCopy_50(dst, src *Resource) { } else { dst.GenericResources = make([]GenericResource, len(src.GenericResources)) } - deriveDeepCopy_56(dst.GenericResources, src.GenericResources) + deriveDeepCopy_57(dst.GenericResources, src.GenericResources) } if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) @@ -2170,7 +2176,7 @@ func deriveDeepCopy_51(dst, src []PlacementPreferences) { for src_i, src_value := range src { func() { field := new(PlacementPreferences) - deriveDeepCopy_57(field, &src_value) + deriveDeepCopy_58(field, &src_value) dst[src_i] = *field }() } @@ -2192,6 +2198,12 @@ func deriveDeepCopy_52(dst, src *ServiceVolumeBind) { // deriveDeepCopy_53 recursively copies the contents of src into dst. func deriveDeepCopy_53(dst, src *ServiceVolumeVolume) { + if src.Labels != nil { + dst.Labels = make(map[string]string, len(src.Labels)) + deriveDeepCopy_4(dst.Labels, src.Labels) + } else { + dst.Labels = nil + } dst.NoCopy = src.NoCopy dst.Subpath = src.Subpath if src.Extensions != nil { @@ -2215,30 +2227,41 @@ func deriveDeepCopy_54(dst, src *ServiceVolumeTmpfs) { } // deriveDeepCopy_55 recursively copies the contents of src into dst. -func deriveDeepCopy_55(dst, src []*IPAMPool) { +func deriveDeepCopy_55(dst, src *ServiceVolumeImage) { + dst.SubPath = src.SubPath + if src.Extensions != nil { + dst.Extensions = make(map[string]any, len(src.Extensions)) + src.Extensions.DeepCopy(dst.Extensions) + } else { + dst.Extensions = nil + } +} + +// deriveDeepCopy_56 recursively copies the contents of src into dst. +func deriveDeepCopy_56(dst, src []*IPAMPool) { for src_i, src_value := range src { if src_value == nil { dst[src_i] = nil } else { dst[src_i] = new(IPAMPool) - deriveDeepCopy_58(dst[src_i], src_value) + deriveDeepCopy_59(dst[src_i], src_value) } } } -// deriveDeepCopy_56 recursively copies the contents of src into dst. -func deriveDeepCopy_56(dst, src []GenericResource) { +// deriveDeepCopy_57 recursively copies the contents of src into dst. +func deriveDeepCopy_57(dst, src []GenericResource) { for src_i, src_value := range src { func() { field := new(GenericResource) - deriveDeepCopy_59(field, &src_value) + deriveDeepCopy_60(field, &src_value) dst[src_i] = *field }() } } -// deriveDeepCopy_57 recursively copies the contents of src into dst. -func deriveDeepCopy_57(dst, src *PlacementPreferences) { +// deriveDeepCopy_58 recursively copies the contents of src into dst. +func deriveDeepCopy_58(dst, src *PlacementPreferences) { dst.Spread = src.Spread if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) @@ -2248,8 +2271,8 @@ func deriveDeepCopy_57(dst, src *PlacementPreferences) { } } -// deriveDeepCopy_58 recursively copies the contents of src into dst. -func deriveDeepCopy_58(dst, src *IPAMPool) { +// deriveDeepCopy_59 recursively copies the contents of src into dst. +func deriveDeepCopy_59(dst, src *IPAMPool) { dst.Subnet = src.Subnet dst.Gateway = src.Gateway dst.IPRange = src.IPRange @@ -2267,13 +2290,13 @@ func deriveDeepCopy_58(dst, src *IPAMPool) { } } -// deriveDeepCopy_59 recursively copies the contents of src into dst. -func deriveDeepCopy_59(dst, src *GenericResource) { +// deriveDeepCopy_60 recursively copies the contents of src into dst. +func deriveDeepCopy_60(dst, src *GenericResource) { if src.DiscreteResourceSpec == nil { dst.DiscreteResourceSpec = nil } else { dst.DiscreteResourceSpec = new(DiscreteGenericResource) - deriveDeepCopy_60(dst.DiscreteResourceSpec, src.DiscreteResourceSpec) + deriveDeepCopy_61(dst.DiscreteResourceSpec, src.DiscreteResourceSpec) } if src.Extensions != nil { dst.Extensions = make(map[string]any, len(src.Extensions)) @@ -2283,8 +2306,8 @@ func deriveDeepCopy_59(dst, src *GenericResource) { } } -// deriveDeepCopy_60 recursively copies the contents of src into dst. -func deriveDeepCopy_60(dst, src *DiscreteGenericResource) { +// deriveDeepCopy_61 recursively copies the contents of src into dst. +func deriveDeepCopy_61(dst, src *DiscreteGenericResource) { dst.Kind = src.Kind dst.Value = src.Value if src.Extensions != nil { diff --git a/types/types.go b/types/types.go index 0144b701..acfb3559 100644 --- a/types/types.go +++ b/types/types.go @@ -541,6 +541,7 @@ type ServiceVolumeConfig struct { Bind *ServiceVolumeBind `yaml:"bind,omitempty" json:"bind,omitempty"` Volume *ServiceVolumeVolume `yaml:"volume,omitempty" json:"volume,omitempty"` Tmpfs *ServiceVolumeTmpfs `yaml:"tmpfs,omitempty" json:"tmpfs,omitempty"` + Image *ServiceVolumeImage `yaml:"image,omitempty" json:"image,omitempty"` Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } @@ -575,6 +576,8 @@ const ( VolumeTypeNamedPipe = "npipe" // VolumeTypeCluster is the type for mounting container storage interface (CSI) volumes VolumeTypeCluster = "cluster" + // VolumeTypeImage is the tpe for mounting an image + VolumeTypeImage = "image" // SElinuxShared share the volume content SElinuxShared = "z" @@ -618,8 +621,9 @@ const ( // ServiceVolumeVolume are options for a service volume of type volume type ServiceVolumeVolume struct { - NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` - Subpath string `yaml:"subpath,omitempty" json:"subpath,omitempty"` + Labels Mapping `yaml:"labels,omitempty" json:"labels,omitempty"` + NoCopy bool `yaml:"nocopy,omitempty" json:"nocopy,omitempty"` + Subpath string `yaml:"subpath,omitempty" json:"subpath,omitempty"` Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } @@ -633,6 +637,11 @@ type ServiceVolumeTmpfs struct { Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` } +type ServiceVolumeImage struct { + SubPath string `yaml:"subpath,omitempty" json:"subpath,omitempty"` + Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"` +} + type FileMode int64 // FileReferenceConfig for a reference to a swarm file object