mirror of
https://github.com/rancher/os.git
synced 2025-07-06 11:36:15 +00:00
300 lines
6.3 KiB
Go
300 lines
6.3 KiB
Go
package project
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
yaml "github.com/cloudfoundry-incubator/candiedyaml"
|
|
"github.com/docker/libcompose/utils"
|
|
)
|
|
|
|
var (
|
|
// ValidRemotes list the of valid prefixes that can be sent to Docker as a build remote location
|
|
// This is public for consumers of libcompose to use
|
|
ValidRemotes = []string{
|
|
"git://",
|
|
"git@github.com:",
|
|
"github.com",
|
|
"http:",
|
|
"https:",
|
|
}
|
|
noMerge = []string{
|
|
"links",
|
|
"volumes_from",
|
|
}
|
|
)
|
|
|
|
type rawService map[string]interface{}
|
|
type rawServiceMap map[string]rawService
|
|
|
|
func mergeProject(p *Project, bytes []byte) (map[string]*ServiceConfig, error) {
|
|
configs := make(map[string]*ServiceConfig)
|
|
|
|
datas := make(rawServiceMap)
|
|
if err := yaml.Unmarshal(bytes, &datas); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := interpolate(p.context.EnvironmentLookup, &datas); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for name, data := range datas {
|
|
data, err := parse(p.context.ConfigLookup, p.context.EnvironmentLookup, p.File, data, datas)
|
|
if err != nil {
|
|
logrus.Errorf("Failed to parse service %s: %v", name, err)
|
|
return nil, err
|
|
}
|
|
|
|
datas[name] = data
|
|
}
|
|
|
|
if err := utils.Convert(datas, &configs); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
adjustValues(configs)
|
|
return configs, nil
|
|
}
|
|
|
|
func adjustValues(configs map[string]*ServiceConfig) {
|
|
// yaml parser turns "no" into "false" but that is not valid for a restart policy
|
|
for _, v := range configs {
|
|
if v.Restart == "false" {
|
|
v.Restart = "no"
|
|
}
|
|
}
|
|
}
|
|
|
|
func readEnvFile(configLookup ConfigLookup, inFile string, serviceData rawService) (rawService, error) {
|
|
var config ServiceConfig
|
|
|
|
if err := utils.Convert(serviceData, &config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(config.EnvFile.Slice()) == 0 {
|
|
return serviceData, nil
|
|
}
|
|
|
|
if configLookup == nil {
|
|
return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
|
|
}
|
|
|
|
vars := config.Environment.Slice()
|
|
|
|
for i := len(config.EnvFile.Slice()) - 1; i >= 0; i-- {
|
|
envFile := config.EnvFile.Slice()[i]
|
|
content, _, err := configLookup.Lookup(envFile, inFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scanner := bufio.NewScanner(bytes.NewBuffer(content))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
key := strings.SplitAfter(line, "=")[0]
|
|
|
|
found := false
|
|
for _, v := range vars {
|
|
if strings.HasPrefix(v, key) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
vars = append(vars, line)
|
|
}
|
|
}
|
|
|
|
if scanner.Err() != nil {
|
|
return nil, scanner.Err()
|
|
}
|
|
}
|
|
|
|
serviceData["environment"] = vars
|
|
|
|
delete(serviceData, "env_file")
|
|
|
|
return serviceData, nil
|
|
}
|
|
|
|
func resolveBuild(inFile string, serviceData rawService) (rawService, error) {
|
|
|
|
build := asString(serviceData["build"])
|
|
if build == "" {
|
|
return serviceData, nil
|
|
}
|
|
|
|
for _, remote := range ValidRemotes {
|
|
if strings.HasPrefix(build, remote) {
|
|
return serviceData, nil
|
|
}
|
|
}
|
|
|
|
current := path.Dir(inFile)
|
|
|
|
if build == "." {
|
|
build = current
|
|
} else {
|
|
current = path.Join(current, build)
|
|
}
|
|
|
|
serviceData["build"] = current
|
|
|
|
return serviceData, nil
|
|
}
|
|
|
|
func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFile string, serviceData rawService, datas rawServiceMap) (rawService, error) {
|
|
serviceData, err := readEnvFile(configLookup, inFile, serviceData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
serviceData, err = resolveBuild(inFile, serviceData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
value, ok := serviceData["extends"]
|
|
if !ok {
|
|
return serviceData, nil
|
|
}
|
|
|
|
mapValue, ok := value.(map[interface{}]interface{})
|
|
if !ok {
|
|
return serviceData, nil
|
|
}
|
|
|
|
if configLookup == nil {
|
|
return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
|
|
}
|
|
|
|
file := asString(mapValue["file"])
|
|
service := asString(mapValue["service"])
|
|
|
|
if service == "" {
|
|
return serviceData, nil
|
|
}
|
|
|
|
var baseService rawService
|
|
|
|
if file == "" {
|
|
if serviceData, ok := datas[service]; ok {
|
|
baseService, err = parse(configLookup, environmentLookup, inFile, serviceData, datas)
|
|
} else {
|
|
return nil, fmt.Errorf("Failed to find service %s to extend", service)
|
|
}
|
|
} else {
|
|
bytes, resolved, err := configLookup.Lookup(file, inFile)
|
|
if err != nil {
|
|
logrus.Errorf("Failed to lookup file %s: %v", file, err)
|
|
return nil, err
|
|
}
|
|
|
|
var baseRawServices rawServiceMap
|
|
if err := yaml.Unmarshal(bytes, &baseRawServices); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = interpolate(environmentLookup, &baseRawServices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
baseService, ok = baseRawServices[service]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
|
|
}
|
|
|
|
baseService, err = parse(configLookup, environmentLookup, resolved, baseService, baseRawServices)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
baseService = clone(baseService)
|
|
|
|
logrus.Debugf("Merging %#v, %#v", baseService, serviceData)
|
|
|
|
for _, k := range noMerge {
|
|
if _, ok := baseService[k]; ok {
|
|
source := file
|
|
if source == "" {
|
|
source = inFile
|
|
}
|
|
return nil, fmt.Errorf("Cannot extend service '%s' in %s: services with '%s' cannot be extended", service, source, k)
|
|
}
|
|
}
|
|
|
|
for k, v := range serviceData {
|
|
// Image and build are mutually exclusive in merge
|
|
if k == "image" {
|
|
delete(baseService, "build")
|
|
} else if k == "build" {
|
|
delete(baseService, "image")
|
|
}
|
|
existing, ok := baseService[k]
|
|
if ok {
|
|
baseService[k] = merge(existing, v)
|
|
} else {
|
|
baseService[k] = v
|
|
}
|
|
}
|
|
|
|
logrus.Debugf("Merged result %#v", baseService)
|
|
|
|
return baseService, nil
|
|
}
|
|
|
|
func merge(existing, value interface{}) interface{} {
|
|
// append strings
|
|
if left, lok := existing.([]interface{}); lok {
|
|
if right, rok := value.([]interface{}); rok {
|
|
return append(left, right...)
|
|
}
|
|
}
|
|
|
|
//merge maps
|
|
if left, lok := existing.(map[interface{}]interface{}); lok {
|
|
if right, rok := value.(map[interface{}]interface{}); rok {
|
|
newLeft := make(map[interface{}]interface{})
|
|
for k, v := range left {
|
|
newLeft[k] = v
|
|
}
|
|
for k, v := range right {
|
|
newLeft[k] = v
|
|
}
|
|
return newLeft
|
|
}
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
func clone(in rawService) rawService {
|
|
result := rawService{}
|
|
for k, v := range in {
|
|
result[k] = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func asString(obj interface{}) string {
|
|
if v, ok := obj.(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|