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 }