github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/compose_yaml.go (about) 1 package ddevapp 2 3 import ( 4 "github.com/ddev/ddev/pkg/dockerutil" 5 "github.com/ddev/ddev/pkg/util" 6 "gopkg.in/yaml.v3" 7 "os" 8 "strings" 9 //compose_cli "github.com/compose-spec/compose-go/cli" 10 //compose_types "github.com/compose-spec/compose-go/types" 11 ) 12 13 // WriteDockerComposeYAML writes a .ddev-docker-compose-base.yaml and related to the .ddev directory. 14 // It then uses `docker-compose convert` to get a canonical version of the full compose file. 15 // It then makes a couple of fixups to the canonical version (networks and approot bind points) by 16 // marshaling the canonical version to YAML and then unmarshaling it back into a canonical version. 17 func (app *DdevApp) WriteDockerComposeYAML() error { 18 var err error 19 20 f, err := os.Create(app.DockerComposeYAMLPath()) 21 if err != nil { 22 return err 23 } 24 defer util.CheckClose(f) 25 26 rendered, err := app.RenderComposeYAML() 27 if err != nil { 28 return err 29 } 30 _, err = f.WriteString(rendered) 31 if err != nil { 32 return err 33 } 34 35 files, err := app.ComposeFiles() 36 if err != nil { 37 return err 38 } 39 fullContents, _, err := dockerutil.ComposeCmd(&dockerutil.ComposeCmdOpts{ 40 ComposeFiles: files, 41 Action: []string{"config"}, 42 }) 43 if err != nil { 44 return err 45 } 46 47 app.ComposeYaml, err = fixupComposeYaml(fullContents, app) 48 if err != nil { 49 return err 50 } 51 fullHandle, err := os.Create(app.DockerComposeFullRenderedYAMLPath()) 52 if err != nil { 53 return err 54 } 55 defer func() { 56 err = fullHandle.Close() 57 if err != nil { 58 util.Warning("Error closing %s: %v", fullHandle.Name(), err) 59 } 60 }() 61 fullContentsBytes, err := yaml.Marshal(app.ComposeYaml) 62 if err != nil { 63 return err 64 } 65 66 _, err = fullHandle.Write(fullContentsBytes) 67 if err != nil { 68 return err 69 } 70 71 return nil 72 } 73 74 // fixupComposeYaml makes minor changes to the `docker-compose config` output 75 // to make sure extra services are always compatible with ddev. 76 func fixupComposeYaml(yamlStr string, app *DdevApp) (map[string]interface{}, error) { 77 tempMap := make(map[string]interface{}) 78 err := yaml.Unmarshal([]byte(yamlStr), &tempMap) 79 if err != nil { 80 return nil, err 81 } 82 83 // Find any services that have bind-mount to AppRoot and make them relative 84 // for https://youtrack.jetbrains.com/issue/WI-61976 - PhpStorm 85 // This is an ugly an shortsighted approach, but otherwise we'd have to parse the yaml. 86 // Note that this issue with docker-compose config was fixed in docker-compose 2.0.0RC4 87 // so it's in Docker Desktop 4.1.0. 88 // https://github.com/docker/compose/issues/8503#issuecomment-930969241 89 90 for _, service := range tempMap["services"].(map[string]interface{}) { 91 if service == nil { 92 continue 93 } 94 serviceMap := service.(map[string]interface{}) 95 96 // Find any services that have bind-mount to app.AppRoot and make them relative 97 if serviceMap["volumes"] != nil { 98 volumes := serviceMap["volumes"].([]interface{}) 99 for k, volume := range volumes { 100 // With docker-compose v1, the volume might not be a map, it might be 101 // old-style "/Users/rfay/workspace/d9/.ddev:/mnt/ddev_config:ro" 102 if volumeMap, ok := volume.(map[string]interface{}); ok { 103 if volumeMap["source"] != nil { 104 if volumeMap["source"].(string) == app.AppRoot { 105 volumeMap["source"] = "../" 106 } 107 } 108 } else if volumeMap, ok := volume.(string); ok { 109 parts := strings.SplitN(volumeMap, ":", 2) 110 if parts[0] == app.AppRoot && len(parts) >= 2 { 111 volumes[k] = "../" + parts[1] 112 } 113 } 114 } 115 } 116 // Make sure all services have our networks stanza 117 serviceMap["networks"] = map[string]interface{}{ 118 "ddev_default": nil, 119 "default": nil, 120 } 121 } 122 123 return tempMap, nil 124 }