func (b *buildFile) clearTmp(containers map[string]struct{}) { for c := range containers { tmp := b.runtime.Get(c) b.runtime.Destroy(tmp) fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c)) } }
// Test that an image can be deleted by its shorthand prefix func TestDeletePrefix(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) img := createTestImage(graph, t) if err := graph.Delete(utils.TruncateID(img.ID)); err != nil { t.Fatal(err) } assertNImages(graph, t, 0) }
func TestEnv(t *testing.T) { os.Setenv("TRUE", "false") os.Setenv("TRICKY", "tri\ncky\n") runtime := mkRuntime(t) defer nuke(runtime) config, _, _, err := docker.ParseRun([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil) if err != nil { t.Fatal(err) } container, _, err := runtime.Create(config, "") if err != nil { t.Fatal(err) } defer runtime.Destroy(container) stdout, err := container.StdoutPipe() if err != nil { t.Fatal(err) } defer stdout.Close() if err := container.Start(); err != nil { t.Fatal(err) } container.Wait() output, err := ioutil.ReadAll(stdout) if err != nil { t.Fatal(err) } actualEnv := strings.Split(string(output), "\n") if actualEnv[len(actualEnv)-1] == "" { actualEnv = actualEnv[:len(actualEnv)-1] } sort.Strings(actualEnv) goodEnv := []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/", "container=lxc", "HOSTNAME=" + utils.TruncateID(container.ID), "FALSE=true", "TRUE=false", "TRICKY=tri", "cky", "", } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } }
// During cleanup aufs needs to unmount all mountpoints func (a *Driver) Cleanup() error { ids, err := loadIds(path.Join(a.rootPath(), "layers")) if err != nil { return err } for _, id := range ids { if err := a.unmount(id); err != nil { utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err) } } return nil }
// Commit the container <id> with the autorun command <autoCmd> func (b *buildFile) commit(id string, autoCmd []string, comment string) error { if b.image == "" { return fmt.Errorf("Please provide a source image with `from` prior to commit") } b.config.Image = b.image if id == "" { cmd := b.config.Cmd b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} defer func(cmd []string) { b.config.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { return err } if hit { return nil } container, warnings, err := b.runtime.Create(b.config, "") if err != nil { return err } for _, warning := range warnings { fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning) } b.tmpContainers[container.ID] = struct{}{} fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID)) id = container.ID if err := container.Mount(); err != nil { return err } defer container.Unmount() } container := b.runtime.Get(id) if container == nil { return fmt.Errorf("An error occured while creating the container") } // Note: Actually copy the struct autoConfig := *b.config autoConfig.Cmd = autoCmd // Commit the container image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig) if err != nil { return err } b.tmpImages[image.ID] = struct{}{} b.image = image.ID return nil }
func TestImagesViz(t *testing.T) { stdout, stdoutPipe := io.Pipe() cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) c := make(chan struct{}) go func() { defer close(c) if err := cli.CmdImages("-viz"); err != nil { t.Fatal(err) } stdoutPipe.Close() }() setTimeout(t, "Reading command output time out", 2*time.Second, func() { cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout)) if err != nil { t.Fatal(err) } cmdOutput := string(cmdOutputBytes) regexpStrings := []string{ "digraph docker {", fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort), fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName), fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"), "base \\[style=invisible]", } compiledRegexps := []*regexp.Regexp{} for _, regexpString := range regexpStrings { regexp, err := regexp.Compile(regexpString) if err != nil { fmt.Println("Error in regex string: ", err) return } compiledRegexps = append(compiledRegexps, regexp) } for _, regexp := range compiledRegexps { if !regexp.MatchString(cmdOutput) { t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp) } } }) }
func (b *buildFile) run() (string, error) { if b.image == "" { return "", fmt.Errorf("Please provide a source image with `from` prior to run") } b.config.Image = b.image // Create the container and start it c, _, err := b.runtime.Create(b.config, "") if err != nil { return "", err } b.tmpContainers[c.ID] = struct{}{} fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(c.ID)) // override the entry point that may have been picked up from the base image c.Path = b.config.Cmd[0] c.Args = b.config.Cmd[1:] var errCh chan error if b.verbose { errCh = utils.Go(func() error { return <-c.Attach(nil, nil, b.outStream, b.errStream) }) } //start the container if err := c.Start(); err != nil { return "", err } if errCh != nil { if err := <-errCh; err != nil { return "", err } } // Wait for it to finish if ret := c.Wait(); ret != 0 { err := &utils.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret), Code: ret, } return "", err } return c.ID, nil }
func TestImagesTree(t *testing.T) { stdout, stdoutPipe := io.Pipe() cli := docker.NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalEngine, t) image := buildTestImages(t, globalEngine) c := make(chan struct{}) go func() { defer close(c) if err := cli.CmdImages("-tree"); err != nil { t.Fatal(err) } stdoutPipe.Close() }() setTimeout(t, "Reading command output time out", 2*time.Second, func() { cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout)) if err != nil { t.Fatal(err) } cmdOutput := string(cmdOutputBytes) regexpStrings := []string{ fmt.Sprintf("└─%s Virtual Size: \\d+.\\d+ MB Tags: %s:latest", unitTestImageIDShort, unitTestImageName), "(?m) └─[0-9a-f]+.*", "(?m) └─[0-9a-f]+.*", "(?m) └─[0-9a-f]+.*", fmt.Sprintf("(?m)^ └─%s Virtual Size: \\d+.\\d+ MB Tags: test:latest", utils.TruncateID(image.ID)), } compiledRegexps := []*regexp.Regexp{} for _, regexpString := range regexpStrings { regexp, err := regexp.Compile(regexpString) if err != nil { fmt.Println("Error in regex string: ", err) return } compiledRegexps = append(compiledRegexps, regexp) } for _, regexp := range compiledRegexps { if !regexp.MatchString(cmdOutput) { t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp) } } }) }
func GenerateID() string { for { id := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, id); err != nil { panic(err) // This shouldn't happen } value := hex.EncodeToString(id) // if we try to parse the truncated for as an int and we don't have // an error then the value is all numberic and causes issues when // used as a hostname. ref #3869 if _, err := strconv.Atoi(utils.TruncateID(value)); err == nil { continue } return value } }
func (runtime *Runtime) ensureName(container *Container) error { if container.Name == "" { name, err := generateRandomName(runtime) if err != nil { name = utils.TruncateID(container.ID) } container.Name = name if err := container.ToDisk(); err != nil { utils.Debugf("Error saving container name %s", err) } if !runtime.containerGraph.Exists(name) { if _, err := runtime.containerGraph.Set(name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } } } return nil }
func (container *Container) Kill() error { if !container.State.IsRunning() { return nil } // 1. Send SIGKILL if err := container.kill(9); err != nil { return err } // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { if container.command == nil { return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) } log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) if err := container.runtime.Kill(container, 9); err != nil { return err } } container.Wait() return nil }
func TestRuntimeCreate(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) // Make sure we start we 0 containers if len(runtime.List()) != 0 { t.Errorf("Expected 0 containers, %v found", len(runtime.List())) } container, _, err := runtime.Create(&docker.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, "", ) if err != nil { t.Fatal(err) } defer func() { if err := runtime.Destroy(container); err != nil { t.Error(err) } }() // Make sure we can find the newly created container with List() if len(runtime.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(runtime.List())) } // Make sure the container List() returns is the right one if runtime.List()[0].ID != container.ID { t.Errorf("Unexpected container %v returned by List", runtime.List()[0]) } // Make sure we can get the container with Get() if runtime.Get(container.ID) == nil { t.Errorf("Unable to get newly created container") } // Make sure it is the right container if runtime.Get(container.ID) != container { t.Errorf("Get() returned the wrong container") } // Make sure Exists returns it as existing if !runtime.Exists(container.ID) { t.Errorf("Exists() returned false for a newly created container") } // Test that conflict error displays correct details testContainer, _, _ := runtime.Create( &docker.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, "conflictname", ) if _, _, err := runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}}, testContainer.Name); err == nil || !strings.Contains(err.Error(), utils.TruncateID(testContainer.ID)) { t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %s", err.Error()) } // Make sure create with bad parameters returns an error if _, _, err = runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID}, ""); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is missing") } if _, _, err := runtime.Create( &docker.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{}, }, "", ); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is empty") } config := &docker.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/ls"}, PortSpecs: []string{"80"}, } container, _, err = runtime.Create(config, "") _, err = runtime.Commit(container, "testrepo", "testtag", "", "", config) if err != nil { t.Error(err) } // test expose 80:8000 container, warnings, err := runtime.Create(&docker.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, PortSpecs: []string{"80:8000"}, }, "", ) if err != nil { t.Fatal(err) } if warnings == nil || len(warnings) != 1 { t.Error("Expected a warning, got none") } }
// TempLayerArchive creates a temporary archive of the given image's filesystem layer. // The archive is stored on disk and will be automatically deleted as soon as has been read. // If output is not nil, a human-readable progress bar will be written to it. // FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, sf *utils.StreamFormatter, output io.Writer) (*archive.TempArchive, error) { image, err := graph.Get(id) if err != nil { return nil, err } tmp, err := graph.Mktemp("") if err != nil { return nil, err } a, err := image.TarLayer() if err != nil { return nil, err } return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf, false, utils.TruncateID(id), "Buffering to disk"), tmp) }
// TestAttachDetachTruncatedID checks that attach in tty mode can be detached func TestAttachDetachTruncatedID(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalEngine, t) // Discard the CmdRun output go stdout.Read(make([]byte, 1024)) setTimeout(t, "Starting container timed out", 2*time.Second, func() { if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { t.Fatal(err) } }) container := waitContainerStart(t, 10*time.Second) state := setRaw(t, container) defer unsetRaw(t, container, state) stdin, stdinPipe = io.Pipe() stdout, stdoutPipe = io.Pipe() cli = docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) ch := make(chan struct{}) go func() { defer close(ch) if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil { if err != io.ErrClosedPipe { t.Fatal(err) } } }() setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 150); err != nil { if err != io.ErrClosedPipe { t.Fatal(err) } } }) setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { stdinPipe.Write([]byte{16}) time.Sleep(100 * time.Millisecond) stdinPipe.Write([]byte{17}) }) // wait for CmdRun to return setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { <-ch }) closeWrap(stdin, stdinPipe, stdout, stdoutPipe) time.Sleep(500 * time.Millisecond) if !container.State.IsRunning() { t.Fatal("The detached container should be still running") } setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { container.Kill() }) }
func (b *buildFile) Build(context io.Reader) (string, error) { tmpdirPath, err := ioutil.TempDir("", "docker-build") if err != nil { return "", err } decompressedStream, err := archive.DecompressStream(context) if err != nil { return "", err } b.context = &utils.TarSum{Reader: decompressedStream, DisableCompression: true} if err := archive.Untar(b.context, tmpdirPath, nil); err != nil { return "", err } defer os.RemoveAll(tmpdirPath) b.contextPath = tmpdirPath filename := path.Join(tmpdirPath, "Dockerfile") if _, err := os.Stat(filename); os.IsNotExist(err) { return "", fmt.Errorf("Can't build a directory with no Dockerfile") } fileBytes, err := ioutil.ReadFile(filename) if err != nil { return "", err } if len(fileBytes) == 0 { return "", ErrDockerfileEmpty } dockerfile := string(fileBytes) dockerfile = lineContinuation.ReplaceAllString(dockerfile, "") stepN := 0 for _, line := range strings.Split(dockerfile, "\n") { line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") // Skip comments and empty line if len(line) == 0 || line[0] == '#' { continue } tmp := strings.SplitN(line, " ", 2) if len(tmp) != 2 { return "", fmt.Errorf("Invalid Dockerfile format") } instruction := strings.ToLower(strings.Trim(tmp[0], " ")) arguments := strings.Trim(tmp[1], " ") method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:])) if !exists { fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) continue } stepN += 1 fmt.Fprintf(b.outStream, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface() if ret != nil { return "", ret.(error) } fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image)) } if b.image != "" { fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image)) if b.rm { b.clearTmp(b.tmpContainers) } return b.image, nil } return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n") }
func (runtime *Runtime) restore() error { if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("Loading containers: ") } dir, err := ioutil.ReadDir(runtime.repository) if err != nil { return err } containers := make(map[string]*Container) currentDriver := runtime.driver.String() for _, v := range dir { id := v.Name() container, err := runtime.load(id) if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Print(".") } if err != nil { utils.Errorf("Failed to load container %v: %v", id, err) continue } // Ignore the container if it does not support the current driver being used by the graph if container.Driver == "" && currentDriver == "aufs" || container.Driver == currentDriver { utils.Debugf("Loaded container %v", container.ID) containers[container.ID] = container } else { utils.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) } } register := func(container *Container) { if err := runtime.Register(container); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) } } if entities := runtime.containerGraph.List("/", -1); entities != nil { for _, p := range entities.Paths() { if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Print(".") } e := entities[p] if container, ok := containers[e.ID()]; ok { register(container) delete(containers, e.ID()) } } } // Any containers that are left over do not exist in the graph for _, container := range containers { // Try to set the default name for a container if it exists prior to links container.Name, err = generateRandomName(runtime) if err != nil { container.Name = utils.TruncateID(container.ID) } if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } register(container) } if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf(": done.\n") } return nil }
// Create creates a new container from the given configuration with a given name. func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) { // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { return nil, nil, err } // We add 2 layers to the depth because the container's rw and // init layer add to the restriction depth, err := img.Depth() if err != nil { return nil, nil, err } if depth+2 >= MaxImageDepth { return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth) } checkDeprecatedExpose := func(config *Config) bool { if config != nil { if config.PortSpecs != nil { for _, p := range config.PortSpecs { if strings.Contains(p, ":") { return true } } } } return false } warnings := []string{} if checkDeprecatedExpose(img.Config) || checkDeprecatedExpose(config) { warnings = append(warnings, "The mapping to public ports on your host has been deprecated. Use -p to publish the ports.") } if img.Config != nil { if err := MergeConfig(config, img.Config); err != nil { return nil, nil, err } } if len(config.Entrypoint) != 0 && config.Cmd == nil { config.Cmd = []string{} } else if config.Cmd == nil || len(config.Cmd) == 0 { return nil, nil, fmt.Errorf("No command specified") } // Generate id id := GenerateID() if name == "" { name, err = generateRandomName(runtime) if err != nil { name = utils.TruncateID(id) } } else { if !validContainerNamePattern.MatchString(name) { return nil, nil, fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) } } if name[0] != '/' { name = "/" + name } // Set the enitity in the graph using the default name specified if _, err := runtime.containerGraph.Set(name, id); err != nil { if !strings.HasSuffix(err.Error(), "name are not unique") { return nil, nil, err } conflictingContainer, err := runtime.GetByName(name) if err != nil { if strings.Contains(err.Error(), "Could not find entity") { return nil, nil, err } // Remove name and continue starting the container if err := runtime.containerGraph.Delete(name); err != nil { return nil, nil, err } } else { nameAsKnownByUser := strings.TrimPrefix(name, "/") return nil, nil, fmt.Errorf( "Conflict, The name %s is already assigned to %s. You have to delete (or rename) that container to be able to assign %s to a container again.", nameAsKnownByUser, utils.TruncateID(conflictingContainer.ID), nameAsKnownByUser) } } // Generate default hostname // FIXME: the lxc template no longer needs to set a default hostname if config.Hostname == "" { config.Hostname = id[:12] } var args []string var entrypoint string if len(config.Entrypoint) != 0 { entrypoint = config.Entrypoint[0] args = append(config.Entrypoint[1:], config.Cmd...) } else { entrypoint = config.Cmd[0] args = config.Cmd[1:] } container := &Container{ // FIXME: we should generate the ID here instead of receiving it as an argument ID: id, Created: time.Now().UTC(), Path: entrypoint, Args: args, //FIXME: de-duplicate from config Config: config, hostConfig: &HostConfig{}, Image: img.ID, // Always use the resolved image id NetworkSettings: &NetworkSettings{}, Name: name, Driver: runtime.driver.String(), } container.root = runtime.containerRoot(container.ID) // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { return nil, nil, err } initID := fmt.Sprintf("%s-init", container.ID) if err := runtime.driver.Create(initID, img.ID); err != nil { return nil, nil, err } initPath, err := runtime.driver.Get(initID) if err != nil { return nil, nil, err } defer runtime.driver.Put(initID) if err := setupInitLayer(initPath); err != nil { return nil, nil, err } if err := runtime.driver.Create(container.ID, initID); err != nil { return nil, nil, err } resolvConf, err := utils.GetResolvConf() if err != nil { return nil, nil, err } if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns runtime.config.Dns = defaultDns } // If custom dns exists, then create a resolv.conf for the container if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 { var dns []string if len(config.Dns) > 0 { dns = config.Dns } else { dns = runtime.config.Dns } container.ResolvConfPath = path.Join(container.root, "resolv.conf") f, err := os.Create(container.ResolvConfPath) if err != nil { return nil, nil, err } defer f.Close() for _, dns := range dns { if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { return nil, nil, err } } } else { container.ResolvConfPath = "/etc/resolv.conf" } // Step 2: save the container json if err := container.ToDisk(); err != nil { return nil, nil, err } // Step 3: register the container if err := runtime.Register(container); err != nil { return nil, nil, err } return container, warnings, nil }
func (store *TagStore) ImageName(id string) string { if names, exists := store.ByID()[id]; exists && len(names) > 0 { return names[0] } return utils.TruncateID(id) }