mirror of https://github.com/docker/compose.git
prompt user to confirm volume recreation
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
332311358e
commit
8e0520e71e
|
@ -46,6 +46,7 @@ type createOptions struct {
|
||||||
timeout int
|
timeout int
|
||||||
quietPull bool
|
quietPull bool
|
||||||
scale []string
|
scale []string
|
||||||
|
AssumeYes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
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
|
||||||
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||||
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||||
|
flags.BoolVarP(&opts.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +109,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
|
||||||
Inherit: !createOpts.noInherit,
|
Inherit: !createOpts.noInherit,
|
||||||
Timeout: createOpts.GetTimeout(),
|
Timeout: createOpts.GetTimeout(),
|
||||||
QuietPull: createOpts.quietPull,
|
QuietPull: createOpts.quietPull,
|
||||||
|
AssumeYes: createOpts.AssumeYes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,6 +145,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||||
flags := upCmd.Flags()
|
flags := upCmd.Flags()
|
||||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
|
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
|
||||||
|
flags.BoolVarP(&create.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
|
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
|
||||||
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||||
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||||
|
@ -255,6 +256,7 @@ func runUp(
|
||||||
Inherit: !createOptions.noInherit,
|
Inherit: !createOptions.noInherit,
|
||||||
Timeout: createOptions.GetTimeout(),
|
Timeout: createOptions.GetTimeout(),
|
||||||
QuietPull: createOptions.quietPull,
|
QuietPull: createOptions.quietPull,
|
||||||
|
AssumeYes: createOptions.AssumeYes,
|
||||||
}
|
}
|
||||||
|
|
||||||
if upOptions.noStart {
|
if upOptions.noStart {
|
||||||
|
|
|
@ -16,6 +16,7 @@ Creates containers for a service
|
||||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||||
|
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
|
@ -53,6 +53,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||||
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
|
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||||
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
|
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
|
||||||
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |
|
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |
|
||||||
|
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
|
|
@ -88,6 +88,17 @@ options:
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
|
- option: "y"
|
||||||
|
shorthand: "y"
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Assume "yes" as answer to all prompts and run non-interactively
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
inherited_options:
|
inherited_options:
|
||||||
- option: dry-run
|
- option: dry-run
|
||||||
value_type: bool
|
value_type: bool
|
||||||
|
|
|
@ -309,6 +309,17 @@ options:
|
||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: false
|
swarm: false
|
||||||
|
- option: "y"
|
||||||
|
shorthand: "y"
|
||||||
|
value_type: bool
|
||||||
|
default_value: "false"
|
||||||
|
description: Assume "yes" as answer to all prompts and run non-interactively
|
||||||
|
deprecated: false
|
||||||
|
hidden: false
|
||||||
|
experimental: false
|
||||||
|
experimentalcli: false
|
||||||
|
kubernetes: false
|
||||||
|
swarm: false
|
||||||
inherited_options:
|
inherited_options:
|
||||||
- option: dry-run
|
- option: dry-run
|
||||||
value_type: bool
|
value_type: bool
|
||||||
|
|
|
@ -207,6 +207,8 @@ type CreateOptions struct {
|
||||||
Timeout *time.Duration
|
Timeout *time.Duration
|
||||||
// QuietPull makes the pulling process quiet
|
// QuietPull makes the pulling process quiet
|
||||||
QuietPull bool
|
QuietPull bool
|
||||||
|
// AssumeYes assume "yes" as answer to all prompts and run non-interactively
|
||||||
|
AssumeYes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartOptions group options of the Start API
|
// StartOptions group options of the Start API
|
||||||
|
|
|
@ -34,6 +34,7 @@ import (
|
||||||
pathutil "github.com/docker/compose/v2/internal/paths"
|
pathutil "github.com/docker/compose/v2/internal/paths"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
|
"github.com/docker/compose/v2/pkg/prompt"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
moby "github.com/docker/docker/api/types"
|
moby "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/blkiodev"
|
"github.com/docker/docker/api/types/blkiodev"
|
||||||
|
@ -92,7 +93,7 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
volumes, err := s.ensureProjectVolumes(ctx, project)
|
volumes, err := s.ensureProjectVolumes(ctx, project, options.AssumeYes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -142,13 +143,13 @@ func (s *composeService) ensureNetworks(ctx context.Context, project *types.Proj
|
||||||
return networks, nil
|
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{}
|
ids := map[string]string{}
|
||||||
for k, volume := range project.Volumes {
|
for k, volume := range project.Volumes {
|
||||||
volume.Labels = volume.Labels.Add(api.VolumeLabel, k)
|
volume.CustomLabels = volume.CustomLabels.Add(api.VolumeLabel, k)
|
||||||
volume.Labels = volume.Labels.Add(api.ProjectLabel, project.Name)
|
volume.CustomLabels = volume.CustomLabels.Add(api.ProjectLabel, project.Name)
|
||||||
volume.Labels = volume.Labels.Add(api.VersionLabel, api.ComposeVersion)
|
volume.CustomLabels = volume.CustomLabels.Add(api.VersionLabel, api.ComposeVersion)
|
||||||
id, err := s.ensureVolume(ctx, volume, project.Name)
|
id, err := s.ensureVolume(ctx, k, volume, project, assumeYes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
inspected, err := s.apiClient().VolumeInspect(ctx, volume.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errdefs.IsNotFound(err) {
|
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)
|
return "", fmt.Errorf("external volume %q not found", volume.Name)
|
||||||
}
|
}
|
||||||
err = s.createVolume(ctx, volume)
|
err = s.createVolume(ctx, volume)
|
||||||
return "", err
|
return volume.Name, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if volume.External {
|
if volume.External {
|
||||||
|
@ -1456,8 +1457,8 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
||||||
if !ok {
|
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)
|
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 {
|
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)
|
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)
|
expected, err := VolumeHash(volume)
|
||||||
|
@ -1466,17 +1467,76 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
|
||||||
}
|
}
|
||||||
actual, ok := inspected.Labels[api.ConfigHashLabel]
|
actual, ok := inspected.Labels[api.ConfigHashLabel]
|
||||||
if ok && actual != expected {
|
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
|
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 {
|
func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error {
|
||||||
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
eventName := fmt.Sprintf("Volume %q", volume.Name)
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
w.Event(progress.CreatingEvent(eventName))
|
w.Event(progress.CreatingEvent(eventName))
|
||||||
_, err := s.apiClient().VolumeCreate(ctx, volumetypes.CreateOptions{
|
hash, err := VolumeHash(volume)
|
||||||
Labels: volume.Labels,
|
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,
|
Name: volume.Name,
|
||||||
Driver: volume.Driver,
|
Driver: volume.Driver,
|
||||||
DriverOpts: volume.DriverOpts,
|
DriverOpts: volume.DriverOpts,
|
||||||
|
|
Loading…
Reference in New Issue