Skip to content

Commit

Permalink
Recreate volumes on volume configuration change
Browse files Browse the repository at this point in the history
Signed-off-by: Joana Hrotko <[email protected]>
  • Loading branch information
jhrotko committed Oct 30, 2024
1 parent fafaa9c commit 52a5d2e
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 81 deletions.
29 changes: 15 additions & 14 deletions cmd/compose/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@ import (
)

type createOptions struct {
Build bool
noBuild bool
Pull string
pullChanged bool
removeOrphans bool
ignoreOrphans bool
forceRecreate bool
noRecreate bool
recreateDeps bool
noInherit bool
timeChanged bool
timeout int
quietPull bool
scale []string
Build bool
noBuild bool
Pull string
pullChanged bool
removeOrphans bool
ignoreOrphans bool
forceRecreate bool
noRecreate bool
recreateDeps bool
noInherit bool
timeChanged bool
timeout int
quietPull bool
scale []string
recreateVolumes bool
}

func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
Expand Down
2 changes: 2 additions & 0 deletions cmd/compose/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
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.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
flags.BoolVar(&create.recreateVolumes, "recreate-volumes", false, "Recreate volumes when volume configuration in the Compose file changes.")
flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output")
flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs")
Expand Down Expand Up @@ -255,6 +256,7 @@ func runUp(
Inherit: !createOptions.noInherit,
Timeout: createOptions.GetTimeout(),
QuietPull: createOptions.quietPull,
RecreateVolumes: createOptions.recreateVolumes,
}

if upOptions.noStart {
Expand Down
1 change: 1 addition & 0 deletions docs/reference/compose_up.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
| `--no-start` | `bool` | | Don't start the services after creating them |
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
| `--quiet-pull` | `bool` | | Pull without printing progress information |
| `--recreate-volumes` | `bool` | | Recreate volumes when volume configuration in the Compose file changes. |
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
| `-V`, `--renew-anon-volumes` | `bool` | | Recreate anonymous volumes instead of retrieving data from the previous containers |
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
Expand Down
11 changes: 11 additions & 0 deletions docs/reference/docker_compose_up.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,17 @@ options:
experimentalcli: false
kubernetes: false
swarm: false
- option: recreate-volumes
value_type: bool
default_value: "false"
description: |
Recreate volumes when volume configuration in the Compose file changes.
deprecated: false
hidden: false
experimental: false
experimentalcli: false
kubernetes: false
swarm: false
- option: remove-orphans
value_type: bool
default_value: "false"
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ type CreateOptions struct {
Timeout *time.Duration
// QuietPull makes the pulling process quiet
QuietPull bool
// Allow recreate volumes when volumes configuration changes
RecreateVolumes bool
}

// StartOptions group options of the Start API
Expand Down
4 changes: 3 additions & 1 deletion pkg/api/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ const (
ProjectLabel = "com.docker.compose.project"
// ServiceLabel allow to track resource related to a compose service
ServiceLabel = "com.docker.compose.service"
// ConfigHashLabel stores configuration hash for a compose service
// ConfigHashLabel stores configuration hash for a compose service or volume
ConfigHashLabel = "com.docker.compose.config-hash"
// ExternalVolumeHashLabel stores volume external configuration hash for a compose service
ExternalVolumeHashLabel = "com.docker.compose.external-volume-hash"
// ContainerNumberLabel stores the container index of a replicated service
ContainerNumberLabel = "com.docker.compose.container-number"
// VolumeLabel allow to track resource related to a compose volume
Expand Down
31 changes: 26 additions & 5 deletions pkg/compose/convergence.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,

sort.Slice(containers, func(i, j int) bool {
// select obsolete containers first, so they get removed as we scale down
if obsolete, _ := mustRecreate(service, containers[i], recreate); obsolete {
if obsolete, _ := mustRecreate(project, service, containers[i], recreate); obsolete {
// i is obsolete, so must be first in the list
return true
}
if obsolete, _ := mustRecreate(service, containers[j], recreate); obsolete {
if obsolete, _ := mustRecreate(project, service, containers[j], recreate); obsolete {
// j is obsolete, so must be first in the list
return false
}
Expand All @@ -154,7 +154,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project,
continue
}

mustRecreate, err := mustRecreate(service, container, recreate)
mustRecreate, err := mustRecreate(project, service, container, recreate)
if err != nil {
return err
}
Expand Down Expand Up @@ -312,7 +312,7 @@ func (c *convergence) resolveSharedNamespaces(service *types.ServiceConfig) erro
return nil
}

func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
func mustRecreate(project *types.Project, expected types.ServiceConfig, actual moby.Container, policy string) (bool, error) {
if policy == api.RecreateNever {
return false, nil
}
Expand All @@ -325,7 +325,28 @@ func mustRecreate(expected types.ServiceConfig, actual moby.Container, policy st
}
configChanged := actual.Labels[api.ConfigHashLabel] != configHash
imageUpdated := actual.Labels[api.ImageDigestLabel] != expected.CustomLabels[api.ImageDigestLabel]
return configChanged || imageUpdated, nil

// it is needed recreate the mount if the external volume name changes
externalChanged := externalVolumeNameChanged(expected, project, actual)
return configChanged || imageUpdated || externalChanged, nil
}

func externalVolumeNameChanged(expected types.ServiceConfig, project *types.Project, actual moby.Container) (externalChanged bool) {
for _, v := range expected.Volumes {
if vol, ok := project.Volumes[v.Source]; ok && bool(vol.External) {

externalFound := false
for _, mount := range actual.Mounts {
if mount.Name == vol.Name {
externalFound = true
}
}
if !externalFound {
externalChanged = true
}
}
}
return externalChanged
}

func getContainerName(projectName string, service types.ServiceConfig, number int) string {
Expand Down
Loading

0 comments on commit 52a5d2e

Please sign in to comment.