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  }