github.com/camronlevanger/libcompose@v0.4.1-0.20180423130544-6bb86d53fa21/config/merge.go (about)

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