diff --git a/cmd/control/reload.go b/cmd/control/reload.go index 6959cf48..a34e3a6c 100644 --- a/cmd/control/reload.go +++ b/cmd/control/reload.go @@ -8,26 +8,26 @@ import ( "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 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) { - cfg, err := config.LoadConfig() + _, err := config.LoadConfig() if err != nil { log.Fatal(err) } - containers := parseContainers(cfg) + containers := map[string]*docker.Container{} //parseContainers(cfg) toStart := make([]*docker.Container, 0, len(c.Args())) for _, id := range c.Args() { diff --git a/cmd/network/network.go b/cmd/network/network.go index db1ec5b2..3a472351 100644 --- a/cmd/network/network.go +++ b/cmd/network/network.go @@ -27,10 +27,10 @@ func Main() { if err != nil { log.Fatal(err) } - applyNetworkConfigs(cfg) + ApplyNetworkConfigs(&cfg.Network) } -func applyNetworkConfigs(cfg *config.Config) error { +func ApplyNetworkConfigs(netCfg *config.NetworkConfig) error { links, err := netlink.LinkList() if err != nil { return err @@ -41,7 +41,7 @@ func applyNetworkConfigs(cfg *config.Config) error { linkName := link.Attrs().Name var match config.InterfaceConfig - for key, netConf := range cfg.Network.Interfaces { + for key, netConf := range netCfg.Interfaces { if netConf.Match == "" { netConf.Match = key } @@ -86,8 +86,8 @@ func applyNetworkConfigs(cfg *config.Config) error { } //post run - if cfg.Network.PostRun != nil { - return docker.StartAndWait(config.DOCKER_SYSTEM_HOST, cfg.Network.PostRun) + if netCfg.PostRun != nil { + return docker.StartAndWait(config.DOCKER_SYSTEM_HOST, netCfg.PostRun) } return nil } diff --git a/config/config.go b/config/config.go index d7ef2e33..d8093cf6 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,8 @@ import ( "os" "strings" + "github.com/rancherio/rancher-compose/project" + log "github.com/Sirupsen/logrus" "github.com/rancherio/os/util" "gopkg.in/yaml.v2" @@ -16,23 +18,10 @@ func (c *Config) privilegedMerge(newConfig Config) error { return err } - toAppend := make([]ContainerConfig, 0, 5) - - for _, newContainer := range newConfig.SystemContainers { - found := false - for i, existingContainer := range c.SystemContainers { - if existingContainer.Id != "" && newContainer.Id == existingContainer.Id { - found = true - c.SystemContainers[i] = newContainer - } - } - if !found { - toAppend = append(toAppend, newContainer) - } + for k, v := range newConfig.SystemContainers { + c.SystemContainers[k] = v } - c.SystemContainers = append(c.SystemContainers, toAppend...) - return nil } @@ -42,8 +31,8 @@ func (c *Config) overlay(newConfig Config) error { } func (c *Config) clearReadOnly() { - c.BootstrapContainers = make([]ContainerConfig, 0) - c.SystemContainers = make([]ContainerConfig, 0) + c.BootstrapContainers = make(map[string]*project.ServiceConfig, 0) + c.SystemContainers = make(map[string]*project.ServiceConfig, 0) } func clearReadOnly(data map[interface{}]interface{}) map[interface{}]interface{} { @@ -103,6 +92,12 @@ func LoadConfig() (*Config, error) { if cfg.Debug { log.SetLevel(log.DebugLevel) + if !util.Contains(cfg.UserDocker.Args, "-D") { + cfg.UserDocker.Args = append(cfg.UserDocker.Args, "-D") + } + if !util.Contains(cfg.SystemDocker.Args, "-D") { + cfg.SystemDocker.Args = append(cfg.SystemDocker.Args, "-D") + } } return cfg, nil @@ -197,20 +192,11 @@ func Dump(private, full bool) (string, error) { } func (c *Config) configureConsole() error { - if !c.Console.Persistent { - return nil - } - - for i := range c.SystemContainers { - // Need to modify original object, not the copy - var container *ContainerConfig = &c.SystemContainers[i] - - if container.Id != CONSOLE_CONTAINER { - continue - } - - if strings.Contains(container.Cmd, "--rm ") { - container.Cmd = strings.Replace(container.Cmd, "--rm ", "", 1) + if console, ok := c.SystemContainers[CONSOLE_CONTAINER]; ok { + if c.Console.Persistent { + console.Labels = append(console.Labels, REMOVE+"=false") + } else { + console.Labels = append(console.Labels, REMOVE+"=true") } } @@ -221,7 +207,6 @@ func (c *Config) readGlobals() error { return util.ShortCircuit( c.readCmdline, c.readArgs, - c.mergeAddons, c.configureConsole, ) } @@ -233,19 +218,6 @@ func (c *Config) Reload() error { ) } -func (c *Config) mergeAddons() error { - for _, addon := range c.EnabledAddons { - if newConfig, ok := c.Addons[addon]; ok { - log.Debugf("Enabling addon %s", addon) - if err := c.privilegedMerge(newConfig); err != nil { - return err - } - } - } - - return nil -} - func (c *Config) Get(key string) (interface{}, error) { data := make(map[interface{}]interface{}) err := util.Convert(c, &data) diff --git a/config/default.go b/config/default.go index c5d07cdd..a0756870 100644 --- a/config/default.go +++ b/config/default.go @@ -1,5 +1,9 @@ package config +import ( + "github.com/rancherio/rancher-compose/project" +) + func NewConfig() *Config { return &Config{ Debug: DEBUG, @@ -43,7 +47,7 @@ func NewConfig() *Config { Nameservers: []string{"8.8.8.8", "8.8.4.4"}, }, Interfaces: map[string]InterfaceConfig{ - "eth*": { + "eth0": { DHCP: true, }, "lo": { @@ -58,193 +62,238 @@ func NewConfig() *Config { Url: "https://releases.rancher.com/os/versions.yml", Image: "rancher/os", }, - BootstrapContainers: []ContainerConfig{ - { - Id: "udev", - Cmd: "--name=udev " + - "--net=none " + - "--privileged " + - "--rm " + - "-v=/dev:/host/dev " + - "-v=/lib/modules:/lib/modules:ro " + - "udev", + BootstrapContainers: map[string]*project.ServiceConfig{ + "udev": { + Net: "host", + Privileged: true, + Labels: []string{ + DETACH + "=false", + }, + Volumes: []string{ + "/dev:/host/dev", + "/lib/modules:/lib/modules:ro", + "/lib/firmware:/lib/firmware:ro", + }, + Image: "udev", }, }, - SystemContainers: []ContainerConfig{ - { - Id: "udev", - Cmd: "--name=udev " + - "--net=none " + - "--privileged " + - "--rm " + - "-v=/dev:/host/dev " + - "-v=/lib/modules:/lib/modules:ro " + - "udev", - CreateOnly: true, + SystemContainers: map[string]*project.ServiceConfig{ + "udev": { + Image: "udev", + Net: "host", + Privileged: true, + Labels: []string{ + DETACH + "=true", + }, + Environment: []string{ + "DAEMON=true", + }, + Volumes: []string{ + "/dev:/host/dev", + "/lib/modules:/lib/modules:ro", + "/lib/firmware:/lib/firmware:ro", + }, }, - { - Id: "system-volumes", - Cmd: "--name=system-volumes " + - "--net=none " + - "--read-only " + - "-v=/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt " + - "-v=/var/lib/rancher/conf:/var/lib/rancher/conf " + - "-v=/lib/modules:/lib/modules:ro " + - "-v=/var/run:/var/run " + - "-v=/var/log:/var/log " + - "state", - CreateOnly: true, + "system-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: []string{ + CREATE_ONLY + "=true", + }, + Volumes: []string{ + "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt", + "/var/lib/rancher/conf:/var/lib/rancher/conf", + "/lib/modules:/lib/modules:ro", + "/lib/firmware:/lib/firmware:ro", + "/var/run:/var/run", + "/var/log:/var/log", + }, }, - { - Id: "command-volumes", - Cmd: "--name=command-volumes " + - "--net=none " + - "--read-only " + - "-v=/init:/sbin/halt:ro " + - "-v=/init:/sbin/poweroff:ro " + - "-v=/init:/sbin/reboot:ro " + - "-v=/init:/sbin/shutdown:ro " + - "-v=/init:/sbin/netconf:ro " + - "-v=/init:/usr/bin/cloud-init:ro " + - "-v=/init:/usr/bin/rancherctl:ro " + - "-v=/init:/usr/bin/respawn:ro " + - "-v=/init:/usr/bin/system-docker:ro " + - "-v=/lib/modules:/lib/modules:ro " + - "-v=/usr/bin/docker:/usr/bin/docker:ro " + - "state", - CreateOnly: true, + "command-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: []string{ + CREATE_ONLY + "=true", + }, + Volumes: []string{ + "/init:/sbin/halt:ro", + "/init:/sbin/poweroff:ro", + "/init:/sbin/reboot:ro", + "/init:/sbin/shutdown:ro", + "/init:/sbin/netconf:ro", + "/init:/usr/bin/cloud-init:ro", + "/init:/usr/bin/rancherctl:ro", + "/init:/usr/bin/respawn:ro", + "/init:/usr/bin/system-docker:ro", + "/lib/modules:/lib/modules:ro", + "/usr/bin/docker:/usr/bin/docker:ro", + }, }, - { - Id: "user-volumes", - Cmd: "--name=user-volumes " + - "--net=none " + - "--read-only " + - "-v=/home:/home " + - "-v=/opt:/opt " + - "state", - CreateOnly: true, + "user-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: []string{ + CREATE_ONLY + "=true", + }, + Volumes: []string{ + "/home:/home", + "/opt:/opt", + }, }, - { - Id: "docker-volumes", - Cmd: "--name=docker-volumes " + - "--net=none " + - "--read-only " + - "-v=/var/lib/rancher:/var/lib/rancher " + - "-v=/var/lib/docker:/var/lib/docker " + - "-v=/var/lib/system-docker:/var/lib/system-docker " + - "state", - CreateOnly: true, + "docker-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: []string{ + CREATE_ONLY + "=true", + }, + Volumes: []string{ + "/var/lib/rancher:/var/lib/rancher", + "/var/lib/docker:/var/lib/docker", + "/var/lib/system-docker:/var/lib/system-docker", + }, }, - { - Id: "all-volumes", - Cmd: "--name=all-volumes " + - "--rm " + - "--net=none " + - "--read-only " + - "--volumes-from=docker-volumes " + - "--volumes-from=command-volumes " + - "--volumes-from=user-volumes " + - "--volumes-from=system-volumes " + - "state", - CreateOnly: true, + "all-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: []string{ + CREATE_ONLY + "=true", + }, + VolumesFrom: []string{ + "docker-volumes", + "command-volumes", + "user-volumes", + "system-volumes", + }, }, - { - Id: "cloud-init-pre", - Cmd: "--name=cloud-init-pre " + - "--rm " + - "--privileged " + - "--net=host " + - "-e CLOUD_INIT_NETWORK=false " + - "--volumes-from=command-volumes " + - "--volumes-from=system-volumes " + - "cloudinit", - ReloadConfig: true, + "cloud-init-pre": { + Image: "cloudinit", + Privileged: true, + Net: "host", + Labels: []string{ + RELOAD_CONFIG + "=true", + DETACH + "=false", + }, + Environment: []string{ + "CLOUD_INIT_NETWORK=false", + }, + VolumesFrom: []string{ + "command-volumes", + "system-volumes", + }, }, - { - Id: "network", - Cmd: "--name=network " + - "--rm " + - "--cap-add=NET_ADMIN " + - "--net=host " + - "--volumes-from=command-volumes " + - "--volumes-from=system-volumes " + + "network": { + Image: "network", + CapAdd: []string{ + "NET_ADMIN", + }, + Net: "host", + Labels: []string{ + DETACH + "=false", + }, + Links: []string{ + "cloud-init-pre", + }, + VolumesFrom: []string{ + "command-volumes", + "system-volumes", + }, + }, + "cloud-init": { + Image: "cloudinit", + Privileged: true, + Labels: []string{ + RELOAD_CONFIG + "=true", + DETACH + "=false", + }, + Net: "host", + Links: []string{ + "cloud-init-pre", "network", + }, + VolumesFrom: []string{ + "command-volumes", + "system-volumes", + }, }, - { - Id: "cloud-init", - Cmd: "--name=cloud-init " + - "--rm " + - "--privileged " + - "--net=host " + - "--volumes-from=command-volumes " + - "--volumes-from=system-volumes " + - "cloudinit", - ReloadConfig: true, + "ntp": { + Image: "ntp", + Privileged: true, + Net: "host", + Links: []string{ + "cloud-init", + "network", + }, }, - { - Id: "ntp", - Cmd: "--name=ntp " + - "--rm " + - "-d " + - "--privileged " + - "--net=host " + - "ntp", + "syslog": { + Image: "syslog", + Privileged: true, + Net: "host", + Links: []string{ + "cloud-init", + "network", + }, + VolumesFrom: []string{ + "system-volumes", + }, }, - { - Id: "syslog", - Cmd: "--name=syslog " + - "-d " + - "--rm " + - "--privileged " + - "--net=host " + - "--ipc=host " + - "--pid=host " + - "--volumes-from=system-volumes " + - "syslog", + "userdocker": { + Image: "userdocker", + Privileged: true, + Pid: "host", + Ipc: "host", + Net: "host", + Links: []string{ + "network", + }, + VolumesFrom: []string{ + "all-volumes", + }, }, - { - Id: "userdocker", - Cmd: "--name=userdocker " + - "-d " + - "--rm " + - "--restart=always " + - "--ipc=host " + - "--pid=host " + - "--net=host " + - "--privileged " + - "--volumes-from=all-volumes " + - "userdocker", - }, - { - Id: "console", - Cmd: "--name=console " + - "-d " + - "--rm " + - "--privileged " + - "--volumes-from=all-volumes " + - "--restart=always " + - "--ipc=host " + - "--net=host " + - "--pid=host " + - "console", + "console": { + Image: "console", + Privileged: true, + Links: []string{ + "cloud-init", + }, + VolumesFrom: []string{ + "all-volumes", + }, + Restart: "always", + Pid: "host", + Ipc: "host", + Net: "host", }, }, EnabledAddons: []string{}, Addons: map[string]Config{ "ubuntu-console": { - SystemContainers: []ContainerConfig{ - { - Id: "console", - Cmd: "--name=ubuntu-console " + - "-d " + - "--rm " + - "--privileged " + - "--volumes-from=all-volumes " + - "--restart=always " + - "--ipc=host " + - "--net=host " + - "--pid=host " + - "rancher/ubuntuconsole:" + VERSION, + SystemContainers: map[string]*project.ServiceConfig{ + "console": { + Image: "rancher/ubuntuconsole:" + VERSION, + Privileged: true, + Labels: []string{ + DETACH + "=true", + }, + Links: []string{ + "cloud-init", + }, + VolumesFrom: []string{ + "all-volumes", + }, + Restart: "always", + Pid: "host", + Ipc: "host", + Net: "host", }, }, }, diff --git a/config/types.go b/config/types.go index 651c0f07..33161c9c 100644 --- a/config/types.go +++ b/config/types.go @@ -1,5 +1,7 @@ package config +import "github.com/rancherio/rancher-compose/project" + const ( CONSOLE_CONTAINER = "console" DOCKER_BIN = "/usr/bin/docker" @@ -12,6 +14,14 @@ const ( USER_INIT = "/sbin/init-user" MODULES_ARCHIVE = "/modules.tar" DEBUG = false + + LABEL = "label" + HASH = "io.rancher.os.hash" + ID = "io.rancher.os.id" + DETACH = "io.rancher.os.detach" + REMOVE = "io.rancher.os.remove" + CREATE_ONLY = "io.rancher.os.createonly" + RELOAD_CONFIG = "io.rancher.os.reloadconfig" ) var ( @@ -22,30 +32,31 @@ var ( ) type ContainerConfig struct { - Id string `yaml:"id,omitempty"` - Cmd string `yaml:"run,omitempty"` - MigrateVolumes bool `yaml:"migrate_volumes,omitempty"` - ReloadConfig bool `yaml:"reload_config,omitempty"` - CreateOnly bool `yaml:create_only,omitempty` + Id string `yaml:"id,omitempty"` + Cmd string `yaml:"run,omitempty"` + MigrateVolumes bool `yaml:"migrate_volumes,omitempty"` + ReloadConfig bool `yaml:"reload_config,omitempty"` + CreateOnly bool `yaml:create_only,omitempty` + Service *project.ServiceConfig `yaml:service,omitempty` } type Config struct { - Addons map[string]Config `yaml:"addons,omitempty"` - BootstrapContainers []ContainerConfig `yaml:"bootstrap_containers,omitempty"` - CloudInit CloudInit `yaml:"cloud_init,omitempty"` - Console ConsoleConfig `yaml:"console,omitempty"` - Debug bool `yaml:"debug,omitempty"` - Disable []string `yaml:"disable,omitempty"` - EnabledAddons []string `yaml:"enabled_addons,omitempty"` - Modules []string `yaml:"modules,omitempty"` - Network NetworkConfig `yaml:"network,omitempty"` - Ssh SshConfig `yaml:"ssh,omitempty"` - State StateConfig `yaml:"state,omitempty"` - SystemContainers []ContainerConfig `yaml:"system_containers,omitempty"` - SystemDocker DockerConfig `yaml:"system_docker,omitempty"` - Upgrade UpgradeConfig `yaml:"upgrade,omitempty"` - UserContainers []ContainerConfig `yaml:"user_containers,omitempty"` - UserDocker DockerConfig `yaml:"user_docker,omitempty"` + Addons map[string]Config `yaml:"addons,omitempty"` + BootstrapContainers map[string]*project.ServiceConfig `yaml:"bootstrap_containers,omitempty"` + CloudInit CloudInit `yaml:"cloud_init,omitempty"` + Console ConsoleConfig `yaml:"console,omitempty"` + Debug bool `yaml:"debug,omitempty"` + //Disable []string `yaml:"disable,omitempty"` + EnabledAddons []string `yaml:"enabled_addons,omitempty"` + Modules []string `yaml:"modules,omitempty"` + Network NetworkConfig `yaml:"network,omitempty"` + Ssh SshConfig `yaml:"ssh,omitempty"` + State StateConfig `yaml:"state,omitempty"` + SystemContainers map[string]*project.ServiceConfig `yaml:"system_containers,omitempty"` + SystemDocker DockerConfig `yaml:"system_docker,omitempty"` + Upgrade UpgradeConfig `yaml:"upgrade,omitempty"` + UserContainers []ContainerConfig `yaml:"user_containers,omitempty"` + UserDocker DockerConfig `yaml:"user_docker,omitempty"` } type ConsoleConfig struct { diff --git a/docker/client.go b/docker/client.go index 5bcc3e46..7e1750c4 100644 --- a/docker/client.go +++ b/docker/client.go @@ -3,6 +3,8 @@ package docker import ( "time" + log "github.com/Sirupsen/logrus" + dockerClient "github.com/fsouza/go-dockerclient" "github.com/rancherio/os/config" ) @@ -26,12 +28,16 @@ func NewClient(endpoint string) (*dockerClient.Client, error) { return nil, err } + retry := false for i := 0; i < (MAX_WAIT / INTERVAL); i++ { _, err = client.Info() if err == nil { break } + retry = true + + log.Infof("Waiting for Docker at %s", endpoint) time.Sleep(INTERVAL * time.Millisecond) } @@ -39,5 +45,8 @@ func NewClient(endpoint string) (*dockerClient.Client, error) { return nil, err } + if retry { + log.Infof("Connected to Docker at %s", endpoint) + } return client, nil } diff --git a/docker/container.go b/docker/container.go index 7e9d5f28..acc29bb5 100644 --- a/docker/container.go +++ b/docker/container.go @@ -4,6 +4,7 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "errors" "fmt" "os" "sort" @@ -16,12 +17,8 @@ import ( dockerClient "github.com/fsouza/go-dockerclient" "github.com/rancherio/os/config" "github.com/rancherio/os/util" -) - -const ( - LABEL = "label" - HASH = "io.rancher.os.hash" - ID = "io.rancher.os.id" + "github.com/rancherio/rancher-compose/docker" + "github.com/rancherio/rancher-compose/project" ) type Container struct { @@ -48,6 +45,10 @@ func getHash(containerCfg *config.ContainerConfig) (string, error) { w.Write([]byte(containerCfg.Id)) w.Write([]byte(containerCfg.Cmd)) + if containerCfg.Service != nil { + //TODO: properly hash + w.Write([]byte(fmt.Sprintf("%v", containerCfg.Service))) + } if w.Err != nil { return "", w.Err @@ -61,6 +62,18 @@ func StartAndWait(dockerHost string, containerCfg *config.ContainerConfig) error 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, @@ -78,7 +91,7 @@ func getByLabel(client *dockerClient.Client, key, value string) (*dockerClient.A containers, err := client.ListContainers(dockerClient.ListContainersOptions{ All: true, Filters: map[string][]string{ - LABEL: []string{fmt.Sprintf("%s=%s", key, value)}, + config.LABEL: []string{fmt.Sprintf("%s=%s", key, value)}, }, }) @@ -114,7 +127,7 @@ func (c *Container) Lookup() *Container { containers, err := client.ListContainers(dockerClient.ListContainersOptions{ All: true, Filters: map[string][]string{ - LABEL: []string{fmt.Sprintf("%s=%s", HASH, hash)}, + config.LABEL: []string{fmt.Sprintf("%s=%s", config.HASH, hash)}, }, }) if err != nil { @@ -157,11 +170,23 @@ func (c *Container) Reset() *Container { return c } -func (c *Container) Parse() *Container { - if c.Config != nil || c.Err != nil { - return c +func (c *Container) parseService() { + 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, "") @@ -170,7 +195,8 @@ func (c *Container) Parse() *Container { args, err := shlex.Split(c.ContainerCfg.Cmd) if err != nil { - return c.returnErr(err) + c.Err = err + return } log.Debugf("Parsing [%s]", strings.Join(args, ",")) @@ -179,6 +205,21 @@ func (c *Container) Parse() *Container { 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 @@ -219,6 +260,7 @@ func (c *Container) Stage() *Container { OutputStream: os.Stdout, }, dockerClient.AuthConfiguration{}) } else if err != nil { + log.Errorf("Failed to stage: %s: %v", c.Config.Image, err) c.Err = err } @@ -291,7 +333,7 @@ func (c *Container) renameOld(client *dockerClient.Client, opts *dockerClient.Cr } var newName string - if label, ok := existing.Config.Labels[HASH]; ok { + 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)) @@ -316,6 +358,7 @@ func (c *Container) renameOld(client *dockerClient.Client, opts *dockerClient.Cr 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 } @@ -323,6 +366,7 @@ func (c *Container) getCreateOpts(client *dockerClient.Client) (*dockerClient.Cr err = json.Unmarshal(bytes, &opts) if err != nil { + log.Errorf("Failed to unmarshall: %s", string(bytes)) return nil, err } @@ -335,8 +379,8 @@ func (c *Container) getCreateOpts(client *dockerClient.Client) (*dockerClient.Cr return nil, err } - opts.Config.Labels[HASH] = hash - opts.Config.Labels[ID] = c.ContainerCfg.Id + opts.Config.Labels[config.HASH] = hash + opts.Config.Labels[config.ID] = c.ContainerCfg.Id return &opts, nil } @@ -346,7 +390,7 @@ func appendVolumesFrom(client *dockerClient.Client, containerCfg *config.Contain return nil } - container, err := getByLabel(client, ID, containerCfg.Id) + container, err := getByLabel(client, config.ID, containerCfg.Id) if err != nil || container == nil { return err } @@ -360,7 +404,7 @@ func appendVolumesFrom(client *dockerClient.Client, containerCfg *config.Contain return nil } -func (c *Container) start(create_only, wait bool) *Container { +func (c *Container) start(createOnly, wait bool) *Container { c.Lookup() c.Stage() @@ -378,6 +422,7 @@ func (c *Container) start(create_only, wait bool) *Container { opts, err := c.getCreateOpts(client) if err != nil { + log.Errorf("Failed to create container create options: %v", err) return c.returnErr(err) } @@ -420,7 +465,7 @@ func (c *Container) start(create_only, wait bool) *Container { hostConfig = opts.HostConfig } - if create_only { + if createOnly { return c } @@ -439,12 +484,17 @@ func (c *Container) start(create_only, wait bool) *Container { err = client.StartContainer(c.Container.ID, hostConfig) if err != nil { + log.Errorf("Error from Docker %s", err) return c.returnErr(err) } } if !c.detach && wait { - _, c.Err = client.WaitContainer(c.Container.ID) + var exitCode int + exitCode, c.Err = client.WaitContainer(c.Container.ID) + if exitCode != 0 { + c.Err = errors.New(fmt.Sprintf("Container %s exited with code %d", c.Name, exitCode)) + } return c } diff --git a/docker/factory.go b/docker/factory.go new file mode 100644 index 00000000..c9d76153 --- /dev/null +++ b/docker/factory.go @@ -0,0 +1,69 @@ +package docker + +import ( + log "github.com/Sirupsen/logrus" + + "github.com/rancherio/os/config" + "github.com/rancherio/rancher-compose/project" +) + +type ContainerFactory struct { +} + +type containerBasedService struct { + name string + project *project.Project + container *Container + serviceConfig *project.ServiceConfig + cfg *config.Config +} + +func (c *containerBasedService) Up() error { + container := c.container + containerCfg := c.container.ContainerCfg + + if containerCfg.CreateOnly { + container.Create() + c.project.Notify(project.CONTAINER_CREATED, c, map[string]string{ + project.CONTAINER_ID: container.Container.ID, + }) + } else { + container.StartAndWait() + c.project.Notify(project.CONTAINER_STARTED, c, map[string]string{ + project.CONTAINER_ID: container.Container.ID, + }) + } + + if container.Err != nil { + log.Errorf("Failed to run %v: %v", containerCfg.Id, container.Err) + } + + if container.Err == nil && containerCfg.ReloadConfig { + return project.ErrRestart + } + + return container.Err +} + +func (c *containerBasedService) Config() *project.ServiceConfig { + return c.serviceConfig +} + +func (c *containerBasedService) Name() string { + return c.name +} + +func (c *ContainerFactory) Create(project *project.Project, name string, serviceConfig *project.ServiceConfig) (project.Service, error) { + container := NewContainerFromService(config.DOCKER_SYSTEM_HOST, name, serviceConfig) + + if container.Err != nil { + return nil, container.Err + } + + return &containerBasedService{ + name: name, + project: project, + container: container, + serviceConfig: serviceConfig, + }, nil +} diff --git a/init/bootstrap.go b/init/bootstrap.go index bed48765..c56a8099 100644 --- a/init/bootstrap.go +++ b/init/bootstrap.go @@ -8,9 +8,10 @@ import ( log "github.com/Sirupsen/logrus" "github.com/rancherio/os/config" "github.com/rancherio/os/util" + "github.com/rancherio/rancher-compose/project" ) -func runBootstrapContainers(cfg *config.Config) error { +func autoformat(cfg *config.Config) error { if len(cfg.State.Autoformat) == 0 || util.ResolveDevice(cfg.State.Dev) != "" { return nil } @@ -54,24 +55,22 @@ outer: if format != "" { log.Infof("Auto formatting : %s", format) - return runContainersFrom("", cfg, append([]config.ContainerConfig{ - { - Id: "auto-format", - Cmd: "--name auto-format " + - "--rm " + - "--net=none " + - "--privileged " + - "autoformat " + - format, + return runServices("autoformat", cfg, map[string]*project.ServiceConfig{ + "autoformat": { + Net: "none", + Privileged: true, + Image: "autoformat", + Command: format, }, - }, cfg.BootstrapContainers...)) + "udev": cfg.BootstrapContainers["udev"], + }) } return nil } -func autoformat(cfg *config.Config) error { - return runContainersFrom("", cfg, cfg.BootstrapContainers) +func runBootstrapContainers(cfg *config.Config) error { + return runServices("bootstrap", cfg, cfg.BootstrapContainers) } func startDocker(cfg *config.Config) (chan interface{}, error) { diff --git a/init/sysinit.go b/init/sysinit.go index 2a314ec7..0a8d191c 100644 --- a/init/sysinit.go +++ b/init/sysinit.go @@ -10,6 +10,8 @@ import ( "github.com/rancherio/os/config" "github.com/rancherio/os/docker" "github.com/rancherio/os/util" + + "github.com/rancherio/rancher-compose/project" ) func importImage(client *dockerClient.Client, name, fileName string) error { @@ -107,50 +109,63 @@ func loadImages(cfg *config.Config) error { return nil } -func runContainersFrom(startFrom string, cfg *config.Config, containerConfigs []config.ContainerConfig) error { - foundStart := false +func runServices(name string, cfg *config.Config, configs map[string]*project.ServiceConfig) error { + project := project.NewProject(name, &docker.ContainerFactory{}) + enabled := make(map[string]bool) - for i, containerConfig := range containerConfigs { - container := docker.NewContainer(config.DOCKER_SYSTEM_HOST, &containerConfig) - - if util.Contains(cfg.Disable, containerConfig.Id) { - log.Infof("%s is disabled : %v", containerConfig.Id, cfg.Disable) - continue - } - - if foundStart || startFrom == "" { - - if containerConfig.CreateOnly { - log.Infof("Creating [%d/%d] %s", i+1, len(containerConfigs), containerConfig.Id) - container.Create() - } else { - log.Infof("Running [%d/%d] %s", i+1, len(containerConfigs), containerConfig.Id) - container.StartAndWait() - } - - if container.Err != nil { - log.Errorf("Failed to run %v: %v", containerConfig.Id, container.Err) - } - - if containerConfig.ReloadConfig { - log.Info("Reloading configuration") - err := cfg.Reload() - if err != nil { - return err - } - - return runContainersFrom(containerConfig.Id, cfg, cfg.SystemContainers) - } - } else if startFrom == containerConfig.Id { - foundStart = true + for name, serviceConfig := range configs { + if err := project.AddConfig(name, serviceConfig); err != nil { + log.Infof("Failed loading service %s", name) } } - return nil + project.ReloadCallback = func() error { + err := cfg.Reload() + if err != nil { + return err + } + + for _, addon := range cfg.EnabledAddons { + if _, ok := enabled[addon]; ok { + continue + } + + if config, ok := cfg.Addons[addon]; ok { + for name, s := range config.SystemContainers { + if err := project.AddConfig(name, s); err != nil { + log.Errorf("Failed to load %s : %v", name, err) + } + } + } else { + bytes, err := util.LoadResource(addon) + if err != nil { + log.Errorf("Failed to load %s : %v", addon, err) + continue + } + + err = project.Load(bytes) + if err != nil { + log.Errorf("Failed to load %s : %v", addon, err) + continue + } + } + + enabled[addon] = true + } + + return nil + } + + err := project.ReloadCallback() + if err != nil { + log.Errorf("Failed to reload %s : %v", name, err) + return err + } + return project.Up() } func runContainers(cfg *config.Config) error { - return runContainersFrom("", cfg, cfg.SystemContainers) + return runServices("system-init", cfg, cfg.SystemContainers) } func tailConsole(cfg *config.Config) error { @@ -163,29 +178,26 @@ func tailConsole(cfg *config.Config) error { return err } - for _, container := range cfg.SystemContainers { - if container.Id != config.CONSOLE_CONTAINER { - continue - } - - c := docker.NewContainer(config.DOCKER_SYSTEM_HOST, &container).Lookup() - if c.Err != nil { - continue - } - - 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, - }) + console, ok := cfg.SystemContainers[config.CONSOLE_CONTAINER] + if !ok { + log.Error("Console not found") + return nil } - 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 { @@ -201,6 +213,10 @@ func SysInit() error { syscall.Sync() return nil }, + func(cfg *config.Config) error { + log.Info("RancherOS booted") + return nil + }, tailConsole, } diff --git a/util/util.go b/util/util.go index 36c49257..b97b9aaa 100644 --- a/util/util.go +++ b/util/util.go @@ -4,9 +4,12 @@ import ( "archive/tar" "fmt" "io" + "io/ioutil" "math/rand" + "net/http" "os" "path" + "strings" "syscall" "github.com/docker/docker/pkg/mount" @@ -182,3 +185,16 @@ func MergeMaps(left, right map[interface{}]interface{}) { } } } + +func LoadResource(location string) ([]byte, error) { + if strings.HasPrefix(location, "http:/") || strings.HasPrefix(location, "https:/") { + resp, err := http.Get(location) + if err != nil { + return nil, err + } + defer resp.Body.Close() + return ioutil.ReadAll(resp.Body) + } else { + return ioutil.ReadFile(location) + } +}