github.com/drud/ddev@v1.21.5-alpha1.0.20230226034409-94fcc4b94453/pkg/ddevapp/compose_yaml.go (about) 1 package ddevapp 2 3 import ( 4 "github.com/drud/ddev/pkg/dockerutil" 5 "github.com/drud/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 // marshalling the canonical version to YAML and then unmarshalling 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(files, "config") 40 if err != nil { 41 return err 42 } 43 44 app.ComposeYaml, err = fixupComposeYaml(fullContents, app) 45 if err != nil { 46 return err 47 } 48 fullHandle, err := os.Create(app.DockerComposeFullRenderedYAMLPath()) 49 if err != nil { 50 return err 51 } 52 defer func() { 53 err = fullHandle.Close() 54 if err != nil { 55 util.Warning("Error closing %s: %v", fullHandle.Name(), err) 56 } 57 }() 58 fullContentsBytes, err := yaml.Marshal(app.ComposeYaml) 59 if err != nil { 60 return err 61 } 62 63 _, err = fullHandle.Write(fullContentsBytes) 64 if err != nil { 65 return err 66 } 67 68 return nil 69 } 70 71 // fixupComposeYaml makes minor changes to the `docker-compose config` output 72 // to make sure extra services are always compatible with ddev. 73 func fixupComposeYaml(yamlStr string, app *DdevApp) (map[string]interface{}, error) { 74 tempMap := make(map[string]interface{}) 75 err := yaml.Unmarshal([]byte(yamlStr), &tempMap) 76 if err != nil { 77 return nil, err 78 } 79 80 // Find any services that have bind-mount to AppRoot and make them relative 81 // for https://youtrack.jetbrains.com/issue/WI-61976 - PhpStorm 82 // This is an ugly an shortsighted approach, but otherwise we'd have to parse the yaml. 83 // Note that this issue with docker-compose config was fixed in docker-compose 2.0.0RC4 84 // so it's in Docker Desktop 4.1.0. 85 // https://github.com/docker/compose/issues/8503#issuecomment-930969241 86 87 for _, service := range tempMap["services"].(map[string]interface{}) { 88 if service == nil { 89 continue 90 } 91 serviceMap := service.(map[string]interface{}) 92 93 // Find any services that have bind-mount to app.AppRoot and make them relative 94 if serviceMap["volumes"] != nil { 95 volumes := serviceMap["volumes"].([]interface{}) 96 for k, volume := range volumes { 97 // With docker-compose v1, the volume might not be a map, it might be 98 // old-style "/Users/rfay/workspace/d9/.ddev:/mnt/ddev_config:ro" 99 if volumeMap, ok := volume.(map[string]interface{}); ok { 100 if volumeMap["source"] != nil { 101 if volumeMap["source"].(string) == app.AppRoot { 102 volumeMap["source"] = "../" 103 } 104 } 105 } else if volumeMap, ok := volume.(string); ok { 106 parts := strings.SplitN(volumeMap, ":", 2) 107 if parts[0] == app.AppRoot && len(parts) >= 2 { 108 volumes[k] = "../" + parts[1] 109 } 110 } 111 } 112 } 113 // Make sure all services have our networks stanza 114 serviceMap["networks"] = map[string]interface{}{ 115 "ddev_default": nil, 116 "default": nil, 117 } 118 } 119 120 return tempMap, nil 121 }