Skip to content

Commit 8e0520e

Browse files
committed
prompt user to confirm volume recreation
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 3323113 commit 8e0520e

File tree

8 files changed

+104
-13
lines changed

8 files changed

+104
-13
lines changed

cmd/compose/create.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type createOptions struct {
4646
timeout int
4747
quietPull bool
4848
scale []string
49+
AssumeYes bool
4950
}
5051

5152
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
@@ -80,6 +81,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
8081
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
8182
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
8283
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
84+
flags.BoolVarP(&opts.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
8385
return cmd
8486
}
8587

@@ -107,6 +109,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
107109
Inherit: !createOpts.noInherit,
108110
Timeout: createOpts.GetTimeout(),
109111
QuietPull: createOpts.quietPull,
112+
AssumeYes: createOpts.AssumeYes,
110113
})
111114
}
112115

cmd/compose/up.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
145145
flags := upCmd.Flags()
146146
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
147147
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
148+
flags.BoolVarP(&create.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
148149
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
149150
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
150151
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
@@ -255,6 +256,7 @@ func runUp(
255256
Inherit: !createOptions.noInherit,
256257
Timeout: createOptions.GetTimeout(),
257258
QuietPull: createOptions.quietPull,
259+
AssumeYes: createOptions.AssumeYes,
258260
}
259261

260262
if upOptions.noStart {

docs/reference/compose_create.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Creates containers for a service
1616
| `--quiet-pull` | `bool` | | Pull without printing progress information |
1717
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
1818
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
19+
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
1920

2021

2122
<!---MARKER_GEN_END-->

docs/reference/compose_up.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
5353
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
5454
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
5555
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |
56+
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
5657

5758

5859
<!---MARKER_GEN_END-->

docs/reference/docker_compose_create.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ options:
8888
experimentalcli: false
8989
kubernetes: false
9090
swarm: false
91+
- option: "y"
92+
shorthand: "y"
93+
value_type: bool
94+
default_value: "false"
95+
description: Assume "yes" as answer to all prompts and run non-interactively
96+
deprecated: false
97+
hidden: false
98+
experimental: false
99+
experimentalcli: false
100+
kubernetes: false
101+
swarm: false
91102
inherited_options:
92103
- option: dry-run
93104
value_type: bool

docs/reference/docker_compose_up.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@ options:
309309
experimentalcli: false
310310
kubernetes: false
311311
swarm: false
312+
- option: "y"
313+
shorthand: "y"
314+
value_type: bool
315+
default_value: "false"
316+
description: Assume "yes" as answer to all prompts and run non-interactively
317+
deprecated: false
318+
hidden: false
319+
experimental: false
320+
experimentalcli: false
321+
kubernetes: false
322+
swarm: false
312323
inherited_options:
313324
- option: dry-run
314325
value_type: bool

pkg/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ type CreateOptions struct {
207207
Timeout *time.Duration
208208
// QuietPull makes the pulling process quiet
209209
QuietPull bool
210+
// AssumeYes assume "yes" as answer to all prompts and run non-interactively
211+
AssumeYes bool
210212
}
211213

212214
// StartOptions group options of the Start API

pkg/compose/create.go

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
pathutil "github.com/docker/compose/v2/internal/paths"
3535
"github.com/docker/compose/v2/pkg/api"
3636
"github.com/docker/compose/v2/pkg/progress"
37+
"github.com/docker/compose/v2/pkg/prompt"
3738
"github.com/docker/compose/v2/pkg/utils"
3839
moby "github.com/docker/docker/api/types"
3940
"github.com/docker/docker/api/types/blkiodev"
@@ -92,7 +93,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
9293
return err
9394
}
9495

95-
volumes, err := s.ensureProjectVolumes(ctx, project)
96+
volumes, err := s.ensureProjectVolumes(ctx, project, options.AssumeYes)
9697
if err != nil {
9798
return err
9899
}
@@ -142,13 +143,13 @@ func (s *composeService) ensureNetworks(ctx context.Context, project *types.Proj
142143
return networks, nil
143144
}
144145

145-
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) (map[string]string, error) {
146+
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project, assumeYes bool) (map[string]string, error) {
146147
ids := map[string]string{}
147148
for k, volume := range project.Volumes {
148-
volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
149-
volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
150-
volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
151-
id, err := s.ensureVolume(ctx, volume, project.Name)
149+
volume.CustomLabels = volume.CustomLabels.Add(api.VolumeLabel, k)
150+
volume.CustomLabels = volume.CustomLabels.Add(api.ProjectLabel, project.Name)
151+
volume.CustomLabels = volume.CustomLabels.Add(api.VersionLabel, api.ComposeVersion)
152+
id, err := s.ensureVolume(ctx, k, volume, project, assumeYes)
152153
if err != nil {
153154
return nil, err
154155
}
@@ -1434,7 +1435,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne
14341435
}
14351436
}
14361437

