diff --git a/local/compose/convergence.go b/local/compose/convergence.go index 9966eb107..e3f46e610 100644 --- a/local/compose/convergence.go +++ b/local/compose/convergence.go @@ -93,7 +93,6 @@ func (s *composeService) ensureService(ctx context.Context, observedState Contai } for _, container := range actual { - container := container name := getCanonicalContainerName(container) diverged := container.Labels[configHashLabel] != expected diff --git a/local/compose/create.go b/local/compose/create.go index 1fef77497..126ef212b 100644 --- a/local/compose/create.go +++ b/local/compose/create.go @@ -50,6 +50,11 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt prepareNetworks(project) + err = prepareVolumes(project) + if err != nil { + return err + } + if err := s.ensureNetworks(ctx, project.Networks); err != nil { 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) { for k, network := range project.Networks { network.Labels = network.Labels.Add(networkLabel, k) @@ -134,7 +162,7 @@ func getImageName(service types.ServiceConfig, projectName string) string { func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, service types.ServiceConfig, number int, inherit *moby.Container, autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { - hash, err := jsonHash(s) + hash, err := jsonHash(service) if err != nil { return nil, nil, nil, err } @@ -171,25 +199,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, stdinOpen = service.StdinOpen attachStdin = false ) - image := getImageName(service, p.Name) - imgInspect, _, err := s.apiClient.ImageInspectWithRaw(ctx, image) + + volumeMounts, binds, mounts, err := s.buildContainerVolumes(ctx, *p, service, inherit) if err != nil { return nil, nil, nil, err } - mountOptions, err := buildContainerMountOptions(*p, service, imgInspect, inherit) - if err != nil { - return nil, nil, nil, err - } - 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:%s", m.Source, m.Target, "rw")) - } - } - } containerConfig := container.Config{ Hostname: service.Hostname, @@ -203,7 +217,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, AttachStderr: true, AttachStdout: true, Cmd: runCmd, - Image: image, + Image: getImageName(service, p.Name), WorkingDir: service.WorkingDir, Entrypoint: entrypoint, NetworkDisabled: service.NetworkMode == "disabled", @@ -212,21 +226,11 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, StopSignal: service.StopSignal, Env: convert.ToMobyEnv(service.Environment), Healthcheck: convert.ToMobyHealthCheck(service.HealthCheck), - // Volumes: // FIXME unclear to me the overlap with HostConfig.Mounts - Volumes: volumeMounts, + Volumes: volumeMounts, + StopTimeout: convert.ToSeconds(service.StopGracePeriod), } - // append secrets mounts - bindMounts, err := buildContainerSecretMounts(*p, service) - if err != nil { - return nil, nil, nil, err - } - for _, m := range mountOptions { - if m.Type == mount.TypeBind || m.Type == mount.TypeTmpfs { - bindMounts = append(bindMounts, m) - } - } portBindings := buildContainerPortBindingOptions(service) resources := getDeployResources(service) @@ -234,7 +238,7 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, hostConfig := container.HostConfig{ AutoRemove: autoRemove, Binds: binds, - Mounts: bindMounts, + Mounts: mounts, CapAdd: strslice.StrSlice(service.CapAdd), CapDrop: strslice.StrSlice(service.CapDrop), NetworkMode: networkMode, @@ -244,6 +248,8 @@ func (s *composeService) getCreateOptions(ctx context.Context, p *types.Project, Sysctls: service.Sysctls, PortBindings: portBindings, Resources: resources, + VolumeDriver: service.VolumeDriver, + VolumesFrom: service.VolumesFrom, } networkConfig := buildDefaultNetworkConfig(service, networkMode, getContainerName(p.Name, service, number)) @@ -297,6 +303,69 @@ func buildContainerPortBindingOptions(s types.ServiceConfig) nat.PortMap { return bindings } +func getVolumesFrom(project *types.Project, volumesFrom []string) ([]string, []string, error) { + var volumes = []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 { @@ -318,7 +387,6 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby } if img.ContainerConfig != nil { for k := range img.ContainerConfig.Volumes { - mount, err := buildMount(p, types.ServiceVolumeConfig{ Type: types.VolumeTypeVolume, Target: k, @@ -327,6 +395,7 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby return nil, err } mounts[k] = mount + } } for _, v := range s.Volumes { @@ -337,6 +406,17 @@ func buildContainerMountOptions(p types.Project, s types.ServiceConfig, img moby 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) @@ -362,9 +442,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount } mount, err := buildMount(p, types.ServiceVolumeConfig{ - Type: types.VolumeTypeBind, - Source: definedSecret.File, - Target: target, + Type: types.VolumeTypeBind, + Source: definedSecret.File, + Target: target, + ReadOnly: true, }) if err != nil { return nil, err