1
0
mirror of https://github.com/rancher/os.git synced 2025-06-22 21:17:02 +00:00
os/compose/project.go
Sven Dowideit d244043ce7 cache the user/system client if its created
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
2017-07-21 09:50:31 +10:00

294 lines
7.5 KiB
Go

package compose
import (
"fmt"
"golang.org/x/net/context"
yaml "github.com/cloudfoundry-incubator/candiedyaml"
dockerClient "github.com/docker/engine-api/client"
"github.com/docker/libcompose/cli/logger"
composeConfig "github.com/docker/libcompose/config"
"github.com/docker/libcompose/docker"
composeClient "github.com/docker/libcompose/docker/client"
"github.com/docker/libcompose/project"
"github.com/docker/libcompose/project/events"
"github.com/docker/libcompose/project/options"
"github.com/rancher/os/config"
rosDocker "github.com/rancher/os/docker"
"github.com/rancher/os/log"
"github.com/rancher/os/util"
"github.com/rancher/os/util/network"
)
func CreateService(cfg *config.CloudConfig, name string, serviceConfig *composeConfig.ServiceConfigV1) (project.Service, error) {
if cfg == nil {
cfg = config.LoadConfig()
}
p, err := CreateServiceSet("once", cfg, map[string]*composeConfig.ServiceConfigV1{
name: serviceConfig,
})
if err != nil {
return nil, err
}
return p.CreateService(name)
}
func CreateServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
p, err := newProject(name, cfg, nil, nil)
if err != nil {
return nil, err
}
addServices(p, map[interface{}]interface{}{}, configs)
return p, nil
}
func RunServiceSet(name string, cfg *config.CloudConfig, configs map[string]*composeConfig.ServiceConfigV1) (*project.Project, error) {
p, err := CreateServiceSet(name, cfg, configs)
if err != nil {
return nil, err
}
return p, p.Up(context.Background(), options.Up{
Log: cfg.Rancher.Log,
})
}
func GetProject(cfg *config.CloudConfig, networkingAvailable, loadConsole bool) (*project.Project, error) {
return newCoreServiceProject(cfg, networkingAvailable, loadConsole)
}
func newProject(name string, cfg *config.CloudConfig, environmentLookup composeConfig.EnvironmentLookup, authLookup *rosDocker.ConfigAuthLookup) (*project.Project, error) {
clientFactory, err := rosDocker.NewClientFactory(composeClient.Options{})
if err != nil {
return nil, err
}
if environmentLookup == nil {
environmentLookup = rosDocker.NewConfigEnvironment(cfg)
}
if authLookup == nil {
authLookup = rosDocker.NewConfigAuthLookup(cfg)
}
serviceFactory := &rosDocker.ServiceFactory{
Deps: map[string][]string{},
}
context := &docker.Context{
ClientFactory: clientFactory,
AuthLookup: authLookup,
Context: project.Context{
ProjectName: name,
EnvironmentLookup: environmentLookup,
ServiceFactory: serviceFactory,
LoggerFactory: logger.NewColorLoggerFactory(),
},
}
serviceFactory.Context = context
authLookup.SetContext(context)
return docker.NewProject(context, &composeConfig.ParseOptions{
Interpolate: true,
Validate: false,
Preprocess: preprocessServiceMap,
})
}
func preprocessServiceMap(serviceMap composeConfig.RawServiceMap) (composeConfig.RawServiceMap, error) {
newServiceMap := make(composeConfig.RawServiceMap)
for k, v := range serviceMap {
newServiceMap[k] = make(composeConfig.RawService)
for k2, v2 := range v {
if k2 == "environment" || k2 == "labels" {
newServiceMap[k][k2] = preprocess(v2, true)
} else {
newServiceMap[k][k2] = preprocess(v2, false)
}
}
}
return newServiceMap, nil
}
func preprocess(item interface{}, replaceTypes bool) interface{} {
switch typedDatas := item.(type) {
case map[interface{}]interface{}:
newMap := make(map[interface{}]interface{})
for key, value := range typedDatas {
newMap[key] = preprocess(value, replaceTypes)
}
return newMap
case []interface{}:
// newArray := make([]interface{}, 0) will cause golint to complain
var newArray []interface{}
newArray = make([]interface{}, 0)
for _, value := range typedDatas {
newArray = append(newArray, preprocess(value, replaceTypes))
}
return newArray
default:
if replaceTypes {
return fmt.Sprint(item)
}
return item
}
}
func addServices(p *project.Project, enabled map[interface{}]interface{}, configs map[string]*composeConfig.ServiceConfigV1) map[interface{}]interface{} {
serviceConfigsV2, _ := composeConfig.ConvertServices(configs)
// Note: we ignore errors while loading services
unchanged := true
for name, serviceConfig := range serviceConfigsV2 {
hash := composeConfig.GetServiceHash(name, serviceConfig)
if enabled[name] == hash {
continue
}
if err := p.AddConfig(name, serviceConfig); err != nil {
log.Infof("Failed loading service %s", name)
continue
}
if unchanged {
enabled = util.MapCopy(enabled)
unchanged = false
}
enabled[name] = hash
}
return enabled
}
func adjustContainerNames(m map[interface{}]interface{}) map[interface{}]interface{} {
for k, v := range m {
if k, ok := k.(string); ok {
if v, ok := v.(map[interface{}]interface{}); ok {
if _, ok := v["container_name"]; !ok {
v["container_name"] = k
}
}
}
}
return m
}
func newCoreServiceProject(cfg *config.CloudConfig, useNetwork, loadConsole bool) (*project.Project, error) {
environmentLookup := rosDocker.NewConfigEnvironment(cfg)
authLookup := rosDocker.NewConfigAuthLookup(cfg)
p, err := newProject("os", cfg, environmentLookup, authLookup)
if err != nil {
return nil, err
}
projectEvents := make(chan events.Event)
p.AddListener(project.NewDefaultListener(p))
p.AddListener(projectEvents)
p.ReloadCallback = projectReload(p, &useNetwork, loadConsole, environmentLookup, authLookup)
go func() {
for event := range projectEvents {
if event.EventType == events.ContainerStarted && event.ServiceName == "network" {
useNetwork = true
}
}
}()
err = p.ReloadCallback()
if err != nil {
log.Errorf("Failed to reload os: %v", err)
return nil, err
}
return p, nil
}
func StageServices(cfg *config.CloudConfig, services ...string) error {
p, err := newProject("stage-services", cfg, nil, nil)
if err != nil {
return err
}
for _, service := range services {
bytes, err := network.LoadServiceResource(service, true, cfg)
if err != nil {
return fmt.Errorf("Failed to load %s : %v", service, err)
}
m := map[interface{}]interface{}{}
if err := yaml.Unmarshal(bytes, &m); err != nil {
return fmt.Errorf("Failed to parse YAML configuration: %s : %v", service, err)
}
bytes, err = yaml.Marshal(m)
if err != nil {
return fmt.Errorf("Failed to marshal YAML configuration: %s : %v", service, err)
}
err = p.Load(bytes)
if err != nil {
return fmt.Errorf("Failed to load %s : %v", service, err)
}
}
// Reduce service configurations to just image and labels
needToPull := false
var client, userClient, systemClient dockerClient.APIClient
for _, serviceName := range p.ServiceConfigs.Keys() {
serviceConfig, _ := p.ServiceConfigs.Get(serviceName)
// test to see if we need to Pull
if serviceConfig.Labels[config.ScopeLabel] != config.System {
if userClient == nil {
userClient, err = rosDocker.NewDefaultClient()
if err != nil {
log.Error(err)
}
}
client = userClient
} else {
if systemClient == nil {
systemClient, err = rosDocker.NewSystemClient()
if err != nil {
log.Error(err)
}
client = systemClient
}
}
if client != nil {
_, _, err := client.ImageInspectWithRaw(context.Background(), serviceConfig.Image, false)
if err == nil {
log.Infof("Service %s using local image %s", serviceName, serviceConfig.Image)
continue
}
}
needToPull = true
p.ServiceConfigs.Add(serviceName, &composeConfig.ServiceConfig{
Image: serviceConfig.Image,
Labels: serviceConfig.Labels,
})
}
if needToPull {
return p.Pull(context.Background())
}
return nil
}