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 }