|
|
@@ -34,6 +34,7 @@ import (
|
|
|
pathutil "github.com/docker/compose/v2/internal/paths"
|
|
|
"github.com/docker/compose/v2/pkg/api"
|
|
|
"github.com/docker/compose/v2/pkg/progress"
|
|
|
+ "github.com/docker/compose/v2/pkg/prompt"
|
|
|
"github.com/docker/compose/v2/pkg/utils"
|
|
|
moby "github.com/docker/docker/api/types"
|
|
|
"github.com/docker/docker/api/types/blkiodev"
|
|
|
@@ -92,7 +93,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- volumes, err := s.ensureProjectVolumes(ctx, project)
|
|
|
+ volumes, err := s.ensureProjectVolumes(ctx, project, options.AssumeYes)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
@@ -142,13 +143,13 @@ func (s *composeService) ensureNetworks(ctx context.Context, project *types.Proj
|
|
|
return networks, nil
|
|
|
}
|
|
|
|
|
|
-func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) (map[string]string, error) {
|
|
|
+func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project, assumeYes bool) (map[string]string, error) {
|
|
|
ids := map[string]string{}
|
|
|
for k, volume := range project.Volumes {
|
|
|
- volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
|
|
|
- volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
|
|
|
- volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
|
|
- id, err := s.ensureVolume(ctx, volume, project.Name)
|
|
|
+ volume.CustomLabels = volume.CustomLabels.Add(api.VolumeLabel, k)
|
|
|
+ volume.CustomLabels = volume.CustomLabels.Add(api.ProjectLabel, project.Name)
|
|
|
+ volume.CustomLabels = volume.CustomLabels.Add(api.VersionLabel, api.ComposeVersion)
|
|
|
+ id, err := s.ensureVolume(ctx, k, volume, project, assumeYes)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
}
|
|
|
@@ -1434,7 +1435,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) (string, error) {
|
|
|
+func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
|
|
|
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
|
|
|
if err != nil {
|
|
|
if !errdefs.IsNotFound(err) {
|
|
|
@@ -1444,7 +1445,7 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
|
|
return "", fmt.Errorf("external volume %q not found", volume.Name)
|
|
|
}
|
|
|
err = s.createVolume(ctx, volume)
|
|
|
- return "", err
|
|
|
+ return volume.Name, err
|
|
|
}
|
|
|
|
|
|
if volume.External {
|
|
|
@@ -1456,8 +1457,8 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
|
|
if !ok {
|
|
|
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
|
|
|
}
|
|
|
- if ok && p != project {
|
|
|
- logrus.Warnf("volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume", volume.Name, p, project)
|
|
|
+ if ok && p != project.Name {
|
|
|
+ logrus.Warnf("volume %q already exists but was created for project %q (expected %q). Use `external: true` to use an existing volume", volume.Name, p, project.Name)
|
|
|
}
|
|
|
|
|
|
expected, err := VolumeHash(volume)
|
|
|
@@ -1466,17 +1467,76 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
|
|
}
|
|
|
actual, ok := inspected.Labels[api.ConfigHashLabel]
|
|
|
if ok && actual != expected {
|
|
|
- logrus.Warnf("volume %q exists but doesn't match configuration in compose file. You should remove it so it get recreated", volume.Name)
|
|
|
+ var confirm = assumeYes
|
|
|
+ if !assumeYes {
|
|
|
+ msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name)
|
|
|
+ confirm, err = prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if confirm {
|
|
|
+ err = s.removeDivergedVolume(ctx, name, volume, project)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return volume.Name, s.createVolume(ctx, volume)
|
|
|
+ }
|
|
|
}
|
|
|
return inspected.Name, nil
|
|
|
}
|
|
|
|
|
|
+func (s *composeService) removeDivergedVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) error {
|
|
|
+ // Remove services mounting divergent volume
|
|
|
+ var services []string
|
|
|
+ for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
|
|
|
+ for _, cfg := range config.Volumes {
|
|
|
+ if cfg.Source == name {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }) {
|
|
|
+ services = append(services, service.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ err := s.stop(ctx, project.Name, api.StopOptions{
|
|
|
+ Services: services,
|
|
|
+ Project: project,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // FIXME (ndeloof) we have to remove container so we can recreate volume
|
|
|
+ // but doing so we can't inherit anonymous volumes from previous instance
|
|
|
+ err = s.remove(ctx, containers, api.RemoveOptions{
|
|
|
+ Services: services,
|
|
|
+ Project: project,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return s.apiClient().VolumeRemove(ctx, volume.Name, true)
|
|
|
+}
|
|
|
+
|
|
|
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
|
|
|
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
|
|
w := progress.ContextWriter(ctx)
|
|
|
w.Event(progress.CreatingEvent(eventName))
|
|
|
- _, err := s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
|
|
|
- Labels: volume.Labels,
|
|
|
+ hash, err := VolumeHash(volume)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ volume.CustomLabels.Add(api.ConfigHashLabel, hash)
|
|
|
+ _, err = s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
|
|
|
+ Labels: mergeLabels(volume.Labels, volume.CustomLabels),
|
|
|
Name: volume.Name,
|
|
|
Driver: volume.Driver,
|
|
|
DriverOpts: volume.DriverOpts,
|