mirror of
https://github.com/docker/compose.git
synced 2025-07-26 23:24:05 +02:00
Merge pull request #1143 from aiordache/local_volume_override
This commit is contained in:
commit
c881e22ab8
@ -93,7 +93,6 @@ func (s *composeService) ensureService(ctx context.Context, observedState Contai
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range actual {
|
for _, container := range actual {
|
||||||
container := container
|
|
||||||
name := getCanonicalContainerName(container)
|
name := getCanonicalContainerName(container)
|
||||||
|
|
||||||
diverged := container.Labels[configHashLabel] != expected
|
diverged := container.Labels[configHashLabel] != expected
|
||||||
@ -251,7 +250,7 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container, autoRemove bool) error {
|
func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container, autoRemove bool) error {
|
||||||
containerConfig, hostConfig, networkingConfig, err := getCreateOptions(project, service, number, container, autoRemove)
|
containerConfig, hostConfig, networkingConfig, err := s.getCreateOptions(ctx, project, service, number, container, autoRemove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,11 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
|
|||||||
|
|
||||||
prepareNetworks(project)
|
prepareNetworks(project)
|
||||||
|
|
||||||
|
err = prepareVolumes(project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
if err := s.ensureNetworks(ctx, project.Networks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -91,6 +96,29 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareVolumes(p *types.Project) error {
|
||||||
|
for i := range p.Services {
|
||||||
|
volumesFrom, dependServices, err := getVolumesFrom(p, p.Services[i].VolumesFrom)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Services[i].VolumesFrom = volumesFrom
|
||||||
|
if len(dependServices) > 0 {
|
||||||
|
if p.Services[i].DependsOn == nil {
|
||||||
|
p.Services[i].DependsOn = make(types.DependsOnConfig, len(dependServices))
|
||||||
|
}
|
||||||
|
for _, service := range p.Services {
|
||||||
|
if contains(dependServices, service.Name) {
|
||||||
|
p.Services[i].DependsOn[service.Name] = types.ServiceDependency{
|
||||||
|
Condition: types.ServiceConditionStarted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func prepareNetworks(project *types.Project) {
|
func prepareNetworks(project *types.Project) {
|
||||||
for k, network := range project.Networks {
|
for k, network := range project.Networks {
|
||||||
network.Labels = network.Labels.Add(networkLabel, k)
|
network.Labels = network.Labels.Add(networkLabel, k)
|
||||||
@ -131,21 +159,23 @@ func getImageName(service types.ServiceConfig, projectName string) string {
|
|||||||
return imageName
|
return imageName
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container, autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig, number int, inherit *moby.Container,
|
||||||
hash, err := jsonHash(s)
|
autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
|
||||||
|
|
||||||
|
hash, err := jsonHash(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := map[string]string{}
|
labels := map[string]string{}
|
||||||
for k, v := range s.Labels {
|
for k, v := range service.Labels {
|
||||||
labels[k] = v
|
labels[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
labels[projectLabel] = p.Name
|
labels[projectLabel] = p.Name
|
||||||
labels[serviceLabel] = s.Name
|
labels[serviceLabel] = service.Name
|
||||||
labels[versionLabel] = ComposeVersion
|
labels[versionLabel] = ComposeVersion
|
||||||
if _, ok := s.Labels[oneoffLabel]; !ok {
|
if _, ok := service.Labels[oneoffLabel]; !ok {
|
||||||
labels[oneoffLabel] = "False"
|
labels[oneoffLabel] = "False"
|
||||||
}
|
}
|
||||||
labels[configHashLabel] = hash
|
labels[configHashLabel] = hash
|
||||||
@ -157,24 +187,29 @@ func getCreateOptions(p *types.Project, s types.ServiceConfig, number int, inher
|
|||||||
runCmd strslice.StrSlice
|
runCmd strslice.StrSlice
|
||||||
entrypoint strslice.StrSlice
|
entrypoint strslice.StrSlice
|
||||||
)
|
)
|
||||||
if len(s.Command) > 0 {
|
if len(service.Command) > 0 {
|
||||||
runCmd = strslice.StrSlice(s.Command)
|
runCmd = strslice.StrSlice(service.Command)
|
||||||
}
|
}
|
||||||
if len(s.Entrypoint) > 0 {
|
if len(service.Entrypoint) > 0 {
|
||||||
entrypoint = strslice.StrSlice(s.Entrypoint)
|
entrypoint = strslice.StrSlice(service.Entrypoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tty = s.Tty
|
tty = service.Tty
|
||||||
stdinOpen = s.StdinOpen
|
stdinOpen = service.StdinOpen
|
||||||
attachStdin = false
|
attachStdin = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
volumeMounts, binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Hostname: s.Hostname,
|
Hostname: service.Hostname,
|
||||||
Domainname: s.DomainName,
|
Domainname: service.DomainName,
|
||||||
User: s.User,
|
User: service.User,
|
||||||
ExposedPorts: buildContainerPorts(s),
|
ExposedPorts: buildContainerPorts(service),
|
||||||
Tty: tty,
|
Tty: tty,
|
||||||
OpenStdin: stdinOpen,
|
OpenStdin: stdinOpen,
|
||||||
StdinOnce: true,
|
StdinOnce: true,
|
||||||
@ -182,42 +217,42 @@ func getCreateOptions(p *types.Project, s types.ServiceConfig, number int, inher
|
|||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
Cmd: runCmd,
|
Cmd: runCmd,
|
||||||
Image: getImageName(s, p.Name),
|
Image: getImageName(service, p.Name),
|
||||||
WorkingDir: s.WorkingDir,
|
WorkingDir: service.WorkingDir,
|
||||||
Entrypoint: entrypoint,
|
Entrypoint: entrypoint,
|
||||||
NetworkDisabled: s.NetworkMode == "disabled",
|
NetworkDisabled: service.NetworkMode == "disabled",
|
||||||
MacAddress: s.MacAddress,
|
MacAddress: service.MacAddress,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
StopSignal: s.StopSignal,
|
StopSignal: service.StopSignal,
|
||||||
Env: convert.ToMobyEnv(s.Environment),
|
Env: convert.ToMobyEnv(service.Environment),
|
||||||
Healthcheck: convert.ToMobyHealthCheck(s.HealthCheck),
|
Healthcheck: convert.ToMobyHealthCheck(service.HealthCheck),
|
||||||
// Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts
|
Volumes: volumeMounts,
|
||||||
StopTimeout: convert.ToSeconds(s.StopGracePeriod),
|
|
||||||
|
StopTimeout: convert.ToSeconds(service.StopGracePeriod),
|
||||||
}
|
}
|
||||||
|
|
||||||
mountOptions, err := buildContainerMountOptions(*p, s, inherit)
|
portBindings := buildContainerPortBindingOptions(service)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
bindings := buildContainerBindingOptions(s)
|
|
||||||
|
|
||||||
resources := getDeployResources(s)
|
resources := getDeployResources(service)
|
||||||
networkMode := getNetworkMode(p, s)
|
networkMode := getNetworkMode(p, service)
|
||||||
hostConfig := container.HostConfig{
|
hostConfig := container.HostConfig{
|
||||||
AutoRemove: autoRemove,
|
AutoRemove: autoRemove,
|
||||||
Mounts: mountOptions,
|
Binds: binds,
|
||||||
CapAdd: strslice.StrSlice(s.CapAdd),
|
Mounts: mounts,
|
||||||
CapDrop: strslice.StrSlice(s.CapDrop),
|
CapAdd: strslice.StrSlice(service.CapAdd),
|
||||||
|
CapDrop: strslice.StrSlice(service.CapDrop),
|
||||||
NetworkMode: networkMode,
|
NetworkMode: networkMode,
|
||||||
Init: s.Init,
|
Init: service.Init,
|
||||||
ReadonlyRootfs: s.ReadOnly,
|
ReadonlyRootfs: service.ReadOnly,
|
||||||
// ShmSize: , TODO
|
// ShmSize: , TODO
|
||||||
Sysctls: s.Sysctls,
|
Sysctls: service.Sysctls,
|
||||||
PortBindings: bindings,
|
PortBindings: portBindings,
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
|
VolumeDriver: service.VolumeDriver,
|
||||||
|
VolumesFrom: service.VolumesFrom,
|
||||||
}
|
}
|
||||||
|
|
||||||
networkConfig := buildDefaultNetworkConfig(s, networkMode, getContainerName(p.Name, s, number))
|
networkConfig := buildDefaultNetworkConfig(service, networkMode, getContainerName(p.Name, service, number))
|
||||||
return &containerConfig, &hostConfig, networkConfig, nil
|
return &containerConfig, &hostConfig, networkConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +288,7 @@ func buildContainerPorts(s types.ServiceConfig) nat.PortSet {
|
|||||||
return ports
|
return ports
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildContainerBindingOptions(s types.ServiceConfig) nat.PortMap {
|
func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap {
|
||||||
bindings := nat.PortMap{}
|
bindings := nat.PortMap{}
|
||||||
for _, port := range s.Ports {
|
for _, port := range s.Ports {
|
||||||
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
|
p := nat.Port(fmt.Sprintf("%d/%s", port.Target, port.Protocol))
|
||||||
@ -268,9 +303,71 @@ func buildContainerBindingOptions(s types.ServiceConfig) nat.PortMap {
|
|||||||
return bindings
|
return bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, inherit *moby.Container) ([]mount.Mount, error) {
|
func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) {
|
||||||
mounts := []mount.Mount{}
|
var volumes = []string{}
|
||||||
var inherited []string
|
var services = []string{}
|
||||||
|
// parse volumes_from
|
||||||
|
if len(volumesFrom) == 0 {
|
||||||
|
return volumes, services, nil
|
||||||
|
}
|
||||||
|
for _, vol := range volumesFrom {
|
||||||
|
spec := strings.Split(vol, ":")
|
||||||
|
if spec[0] == "container" {
|
||||||
|
volumes = append(volumes, strings.Join(spec[1:], ":"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
serviceName := spec[0]
|
||||||
|
services = append(services, serviceName)
|
||||||
|
service, err := project.GetService(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
firstContainer := getContainerName(project.Name, service, 1)
|
||||||
|
v := fmt.Sprintf("%s:%s", firstContainer, strings.Join(spec[1:], ":"))
|
||||||
|
volumes = append(volumes, v)
|
||||||
|
}
|
||||||
|
return volumes, services, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *composeService) buildContainerVolumes(ctx context.Context, p types.Project, service types.ServiceConfig,
|
||||||
|
inherit *moby.Container) (map[string]struct{}, []string, []mount.Mount, error) {
|
||||||
|
var mounts = []mount.Mount{}
|
||||||
|
|
||||||
|
image := getImageName(service, p.Name)
|
||||||
|
imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mountOptions, err := buildContainerMountOptions(p, service, imgInspect, inherit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter binds and volumes mount targets
|
||||||
|
volumeMounts := map[string]struct{}{}
|
||||||
|
binds := []string{}
|
||||||
|
for _, m := range mountOptions {
|
||||||
|
|
||||||
|
if m.Type == mount.TypeVolume {
|
||||||
|
volumeMounts[m.Target] = struct{}{}
|
||||||
|
if m.Source != "" {
|
||||||
|
binds = append(binds, fmt.Sprintf("%s:%s:rw", m.Source, m.Target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, m := range mountOptions {
|
||||||
|
if m.Type == mount.TypeBind || m.Type == mount.TypeTmpfs {
|
||||||
|
mounts = append(mounts, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return volumeMounts, binds, mounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby.ImageInspect, inherit *moby.Container) ([]mount.Mount, error) {
|
||||||
|
var mounts = map[string]mount.Mount{}
|
||||||
if inherit != nil {
|
if inherit != nil {
|
||||||
for _, m := range inherit.Mounts {
|
for _, m := range inherit.Mounts {
|
||||||
if m.Type == "tmpfs" {
|
if m.Type == "tmpfs" {
|
||||||
@ -280,27 +377,56 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, inherit
|
|||||||
if m.Type == "volume" {
|
if m.Type == "volume" {
|
||||||
src = m.Name
|
src = m.Name
|
||||||
}
|
}
|
||||||
mounts = append(mounts, mount.Mount{
|
mounts[m.Destination] = mount.Mount{
|
||||||
Type: m.Type,
|
Type: m.Type,
|
||||||
Source: src,
|
Source: src,
|
||||||
Target: m.Destination,
|
Target: m.Destination,
|
||||||
ReadOnly: !m.RW,
|
ReadOnly: !m.RW,
|
||||||
})
|
}
|
||||||
inherited = append(inherited, m.Destination)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if img.ContainerConfig != nil {
|
||||||
|
for k := range img.ContainerConfig.Volumes {
|
||||||
|
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||||
|
Type: types.VolumeTypeVolume,
|
||||||
|
Target: k,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mounts[k] = mount
|
||||||
|
|
||||||
for _, v := range s.Volumes {
|
|
||||||
if contains(inherited, v.Target) {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
for _, v := range s.Volumes {
|
||||||
mount, err := buildMount(p, v)
|
mount, err := buildMount(p, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mounts = append(mounts, mount)
|
mounts[mount.Target] = mount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secrets, err := buildContainerSecretMounts(p, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, s := range secrets {
|
||||||
|
if _, found := mounts[s.Target]; found {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mounts[s.Target] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make([]mount.Mount, 0, len(mounts))
|
||||||
|
for _, v := range mounts {
|
||||||
|
values = append(values, v)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount.Mount, error) {
|
||||||
|
var mounts = map[string]mount.Mount{}
|
||||||
|
|
||||||
secretsDir := "/run/secrets"
|
secretsDir := "/run/secrets"
|
||||||
for _, secret := range s.Secrets {
|
for _, secret := range s.Secrets {
|
||||||
target := secret.Target
|
target := secret.Target
|
||||||
@ -315,27 +441,22 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, inherit
|
|||||||
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
return nil, fmt.Errorf("unsupported external secret %s", definedSecret.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if contains(inherited, target) {
|
|
||||||
// remove inherited mount
|
|
||||||
pos := indexOf(inherited, target)
|
|
||||||
if pos >= 0 {
|
|
||||||
mounts = append(mounts[:pos], mounts[pos+1])
|
|
||||||
inherited = append(inherited[:pos], inherited[pos+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
mount, err := buildMount(p, types.ServiceVolumeConfig{
|
||||||
Type: types.VolumeTypeBind,
|
Type: types.VolumeTypeBind,
|
||||||
Source: definedSecret.File,
|
Source: definedSecret.File,
|
||||||
Target: target,
|
Target: target,
|
||||||
|
ReadOnly: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mounts = append(mounts, mount)
|
mounts[target] = mount
|
||||||
}
|
}
|
||||||
|
values := make([]mount.Mount, 0, len(mounts))
|
||||||
return mounts, nil
|
for _, v := range mounts {
|
||||||
|
values = append(values, v)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
|
func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.Mount, error) {
|
||||||
@ -349,10 +470,14 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if volume.Type == types.VolumeTypeVolume {
|
if volume.Type == types.VolumeTypeVolume {
|
||||||
pVolume, ok := project.Volumes[volume.Source]
|
if volume.Source != "" {
|
||||||
if ok {
|
|
||||||
source = pVolume.Name
|
pVolume, ok := project.Volumes[volume.Source]
|
||||||
|
if ok {
|
||||||
|
source = pVolume.Name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mount.Mount{
|
return mount.Mount{
|
||||||
|
@ -38,12 +38,3 @@ func contains(slice []string, item string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexOf(slice []string, item string) int {
|
|
||||||
for i, v := range slice {
|
|
||||||
if v == item {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
@ -261,9 +261,18 @@ func TestLocalComposeVolume(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("check container volume specs", func(t *testing.T) {
|
t.Run("check container volume specs", func(t *testing.T) {
|
||||||
res := c.RunDockerCmd("inspect", "compose-e2e-volume_nginx2_1", "--format", "{{ json .HostConfig.Mounts }}")
|
res := c.RunDockerCmd("inspect", "compose-e2e-volume_nginx2_1", "--format", "{{ json .Mounts }}")
|
||||||
|
output := res.Stdout()
|
||||||
//nolint
|
//nolint
|
||||||
res.Assert(t, icmd.Expected{Out: `[{"Type":"volume","Source":"compose-e2e-volume_staticVol","Target":"/usr/share/nginx/html","ReadOnly":true},{"Type":"volume","Target":"/usr/src/app/node_modules"},{"Type":"volume","Source":"myVolume","Target":"/usr/share/nginx/test"}]`})
|
assert.Assert(t, strings.Contains(output, `"Destination":"/usr/src/app/node_modules","Driver":"local","Mode":"","RW":true,"Propagation":""`))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check container bind-mounts specs", func(t *testing.T) {
|
||||||
|
res := c.RunDockerCmd("inspect", "compose-e2e-volume_nginx_1", "--format", "{{ json .HostConfig.Mounts }}")
|
||||||
|
output := res.Stdout()
|
||||||
|
//nolint
|
||||||
|
assert.Assert(t, strings.Contains(output, `"Type":"bind"`))
|
||||||
|
assert.Assert(t, strings.Contains(output, `"Target":"/usr/share/nginx/html"`))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("cleanup volume project", func(t *testing.T) {
|
t.Run("cleanup volume project", func(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user