Merge pull request #5839 from unclejack/improve_build_rm
add --force-rm to clean up after a failed build
This commit is contained in:
commit
db1a3551a3
|
@ -110,6 +110,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
||||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||||
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
|
||||||
|
forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers, even after unsuccessful builds")
|
||||||
if err := cmd.Parse(args); err != nil {
|
if err := cmd.Parse(args); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -197,6 +198,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
}
|
}
|
||||||
if *rm {
|
if *rm {
|
||||||
v.Set("rm", "1")
|
v.Set("rm", "1")
|
||||||
|
} else {
|
||||||
|
v.Set("rm", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *forceRm {
|
||||||
|
v.Set("forcerm", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.LoadConfigFile()
|
cli.LoadConfigFile()
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
APIVERSION version.Version = "1.11"
|
APIVERSION version.Version = "1.12"
|
||||||
DEFAULTHTTPHOST = "127.0.0.1"
|
DEFAULTHTTPHOST = "127.0.0.1"
|
||||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||||
)
|
)
|
||||||
|
|
|
@ -901,12 +901,20 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
|
||||||
} else {
|
} else {
|
||||||
job.Stdout.Add(utils.NewWriteFlusher(w))
|
job.Stdout.Add(utils.NewWriteFlusher(w))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") {
|
||||||
|
job.Setenv("rm", "1")
|
||||||
|
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
||||||
|
job.Setenv("rm", "1")
|
||||||
|
} else {
|
||||||
|
job.Setenv("rm", r.FormValue("rm"))
|
||||||
|
}
|
||||||
job.Stdin.Add(r.Body)
|
job.Stdin.Add(r.Body)
|
||||||
job.Setenv("remote", r.FormValue("remote"))
|
job.Setenv("remote", r.FormValue("remote"))
|
||||||
job.Setenv("t", r.FormValue("t"))
|
job.Setenv("t", r.FormValue("t"))
|
||||||
job.Setenv("q", r.FormValue("q"))
|
job.Setenv("q", r.FormValue("q"))
|
||||||
job.Setenv("nocache", r.FormValue("nocache"))
|
job.Setenv("nocache", r.FormValue("nocache"))
|
||||||
job.Setenv("rm", r.FormValue("rm"))
|
job.Setenv("forcerm", r.FormValue("forcerm"))
|
||||||
job.SetenvJson("authConfig", authConfig)
|
job.SetenvJson("authConfig", authConfig)
|
||||||
job.SetenvJson("configFile", configFile)
|
job.SetenvJson("configFile", configFile)
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,23 @@ page_keywords: API, Docker, rcli, REST, documentation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The current version of the API is v1.11
|
The current version of the API is v1.12
|
||||||
|
|
||||||
Calling /images/<name>/insert is the same as calling
|
Calling /images/<name>/insert is the same as calling
|
||||||
/v1.11/images/<name>/insert
|
/v1.12/images/<name>/insert
|
||||||
|
|
||||||
You can still call an old version of the api using
|
You can still call an old version of the api using
|
||||||
/v1.11/images/<name>/insert
|
/v1.12/images/<name>/insert
|
||||||
|
|
||||||
|
## v1.12
|
||||||
|
|
||||||
|
### Full Documentation
|
||||||
|
|
||||||
|
[*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/)
|
||||||
|
|
||||||
|
### What's new
|
||||||
|
|
||||||
|
docker build now has support for the `forcerm` parameter to always remove containers
|
||||||
|
|
||||||
## v1.11
|
## v1.11
|
||||||
|
|
||||||
|
|
|
@ -1023,6 +1023,7 @@ Build an image from Dockerfile via stdin
|
||||||
the resulting image in case of success
|
the resulting image in case of success
|
||||||
- **q** – suppress verbose build output
|
- **q** – suppress verbose build output
|
||||||
- **nocache** – do not use the cache when building the image
|
- **nocache** – do not use the cache when building the image
|
||||||
|
- **rm** - remove intermediate containers after a successful build
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
|
|
|
@ -1063,6 +1063,7 @@ Build an image from Dockerfile via stdin
|
||||||
the resulting image in case of success
|
the resulting image in case of success
|
||||||
- **q** – suppress verbose build output
|
- **q** – suppress verbose build output
|
||||||
- **nocache** – do not use the cache when building the image
|
- **nocache** – do not use the cache when building the image
|
||||||
|
- **rm** - remove intermediate containers after a successful build
|
||||||
|
|
||||||
Request Headers:
|
Request Headers:
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -192,6 +192,7 @@ To kill the container, use `docker kill`.
|
||||||
|
|
||||||
Build a new container image from the source code at PATH
|
Build a new container image from the source code at PATH
|
||||||
|
|
||||||
|
--force-rm=false Always remove intermediate containers, even after unsuccessful builds
|
||||||
--no-cache=false Do not use cache when building the image
|
--no-cache=false Do not use cache when building the image
|
||||||
-q, --quiet=false Suppress the verbose output generated by the containers
|
-q, --quiet=false Suppress the verbose output generated by the containers
|
||||||
--rm=true Remove intermediate containers after a successful build
|
--rm=true Remove intermediate containers after a successful build
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
FROM busybox
|
||||||
|
RUN true
|
||||||
|
RUN thiswillfail
|
|
@ -0,0 +1,4 @@
|
||||||
|
FROM busybox
|
||||||
|
ADD foo /
|
||||||
|
ADD foo /
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
bar
|
|
@ -264,6 +264,118 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) {
|
||||||
logDone("build - ADD from context with accessible links must work")
|
logDone("build - ADD from context with accessible links must work")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildForceRm(t *testing.T) {
|
||||||
|
containerCountBefore, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildForceRm")
|
||||||
|
buildCmd := exec.Command(dockerBinary, "build", "--force-rm", ".")
|
||||||
|
buildCmd.Dir = buildDirectory
|
||||||
|
_, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
|
|
||||||
|
if err == nil || exitCode == 0 {
|
||||||
|
t.Fatal("failed to build the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCountAfter, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerCountBefore != containerCountAfter {
|
||||||
|
t.Fatalf("--force-rm shouldn't have left containers behind")
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("build - ensure --force-rm doesn't leave containers behind")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildRm(t *testing.T) {
|
||||||
|
{
|
||||||
|
containerCountBefore, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm")
|
||||||
|
buildCmd := exec.Command(dockerBinary, "build", "--rm", "-t", "testbuildrm", ".")
|
||||||
|
buildCmd.Dir = buildDirectory
|
||||||
|
_, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
|
|
||||||
|
if err != nil || exitCode != 0 {
|
||||||
|
t.Fatal("failed to build the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCountAfter, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerCountBefore != containerCountAfter {
|
||||||
|
t.Fatalf("-rm shouldn't have left containers behind")
|
||||||
|
}
|
||||||
|
deleteImages("testbuildrm")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
containerCountBefore, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm")
|
||||||
|
buildCmd := exec.Command(dockerBinary, "build", "-t", "testbuildrm", ".")
|
||||||
|
buildCmd.Dir = buildDirectory
|
||||||
|
_, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
|
|
||||||
|
if err != nil || exitCode != 0 {
|
||||||
|
t.Fatal("failed to build the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCountAfter, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerCountBefore != containerCountAfter {
|
||||||
|
t.Fatalf("--rm shouldn't have left containers behind")
|
||||||
|
}
|
||||||
|
deleteImages("testbuildrm")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
containerCountBefore, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildDirectory := filepath.Join(workingDirectory, "build_tests", "TestBuildRm")
|
||||||
|
buildCmd := exec.Command(dockerBinary, "build", "--rm=false", "-t", "testbuildrm", ".")
|
||||||
|
buildCmd.Dir = buildDirectory
|
||||||
|
_, exitCode, err := runCommandWithOutput(buildCmd)
|
||||||
|
|
||||||
|
if err != nil || exitCode != 0 {
|
||||||
|
t.Fatal("failed to build the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCountAfter, err := getContainerCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get the container count: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if containerCountBefore == containerCountAfter {
|
||||||
|
t.Fatalf("--rm=false should have left containers behind")
|
||||||
|
}
|
||||||
|
deleteAllContainers()
|
||||||
|
deleteImages("testbuildrm")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default")
|
||||||
|
logDone("build - ensure --rm=false overrides the default")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: TestCaching
|
// TODO: TestCaching
|
||||||
|
|
||||||
// TODO: TestADDCacheInvalidation
|
// TODO: TestADDCacheInvalidation
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -71,3 +72,28 @@ func findContainerIp(t *testing.T, id string) string {
|
||||||
|
|
||||||
return strings.Trim(out, " \r\n'")
|
return strings.Trim(out, " \r\n'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContainerCount() (int, error) {
|
||||||
|
const containers = "Containers:"
|
||||||
|
|
||||||
|
cmd := exec.Command(dockerBinary, "info")
|
||||||
|
out, _, err := runCommandWithOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, containers) {
|
||||||
|
output := stripTrailingCharacters(line)
|
||||||
|
output = strings.TrimLeft(output, containers)
|
||||||
|
output = strings.Trim(output, " ")
|
||||||
|
containerCount, err := strconv.Atoi(output)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return containerCount, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("couldn't find the Container count in the output")
|
||||||
|
}
|
||||||
|
|
|
@ -397,7 +397,7 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u
|
||||||
}
|
}
|
||||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||||
|
|
||||||
buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
||||||
id, err := buildfile.Build(context.Archive(dockerfile, t))
|
id, err := buildfile.Build(context.Archive(dockerfile, t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -839,7 +839,7 @@ func TestForbiddenContextPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||||
|
|
||||||
buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
buildfile := server.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
||||||
_, err = buildfile.Build(context.Archive(dockerfile, t))
|
_, err = buildfile.Build(context.Archive(dockerfile, t))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -885,7 +885,7 @@ func TestBuildADDFileNotFound(t *testing.T) {
|
||||||
}
|
}
|
||||||
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
dockerfile := constructDockerfile(context.dockerfile, ip, port)
|
||||||
|
|
||||||
buildfile := server.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
buildfile := server.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil)
|
||||||
_, err = buildfile.Build(context.Archive(dockerfile, t))
|
_, err = buildfile.Build(context.Archive(dockerfile, t))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -52,6 +52,7 @@ type buildFile struct {
|
||||||
verbose bool
|
verbose bool
|
||||||
utilizeCache bool
|
utilizeCache bool
|
||||||
rm bool
|
rm bool
|
||||||
|
forceRm bool
|
||||||
|
|
||||||
authConfig *registry.AuthConfig
|
authConfig *registry.AuthConfig
|
||||||
configFile *registry.ConfigFile
|
configFile *registry.ConfigFile
|
||||||
|
@ -817,6 +818,9 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
|
if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil {
|
||||||
|
if b.forceRm {
|
||||||
|
b.clearTmp(b.tmpContainers)
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
} else if b.rm {
|
} else if b.rm {
|
||||||
b.clearTmp(b.tmpContainers)
|
b.clearTmp(b.tmpContainers)
|
||||||
|
@ -869,7 +873,7 @@ func stripComments(raw []byte) string {
|
||||||
return strings.Join(out, "\n")
|
return strings.Join(out, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile {
|
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, forceRm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile {
|
||||||
return &buildFile{
|
return &buildFile{
|
||||||
daemon: srv.daemon,
|
daemon: srv.daemon,
|
||||||
srv: srv,
|
srv: srv,
|
||||||
|
@ -881,6 +885,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
utilizeCache: utilizeCache,
|
utilizeCache: utilizeCache,
|
||||||
rm: rm,
|
rm: rm,
|
||||||
|
forceRm: forceRm,
|
||||||
sf: sf,
|
sf: sf,
|
||||||
authConfig: auth,
|
authConfig: auth,
|
||||||
configFile: authConfigFile,
|
configFile: authConfigFile,
|
||||||
|
|
|
@ -424,6 +424,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
|
||||||
suppressOutput = job.GetenvBool("q")
|
suppressOutput = job.GetenvBool("q")
|
||||||
noCache = job.GetenvBool("nocache")
|
noCache = job.GetenvBool("nocache")
|
||||||
rm = job.GetenvBool("rm")
|
rm = job.GetenvBool("rm")
|
||||||
|
forceRm = job.GetenvBool("forcerm")
|
||||||
authConfig = ®istry.AuthConfig{}
|
authConfig = ®istry.AuthConfig{}
|
||||||
configFile = ®istry.ConfigFile{}
|
configFile = ®istry.ConfigFile{}
|
||||||
tag string
|
tag string
|
||||||
|
@ -482,7 +483,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
|
||||||
Writer: job.Stdout,
|
Writer: job.Stdout,
|
||||||
StreamFormatter: sf,
|
StreamFormatter: sf,
|
||||||
},
|
},
|
||||||
!suppressOutput, !noCache, rm, job.Stdout, sf, authConfig, configFile)
|
!suppressOutput, !noCache, rm, forceRm, job.Stdout, sf, authConfig, configFile)
|
||||||
id, err := b.Build(context)
|
id, err := b.Build(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return job.Error(err)
|
return job.Error(err)
|
||||||
|
|
Loading…
Reference in New Issue