gopkg.in/docker/libcompose.v0@v0.4.0/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  // CreateConfig unmarshals bytes to config and creates config based on version
    27  func CreateConfig(bytes []byte) (*Config, error) {
    28  	var config Config
    29  	if err := yaml.Unmarshal(bytes, &config); err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	if config.Version != "2" {
    34  		var baseRawServices RawServiceMap
    35  		if err := yaml.Unmarshal(bytes, &baseRawServices); err != nil {
    36  			return nil, err
    37  		}
    38  		config.Services = baseRawServices
    39  	}
    40  
    41  	if config.Volumes == nil {
    42  		config.Volumes = make(map[string]interface{})
    43  	}
    44  	if config.Networks == nil {
    45  		config.Networks = make(map[string]interface{})
    46  	}
    47  
    48  	return &config, nil
    49  }
    50  
    51  // Merge merges a compose file into an existing set of service configs
    52  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) {
    53  	if options == nil {
    54  		options = &defaultParseOptions
    55  	}
    56  
    57  	config, err := CreateConfig(bytes)
    58  	if err != nil {
    59  		return "", nil, nil, nil, err
    60  	}
    61  	baseRawServices := config.Services
    62  
    63  	if options.Interpolate {
    64  		if err := InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
    65  			return "", nil, nil, nil, err
    66  		}
    67  
    68  		for k, v := range config.Volumes {
    69  			if err := Interpolate(k, &v, environmentLookup); err != nil {
    70  				return "", nil, nil, nil, err
    71  			}
    72  			config.Volumes[k] = v
    73  		}
    74  
    75  		for k, v := range config.Networks {
    76  			if err := Interpolate(k, &v, environmentLookup); err != nil {
    77  				return "", nil, nil, nil, err
    78  			}
    79  			config.Networks[k] = v
    80  		}
    81  	}
    82  
    83  	if options.Preprocess != nil {
    84  		var err error
    85  		baseRawServices, err = options.Preprocess(baseRawServices)
    86  		if err != nil {
    87  			return "", nil, nil, nil, err
    88  		}
    89  	}
    90  
    91  	var serviceConfigs map[string]*ServiceConfig
    92  	if config.Version == "2" {
    93  		var err error
    94  		serviceConfigs, err = MergeServicesV2(existingServices, environmentLookup, resourceLookup, file, baseRawServices, options)
    95  		if err != nil {
    96  			return "", nil, nil, nil, err
    97  		}
    98  	} else {
    99  		serviceConfigsV1, err := MergeServicesV1(existingServices, environmentLookup, resourceLookup, file, baseRawServices, options)
   100  		if err != nil {
   101  			return "", nil, nil, nil, err
   102  		}
   103  		serviceConfigs, err = ConvertServices(serviceConfigsV1)
   104  		if err != nil {
   105  			return "", nil, nil, nil, err
   106  		}
   107  	}
   108  
   109  	adjustValues(serviceConfigs)
   110  
   111  	if options.Postprocess != nil {
   112  		var err error
   113  		serviceConfigs, err = options.Postprocess(serviceConfigs)
   114  		if err != nil {
   115  			return "", nil, nil, nil, err
   116  		}
   117  	}
   118  
   119  	var volumes map[string]*VolumeConfig
   120  	var networks map[string]*NetworkConfig
   121  	if err := utils.Convert(config.Volumes, &volumes); err != nil {
   122  		return "", nil, nil, nil, err
   123  	}
   124  	if err := utils.Convert(config.Networks, &networks); err != nil {
   125  		return "", nil, nil, nil, err
   126  	}
   127  
   128  	return config.Version, serviceConfigs, volumes, networks, nil
   129  }
   130  
   131  // InterpolateRawServiceMap replaces varialbse in raw service map struct based on environment lookup
   132  func InterpolateRawServiceMap(baseRawServices *RawServiceMap, environmentLookup EnvironmentLookup) error {
   133  	for k, v := range *baseRawServices {
   134  		for k2, v2 := range v {
   135  			if err := Interpolate(k2, &v2, environmentLookup); err != nil {
   136  				return err
   137  			}
   138  			(*baseRawServices)[k][k2] = v2
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  func adjustValues(configs map[string]*ServiceConfig) {
   145  	// yaml parser turns "no" into "false" but that is not valid for a restart policy
   146  	for _, v := range configs {
   147  		if v.Restart == "false" {
   148  			v.Restart = "no"
   149  		}
   150  	}
   151  }
   152  
   153  func readEnvFile(resourceLookup ResourceLookup, inFile string, serviceData RawService) (RawService, error) {
   154  	if _, ok := serviceData["env_file"]; !ok {
   155  		return serviceData, nil
   156  	}
   157  
   158  	var envFiles composeYaml.Stringorslice
   159  
   160  	if err := utils.Convert(serviceData["env_file"], &envFiles); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	if len(envFiles) == 0 {
   165  		return serviceData, nil
   166  	}
   167  
   168  	if resourceLookup == nil {
   169  		return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
   170  	}
   171  
   172  	var vars composeYaml.MaporEqualSlice
   173  
   174  	if _, ok := serviceData["environment"]; ok {
   175  		if err := utils.Convert(serviceData["environment"], &vars); err != nil {
   176  			return nil, err
   177  		}
   178  	}
   179  
   180  	for i := len(envFiles) - 1; i >= 0; i-- {
   181  		envFile := envFiles[i]
   182  		content, _, err := resourceLookup.Lookup(envFile, inFile)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  
   191  		scanner := bufio.NewScanner(bytes.NewBuffer(content))
   192  		for scanner.Scan() {
   193  			line := strings.TrimSpace(scanner.Text())
   194  
   195  			if len(line) > 0 && !strings.HasPrefix(line, "#") {
   196  				key := strings.SplitAfter(line, "=")[0]
   197  
   198  				found := false
   199  				for _, v := range vars {
   200  					if strings.HasPrefix(v, key) {
   201  						found = true
   202  						break
   203  					}
   204  				}
   205  
   206  				if !found {
   207  					vars = append(vars, line)
   208  				}
   209  			}
   210  		}
   211  
   212  		if scanner.Err() != nil {
   213  			return nil, scanner.Err()
   214  		}
   215  	}
   216  
   217  	serviceData["environment"] = vars
   218  
   219  	delete(serviceData, "env_file")
   220  
   221  	return serviceData, nil
   222  }
   223  
   224  func mergeConfig(baseService, serviceData RawService) RawService {
   225  	for k, v := range serviceData {
   226  		// Image and build are mutually exclusive in merge
   227  		if k == "image" {
   228  			delete(baseService, "build")
   229  		} else if k == "build" {
   230  			delete(baseService, "image")
   231  		}
   232  		existing, ok := baseService[k]
   233  		if ok {
   234  			baseService[k] = merge(existing, v)
   235  		} else {
   236  			baseService[k] = v
   237  		}
   238  	}
   239  
   240  	return baseService
   241  }
   242  
   243  // IsValidRemote checks if the specified string is a valid remote (for builds)
   244  func IsValidRemote(remote string) bool {
   245  	return urlutil.IsGitURL(remote) || urlutil.IsURL(remote)
   246  }