1437-
func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeConfig, project string) (string, error) {
1438+
func (s *composeService) ensureVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project, assumeYes bool) (string, error) {
14381439
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
14391440
if err != nil {
14401441
if !errdefs.IsNotFound(err) {
@@ -1444,7 +1445,7 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
14441445
return "", fmt.Errorf("external volume %q not found", volume.Name)
14451446
}
14461447
err = s.createVolume(ctx, volume)
1447-
return "", err
1448+
return volume.Name, err
14481449
}
14491450

14501451
if volume.External {
@@ -1456,8 +1457,8 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
14561457
if !ok {
14571458
logrus.Warnf("volume %q already exists but was not created by Docker Compose. Use `external: true` to use an existing volume", volume.Name)
14581459
}
1459-
if ok && p != project {
1460-
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)
1460+
if ok && p != project.Name {
1461+
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)
14611462
}
14621463

14631464
expected, err := VolumeHash(volume)
@@ -1466,17 +1467,76 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
14661467
}
14671468
actual, ok := inspected.Labels[api.ConfigHashLabel]
14681469
if ok && actual != expected {
1469-
logrus.Warnf("volume %q exists but doesn't match configuration in compose file. You should remove it so it get recreated", volume.Name)
1470+
var confirm = assumeYes
1471+
if !assumeYes {
1472+
msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name)
1473+
confirm, err = prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false)
1474+
if err != nil {
1475+
return "", err
1476+
}
1477+
}
1478+
if confirm {
1479+
err = s.removeDivergedVolume(ctx, name, volume, project)
1480+
if err != nil {
1481+
return "", err
1482+
}
1483+
return volume.Name, s.createVolume(ctx, volume)
1484+
}
14701485
}
14711486
return inspected.Name, nil
14721487
}
14731488

1489+
func (s *composeService) removeDivergedVolume(ctx context.Context, name string, volume types.VolumeConfig, project *types.Project) error {
1490+
// Remove services mounting divergent volume
1491+
var services []string
1492+
for _, service := range project.Services.Filter(func(config types.ServiceConfig) bool {
1493+
for _, cfg := range config.Volumes {
1494+
if cfg.Source == name {
1495+
return true
1496+
}
1497+
}
1498+
return false
1499+
}) {
1500+
services = append(services, service.Name)
1501+
}
1502+
1503+
err := s.stop(ctx, project.Name, api.StopOptions{
1504+
Services: services,
1505+
Project: project,
1506+
})
1507+
if err != nil {
1508+
return err
1509+
}
1510+
1511+
containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, services...)
1512+
if err != nil {
1513+
return err
1514+
}
1515+
1516+
// FIXME (ndeloof) we have to remove container so we can recreate volume
1517+
// but doing so we can't inherit anonymous volumes from previous instance
1518+
err = s.remove(ctx, containers, api.RemoveOptions{
1519+
Services: services,
1520+
Project: project,
1521+
})
1522+
if err != nil {
1523+
return err
1524+
}
1525+
1526+
return s.apiClient().VolumeRemove(ctx, volume.Name, true)
1527+
}
1528+
14741529
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
14751530
eventName := fmt.Sprintf("Volume %q", volume.Name)
14761531
w := progress.ContextWriter(ctx)
14771532
w.Event(progress.CreatingEvent(eventName))
1478-
_, err := s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
1479-
Labels: volume.Labels,
1533+
hash, err := VolumeHash(volume)
1534+
if err != nil {
1535+
return err
1536+
}
1537+
volume.CustomLabels.Add(api.ConfigHashLabel, hash)
1538+
_, err = s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
1539+
Labels: mergeLabels(volume.Labels, volume.CustomLabels),
14801540
Name: volume.Name,
14811541
Driver: volume.Driver,
14821542
DriverOpts: volume.DriverOpts,

0 commit comments

Comments
 (0)