| package docker |
| |
| import ( |
| "fmt" |
| "github.com/dotcloud/docker/archive" |
| "github.com/dotcloud/docker/pkg/namesgenerator" |
| "github.com/dotcloud/docker/utils" |
| "io/ioutil" |
| "strconv" |
| "strings" |
| ) |
| |
| type Change struct { |
| archive.Change |
| } |
| |
| // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields |
| // If OpenStdin is set, then it differs |
| func CompareConfig(a, b *Config) bool { |
| if a == nil || b == nil || |
| a.OpenStdin || b.OpenStdin { |
| return false |
| } |
| if a.AttachStdout != b.AttachStdout || |
| a.AttachStderr != b.AttachStderr || |
| a.User != b.User || |
| a.Memory != b.Memory || |
| a.MemorySwap != b.MemorySwap || |
| a.CpuShares != b.CpuShares || |
| a.OpenStdin != b.OpenStdin || |
| a.Tty != b.Tty || |
| a.VolumesFrom != b.VolumesFrom { |
| return false |
| } |
| if len(a.Cmd) != len(b.Cmd) || |
| len(a.Dns) != len(b.Dns) || |
| len(a.Env) != len(b.Env) || |
| len(a.PortSpecs) != len(b.PortSpecs) || |
| len(a.ExposedPorts) != len(b.ExposedPorts) || |
| len(a.Entrypoint) != len(b.Entrypoint) || |
| len(a.Volumes) != len(b.Volumes) { |
| return false |
| } |
| |
| for i := 0; i < len(a.Cmd); i++ { |
| if a.Cmd[i] != b.Cmd[i] { |
| return false |
| } |
| } |
| for i := 0; i < len(a.Dns); i++ { |
| if a.Dns[i] != b.Dns[i] { |
| return false |
| } |
| } |
| for i := 0; i < len(a.Env); i++ { |
| if a.Env[i] != b.Env[i] { |
| return false |
| } |
| } |
| for i := 0; i < len(a.PortSpecs); i++ { |
| if a.PortSpecs[i] != b.PortSpecs[i] { |
| return false |
| } |
| } |
| for k := range a.ExposedPorts { |
| if _, exists := b.ExposedPorts[k]; !exists { |
| return false |
| } |
| } |
| for i := 0; i < len(a.Entrypoint); i++ { |
| if a.Entrypoint[i] != b.Entrypoint[i] { |
| return false |
| } |
| } |
| for key := range a.Volumes { |
| if _, exists := b.Volumes[key]; !exists { |
| return false |
| } |
| } |
| return true |
| } |
| |
| func MergeConfig(userConf, imageConf *Config) error { |
| if userConf.User == "" { |
| userConf.User = imageConf.User |
| } |
| if userConf.Memory == 0 { |
| userConf.Memory = imageConf.Memory |
| } |
| if userConf.MemorySwap == 0 { |
| userConf.MemorySwap = imageConf.MemorySwap |
| } |
| if userConf.CpuShares == 0 { |
| userConf.CpuShares = imageConf.CpuShares |
| } |
| if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { |
| userConf.ExposedPorts = imageConf.ExposedPorts |
| } else if imageConf.ExposedPorts != nil { |
| if userConf.ExposedPorts == nil { |
| userConf.ExposedPorts = make(map[Port]struct{}) |
| } |
| for port := range imageConf.ExposedPorts { |
| if _, exists := userConf.ExposedPorts[port]; !exists { |
| userConf.ExposedPorts[port] = struct{}{} |
| } |
| } |
| } |
| |
| if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { |
| if userConf.ExposedPorts == nil { |
| userConf.ExposedPorts = make(map[Port]struct{}) |
| } |
| ports, _, err := parsePortSpecs(userConf.PortSpecs) |
| if err != nil { |
| return err |
| } |
| for port := range ports { |
| if _, exists := userConf.ExposedPorts[port]; !exists { |
| userConf.ExposedPorts[port] = struct{}{} |
| } |
| } |
| userConf.PortSpecs = nil |
| } |
| if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { |
| utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) |
| if userConf.ExposedPorts == nil { |
| userConf.ExposedPorts = make(map[Port]struct{}) |
| } |
| |
| ports, _, err := parsePortSpecs(imageConf.PortSpecs) |
| if err != nil { |
| return err |
| } |
| for port := range ports { |
| if _, exists := userConf.ExposedPorts[port]; !exists { |
| userConf.ExposedPorts[port] = struct{}{} |
| } |
| } |
| } |
| if !userConf.Tty { |
| userConf.Tty = imageConf.Tty |
| } |
| if !userConf.OpenStdin { |
| userConf.OpenStdin = imageConf.OpenStdin |
| } |
| if !userConf.StdinOnce { |
| userConf.StdinOnce = imageConf.StdinOnce |
| } |
| if userConf.Env == nil || len(userConf.Env) == 0 { |
| userConf.Env = imageConf.Env |
| } else { |
| for _, imageEnv := range imageConf.Env { |
| found := false |
| imageEnvKey := strings.Split(imageEnv, "=")[0] |
| for _, userEnv := range userConf.Env { |
| userEnvKey := strings.Split(userEnv, "=")[0] |
| if imageEnvKey == userEnvKey { |
| found = true |
| } |
| } |
| if !found { |
| userConf.Env = append(userConf.Env, imageEnv) |
| } |
| } |
| } |
| if userConf.Cmd == nil || len(userConf.Cmd) == 0 { |
| userConf.Cmd = imageConf.Cmd |
| } |
| if userConf.Dns == nil || len(userConf.Dns) == 0 { |
| userConf.Dns = imageConf.Dns |
| } else { |
| //duplicates aren't an issue here |
| userConf.Dns = append(userConf.Dns, imageConf.Dns...) |
| } |
| if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { |
| userConf.Entrypoint = imageConf.Entrypoint |
| } |
| if userConf.WorkingDir == "" { |
| userConf.WorkingDir = imageConf.WorkingDir |
| } |
| if userConf.VolumesFrom == "" { |
| userConf.VolumesFrom = imageConf.VolumesFrom |
| } |
| if userConf.Volumes == nil || len(userConf.Volumes) == 0 { |
| userConf.Volumes = imageConf.Volumes |
| } else { |
| for k, v := range imageConf.Volumes { |
| userConf.Volumes[k] = v |
| } |
| } |
| return nil |
| } |
| |
| func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) { |
| out := make([]KeyValuePair, opts.Len()) |
| for i, o := range opts.GetAll() { |
| k, v, err := parseLxcOpt(o) |
| if err != nil { |
| return nil, err |
| } |
| out[i] = KeyValuePair{Key: k, Value: v} |
| } |
| return out, nil |
| } |
| |
| func parseLxcOpt(opt string) (string, string, error) { |
| parts := strings.SplitN(opt, "=", 2) |
| if len(parts) != 2 { |
| return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) |
| } |
| return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil |
| } |
| |
| // FIXME: network related stuff (including parsing) should be grouped in network file |
| const ( |
| PortSpecTemplate = "ip:hostPort:containerPort" |
| PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" |
| ) |
| |
| // We will receive port specs in the format of ip:public:private/proto and these need to be |
| // parsed in the internal types |
| func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { |
| var ( |
| exposedPorts = make(map[Port]struct{}, len(ports)) |
| bindings = make(map[Port][]PortBinding) |
| ) |
| |
| for _, rawPort := range ports { |
| proto := "tcp" |
| |
| if i := strings.LastIndex(rawPort, "/"); i != -1 { |
| proto = rawPort[i+1:] |
| rawPort = rawPort[:i] |
| } |
| if !strings.Contains(rawPort, ":") { |
| rawPort = fmt.Sprintf("::%s", rawPort) |
| } else if len(strings.Split(rawPort, ":")) == 2 { |
| rawPort = fmt.Sprintf(":%s", rawPort) |
| } |
| |
| parts, err := utils.PartParser(PortSpecTemplate, rawPort) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| var ( |
| containerPort = parts["containerPort"] |
| rawIp = parts["ip"] |
| hostPort = parts["hostPort"] |
| ) |
| |
| if containerPort == "" { |
| return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort) |
| } |
| if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil { |
| return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort) |
| } |
| if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil { |
| return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) |
| } |
| |
| port := NewPort(proto, containerPort) |
| if _, exists := exposedPorts[port]; !exists { |
| exposedPorts[port] = struct{}{} |
| } |
| |
| binding := PortBinding{ |
| HostIp: rawIp, |
| HostPort: hostPort, |
| } |
| bslice, exists := bindings[port] |
| if !exists { |
| bslice = []PortBinding{} |
| } |
| bindings[port] = append(bslice, binding) |
| } |
| return exposedPorts, bindings, nil |
| } |
| |
| // Splits a port in the format of port/proto |
| func splitProtoPort(rawPort string) (string, string) { |
| parts := strings.Split(rawPort, "/") |
| l := len(parts) |
| if l == 0 { |
| return "", "" |
| } |
| if l == 1 { |
| return "tcp", rawPort |
| } |
| return parts[0], parts[1] |
| } |
| |
| func parsePort(rawPort string) (int, error) { |
| port, err := strconv.ParseUint(rawPort, 10, 16) |
| if err != nil { |
| return 0, err |
| } |
| return int(port), nil |
| } |
| |
| func migratePortMappings(config *Config, hostConfig *HostConfig) error { |
| if config.PortSpecs != nil { |
| ports, bindings, err := parsePortSpecs(config.PortSpecs) |
| if err != nil { |
| return err |
| } |
| config.PortSpecs = nil |
| if len(bindings) > 0 { |
| if hostConfig == nil { |
| hostConfig = &HostConfig{} |
| } |
| hostConfig.PortBindings = bindings |
| } |
| |
| if config.ExposedPorts == nil { |
| config.ExposedPorts = make(map[Port]struct{}, len(ports)) |
| } |
| for k, v := range ports { |
| config.ExposedPorts[k] = v |
| } |
| } |
| return nil |
| } |
| |
| // Links come in the format of |
| // name:alias |
| func parseLink(rawLink string) (map[string]string, error) { |
| return utils.PartParser("name:alias", rawLink) |
| } |
| |
| func RootIsShared() bool { |
| if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { |
| for _, line := range strings.Split(string(data), "\n") { |
| cols := strings.Split(line, " ") |
| if len(cols) >= 6 && cols[4] == "/" { |
| return strings.HasPrefix(cols[6], "shared") |
| } |
| } |
| } |
| |
| // No idea, probably safe to assume so |
| return true |
| } |
| |
| type checker struct { |
| runtime *Runtime |
| } |
| |
| func (c *checker) Exists(name string) bool { |
| return c.runtime.containerGraph.Exists("/" + name) |
| } |
| |
| // Generate a random and unique name |
| func generateRandomName(runtime *Runtime) (string, error) { |
| return namesgenerator.GenerateRandomName(&checker{runtime}) |
| } |