github.com/bdwilliams/libcompose@v0.3.1-0.20160826154243-d81a9bdacff0/config/merge.go (about)

     1  package config
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/docker/docker/pkg/urlutil"
    10  	"github.com/docker/libcompose/utils"
    11  	composeYaml "github.com/docker/libcompose/yaml"
    12  	"gopkg.in/yaml.v2"
    13  )
    14  
    15  var (
    16  	noMerge = []string{
    17  		"links",
    18  		"volumes_from",
    19  	}
    20  	defaultParseOptions = ParseOptions{
    21  		Interpolate: true,
    22  		Validate:    true,
    23  	}
    24  )
    25  
    26  // Merge merges a compose file into an existing set of service configs
    27  func Merge(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte, options *ParseOptions) (string, map[string]*ServiceConfig, map[string]*VolumeConfig, map[string]*NetworkConfig, error) {
    28  	if options == nil {
    29  		options = &defaultParseOptions
    30  	}
    31  
    32  	var config Config
    33  	if err := yaml.Unmarshal(bytes, &config); err != nil {
    34  		return "", nil, nil, nil, err
    35  	}
    36  
    37  	var serviceConfigs map[string]*ServiceConfig
    38  	var volumeConfigs map[string]*VolumeConfig
    39  	var networkConfigs map[string]*NetworkConfig
    40  	if config.Version == "2" {
    41  		var err error
    42  		serviceConfigs, err = MergeServicesV2(existingServices, environmentLookup, resourceLookup, file, bytes, options)
    43  		if err != nil {
    44  			return "", nil, nil, nil, err
    45  		}
    46  		volumeConfigs, err = ParseVolumes(bytes)
    47  		if err != nil {
    48  			return "", nil, nil, nil, err
    49  		}
    50  		networkConfigs, err = ParseNetworks(bytes)
    51  		if err != nil {
    52  			return "", nil, nil, nil, err
    53  		}
    54  	} else {
    55  		serviceConfigsV1, err := MergeServicesV1(existingServices, environmentLookup, resourceLookup, file, bytes, options)
    56  		if err != nil {
    57  			return "", nil, nil, nil, err
    58  		}
    59  		serviceConfigs, err = ConvertServices(serviceConfigsV1)
    60  		if err != nil {
    61  			return "", nil, nil, nil, err
    62  		}
    63  	}
    64  
    65  	adjustValues(serviceConfigs)
    66  
    67  	if options.Postprocess != nil {
    68  		var err error
    69  		serviceConfigs, err = options.Postprocess(serviceConfigs)
    70  		if err != nil {
    71  			return "", nil, nil, nil, err
    72  		}
    73  	}
    74  
    75  	return config.Version, serviceConfigs, volumeConfigs, networkConfigs, nil
    76  }
    77  
    78  func adjustValues(configs map[string]*ServiceConfig) {
    79  	// yaml parser turns "no" into "false" but that is not valid for a restart policy
    80  	for _, v := range configs {
    81  		if v.Restart == "false" {
    82  			v.Restart = "no"
    83  		}
    84  	}
    85  }
    86  
    87  func readEnvFile(resourceLookup ResourceLookup, inFile string, serviceData RawService) (RawService, error) {
    88  	if _, ok := serviceData["env_file"]; !ok {
    89  		return serviceData, nil
    90  	}
    91  
    92  	var envFiles composeYaml.Stringorslice
    93  
    94  	if err := utils.Convert(serviceData["env_file"], &envFiles); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	if len(envFiles) == 0 {
    99  		return serviceData, nil
   100  	}
   101  
   102  	if resourceLookup == nil {
   103  		return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
   104  	}
   105  
   106  	var vars composeYaml.MaporEqualSlice
   107  
   108  	if _, ok := serviceData["environment"]; ok {
   109  		if err := utils.Convert(serviceData["environment"], &vars); err != nil {
   110  			return nil, err
   111  		}
   112  	}
   113  
   114  	for i := len(envFiles) - 1; i >= 0; i-- {
   115  		envFile := envFiles[i]
   116  		content, _, err := resourceLookup.Lookup(envFile, inFile)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		scanner := bufio.NewScanner(bytes.NewBuffer(content))
   126  		for scanner.Scan() {
   127  			line := strings.TrimSpace(scanner.Text())
   128  
   129  			if len(line) > 0 && !strings.HasPrefix(line, "#") {
   130  				key := strings.SplitAfter(line, "=")[0]
   131  
   132  				found := false
   133  				for _, v := range vars {
   134  					if strings.HasPrefix(v, key) {
   135  						found = true
   136  						break
   137  					}
   138  				}
   139  
   140  				if !found {
   141  					vars = append(vars, line)
   142  				}
   143  			}
   144  		}
   145  
   146  		if scanner.Err() != nil {
   147  			return nil, scanner.Err()
   148  		}
   149  	}
   150  
   151  	serviceData["environment"] = vars
   152  
   153  	delete(serviceData, "env_file")
   154  
   155  	return serviceData, nil
   156  }
   157  
   158  func mergeConfig(baseService, serviceData RawService) RawService {
   159  	for k, v := range serviceData {
   160  		// Image and build are mutually exclusive in merge
   161  		if k == "image" {
   162  			delete(baseService, "build")
   163  		} else if k == "build" {
   164  			delete(baseService, "image")
   165  		}
   166  		existing, ok := baseService[k]
   167  		if ok {
   168  			baseService[k] = merge(existing, v)
   169  		} else {
   170  			baseService[k] = v
   171  		}
   172  	}
   173  
   174  	return baseService
   175  }
   176  
   177  // IsValidRemote checks if the specified string is a valid remote (for builds)
   178  func IsValidRemote(remote string) bool {
   179  	return urlutil.IsGitURL(remote) || urlutil.IsURL(remote)
   180  }