1
0
mirror of https://github.com/rancher/os.git synced 2025-06-26 15:01:34 +00:00
os/docker/container.go

633 lines
14 KiB
Go
Raw Normal View History

package docker
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
2015-05-17 15:39:31 +00:00
"io"
"os"
2015-05-07 21:29:30 +00:00
"reflect"
"sort"
"strings"
2015-02-17 21:31:20 +00:00
log "github.com/Sirupsen/logrus"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/runconfig"
2015-02-17 21:31:20 +00:00
shlex "github.com/flynn/go-shlex"
dockerClient "github.com/fsouza/go-dockerclient"
"github.com/rancherio/os/config"
"github.com/rancherio/os/util"
2015-05-05 20:36:52 +00:00
"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
2015-02-17 08:18:48 +00:00
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 }
2015-05-17 12:49:04 +00:00
func getHash(containerCfg *config.ContainerConfig) string {
hash := sha1.New()
2015-05-17 15:39:31 +00:00
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 {
2015-05-07 21:29:30 +00:00
//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
2015-05-17 15:39:31 +00:00
serviceKeys := []string{}
2015-05-07 21:29:30 +00:00
//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
2015-05-17 15:39:31 +00:00
for _, serviceKey := range serviceKeys {
serviceValue := unsortedKeyValue[serviceKey]
io.WriteString(hash, fmt.Sprintf("\n %v: ", serviceKey))
2015-05-07 21:29:30 +00:00
2015-05-11 17:02:15 +00:00
switch s := serviceValue.(type) {
2015-05-17 15:39:31 +00:00
case project.SliceorMap:
sliceKeys := []string{}
2015-05-11 17:02:15 +00:00
for lkey := range s.MapParts() {
if lkey != "io.rancher.os.hash" {
sliceKeys = append(sliceKeys, lkey)
}
2015-05-07 21:29:30 +00:00
}
sort.Strings(sliceKeys)
2015-05-07 21:29:30 +00:00
2015-05-17 15:39:31 +00:00
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s.MapParts()[sliceKey]))
}
case project.MaporEqualSlice:
2015-05-17 15:39:31 +00:00
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
2015-05-17 15:39:31 +00:00
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))
}
2015-05-11 17:02:15 +00:00
case []string:
2015-05-17 15:39:31 +00:00
sliceKeys := s
sort.Strings(sliceKeys)
2015-05-17 15:39:31 +00:00
for _, sliceKey := range sliceKeys {
io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey))
2015-05-07 21:29:30 +00:00
}
2015-05-17 15:39:31 +00:00
default:
io.WriteString(hash, fmt.Sprintf("%v", serviceValue))
2015-05-07 21:29:30 +00:00
}
}
}
2015-05-17 15:39:31 +00:00
return hex.EncodeToString(hash.Sum(nil))
}
2015-02-17 08:18:48 +00:00
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,
},
}
2015-04-16 05:57:59 +00:00
return c.Parse()
}
2015-02-17 08:18:48 +00:00
func NewContainer(dockerHost string, containerCfg *config.ContainerConfig) *Container {
c := &Container{
dockerHost: dockerHost,
ContainerCfg: containerCfg,
}
2015-02-17 08:18:48 +00:00
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
}
2015-05-17 12:49:04 +00:00
hash := getHash(c.ContainerCfg)
2015-02-17 08:18:48 +00:00
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
}
2015-04-16 06:16:23 +00:00
func (c *Container) requiresSyslog() bool {
return (c.ContainerCfg.Service.LogDriver == "" || c.ContainerCfg.Service.LogDriver == "syslog")
}
2015-04-16 05:57:59 +00:00
func (c *Container) requiresUserDocker() bool {
if c.dockerHost == config.DOCKER_HOST {
return true
}
return false
}
2015-04-16 06:16:23 +00:00
func (c *Container) hasLink(link string) bool {
return util.Contains(c.ContainerCfg.Service.Links.Slice(), link)
2015-04-16 06:16:23 +00:00
}
func (c *Container) addLink(link string) {
2015-04-16 05:57:59 +00:00
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))
2015-04-16 06:16:23 +00:00
}
func (c *Container) parseService() {
2015-04-16 05:57:59 +00:00
if c.requiresSyslog() {
c.addLink("syslog")
log.Infof("[%v]: Implicitly linked to 'syslog'", c.Name)
2015-04-16 06:16:23 +00:00
}
2015-04-16 05:57:59 +00:00
if c.requiresUserDocker() {
c.addLink("dockerwait")
log.Infof("[%v]: Implicitly linked to 'dockerwait'", c.Name)
2015-04-16 05:57:59 +00:00
} else if c.ContainerCfg.Service.Image != "" {
client, err := NewClient(c.dockerHost)
if err != nil {
c.Err = err
return
}
2015-04-16 06:16:23 +00:00
i, _ := client.InspectImage(c.ContainerCfg.Service.Image)
2015-04-16 05:57:59 +00:00
if i == nil {
2015-04-16 06:16:23 +00:00
c.addLink("network")
log.Infof("[%v]: Implicitly linked to 'network'", c.Name)
2015-04-16 06:16:23 +00:00
}
}
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)
2015-02-17 21:31:20 +00:00
if err != nil {
c.Err = err
return
2015-02-17 21:31:20 +00:00
}
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
}
2015-02-17 08:18:48 +00:00
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
}
2015-02-17 08:18:48 +00:00
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
}
2015-03-15 04:34:05 +00:00
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)
2015-03-15 04:34:05 +00:00
if _, ok := err.(*dockerClient.NoSuchContainer); ok {
return nil
}
if err != nil {
return nil
}
if c.Container != nil && existing.ID == c.Container.ID {
return nil
}
2015-03-07 04:21:42 +00:00
var newName string
if label, ok := existing.Config.Labels[config.HASH]; ok {
2015-03-07 04:21:42 +00:00
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
}
2015-03-07 04:21:42 +00:00
_, err = client.WaitContainer(existing.ID)
if err != nil {
return err
}
}
2015-03-07 04:21:42 +00:00
log.Debugf("Renaming %s to %s", existing.Name, newName)
2015-03-15 04:34:05 +00:00
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)
}
2015-05-17 12:49:04 +00:00
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 {
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)
2015-02-20 18:29:17 +00:00
c.Delete()
if c.Err != nil {
return c
}
2015-02-20 18:29:17 +00:00
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)
}
}
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
}