@@ -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+
14741529func (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