mirror of
https://github.com/rancher/os.git
synced 2025-06-24 05:57:03 +00:00
503 lines
10 KiB
Go
503 lines
10 KiB
Go
package docker
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
flag "github.com/docker/docker/pkg/mflag"
|
|
"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/docker"
|
|
"github.com/rancherio/rancher-compose/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, error) {
|
|
hash := sha1.New()
|
|
w := util.NewErrorWriter(hash)
|
|
|
|
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
|
|
}
|
|
|
|
return hex.EncodeToString(hash.Sum([]byte{})), 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: []string{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, err := getHash(c.ContainerCfg)
|
|
if err != nil {
|
|
return c.returnErr(err)
|
|
}
|
|
|
|
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: []string{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) 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, "")
|
|
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 {
|
|
c.Err = client.PullImage(dockerClient.PullImageOptions{
|
|
Repository: c.Config.Image,
|
|
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, err := getHash(c.ContainerCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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 {
|
|
c.Lookup()
|
|
c.Stage()
|
|
|
|
if c.Err != nil {
|
|
return c
|
|
}
|
|
|
|
client, err := NewClient(c.dockerHost)
|
|
if err != nil {
|
|
return c.returnErr(err)
|
|
}
|
|
|
|
container := c.Container
|
|
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)
|
|
}
|
|
|
|
container, err = client.CreateContainer(*opts)
|
|
created = true
|
|
if err != nil {
|
|
return c.returnErr(err)
|
|
}
|
|
}
|
|
|
|
c.Container = container
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
if !c.detach && wait {
|
|
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
|
|
}
|
|
|
|
return c
|
|
}
|