mirror of
https://github.com/rancher/os.git
synced 2025-06-24 22:11:33 +00:00
Refactor to use libcompose
This commit is contained in:
parent
19f9a1b281
commit
9d76b79ac3
@ -47,12 +47,6 @@ func Main() {
|
|||||||
HideHelp: true,
|
HideHelp: true,
|
||||||
Subcommands: serviceSubCommands(),
|
Subcommands: serviceSubCommands(),
|
||||||
},
|
},
|
||||||
//{
|
|
||||||
// Name: "reload",
|
|
||||||
// ShortName: "a",
|
|
||||||
// Usage: "reload configuration of a service and restart the container",
|
|
||||||
// Action: reload,
|
|
||||||
//},
|
|
||||||
{
|
{
|
||||||
Name: "os",
|
Name: "os",
|
||||||
Usage: "operating system upgrade/downgrade",
|
Usage: "operating system upgrade/downgrade",
|
||||||
|
@ -15,7 +15,9 @@ import (
|
|||||||
dockerClient "github.com/fsouza/go-dockerclient"
|
dockerClient "github.com/fsouza/go-dockerclient"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
"github.com/rancherio/os/cmd/power"
|
"github.com/rancherio/os/cmd/power"
|
||||||
|
"github.com/rancherio/os/compose"
|
||||||
"github.com/rancherio/os/config"
|
"github.com/rancherio/os/config"
|
||||||
"github.com/rancherio/os/docker"
|
"github.com/rancherio/os/docker"
|
||||||
)
|
)
|
||||||
@ -147,7 +149,9 @@ func osUpgrade(c *cli.Context) {
|
|||||||
if c.Args().Present() {
|
if c.Args().Present() {
|
||||||
log.Fatalf("invalid arguments %v", c.Args())
|
log.Fatalf("invalid arguments %v", c.Args())
|
||||||
}
|
}
|
||||||
startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot"))
|
if err := startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func osVersion(c *cli.Context) {
|
func osVersion(c *cli.Context) {
|
||||||
@ -164,22 +168,28 @@ func yes(in *bufio.Reader, question string) bool {
|
|||||||
return strings.ToLower(line[0:1]) == "y"
|
return strings.ToLower(line[0:1]) == "y"
|
||||||
}
|
}
|
||||||
|
|
||||||
func startUpgradeContainer(image string, stage, force, reboot bool) {
|
func startUpgradeContainer(image string, stage, force, reboot bool) error {
|
||||||
in := bufio.NewReader(os.Stdin)
|
in := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
container := docker.NewContainer(config.DOCKER_SYSTEM_HOST, &config.ContainerConfig{
|
container, err := compose.CreateService(nil, "os-upgrade", &project.ServiceConfig{
|
||||||
Cmd: "--name=os-upgrade " +
|
LogDriver: "json-file",
|
||||||
"--log-driver=json-file " +
|
Privileged: true,
|
||||||
"--rm " +
|
Net: "host",
|
||||||
"--privileged " +
|
Image: image,
|
||||||
"--net=host " +
|
Labels: project.NewSliceorMap(map[string]string{
|
||||||
image + " " +
|
config.SCOPE: config.SYSTEM,
|
||||||
"-t rancher-upgrade " +
|
}),
|
||||||
"-r " + config.VERSION,
|
Command: project.NewCommand(
|
||||||
}).Stage()
|
"-t", "rancher-upgrade",
|
||||||
|
"-r", config.VERSION,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if container.Err != nil {
|
if err := container.Pull(); err != nil {
|
||||||
log.Fatal(container.Err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stage {
|
if !stage {
|
||||||
@ -191,46 +201,25 @@ func startUpgradeContainer(image string, stage, force, reboot bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container.Start()
|
if err := container.Start(); err != nil {
|
||||||
if container.Err != nil {
|
return err
|
||||||
log.Fatal(container.Err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := docker.NewClient(config.DOCKER_SYSTEM_HOST)
|
if err := container.Log(); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
if err := container.Up(); err != nil {
|
||||||
client.Logs(dockerClient.LogsOptions{
|
return err
|
||||||
Container: container.Container.ID,
|
|
||||||
OutputStream: os.Stdout,
|
|
||||||
ErrorStream: os.Stderr,
|
|
||||||
Follow: true,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
exit, err := client.WaitContainer(container.Container.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.Err != nil {
|
|
||||||
log.Fatal(container.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exit == 0 {
|
|
||||||
if reboot && (force || yes(in, "Continue with reboot")) {
|
if reboot && (force || yes(in, "Continue with reboot")) {
|
||||||
log.Info("Rebooting")
|
log.Info("Rebooting")
|
||||||
power.Reboot()
|
power.Reboot()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
log.Error("Upgrade failed")
|
|
||||||
os.Exit(exit)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBody(body []byte) (*Images, error) {
|
func parseBody(body []byte) (*Images, error) {
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/rancherio/os/config"
|
|
||||||
"github.com/rancherio/os/docker"
|
|
||||||
)
|
|
||||||
|
|
||||||
//func parseContainers(cfg *config.Config) map[string]*docker.Container {
|
|
||||||
// result := map[string]*docker.Container{}
|
|
||||||
//
|
|
||||||
// for _, containerConfig := range cfg.SystemContainers {
|
|
||||||
// container := docker.NewContainer(config.DOCKER_SYSTEM_HOST, &containerConfig)
|
|
||||||
// if containerConfig.Id != "" {
|
|
||||||
// result[containerConfig.Id] = container
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return result
|
|
||||||
//}
|
|
||||||
|
|
||||||
func reload(c *cli.Context) {
|
|
||||||
_, err := config.LoadConfig()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containers := map[string]*docker.Container{} //parseContainers(cfg)
|
|
||||||
toStart := make([]*docker.Container, 0, len(c.Args()))
|
|
||||||
|
|
||||||
for _, id := range c.Args() {
|
|
||||||
if container, ok := containers[id]; ok {
|
|
||||||
toStart = append(toStart, container.Stage())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
for _, c := range toStart {
|
|
||||||
err := c.Start().Err
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to start %s : %v", c.ContainerCfg.Id, err)
|
|
||||||
if firstErr != nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if firstErr != nil {
|
|
||||||
log.Fatal(firstErr)
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
"github.com/rancherio/os/compose"
|
||||||
"github.com/rancherio/os/config"
|
"github.com/rancherio/os/config"
|
||||||
"github.com/rancherio/os/docker"
|
|
||||||
"github.com/rancherio/os/util"
|
"github.com/rancherio/os/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ func enable(c *cli.Context) {
|
|||||||
if strings.HasPrefix(service, "/") && !strings.HasPrefix(service, "/var/lib/rancher/conf") {
|
if strings.HasPrefix(service, "/") && !strings.HasPrefix(service, "/var/lib/rancher/conf") {
|
||||||
log.Fatalf("ERROR: Service should be in path /var/lib/rancher/conf")
|
log.Fatalf("ERROR: Service should be in path /var/lib/rancher/conf")
|
||||||
}
|
}
|
||||||
if _, err := docker.LoadServiceResource(service, true, cfg); err != nil {
|
if _, err := compose.LoadServiceResource(service, true, cfg); err != nil {
|
||||||
log.Fatalf("could not load service %s", service)
|
log.Fatalf("could not load service %s", service)
|
||||||
}
|
}
|
||||||
cfg.Rancher.ServicesInclude[service] = true
|
cfg.Rancher.ServicesInclude[service] = true
|
||||||
|
162
compose/project.go
Normal file
162
compose/project.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package compose
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/libcompose/cli/logger"
|
||||||
|
"github.com/docker/libcompose/docker"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/rancherio/os/config"
|
||||||
|
rosDocker "github.com/rancherio/os/docker"
|
||||||
|
"github.com/rancherio/os/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateService(cfg *config.CloudConfig, name string, serviceConfig *project.ServiceConfig) (project.Service, error) {
|
||||||
|
if cfg == nil {
|
||||||
|
var err error
|
||||||
|
cfg, err = config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := RunServiceSet("once", cfg, map[string]*project.ServiceConfig{
|
||||||
|
name: serviceConfig,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.CreateService(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunServiceSet(name string, cfg *config.CloudConfig, configs map[string]*project.ServiceConfig) (*project.Project, error) {
|
||||||
|
p, err := newProject(name, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addServices(p, cfg, map[string]string{}, configs)
|
||||||
|
|
||||||
|
return p, p.Up()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunServices(cfg *config.CloudConfig) error {
|
||||||
|
p, err := newCoreServiceProject(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.Up()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProject(name string, cfg *config.CloudConfig) (*project.Project, error) {
|
||||||
|
clientFactory, err := rosDocker.NewClientFactory(docker.ClientOpts{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceFactory := &rosDocker.ServiceFactory{
|
||||||
|
Deps: map[string][]string{},
|
||||||
|
}
|
||||||
|
context := &docker.Context{
|
||||||
|
ClientFactory: clientFactory,
|
||||||
|
Context: project.Context{
|
||||||
|
ProjectName: name,
|
||||||
|
EnvironmentLookup: rosDocker.NewConfigEnvironment(cfg),
|
||||||
|
ServiceFactory: serviceFactory,
|
||||||
|
Rebuild: true,
|
||||||
|
Log: cfg.Rancher.Log,
|
||||||
|
LoggerFactory: logger.NewColorLoggerFactory(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serviceFactory.Context = context
|
||||||
|
|
||||||
|
return docker.NewProject(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addServices(p *project.Project, cfg *config.CloudConfig, enabled map[string]string, configs map[string]*project.ServiceConfig) {
|
||||||
|
// Note: we ignore errors while loading services
|
||||||
|
for name, serviceConfig := range cfg.Rancher.Services {
|
||||||
|
hash := project.GetServiceHash(name, *serviceConfig)
|
||||||
|
|
||||||
|
if enabled[name] == hash {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.AddConfig(name, serviceConfig); err != nil {
|
||||||
|
log.Infof("Failed loading service %s", name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled[name] = hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCoreServiceProject(cfg *config.CloudConfig) (*project.Project, error) {
|
||||||
|
network := false
|
||||||
|
projectEvents := make(chan project.ProjectEvent)
|
||||||
|
enabled := make(map[string]string)
|
||||||
|
|
||||||
|
p, err := newProject("os", cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.AddListener(project.NewDefaultListener(p))
|
||||||
|
p.AddListener(projectEvents)
|
||||||
|
|
||||||
|
p.ReloadCallback = func() error {
|
||||||
|
err := cfg.Reload()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
||||||
|
if enabled[service] != "" || !serviceEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := LoadServiceResource(service, network, cfg)
|
||||||
|
if err != nil {
|
||||||
|
if err == util.ErrNoNetwork {
|
||||||
|
log.Debugf("Can not load %s, networking not enabled", service)
|
||||||
|
} else {
|
||||||
|
log.Errorf("Failed to load %s : %v", service, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.Load(bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to load %s : %v", service, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled[service] = service
|
||||||
|
}
|
||||||
|
|
||||||
|
addServices(p, cfg, enabled, cfg.Rancher.Services)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for event := range projectEvents {
|
||||||
|
if event.Event == project.CONTAINER_STARTED && event.ServiceName == "network" {
|
||||||
|
network = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = p.ReloadCallback()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to reload os: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadServiceResource(name string, network bool, cfg *config.CloudConfig) ([]byte, error) {
|
||||||
|
return util.LoadResource(name, network, cfg.Rancher.Repositories.ToArray())
|
||||||
|
}
|
@ -5,8 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
"github.com/rancherio/os/util"
|
"github.com/rancherio/os/util"
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -139,18 +139,6 @@ func Dump(private, full bool) (string, error) {
|
|||||||
return string(bytes), err
|
return string(bytes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudConfig) configureConsole() error {
|
|
||||||
if console, ok := c.Rancher.Services[CONSOLE_CONTAINER]; ok {
|
|
||||||
if c.Rancher.Console.Persistent {
|
|
||||||
console.Labels.MapParts()[REMOVE] = "false"
|
|
||||||
} else {
|
|
||||||
console.Labels.MapParts()[REMOVE] = "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CloudConfig) amendNils() error {
|
func (c *CloudConfig) amendNils() error {
|
||||||
if c.Rancher.Environment == nil {
|
if c.Rancher.Environment == nil {
|
||||||
c.Rancher.Environment = map[string]string{}
|
c.Rancher.Environment = map[string]string{}
|
||||||
@ -173,7 +161,6 @@ func (c *CloudConfig) amendNils() error {
|
|||||||
func (c *CloudConfig) readGlobals() error {
|
func (c *CloudConfig) readGlobals() error {
|
||||||
return util.ShortCircuit(
|
return util.ShortCircuit(
|
||||||
c.readCmdline,
|
c.readCmdline,
|
||||||
c.configureConsole, // TODO: this smells (it is a write hidden inside a read)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,27 +203,6 @@ func (c *CloudConfig) Set(key string, value interface{}) error {
|
|||||||
return c.Reload()
|
return c.Reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DockerConfig) BridgeConfig() (string, string) {
|
|
||||||
var name, cidr string
|
|
||||||
|
|
||||||
args := append(d.Args, d.ExtraArgs...)
|
|
||||||
for i, opt := range args {
|
|
||||||
if opt == "-b" && i < len(args)-1 {
|
|
||||||
name = args[i+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt == "--fixed-cidr" && i < len(args)-1 {
|
|
||||||
cidr = args[i+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "" || name == "none" {
|
|
||||||
return "", ""
|
|
||||||
} else {
|
|
||||||
return name, cidr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Repositories) ToArray() []string {
|
func (r Repositories) ToArray() []string {
|
||||||
result := make([]string, 0, len(r))
|
result := make([]string, 0, len(r))
|
||||||
for _, repo := range r {
|
for _, repo := range r {
|
||||||
|
@ -2,12 +2,11 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
"github.com/rancher/netconf"
|
"github.com/rancher/netconf"
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CONSOLE_CONTAINER = "console"
|
|
||||||
DOCKER_BIN = "/usr/bin/docker"
|
DOCKER_BIN = "/usr/bin/docker"
|
||||||
ROS_BIN = "/usr/bin/ros"
|
ROS_BIN = "/usr/bin/ros"
|
||||||
SYSINIT_BIN = "/usr/bin/ros-sysinit"
|
SYSINIT_BIN = "/usr/bin/ros-sysinit"
|
||||||
@ -24,7 +23,6 @@ const (
|
|||||||
HASH = "io.rancher.os.hash"
|
HASH = "io.rancher.os.hash"
|
||||||
ID = "io.rancher.os.id"
|
ID = "io.rancher.os.id"
|
||||||
DETACH = "io.rancher.os.detach"
|
DETACH = "io.rancher.os.detach"
|
||||||
REMOVE = "io.rancher.os.remove"
|
|
||||||
CREATE_ONLY = "io.rancher.os.createonly"
|
CREATE_ONLY = "io.rancher.os.createonly"
|
||||||
RELOAD_CONFIG = "io.rancher.os.reloadconfig"
|
RELOAD_CONFIG = "io.rancher.os.reloadconfig"
|
||||||
SCOPE = "io.rancher.os.scope"
|
SCOPE = "io.rancher.os.scope"
|
||||||
@ -73,8 +71,8 @@ type RancherConfig struct {
|
|||||||
Autoformat map[string]*project.ServiceConfig `yaml:"autoformat,omitempty"`
|
Autoformat map[string]*project.ServiceConfig `yaml:"autoformat,omitempty"`
|
||||||
BootstrapDocker DockerConfig `yaml:"bootstrap_docker,omitempty"`
|
BootstrapDocker DockerConfig `yaml:"bootstrap_docker,omitempty"`
|
||||||
CloudInit CloudInit `yaml:"cloud_init,omitempty"`
|
CloudInit CloudInit `yaml:"cloud_init,omitempty"`
|
||||||
Console ConsoleConfig `yaml:"console,omitempty"`
|
|
||||||
Debug bool `yaml:"debug,omitempty"`
|
Debug bool `yaml:"debug,omitempty"`
|
||||||
|
Log bool `yaml:"log,omitempty"`
|
||||||
Disable []string `yaml:"disable,omitempty"`
|
Disable []string `yaml:"disable,omitempty"`
|
||||||
ServicesInclude map[string]bool `yaml:"services_include,omitempty"`
|
ServicesInclude map[string]bool `yaml:"services_include,omitempty"`
|
||||||
Modules []string `yaml:"modules,omitempty"`
|
Modules []string `yaml:"modules,omitempty"`
|
||||||
@ -84,15 +82,9 @@ type RancherConfig struct {
|
|||||||
State StateConfig `yaml:"state,omitempty"`
|
State StateConfig `yaml:"state,omitempty"`
|
||||||
SystemDocker DockerConfig `yaml:"system_docker,omitempty"`
|
SystemDocker DockerConfig `yaml:"system_docker,omitempty"`
|
||||||
Upgrade UpgradeConfig `yaml:"upgrade,omitempty"`
|
Upgrade UpgradeConfig `yaml:"upgrade,omitempty"`
|
||||||
UserContainers []ContainerConfig `yaml:"user_containers,omitempty"`
|
|
||||||
UserDocker DockerConfig `yaml:"user_docker,omitempty"`
|
UserDocker DockerConfig `yaml:"user_docker,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConsoleConfig struct {
|
|
||||||
Tail bool `yaml:"tail,omitempty"`
|
|
||||||
Persistent bool `yaml:"persistent,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpgradeConfig struct {
|
type UpgradeConfig struct {
|
||||||
Url string `yaml:"url,omitempty"`
|
Url string `yaml:"url,omitempty"`
|
||||||
Image string `yaml:"image,omitempty"`
|
Image string `yaml:"image,omitempty"`
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
dockerClient "github.com/fsouza/go-dockerclient"
|
dockerClient "github.com/fsouza/go-dockerclient"
|
||||||
"github.com/rancherio/os/config"
|
"github.com/rancherio/os/config"
|
||||||
)
|
)
|
||||||
@ -28,25 +24,10 @@ func NewClient(endpoint string) (*dockerClient.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
retry := false
|
err = ClientOK(endpoint, func() bool {
|
||||||
for i := 0; i < (MAX_WAIT / INTERVAL); i++ {
|
_, err := client.Info()
|
||||||
_, err = client.Info()
|
return err == nil
|
||||||
if err == nil {
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
retry = true
|
return client, err
|
||||||
|
|
||||||
log.Infof("Waiting for Docker at %s", endpoint)
|
|
||||||
time.Sleep(INTERVAL * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if retry {
|
|
||||||
log.Infof("Connected to Docker at %s", endpoint)
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
94
docker/client_factory.go
Normal file
94
docker/client_factory.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/libcompose/docker"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
|
"github.com/rancherio/os/config"
|
||||||
|
"github.com/rancherio/os/util"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientFactory struct {
|
||||||
|
userClient dockerclient.Client
|
||||||
|
systemClient dockerclient.Client
|
||||||
|
userOnce sync.Once
|
||||||
|
systemOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientFactory(opts docker.ClientOpts) (docker.ClientFactory, error) {
|
||||||
|
userOpts := opts
|
||||||
|
systemOpts := opts
|
||||||
|
|
||||||
|
userOpts.Host = config.DOCKER_HOST
|
||||||
|
systemOpts.Host = config.DOCKER_SYSTEM_HOST
|
||||||
|
|
||||||
|
userClient, err := docker.CreateClient(userOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
systemClient, err := docker.CreateClient(systemOpts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ClientFactory{
|
||||||
|
userClient: userClient,
|
||||||
|
systemClient: systemClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientFactory) Create(service project.Service) dockerclient.Client {
|
||||||
|
if IsSystemContainer(service.Config()) {
|
||||||
|
waitFor(&c.systemOnce, c.systemClient, config.DOCKER_SYSTEM_HOST)
|
||||||
|
return c.systemClient
|
||||||
|
}
|
||||||
|
|
||||||
|
waitFor(&c.userOnce, c.userClient, config.DOCKER_HOST)
|
||||||
|
return c.userClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitFor(once *sync.Once, client dockerclient.Client, endpoint string) {
|
||||||
|
once.Do(func() {
|
||||||
|
err := ClientOK(endpoint, func() bool {
|
||||||
|
_, err := client.Info()
|
||||||
|
return err == nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientOK(endpoint string, test func() bool) error {
|
||||||
|
backoff := util.Backoff{}
|
||||||
|
defer backoff.Close()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
retry := false
|
||||||
|
for ok := range backoff.Start() {
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("Timeout waiting for Docker at %s", endpoint)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if test() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
retry = true
|
||||||
|
log.Infof("Waiting for Docker at %s", endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if retry {
|
||||||
|
log.Infof("Connected to Docker at %s", endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,632 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
shlex "github.com/flynn/go-shlex"
|
|
||||||
dockerClient "github.com/fsouza/go-dockerclient"
|
|
||||||
"github.com/rancherio/os/config"
|
|
||||||
"github.com/rancherio/os/util"
|
|
||||||
"github.com/rancherio/rancher-compose/librcompose/docker"
|
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Container struct {
|
|
||||||
Err error
|
|
||||||
Name string
|
|
||||||
remove bool
|
|
||||||
detach bool
|
|
||||||
Config *runconfig.Config
|
|
||||||
HostConfig *runconfig.HostConfig
|
|
||||||
dockerHost string
|
|
||||||
Container *dockerClient.Container
|
|
||||||
ContainerCfg *config.ContainerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type ByCreated []dockerClient.APIContainers
|
|
||||||
|
|
||||||
func (c ByCreated) Len() int { return len(c) }
|
|
||||||
func (c ByCreated) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
|
||||||
func (c ByCreated) Less(i, j int) bool { return c[j].Created < c[i].Created }
|
|
||||||
|
|
||||||
func getHash(containerCfg *config.ContainerConfig) string {
|
|
||||||
hash := sha1.New()
|
|
||||||
|
|
||||||
io.WriteString(hash, fmt.Sprintln(containerCfg.Id))
|
|
||||||
io.WriteString(hash, fmt.Sprintln(containerCfg.Cmd))
|
|
||||||
io.WriteString(hash, fmt.Sprintln(containerCfg.MigrateVolumes))
|
|
||||||
io.WriteString(hash, fmt.Sprintln(containerCfg.ReloadConfig))
|
|
||||||
io.WriteString(hash, fmt.Sprintln(containerCfg.CreateOnly))
|
|
||||||
|
|
||||||
if containerCfg.Service != nil {
|
|
||||||
//Get values of Service through reflection
|
|
||||||
val := reflect.ValueOf(containerCfg.Service).Elem()
|
|
||||||
|
|
||||||
//Create slice to sort the keys in Service Config, which allow constant hash ordering
|
|
||||||
serviceKeys := []string{}
|
|
||||||
|
|
||||||
//Create a data structure of map of values keyed by a string
|
|
||||||
unsortedKeyValue := make(map[string]interface{})
|
|
||||||
|
|
||||||
//Get all keys and values in Service Configuration
|
|
||||||
for i := 0; i < val.NumField(); i++ {
|
|
||||||
valueField := val.Field(i)
|
|
||||||
keyField := val.Type().Field(i)
|
|
||||||
|
|
||||||
serviceKeys = append(serviceKeys, keyField.Name)
|
|
||||||
unsortedKeyValue[keyField.Name] = valueField.Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sort serviceKeys alphabetically
|
|
||||||
sort.Strings(serviceKeys)
|
|
||||||
|
|
||||||
//Go through keys and write hash
|
|
||||||
for _, serviceKey := range serviceKeys {
|
|
||||||
serviceValue := unsortedKeyValue[serviceKey]
|
|
||||||
|
|
||||||
io.WriteString(hash, fmt.Sprintf("\n %v: ", serviceKey))
|
|
||||||
|
|
||||||
switch s := serviceValue.(type) {
|
|
||||||
case project.SliceorMap:
|
|
||||||
sliceKeys := []string{}
|
|
||||||
for lkey := range s.MapParts() {
|
|
||||||
if lkey != "io.rancher.os.hash" {
|
|
||||||
sliceKeys = append(sliceKeys, lkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(sliceKeys)
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s.MapParts()[sliceKey]))
|
|
||||||
}
|
|
||||||
case project.MaporEqualSlice:
|
|
||||||
sliceKeys := s.Slice()
|
|
||||||
// do not sort keys as the order matters
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
case project.MaporColonSlice:
|
|
||||||
sliceKeys := s.Slice()
|
|
||||||
// do not sort keys as the order matters
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
case project.MaporSpaceSlice:
|
|
||||||
sliceKeys := s.Slice()
|
|
||||||
// do not sort keys as the order matters
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
case project.Command:
|
|
||||||
sliceKeys := s.Slice()
|
|
||||||
// do not sort keys as the order matters
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
case project.Stringorslice:
|
|
||||||
sliceKeys := s.Slice()
|
|
||||||
sort.Strings(sliceKeys)
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
case []string:
|
|
||||||
sliceKeys := s
|
|
||||||
sort.Strings(sliceKeys)
|
|
||||||
|
|
||||||
for _, sliceKey := range sliceKeys {
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
io.WriteString(hash, fmt.Sprintf("%v", serviceValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hex.EncodeToString(hash.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartAndWait(dockerHost string, containerCfg *config.ContainerConfig) error {
|
|
||||||
container := NewContainer(dockerHost, containerCfg).start(false, true)
|
|
||||||
return container.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainerFromService(dockerHost string, name string, service *project.ServiceConfig) *Container {
|
|
||||||
c := &Container{
|
|
||||||
Name: name,
|
|
||||||
dockerHost: dockerHost,
|
|
||||||
ContainerCfg: &config.ContainerConfig{
|
|
||||||
Id: name,
|
|
||||||
Service: service,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainer(dockerHost string, containerCfg *config.ContainerConfig) *Container {
|
|
||||||
c := &Container{
|
|
||||||
dockerHost: dockerHost,
|
|
||||||
ContainerCfg: containerCfg,
|
|
||||||
}
|
|
||||||
return c.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) returnErr(err error) *Container {
|
|
||||||
c.Err = err
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func getByLabel(client *dockerClient.Client, key, value string) (*dockerClient.APIContainers, error) {
|
|
||||||
containers, err := client.ListContainers(dockerClient.ListContainersOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: map[string][]string{
|
|
||||||
config.LABEL: {fmt.Sprintf("%s=%s", key, value)},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(containers) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(ByCreated(containers))
|
|
||||||
return &containers[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Lookup() *Container {
|
|
||||||
c.Parse()
|
|
||||||
|
|
||||||
if c.Err != nil || (c.Container != nil && c.Container.HostConfig != nil) {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := getHash(c.ContainerCfg)
|
|
||||||
|
|
||||||
client, err := NewClient(c.dockerHost)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := client.ListContainers(dockerClient.ListContainersOptions{
|
|
||||||
All: true,
|
|
||||||
Filters: map[string][]string{
|
|
||||||
config.LABEL: {fmt.Sprintf("%s=%s", config.HASH, hash)},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(containers) == 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Container, c.Err = inspect(client, containers[0].ID)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func inspect(client *dockerClient.Client, id string) (*dockerClient.Container, error) {
|
|
||||||
c, err := client.InspectContainer(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(c.Name, "/") {
|
|
||||||
c.Name = c.Name[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Exists() bool {
|
|
||||||
c.Lookup()
|
|
||||||
return c.Container != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Reset() *Container {
|
|
||||||
c.Config = nil
|
|
||||||
c.HostConfig = nil
|
|
||||||
c.Container = nil
|
|
||||||
c.Err = nil
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) requiresSyslog() bool {
|
|
||||||
return (c.ContainerCfg.Service.LogDriver == "" || c.ContainerCfg.Service.LogDriver == "syslog")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) requiresUserDocker() bool {
|
|
||||||
if c.dockerHost == config.DOCKER_HOST {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) hasLink(link string) bool {
|
|
||||||
return util.Contains(c.ContainerCfg.Service.Links.Slice(), link)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) addLink(link string) {
|
|
||||||
if c.hasLink(link) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Adding %s link to %s", link, c.Name)
|
|
||||||
c.ContainerCfg.Service.Links = project.NewMaporColonSlice(append(c.ContainerCfg.Service.Links.Slice(), link))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) parseService() {
|
|
||||||
if c.requiresSyslog() {
|
|
||||||
c.addLink("syslog")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.requiresUserDocker() {
|
|
||||||
c.addLink("dockerwait")
|
|
||||||
} else if c.ContainerCfg.Service.Image != "" {
|
|
||||||
client, err := NewClient(c.dockerHost)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i, _ := client.InspectImage(c.ContainerCfg.Service.Image)
|
|
||||||
if i == nil {
|
|
||||||
c.addLink("network")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, hostConfig, err := docker.Convert(c.ContainerCfg.Service)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Config = cfg
|
|
||||||
c.HostConfig = hostConfig
|
|
||||||
|
|
||||||
c.detach = c.Config.Labels[config.DETACH] != "false"
|
|
||||||
c.remove = c.Config.Labels[config.REMOVE] != "false"
|
|
||||||
c.ContainerCfg.CreateOnly = c.Config.Labels[config.CREATE_ONLY] == "true"
|
|
||||||
c.ContainerCfg.ReloadConfig = c.Config.Labels[config.RELOAD_CONFIG] == "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) parseCmd() {
|
|
||||||
flags := flag.NewFlagSet("run", flag.ExitOnError)
|
|
||||||
|
|
||||||
flRemove := flags.Bool([]string{"#rm", "-rm"}, false, "")
|
|
||||||
flDetach := flags.Bool([]string{"d", "-detach"}, false, "")
|
|
||||||
flName := flags.String([]string{"#name", "-name"}, "", "")
|
|
||||||
|
|
||||||
args, err := shlex.Split(c.ContainerCfg.Cmd)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Parsing [%s]", strings.Join(args, ","))
|
|
||||||
c.Config, c.HostConfig, _, c.Err = runconfig.Parse(flags, args)
|
|
||||||
|
|
||||||
c.Name = *flName
|
|
||||||
c.detach = *flDetach
|
|
||||||
c.remove = *flRemove
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Parse() *Container {
|
|
||||||
if c.Config != nil || c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.ContainerCfg.Cmd) > 0 {
|
|
||||||
c.parseCmd()
|
|
||||||
} else if c.ContainerCfg.Service != nil {
|
|
||||||
c.parseService()
|
|
||||||
} else {
|
|
||||||
c.Err = errors.New("Cmd or Service must be set")
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.ContainerCfg.Id == "" {
|
|
||||||
c.ContainerCfg.Id = c.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Create() *Container {
|
|
||||||
return c.start(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Start() *Container {
|
|
||||||
return c.start(false, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) StartAndWait() *Container {
|
|
||||||
return c.start(false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Stage() *Container {
|
|
||||||
c.Parse()
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(c.dockerHost)
|
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.InspectImage(c.Config.Image)
|
|
||||||
if err == dockerClient.ErrNoSuchImage {
|
|
||||||
toPull := c.Config.Image
|
|
||||||
_, tag := parsers.ParseRepositoryTag(toPull)
|
|
||||||
if tag == "" {
|
|
||||||
toPull += ":latest"
|
|
||||||
}
|
|
||||||
c.Err = client.PullImage(dockerClient.PullImageOptions{
|
|
||||||
Repository: toPull,
|
|
||||||
OutputStream: os.Stdout,
|
|
||||||
}, dockerClient.AuthConfiguration{})
|
|
||||||
} else if err != nil {
|
|
||||||
log.Errorf("Failed to stage: %s: %v", c.Config.Image, err)
|
|
||||||
c.Err = err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Delete() *Container {
|
|
||||||
c.Parse()
|
|
||||||
c.Stage()
|
|
||||||
c.Lookup()
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Exists() {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(c.dockerHost)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.RemoveContainer(dockerClient.RemoveContainerOptions{
|
|
||||||
ID: c.Container.ID,
|
|
||||||
Force: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) renameCurrent(client *dockerClient.Client) error {
|
|
||||||
if c.Name == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Name == c.Container.Name {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.RenameContainer(dockerClient.RenameContainerOptions{ID: c.Container.ID, Name: c.Name})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Container, err = inspect(client, c.Container.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) renameOld(client *dockerClient.Client, opts *dockerClient.CreateContainerOptions) error {
|
|
||||||
if len(opts.Name) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
existing, err := inspect(client, opts.Name)
|
|
||||||
if _, ok := err.(*dockerClient.NoSuchContainer); ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Container != nil && existing.ID == c.Container.ID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var newName string
|
|
||||||
if label, ok := existing.Config.Labels[config.HASH]; ok {
|
|
||||||
newName = fmt.Sprintf("%s-%s", existing.Name, label)
|
|
||||||
} else {
|
|
||||||
newName = fmt.Sprintf("%s-unknown-%s", existing.Name, util.RandSeq(12))
|
|
||||||
}
|
|
||||||
|
|
||||||
if existing.State.Running {
|
|
||||||
err := client.StopContainer(existing.ID, 2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.WaitContainer(existing.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Renaming %s to %s", existing.Name, newName)
|
|
||||||
return client.RenameContainer(dockerClient.RenameContainerOptions{ID: existing.ID, Name: newName})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) getCreateOpts(client *dockerClient.Client) (*dockerClient.CreateContainerOptions, error) {
|
|
||||||
bytes, err := json.Marshal(c)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to marshall: %v", c)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts dockerClient.CreateContainerOptions
|
|
||||||
|
|
||||||
err = json.Unmarshal(bytes, &opts)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to unmarshall: %s", string(bytes))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Config.Labels == nil {
|
|
||||||
opts.Config.Labels = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := getHash(c.ContainerCfg)
|
|
||||||
|
|
||||||
opts.Config.Labels[config.HASH] = hash
|
|
||||||
opts.Config.Labels[config.ID] = c.ContainerCfg.Id
|
|
||||||
|
|
||||||
return &opts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendVolumesFrom(client *dockerClient.Client, containerCfg *config.ContainerConfig, opts *dockerClient.CreateContainerOptions) error {
|
|
||||||
if !containerCfg.MigrateVolumes {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
container, err := getByLabel(client, config.ID, containerCfg.Id)
|
|
||||||
if err != nil || container == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.HostConfig.VolumesFrom == nil {
|
|
||||||
opts.HostConfig.VolumesFrom = []string{container.ID}
|
|
||||||
} else {
|
|
||||||
opts.HostConfig.VolumesFrom = append(opts.HostConfig.VolumesFrom, container.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) start(createOnly, wait bool) *Container {
|
|
||||||
log.Debugf("Container: STARTING '%v', createOnly: %v, !detach: %v, wait: %v", c.Name, createOnly, !c.detach, wait)
|
|
||||||
c.Lookup()
|
|
||||||
c.Stage()
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := NewClient(c.dockerHost)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
created := false
|
|
||||||
|
|
||||||
opts, err := c.getCreateOpts(client)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to create container create options: %v", err)
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Exists() && c.remove {
|
|
||||||
log.Debugf("Deleting container %s", c.Container.ID)
|
|
||||||
c.Delete()
|
|
||||||
|
|
||||||
if c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Reset().Lookup()
|
|
||||||
if c.Err != nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Exists() {
|
|
||||||
err = c.renameOld(client, opts)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := appendVolumesFrom(client, c.ContainerCfg, opts)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Container, err = client.CreateContainer(*opts)
|
|
||||||
created = true
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig := c.Container.HostConfig
|
|
||||||
if created {
|
|
||||||
hostConfig = opts.HostConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
if createOnly {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.Container.State.Running {
|
|
||||||
if !created {
|
|
||||||
err = c.renameOld(client, opts)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.renameCurrent(client)
|
|
||||||
if err != nil {
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.StartContainer(c.Container.ID, hostConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Error from Docker %s", err)
|
|
||||||
return c.returnErr(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Container: WAIT? '%v' !c.detach && wait: %v", c.Name, !c.detach && wait)
|
|
||||||
if !c.detach && wait {
|
|
||||||
var exitCode int
|
|
||||||
exitCode, c.Err = client.WaitContainer(c.Container.ID)
|
|
||||||
log.Debugf("Container: FINISHED '%v', exitCode: %v", c.Name, exitCode)
|
|
||||||
if exitCode != 0 {
|
|
||||||
c.Err = errors.New(fmt.Sprintf("Container %s exited with code %d", c.Name, exitCode))
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
@ -1,307 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/rancherio/os/config"
|
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
dockerClient "github.com/fsouza/go-dockerclient"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testDockerHost(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
assert.Equal(os.Getenv("DOCKER_HOST"), config.DOCKER_HOST)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHash(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
hash := getHash(&config.ContainerConfig{
|
|
||||||
Id: "id",
|
|
||||||
Cmd: "1 2 3",
|
|
||||||
})
|
|
||||||
|
|
||||||
hash2 := getHash(&config.ContainerConfig{
|
|
||||||
Id: "id2",
|
|
||||||
Cmd: "1 2 3",
|
|
||||||
})
|
|
||||||
|
|
||||||
hash3 := getHash(&config.ContainerConfig{
|
|
||||||
Id: "id3",
|
|
||||||
Cmd: "1 2 3 4",
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal("d601444333c7fb4cb955bcca36c5ed59b6fa8c3f", hash, "")
|
|
||||||
assert.NotEqual(hash, hash2, "")
|
|
||||||
assert.NotEqual(hash2, hash3, "")
|
|
||||||
assert.NotEqual(hash, hash3, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHash2(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
cfg := &config.ContainerConfig{
|
|
||||||
Id: "docker-volumes",
|
|
||||||
Cmd: "",
|
|
||||||
MigrateVolumes: false,
|
|
||||||
ReloadConfig: false,
|
|
||||||
CreateOnly: true,
|
|
||||||
Service: &project.ServiceConfig{
|
|
||||||
CapAdd: nil,
|
|
||||||
CapDrop: nil,
|
|
||||||
CpuShares: 0,
|
|
||||||
Command: project.NewCommand(),
|
|
||||||
Detach: "",
|
|
||||||
Dns: project.NewStringorslice(),
|
|
||||||
DnsSearch: project.NewStringorslice(),
|
|
||||||
DomainName: "",
|
|
||||||
Entrypoint: project.NewCommand(),
|
|
||||||
EnvFile: project.NewStringorslice(),
|
|
||||||
Environment: project.NewMaporEqualSlice([]string{}),
|
|
||||||
Hostname: "",
|
|
||||||
Image: "state",
|
|
||||||
Labels: project.NewSliceorMap(map[string]string{
|
|
||||||
"io.rancher.os.createonly": "true",
|
|
||||||
"io.rancher.os.scope": "system"}),
|
|
||||||
Links: project.NewMaporColonSlice(nil),
|
|
||||||
LogDriver: "json-file",
|
|
||||||
MemLimit: 0,
|
|
||||||
Name: "",
|
|
||||||
Net: "none",
|
|
||||||
Pid: "",
|
|
||||||
Ipc: "",
|
|
||||||
Ports: nil,
|
|
||||||
Privileged: true,
|
|
||||||
Restart: "",
|
|
||||||
ReadOnly: true,
|
|
||||||
StdinOpen: false,
|
|
||||||
Tty: false,
|
|
||||||
User: "",
|
|
||||||
Volumes: []string{
|
|
||||||
"/var/lib/docker:/var/lib/docker",
|
|
||||||
"/var/lib/rancher/conf:/var/lib/rancher/conf",
|
|
||||||
"/var/lib/system-docker:/var/lib/system-docker"},
|
|
||||||
VolumesFrom: nil,
|
|
||||||
WorkingDir: "",
|
|
||||||
Expose: nil,
|
|
||||||
ExternalLinks: nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
assert.Equal(getHash(cfg), getHash(cfg), fmt.Sprintf("Failed at iteration: %v", i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBool2String(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
assert.Equal("true", fmt.Sprint(true), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
cfg := &config.ContainerConfig{
|
|
||||||
Cmd: "--name c1 " +
|
|
||||||
"-d " +
|
|
||||||
"--rm " +
|
|
||||||
"--privileged " +
|
|
||||||
"test/image " +
|
|
||||||
"arg1 " +
|
|
||||||
"arg2 ",
|
|
||||||
}
|
|
||||||
|
|
||||||
c := NewContainer("", cfg).Parse()
|
|
||||||
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
assert.Equal(cfg.Id, "c1", "Id doesn't match")
|
|
||||||
assert.Equal(c.Name, "c1", "Name doesn't match")
|
|
||||||
assert.True(c.remove, "Remove doesn't match")
|
|
||||||
assert.True(c.detach, "Detach doesn't match")
|
|
||||||
assert.Equal(c.Config.Cmd.Len(), 2, "Args doesn't match")
|
|
||||||
assert.Equal(c.Config.Cmd.Slice()[0], "arg1", "Arg1 doesn't match")
|
|
||||||
assert.Equal(c.Config.Cmd.Slice()[1], "arg2", "Arg2 doesn't match")
|
|
||||||
assert.True(c.HostConfig.Privileged, "Privileged doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIdFromName(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
cfg := &config.ContainerConfig{
|
|
||||||
Cmd: "--name foo -v /test busybox echo hi",
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal("", cfg.Id)
|
|
||||||
NewContainer(config.DOCKER_HOST, cfg)
|
|
||||||
assert.Equal("foo", cfg.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMigrateVolumes(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
c := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--name foo -v /test busybox echo hi",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
test_path, ok := c.Container.Volumes["/test"]
|
|
||||||
assert.True(ok, "")
|
|
||||||
|
|
||||||
c2 := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
MigrateVolumes: true,
|
|
||||||
Cmd: "--name foo -v /test2 busybox echo hi",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c2.Err, "")
|
|
||||||
|
|
||||||
assert.True(c2.Container != nil)
|
|
||||||
|
|
||||||
_, ok = c2.Container.Volumes["/test2"]
|
|
||||||
assert.True(ok, "")
|
|
||||||
assert.Equal(test_path, c2.Container.Volumes["/test"])
|
|
||||||
|
|
||||||
c.Delete()
|
|
||||||
c2.Delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRollback(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
c := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--name rollback busybox echo hi",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
assert.Equal("rollback", c.Container.Name)
|
|
||||||
|
|
||||||
c2 := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--name rollback busybox echo bye",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.Equal("rollback", c2.Container.Name)
|
|
||||||
assert.NoError(c2.Err, "")
|
|
||||||
assert.NotEqual(c.Container.ID, c2.Container.ID)
|
|
||||||
|
|
||||||
c3 := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--name rollback busybox echo hi",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c3.Err, "")
|
|
||||||
assert.Equal(c.Container.ID, c3.Container.ID)
|
|
||||||
assert.Equal("rollback", c3.Container.Name)
|
|
||||||
|
|
||||||
c2.Reset().Lookup()
|
|
||||||
assert.NoError(c2.Err, "")
|
|
||||||
assert.True(strings.HasPrefix(c2.Container.Name, "rollback-"))
|
|
||||||
|
|
||||||
c.Delete()
|
|
||||||
c2.Delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStart(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
c := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--pid=host --privileged --rm busybox echo hi",
|
|
||||||
}).Parse().Start().Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
assert.True(c.HostConfig.Privileged, "")
|
|
||||||
assert.True(c.Container.HostConfig.Privileged, "")
|
|
||||||
assert.Equal("host", c.Container.HostConfig.PidMode, "")
|
|
||||||
|
|
||||||
c.Delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLookup(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
cfg := &config.ContainerConfig{
|
|
||||||
Cmd: "--rm busybox echo hi",
|
|
||||||
}
|
|
||||||
c := NewContainer(config.DOCKER_HOST, cfg).Parse().Start()
|
|
||||||
|
|
||||||
cfg2 := &config.ContainerConfig{
|
|
||||||
Cmd: "--rm busybox echo hi2",
|
|
||||||
}
|
|
||||||
c2 := NewContainer(config.DOCKER_HOST, cfg2).Parse().Start()
|
|
||||||
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
assert.NoError(c2.Err, "")
|
|
||||||
|
|
||||||
c1Lookup := NewContainer(config.DOCKER_HOST, cfg).Lookup()
|
|
||||||
c2Lookup := NewContainer(config.DOCKER_HOST, cfg2).Lookup()
|
|
||||||
|
|
||||||
assert.NoError(c1Lookup.Err, "")
|
|
||||||
assert.NoError(c2Lookup.Err, "")
|
|
||||||
|
|
||||||
assert.Equal(c.Container.ID, c1Lookup.Container.ID, "")
|
|
||||||
assert.Equal(c2.Container.ID, c2Lookup.Container.ID, "")
|
|
||||||
|
|
||||||
c.Delete()
|
|
||||||
c2.Delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDelete(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
|
|
||||||
c := NewContainer(config.DOCKER_HOST, &config.ContainerConfig{
|
|
||||||
Cmd: "--rm busybox echo hi",
|
|
||||||
}).Parse()
|
|
||||||
|
|
||||||
assert.False(c.Exists())
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
c.Start()
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
c.Reset()
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
assert.True(c.Exists())
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
c.Delete()
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
|
|
||||||
c.Reset()
|
|
||||||
assert.False(c.Exists())
|
|
||||||
assert.NoError(c.Err, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDockerClientNames(t *testing.T) {
|
|
||||||
assert := require.New(t)
|
|
||||||
client, err := dockerClient.NewClient(config.DOCKER_HOST)
|
|
||||||
|
|
||||||
assert.NoError(err, "")
|
|
||||||
|
|
||||||
c, err := client.CreateContainer(dockerClient.CreateContainerOptions{
|
|
||||||
Name: "foo",
|
|
||||||
Config: &dockerClient.Config{
|
|
||||||
Image: "ubuntu",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.NoError(err, "")
|
|
||||||
assert.Equal("foo", c.Name)
|
|
||||||
|
|
||||||
c2, err := client.InspectContainer(c.ID)
|
|
||||||
|
|
||||||
assert.NoError(err, "")
|
|
||||||
assert.Equal("/foo", c2.Name)
|
|
||||||
|
|
||||||
c2, err = inspect(client, c.ID)
|
|
||||||
|
|
||||||
assert.NoError(err, "")
|
|
||||||
assert.Equal("foo", c2.Name)
|
|
||||||
|
|
||||||
client.RemoveContainer(dockerClient.RemoveContainerOptions{
|
|
||||||
ID: c2.ID,
|
|
||||||
Force: true,
|
|
||||||
})
|
|
||||||
}
|
|
55
docker/env.go
Normal file
55
docker/env.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/rancherio/os/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigEnvironment struct {
|
||||||
|
cfg *config.CloudConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfigEnvironment(cfg *config.CloudConfig) *ConfigEnvironment {
|
||||||
|
return &ConfigEnvironment{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendEnv(array []string, key, value string) []string {
|
||||||
|
parts := strings.SplitN(key, "/", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
key = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(array, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupKeys(cfg *config.CloudConfig, keys ...string) []string {
|
||||||
|
for _, key := range keys {
|
||||||
|
if strings.HasSuffix(key, "*") {
|
||||||
|
result := []string{}
|
||||||
|
for envKey, envValue := range cfg.Rancher.Environment {
|
||||||
|
keyPrefix := key[:len(key)-1]
|
||||||
|
if strings.HasPrefix(envKey, keyPrefix) {
|
||||||
|
result = appendEnv(result, envKey, envValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) > 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} else if value, ok := cfg.Rancher.Environment[key]; ok {
|
||||||
|
return appendEnv([]string{}, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigEnvironment) Lookup(key, serviceName string, serviceConfig *project.ServiceConfig) []string {
|
||||||
|
fullKey := fmt.Sprintf("%s/%s", serviceName, key)
|
||||||
|
return lookupKeys(c.cfg, fullKey, key)
|
||||||
|
}
|
@ -1,103 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/rancherio/os/config"
|
|
||||||
"github.com/rancherio/os/util"
|
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContainerFactory struct {
|
|
||||||
cfg *config.CloudConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type containerBasedService struct {
|
|
||||||
project.EmptyService
|
|
||||||
name string
|
|
||||||
project *project.Project
|
|
||||||
container *Container
|
|
||||||
serviceConfig *project.ServiceConfig
|
|
||||||
cfg *config.CloudConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainerFactory(cfg *config.CloudConfig) *ContainerFactory {
|
|
||||||
return &ContainerFactory{
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerBasedService) Up() error {
|
|
||||||
container := c.container
|
|
||||||
containerCfg := c.container.ContainerCfg
|
|
||||||
|
|
||||||
fakeCreate := false
|
|
||||||
create := containerCfg.CreateOnly
|
|
||||||
|
|
||||||
if util.Contains(c.cfg.Rancher.Disable, c.name) {
|
|
||||||
fakeCreate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var event project.Event
|
|
||||||
|
|
||||||
c.project.Notify(project.CONTAINER_STARTING, c.name, map[string]string{})
|
|
||||||
|
|
||||||
if fakeCreate {
|
|
||||||
event = project.CONTAINER_CREATED
|
|
||||||
} else if create {
|
|
||||||
container.Create()
|
|
||||||
event = project.CONTAINER_CREATED
|
|
||||||
} else {
|
|
||||||
container.StartAndWait()
|
|
||||||
event = project.CONTAINER_STARTED
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Err != nil {
|
|
||||||
log.Errorf("Failed to run %v: %v", containerCfg.Id, container.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Err == nil && containerCfg.ReloadConfig {
|
|
||||||
return project.ErrRestart
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Container != nil {
|
|
||||||
c.project.Notify(event, c.name, map[string]string{
|
|
||||||
project.CONTAINER_ID: container.Container.ID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerBasedService) Config() *project.ServiceConfig {
|
|
||||||
return c.serviceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *containerBasedService) Name() string {
|
|
||||||
return c.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSystemService(serviceConfig *project.ServiceConfig) bool {
|
|
||||||
return serviceConfig.Labels.MapParts()[config.SCOPE] == config.SYSTEM
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContainerFactory) Create(project *project.Project, name string, serviceConfig *project.ServiceConfig) (project.Service, error) {
|
|
||||||
host := config.DOCKER_HOST
|
|
||||||
if isSystemService(serviceConfig) {
|
|
||||||
host = config.DOCKER_SYSTEM_HOST
|
|
||||||
}
|
|
||||||
|
|
||||||
container := NewContainerFromService(host, name, serviceConfig)
|
|
||||||
|
|
||||||
if container.Err != nil {
|
|
||||||
return nil, container.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &containerBasedService{
|
|
||||||
name: name,
|
|
||||||
project: project,
|
|
||||||
container: container,
|
|
||||||
serviceConfig: serviceConfig,
|
|
||||||
cfg: c.cfg,
|
|
||||||
}, nil
|
|
||||||
}
|
|
159
docker/service.go
Normal file
159
docker/service.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/libcompose/docker"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/docker/machine/log"
|
||||||
|
"github.com/rancherio/os/config"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
*docker.Service
|
||||||
|
deps map[string][]string
|
||||||
|
context *docker.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(factory *ServiceFactory, name string, serviceConfig *project.ServiceConfig, context *docker.Context) *Service {
|
||||||
|
return &Service{
|
||||||
|
Service: docker.NewService(name, serviceConfig, context),
|
||||||
|
deps: factory.Deps,
|
||||||
|
context: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DependentServices() []project.ServiceRelationship {
|
||||||
|
rels := s.Service.DependentServices()
|
||||||
|
for _, dep := range s.deps[s.Name()] {
|
||||||
|
rels = appendLink(rels, dep, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.requiresSyslog() {
|
||||||
|
rels = appendLink(rels, "syslog", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.requiresUserDocker() {
|
||||||
|
// Linking to cloud-init is a hack really. The problem is we need to link to something
|
||||||
|
// that will trigger a reload
|
||||||
|
rels = appendLink(rels, "cloud-init", false)
|
||||||
|
} else if s.missingImage() {
|
||||||
|
rels = appendLink(rels, "network", false)
|
||||||
|
}
|
||||||
|
return rels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) missingImage() bool {
|
||||||
|
image := s.Config().Image
|
||||||
|
if image == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
client := s.context.ClientFactory.Create(s)
|
||||||
|
i, err := client.InspectImage(s.Config().Image)
|
||||||
|
return err != nil || i == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) requiresSyslog() bool {
|
||||||
|
return s.Config().LogDriver == "syslog"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) requiresUserDocker() bool {
|
||||||
|
return s.Config().Labels.MapParts()[config.SCOPE] != config.SYSTEM
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendLink(deps []project.ServiceRelationship, name string, optional bool) []project.ServiceRelationship {
|
||||||
|
rel := project.NewServiceRelationship(name, project.REL_TYPE_LINK)
|
||||||
|
rel.Optional = optional
|
||||||
|
return append(deps, rel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Up() error {
|
||||||
|
labels := s.Config().Labels.MapParts()
|
||||||
|
|
||||||
|
if err := s.Service.Create(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.rename(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if labels[config.CREATE_ONLY] == "true" {
|
||||||
|
return s.checkReload(labels)
|
||||||
|
}
|
||||||
|
if err := s.Service.Up(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if labels[config.DETACH] == "false" {
|
||||||
|
if err := s.wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.checkReload(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) checkReload(labels map[string]string) error {
|
||||||
|
if labels[config.RELOAD_CONFIG] == "true" {
|
||||||
|
return project.ErrRestart
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Create() error {
|
||||||
|
if err := s.Service.Create(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.rename()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getContainer() (dockerclient.Client, *dockerclient.ContainerInfo, error) {
|
||||||
|
containers, err := s.Service.Containers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(containers) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := containers[0].Id()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s.context.ClientFactory.Create(s)
|
||||||
|
info, err := client.InspectContainer(id)
|
||||||
|
return client, info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) wait() error {
|
||||||
|
client, info, err := s.getContainer()
|
||||||
|
if err != nil || info == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
status := <-client.Wait(info.Id)
|
||||||
|
if status.Error != nil {
|
||||||
|
return status.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.ExitCode == 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("ExitCode %d", status.ExitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) rename() error {
|
||||||
|
client, info, err := s.getContainer()
|
||||||
|
if err != nil || info == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(info.Name) > 0 && info.Name[1:] != s.Name() {
|
||||||
|
log.Debugf("Renaming container %s => %s", info.Name[1:], s.Name())
|
||||||
|
return client.RenameContainer(info.Name[1:], s.Name())
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
27
docker/service_factory.go
Normal file
27
docker/service_factory.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/libcompose/docker"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/rancherio/os/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceFactory struct {
|
||||||
|
Context *docker.Context
|
||||||
|
Deps map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServiceFactory) Create(project *project.Project, name string, serviceConfig *project.ServiceConfig) (project.Service, error) {
|
||||||
|
if after := serviceConfig.Labels.MapParts()["io.rancher.os.after"]; after != "" {
|
||||||
|
for _, dep := range util.TrimSplit(after, ",") {
|
||||||
|
s.Deps[name] = append(s.Deps[name], dep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if before := serviceConfig.Labels.MapParts()["io.rancher.os.before"]; before != "" {
|
||||||
|
for _, dep := range util.TrimSplit(before, ",") {
|
||||||
|
s.Deps[dep] = append(s.Deps[dep], name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewService(s, name, serviceConfig, s.Context), nil
|
||||||
|
}
|
@ -1,137 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/rancherio/os/config"
|
|
||||||
"github.com/rancherio/os/util"
|
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
)
|
|
||||||
|
|
||||||
type configEnvironment struct {
|
|
||||||
cfg *config.CloudConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendEnv(array []string, key, value string) []string {
|
|
||||||
parts := strings.SplitN(key, "/", 2)
|
|
||||||
if len(parts) == 2 {
|
|
||||||
key = parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(array, fmt.Sprintf("%s=%s", key, value))
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupKeys(cfg *config.CloudConfig, keys ...string) []string {
|
|
||||||
for _, key := range keys {
|
|
||||||
if strings.HasSuffix(key, "*") {
|
|
||||||
result := []string{}
|
|
||||||
for envKey, envValue := range cfg.Rancher.Environment {
|
|
||||||
keyPrefix := key[:len(key)-1]
|
|
||||||
if strings.HasPrefix(envKey, keyPrefix) {
|
|
||||||
result = appendEnv(result, envKey, envValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(result) > 0 {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
} else if value, ok := cfg.Rancher.Environment[key]; ok {
|
|
||||||
return appendEnv([]string{}, key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configEnvironment) Lookup(key, serviceName string, serviceConfig *project.ServiceConfig) []string {
|
|
||||||
fullKey := fmt.Sprintf("%s/%s", serviceName, key)
|
|
||||||
return lookupKeys(c.cfg, fullKey, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunServices(name string, cfg *config.CloudConfig, configs map[string]*project.ServiceConfig) error {
|
|
||||||
network := false
|
|
||||||
projectEvents := make(chan project.ProjectEvent)
|
|
||||||
p := project.NewProject(name, NewContainerFactory(cfg))
|
|
||||||
p.EnvironmentLookup = &configEnvironment{cfg: cfg}
|
|
||||||
p.AddListener(projectEvents)
|
|
||||||
enabled := make(map[string]bool)
|
|
||||||
|
|
||||||
for name, serviceConfig := range configs {
|
|
||||||
if err := p.AddConfig(name, serviceConfig); err != nil {
|
|
||||||
log.Infof("Failed loading service %s", name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
enabled[name] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ReloadCallback = func() error {
|
|
||||||
if p.Name != "system-init" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cfg.Reload(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for service, serviceEnabled := range cfg.Rancher.ServicesInclude {
|
|
||||||
if !serviceEnabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if en, ok := enabled[service]; ok && en {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := LoadServiceResource(service, network, cfg)
|
|
||||||
if err != nil {
|
|
||||||
if err == util.ErrNoNetwork {
|
|
||||||
log.Debugf("Can not load %s, networking not enabled", service)
|
|
||||||
} else {
|
|
||||||
log.Errorf("Failed to load %s : %v", service, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.Load(bytes); err != nil {
|
|
||||||
log.Errorf("Failed to load %s : %v", service, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled[service] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for service, config := range cfg.Rancher.Services {
|
|
||||||
if en, ok := enabled[service]; ok && en {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.AddConfig(service, config); err != nil {
|
|
||||||
log.Errorf("Failed to load %s : %v", service, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
enabled[service] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for event := range projectEvents {
|
|
||||||
if event.Event == project.CONTAINER_STARTED && event.ServiceName == "network" {
|
|
||||||
network = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := p.ReloadCallback(); err != nil {
|
|
||||||
log.Errorf("Failed to reload %s : %v", name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return p.Up()
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadServiceResource(name string, network bool, cfg *config.CloudConfig) ([]byte, error) {
|
|
||||||
return util.LoadResource(name, network, cfg.Rancher.Repositories.ToArray())
|
|
||||||
}
|
|
11
docker/util.go
Normal file
11
docker/util.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
|
"github.com/rancherio/os/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsSystemContainer(serviceConfig *project.ServiceConfig) bool {
|
||||||
|
return serviceConfig.Labels.MapParts()[config.SCOPE] == config.SYSTEM
|
||||||
|
|
||||||
|
}
|
@ -8,11 +8,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/libcompose/project"
|
||||||
"github.com/rancher/docker-from-scratch"
|
"github.com/rancher/docker-from-scratch"
|
||||||
|
"github.com/rancherio/os/compose"
|
||||||
"github.com/rancherio/os/config"
|
"github.com/rancherio/os/config"
|
||||||
"github.com/rancherio/os/docker"
|
|
||||||
"github.com/rancherio/os/util"
|
"github.com/rancherio/os/util"
|
||||||
"github.com/rancherio/rancher-compose/librcompose/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func autoformat(cfg *config.CloudConfig) error {
|
func autoformat(cfg *config.CloudConfig) error {
|
||||||
@ -23,16 +23,17 @@ func autoformat(cfg *config.CloudConfig) error {
|
|||||||
FORMATZERO := "FORMATZERO=" + fmt.Sprint(cfg.Rancher.State.FormatZero)
|
FORMATZERO := "FORMATZERO=" + fmt.Sprint(cfg.Rancher.State.FormatZero)
|
||||||
cfg.Rancher.Autoformat["autoformat"].Environment = project.NewMaporEqualSlice([]string{AUTOFORMAT, FORMATZERO})
|
cfg.Rancher.Autoformat["autoformat"].Environment = project.NewMaporEqualSlice([]string{AUTOFORMAT, FORMATZERO})
|
||||||
log.Info("Running Autoformat services")
|
log.Info("Running Autoformat services")
|
||||||
err := docker.RunServices("autoformat", cfg, cfg.Rancher.Autoformat)
|
_, err := compose.RunServiceSet("autoformat", cfg, cfg.Rancher.Autoformat)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBootstrapContainers(cfg *config.CloudConfig) error {
|
func runBootstrapContainers(cfg *config.CloudConfig) error {
|
||||||
log.Info("Running Bootstrap services")
|
log.Info("Running Bootstrap services")
|
||||||
return docker.RunServices("bootstrap", cfg, cfg.Rancher.BootstrapContainers)
|
_, err := compose.RunServiceSet("bootstrap", cfg, cfg.Rancher.BootstrapContainers)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func startDocker(cfg *config.Config) (chan interface{}, error) {
|
func startDocker(cfg *config.CloudConfig) (chan interface{}, error) {
|
||||||
|
|
||||||
launchConfig, args := getLaunchConfig(cfg, &cfg.Rancher.BootstrapDocker)
|
launchConfig, args := getLaunchConfig(cfg, &cfg.Rancher.BootstrapDocker)
|
||||||
launchConfig.Fork = true
|
launchConfig.Fork = true
|
||||||
|
42
init/init.go
42
init/init.go
@ -30,7 +30,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadModules(cfg *config.Config) error {
|
func loadModules(cfg *config.CloudConfig) error {
|
||||||
mounted := map[string]bool{}
|
mounted := map[string]bool{}
|
||||||
|
|
||||||
f, err := os.Open("/proc/modules")
|
f, err := os.Open("/proc/modules")
|
||||||
@ -44,7 +44,7 @@ func loadModules(cfg *config.Config) error {
|
|||||||
mounted[strings.SplitN(reader.Text(), " ", 2)[0]] = true
|
mounted[strings.SplitN(reader.Text(), " ", 2)[0]] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, module := range cfg.Modules {
|
for _, module := range cfg.Rancher.Modules {
|
||||||
if mounted[module] {
|
if mounted[module] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ func loadModules(cfg *config.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sysInit(cfg *config.Config) error {
|
func sysInit(cfg *config.CloudConfig) error {
|
||||||
args := append([]string{config.SYSINIT_BIN}, os.Args[1:]...)
|
args := append([]string{config.SYSINIT_BIN}, os.Args[1:]...)
|
||||||
|
|
||||||
cmd := &exec.Cmd{
|
cmd := &exec.Cmd{
|
||||||
@ -83,18 +83,18 @@ func MainInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mountState(cfg *config.Config) error {
|
func mountState(cfg *config.CloudConfig) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if cfg.State.Dev == "" {
|
if cfg.Rancher.State.Dev == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dev := util.ResolveDevice(cfg.State.Dev)
|
dev := util.ResolveDevice(cfg.Rancher.State.Dev)
|
||||||
if dev == "" {
|
if dev == "" {
|
||||||
return fmt.Errorf("Could not resolve device %q", cfg.State.Dev)
|
return fmt.Errorf("Could not resolve device %q", cfg.Rancher.State.Dev)
|
||||||
}
|
}
|
||||||
fsType := cfg.State.FsType
|
fsType := cfg.Rancher.State.FsType
|
||||||
if fsType == "auto" {
|
if fsType == "auto" {
|
||||||
fsType, err = util.GetFsType(dev)
|
fsType, err = util.GetFsType(dev)
|
||||||
}
|
}
|
||||||
@ -108,7 +108,7 @@ func mountState(cfg *config.Config) error {
|
|||||||
return util.Mount(dev, STATE, fsType, "")
|
return util.Mount(dev, STATE, fsType, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryMountState(cfg *config.Config) error {
|
func tryMountState(cfg *config.CloudConfig) error {
|
||||||
if mountState(cfg) == nil {
|
if mountState(cfg) == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -121,8 +121,8 @@ func tryMountState(cfg *config.Config) error {
|
|||||||
return mountState(cfg)
|
return mountState(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryMountAndBootstrap(cfg *config.Config) error {
|
func tryMountAndBootstrap(cfg *config.CloudConfig) error {
|
||||||
if err := tryMountState(cfg); !cfg.State.Required && err != nil {
|
if err := tryMountState(cfg); !cfg.Rancher.State.Required && err != nil {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -132,15 +132,15 @@ func tryMountAndBootstrap(cfg *config.Config) error {
|
|||||||
return switchRoot(STATE)
|
return switchRoot(STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLaunchConfig(cfg *config.Config, dockerCfg *config.DockerConfig) (*dockerlaunch.Config, []string) {
|
func getLaunchConfig(cfg *config.CloudConfig, dockerCfg *config.DockerConfig) (*dockerlaunch.Config, []string) {
|
||||||
var launchConfig dockerlaunch.Config
|
var launchConfig dockerlaunch.Config
|
||||||
|
|
||||||
args := dockerlaunch.ParseConfig(&launchConfig, append(dockerCfg.Args, dockerCfg.ExtraArgs...)...)
|
args := dockerlaunch.ParseConfig(&launchConfig, append(dockerCfg.Args, dockerCfg.ExtraArgs...)...)
|
||||||
|
|
||||||
launchConfig.DnsConfig.Nameservers = cfg.Network.Dns.Nameservers
|
launchConfig.DnsConfig.Nameservers = cfg.Rancher.Network.Dns.Nameservers
|
||||||
launchConfig.DnsConfig.Search = cfg.Network.Dns.Search
|
launchConfig.DnsConfig.Search = cfg.Rancher.Network.Dns.Search
|
||||||
|
|
||||||
if !cfg.Debug {
|
if !cfg.Rancher.Debug {
|
||||||
launchConfig.LogFile = config.SYSTEM_DOCKER_LOG
|
launchConfig.LogFile = config.SYSTEM_DOCKER_LOG
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,17 +148,17 @@ func getLaunchConfig(cfg *config.Config, dockerCfg *config.DockerConfig) (*docke
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RunInit() error {
|
func RunInit() error {
|
||||||
var cfg config.Config
|
var cfg config.CloudConfig
|
||||||
|
|
||||||
os.Setenv("PATH", "/sbin:/usr/sbin:/usr/bin")
|
os.Setenv("PATH", "/sbin:/usr/sbin:/usr/bin")
|
||||||
// Magic setting to tell Docker to do switch_root and not pivot_root
|
// Magic setting to tell Docker to do switch_root and not pivot_root
|
||||||
os.Setenv("DOCKER_RAMDISK", "true")
|
os.Setenv("DOCKER_RAMDISK", "true")
|
||||||
|
|
||||||
initFuncs := []config.InitFunc{
|
initFuncs := []config.InitFunc{
|
||||||
func(cfg *config.Config) error {
|
func(cfg *config.CloudConfig) error {
|
||||||
return dockerlaunch.PrepareFs(&mountConfig)
|
return dockerlaunch.PrepareFs(&mountConfig)
|
||||||
},
|
},
|
||||||
func(cfg *config.Config) error {
|
func(cfg *config.CloudConfig) error {
|
||||||
newCfg, err := config.LoadConfig()
|
newCfg, err := config.LoadConfig()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
newCfg, err = config.LoadConfig()
|
newCfg, err = config.LoadConfig()
|
||||||
@ -167,7 +167,7 @@ func RunInit() error {
|
|||||||
*cfg = *newCfg
|
*cfg = *newCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Debug {
|
if cfg.Rancher.Debug {
|
||||||
cfgString, _ := config.Dump(false, true)
|
cfgString, _ := config.Dump(false, true)
|
||||||
if cfgString != "" {
|
if cfgString != "" {
|
||||||
log.Debugf("Config: %s", cfgString)
|
log.Debugf("Config: %s", cfgString)
|
||||||
@ -178,7 +178,7 @@ func RunInit() error {
|
|||||||
},
|
},
|
||||||
loadModules,
|
loadModules,
|
||||||
tryMountAndBootstrap,
|
tryMountAndBootstrap,
|
||||||
func(cfg *config.Config) error {
|
func(cfg *config.CloudConfig) error {
|
||||||
return cfg.Reload()
|
return cfg.Reload()
|
||||||
},
|
},
|
||||||
loadModules,
|
loadModules,
|
||||||
@ -189,7 +189,7 @@ func RunInit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
launchConfig, args := getLaunchConfig(&cfg, &cfg.SystemDocker)
|
launchConfig, args := getLaunchConfig(&cfg, &cfg.Rancher.SystemDocker)
|
||||||
|
|
||||||
log.Info("Launching System Docker")
|
log.Info("Launching System Docker")
|
||||||
_, err := dockerlaunch.LaunchDocker(launchConfig, config.DOCKER_BIN, args...)
|
_, err := dockerlaunch.LaunchDocker(launchConfig, config.DOCKER_BIN, args...)
|
||||||
|
@ -44,15 +44,14 @@ func copyMoveRoot(rootfs string) error {
|
|||||||
filename := path.Join("/", file.Name())
|
filename := path.Join("/", file.Name())
|
||||||
|
|
||||||
if filename == rootfs {
|
if filename == rootfs {
|
||||||
|
log.Debugf("Skipping Deleting %s", filename)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Deleting %s", filename)
|
log.Debugf("Deleting %s", filename)
|
||||||
//if err := os.Remove(filename); err != nil {
|
|
||||||
if err := os.RemoveAll(filename); err != nil {
|
if err := os.RemoveAll(filename); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -90,7 +89,7 @@ func switchRoot(rootfs string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Successfully moved to new root at %s", rootfs)
|
log.Debugf("Successfully moved to new root at %s", rootfs)
|
||||||
os.Setenv("DOCKER_RAMDISK", "false")
|
os.Unsetenv("DOCKER_RAMDISK")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
dockerClient "github.com/fsouza/go-dockerclient"
|
dockerClient "github.com/fsouza/go-dockerclient"
|
||||||
|
"github.com/rancherio/os/compose"
|
||||||
"github.com/rancherio/os/config"
|
"github.com/rancherio/os/config"
|
||||||
"github.com/rancherio/os/docker"
|
"github.com/rancherio/os/docker"
|
||||||
)
|
)
|
||||||
@ -88,42 +89,6 @@ func loadImages(cfg *config.CloudConfig) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runContainers(cfg *config.CloudConfig) error {
|
|
||||||
return docker.RunServices("system-init", cfg, cfg.Rancher.Services)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailConsole(cfg *config.CloudConfig) error {
|
|
||||||
if !cfg.Rancher.Console.Tail {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := docker.NewSystemClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
console, ok := cfg.Rancher.Services[config.CONSOLE_CONTAINER]
|
|
||||||
if !ok {
|
|
||||||
log.Error("Console not found")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c := docker.NewContainerFromService(config.DOCKER_SYSTEM_HOST, config.CONSOLE_CONTAINER, console)
|
|
||||||
if c.Err != nil {
|
|
||||||
return c.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Tailing console : %s", c.Name)
|
|
||||||
return client.Logs(dockerClient.LogsOptions{
|
|
||||||
Container: c.Name,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
Follow: true,
|
|
||||||
OutputStream: os.Stdout,
|
|
||||||
ErrorStream: os.Stderr,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SysInit() error {
|
func SysInit() error {
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -132,7 +97,9 @@ func SysInit() error {
|
|||||||
|
|
||||||
initFuncs := []config.InitFunc{
|
initFuncs := []config.InitFunc{
|
||||||
loadImages,
|
loadImages,
|
||||||
runContainers,
|
func(cfg *config.CloudConfig) error {
|
||||||
|
return compose.RunServices(cfg)
|
||||||
|
},
|
||||||
func(cfg *config.CloudConfig) error {
|
func(cfg *config.CloudConfig) error {
|
||||||
syscall.Sync()
|
syscall.Sync()
|
||||||
return nil
|
return nil
|
||||||
@ -141,7 +108,6 @@ func SysInit() error {
|
|||||||
log.Infof("RancherOS %s started", config.VERSION)
|
log.Infof("RancherOS %s started", config.VERSION)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
tailConsole,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.RunInitFuncs(cfg, initFuncs)
|
return config.RunInitFuncs(cfg, initFuncs)
|
||||||
|
2
main.go
2
main.go
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
dockerlaunchMain "github.com/rancher/docker-from-scratch/main"
|
||||||
"github.com/rancherio/os/cmd/cloudinit"
|
"github.com/rancherio/os/cmd/cloudinit"
|
||||||
"github.com/rancherio/os/cmd/control"
|
"github.com/rancherio/os/cmd/control"
|
||||||
"github.com/rancherio/os/cmd/network"
|
"github.com/rancherio/os/cmd/network"
|
||||||
@ -39,6 +40,7 @@ func registerCmd(cmd string, mainFunc func()) {
|
|||||||
func main() {
|
func main() {
|
||||||
registerCmd("/init", osInit.MainInit)
|
registerCmd("/init", osInit.MainInit)
|
||||||
registerCmd(config.SYSINIT_BIN, sysinit.Main)
|
registerCmd(config.SYSINIT_BIN, sysinit.Main)
|
||||||
|
registerCmd("/usr/bin/dockerlaunch", dockerlaunchMain.Main)
|
||||||
registerCmd("/usr/bin/system-docker", systemdocker.Main)
|
registerCmd("/usr/bin/system-docker", systemdocker.Main)
|
||||||
registerCmd("/sbin/poweroff", power.PowerOff)
|
registerCmd("/sbin/poweroff", power.PowerOff)
|
||||||
registerCmd("/sbin/reboot", power.Reboot)
|
registerCmd("/sbin/reboot", power.Reboot)
|
||||||
|
@ -27,8 +27,7 @@ rancher:
|
|||||||
labels:
|
labels:
|
||||||
io.rancher.os.detach: false
|
io.rancher.os.detach: false
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: autoformat
|
||||||
- autoformat
|
|
||||||
log_driver: json-file
|
log_driver: json-file
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
@ -89,10 +88,7 @@ rancher:
|
|||||||
io.rancher.os.detach: false
|
io.rancher.os.detach: false
|
||||||
io.rancher.os.reloadconfig: true
|
io.rancher.os.reloadconfig: true
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: cloud-init-pre,network
|
||||||
- preload-user-images
|
|
||||||
- cloud-init-pre
|
|
||||||
- network
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -107,8 +103,7 @@ rancher:
|
|||||||
io.rancher.os.detach: false
|
io.rancher.os.detach: false
|
||||||
io.rancher.os.reloadconfig: true
|
io.rancher.os.reloadconfig: true
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: preload-system-images
|
||||||
- preload-system-images
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -136,15 +131,13 @@ rancher:
|
|||||||
- /usr/bin/ros:/usr/bin/respawn:ro
|
- /usr/bin/ros:/usr/bin/respawn:ro
|
||||||
- /usr/bin/ros:/usr/bin/system-docker:ro
|
- /usr/bin/ros:/usr/bin/system-docker:ro
|
||||||
- /usr/bin/ros:/usr/sbin/wait-for-docker:ro
|
- /usr/bin/ros:/usr/sbin/wait-for-docker:ro
|
||||||
- /lib/modules:/lib/modules
|
- /usr/bin/ros:/usr/sbin/dockerlaunch:ro
|
||||||
- /usr/bin/docker:/usr/bin/docker:ro
|
- /usr/bin/docker:/usr/bin/docker:ro
|
||||||
console:
|
console:
|
||||||
image: rancher/os-console:v0.4.0-dev
|
image: rancher/os-console:v0.4.0-dev
|
||||||
labels:
|
labels:
|
||||||
io.rancher.os.remove: true
|
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: cloud-init
|
||||||
- cloud-init
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
pid: host
|
pid: host
|
||||||
@ -157,8 +150,7 @@ rancher:
|
|||||||
image: rancher/os-docker:v0.4.0-dev
|
image: rancher/os-docker:v0.4.0-dev
|
||||||
labels:
|
labels:
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: network
|
||||||
- network
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
pid: host
|
pid: host
|
||||||
@ -177,27 +169,14 @@ rancher:
|
|||||||
privileged: true
|
privileged: true
|
||||||
read_only: true
|
read_only: true
|
||||||
volumes:
|
volumes:
|
||||||
- /var/lib/rancher/conf:/var/lib/rancher/conf
|
|
||||||
- /var/lib/docker:/var/lib/docker
|
- /var/lib/docker:/var/lib/docker
|
||||||
- /var/lib/system-docker:/var/lib/system-docker
|
- /var/lib/system-docker:/var/lib/system-docker
|
||||||
dockerwait:
|
|
||||||
image: rancher/os-dockerwait:v0.4.0-dev
|
|
||||||
labels:
|
|
||||||
io.rancher.os.detach: false
|
|
||||||
io.rancher.os.scope: system
|
|
||||||
links:
|
|
||||||
- docker
|
|
||||||
net: host
|
|
||||||
uts: host
|
|
||||||
volumes_from:
|
|
||||||
- all-volumes
|
|
||||||
network:
|
network:
|
||||||
image: rancher/os-network:v0.4.0-dev
|
image: rancher/os-network:v0.4.0-dev
|
||||||
labels:
|
labels:
|
||||||
io.rancher.os.detach: false
|
io.rancher.os.detach: false
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: cloud-init-pre
|
||||||
- cloud-init-pre
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -208,9 +187,7 @@ rancher:
|
|||||||
image: rancher/os-ntp:v0.4.0-dev
|
image: rancher/os-ntp:v0.4.0-dev
|
||||||
labels:
|
labels:
|
||||||
io.rancher.os.scope: system
|
io.rancher.os.scope: system
|
||||||
links:
|
io.rancher.os.after: cloud-init, network
|
||||||
- cloud-init
|
|
||||||
- network
|
|
||||||
net: host
|
net: host
|
||||||
uts: host
|
uts: host
|
||||||
privileged: true
|
privileged: true
|
||||||
@ -231,9 +208,6 @@ rancher:
|
|||||||
image: rancher/os-preload:v0.4.0-dev
|
image: rancher/os-preload:v0.4.0-dev
|
||||||
labels:
|
labels:
|
||||||
io.rancher.os.detach: false
|
io.rancher.os.detach: false
|
||||||
io.rancher.os.scope: system
|
|
||||||
links:
|
|
||||||
- dockerwait
|
|
||||||
privileged: true
|
privileged: true
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
@ -263,7 +237,7 @@ rancher:
|
|||||||
read_only: true
|
read_only: true
|
||||||
volumes:
|
volumes:
|
||||||
- /dev:/host/dev
|
- /dev:/host/dev
|
||||||
- /os-config.yml:/os-config.yml
|
- /usr/share/ros/os-config.yml:/usr/share/ros/os-config.yml
|
||||||
- /var/lib/rancher:/var/lib/rancher
|
- /var/lib/rancher:/var/lib/rancher
|
||||||
- /var/lib/rancher/conf:/var/lib/rancher/conf
|
- /var/lib/rancher/conf:/var/lib/rancher/conf
|
||||||
- /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher
|
- /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher
|
||||||
|
@ -99,7 +99,7 @@ else
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
KERNEL_ARGS="rancher.password=rancher console=ttyS0 ${QEMU_APPEND}"
|
KERNEL_ARGS="rancher.password=rancher rancher.modules=[9p,9pnet_virtio] console=ttyS0 ${QEMU_APPEND}"
|
||||||
|
|
||||||
if [ "$UNAME" == "Darwin" ] && [ -x $(which xhyve) ]; then
|
if [ "$UNAME" == "Darwin" ] && [ -x $(which xhyve) ]; then
|
||||||
|
|
||||||
|
54
util/backoff.go
Normal file
54
util/backoff.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Backoff struct {
|
||||||
|
StartMillis, MaxIntervalMillis, MaxMillis int
|
||||||
|
c chan bool
|
||||||
|
done chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backoff) Start() <-chan bool {
|
||||||
|
b.c = make(chan bool)
|
||||||
|
b.done = make(chan bool)
|
||||||
|
go b.backoff()
|
||||||
|
return b.c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backoff) Close() error {
|
||||||
|
b.done <- true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backoff) backoff() {
|
||||||
|
if b.StartMillis == 0 && b.MaxIntervalMillis == 0 {
|
||||||
|
b.StartMillis = 100
|
||||||
|
b.MaxIntervalMillis = 2000
|
||||||
|
b.MaxMillis = 300000
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
currentMillis := b.StartMillis
|
||||||
|
|
||||||
|
for {
|
||||||
|
writeVal := true
|
||||||
|
if time.Now().Sub(start) > (time.Duration(b.MaxMillis) * time.Millisecond) {
|
||||||
|
b.c <- false
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-b.done:
|
||||||
|
close(b.done)
|
||||||
|
close(b.c)
|
||||||
|
return
|
||||||
|
case b.c <- writeVal:
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(currentMillis) * time.Millisecond)
|
||||||
|
|
||||||
|
currentMillis *= 2
|
||||||
|
if currentMillis > b.MaxIntervalMillis {
|
||||||
|
currentMillis = b.MaxIntervalMillis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
util/util.go
13
util/util.go
@ -345,3 +345,16 @@ func KVPairs2Map(kvs []string) map[string]string {
|
|||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TrimSplitN(str, sep string, count int) []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, part := range strings.SplitN(strings.TrimSpace(str), sep, count) {
|
||||||
|
result = append(result, strings.TrimSpace(part))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrimSplit(str, sep string) []string {
|
||||||
|
return TrimSplitN(str, sep, -1)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user