1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #1158 from cespare/1142-docker-add-fix

*Buildfile: determine a filename from a URL if the destination is a directory
This commit is contained in:
Victor Vieux 2013-07-22 07:07:04 -07:00
commit 4e7f2b757e
3 changed files with 143 additions and 43 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"os" "os"
"path" "path"
"reflect" "reflect"
@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
} }
defer file.Body.Close() defer file.Body.Close()
// If the destination is a directory, figure out the filename.
if strings.HasSuffix(dest, "/") {
u, err := url.Parse(orig)
if err != nil {
return err
}
path := u.Path
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
parts := strings.Split(path, "/")
filename := parts[len(parts)-1]
if filename == "" {
return fmt.Errorf("cannot determine filename from url: %s", u)
}
dest = dest + filename
}
return container.Inject(file.Body, dest) return container.Inject(file.Body, dest)
} }
@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
origPath := path.Join(b.context, orig) origPath := path.Join(b.context, orig)
destPath := path.Join(container.RootfsPath(), dest) destPath := path.Join(container.RootfsPath(), dest)
// Preserve the trailing '/' // Preserve the trailing '/'
if dest[len(dest)-1] == '/' { if strings.HasSuffix(dest, "/") {
destPath = destPath + "/" destPath = destPath + "/"
} }
fi, err := os.Stat(origPath) fi, err := os.Stat(origPath)

View file

@ -3,13 +3,17 @@ package docker
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing" "testing"
) )
// mkTestContext generates a build context from the contents of the provided dockerfile. // mkTestContext generates a build context from the contents of the provided dockerfile.
// This context is suitable for use as an argument to BuildFile.Build() // This context is suitable for use as an argument to BuildFile.Build()
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive { func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files) context, err := mkBuildContext(dockerfile, files)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -22,6 +26,8 @@ type testContextTemplate struct {
dockerfile string dockerfile string
// Additional files in the context, eg [][2]string{"./passwd", "gordon"} // Additional files in the context, eg [][2]string{"./passwd", "gordon"}
files [][2]string files [][2]string
// Additional remote files to host on a local HTTP server.
remoteFiles [][2]string
} }
// A table of all the contexts to build and test. // A table of all the contexts to build and test.
@ -29,27 +35,31 @@ type testContextTemplate struct {
var testContexts = []testContextTemplate{ var testContexts = []testContextTemplate{
{ {
` `
from %s from {IMAGE}
run sh -c 'echo root:testpass > /tmp/passwd' run sh -c 'echo root:testpass > /tmp/passwd'
run mkdir -p /var/run/sshd run mkdir -p /var/run/sshd
run [ "$(cat /tmp/passwd)" = "root:testpass" ] run [ "$(cat /tmp/passwd)" = "root:testpass" ]
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
`, `,
nil, nil,
nil,
}, },
{ {
` `
from %s from {IMAGE}
add foo /usr/lib/bla/bar add foo /usr/lib/bla/bar
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ] run [ "$(cat /usr/lib/bla/bar)" = 'hello' ]
add http://{SERVERADDR}/baz /usr/lib/baz/quux
run [ "$(cat /usr/lib/baz/quux)" = 'world!' ]
`, `,
[][2]string{{"foo", "hello world!"}}, [][2]string{{"foo", "hello"}},
[][2]string{{"/baz", "world!"}},
}, },
{ {
` `
from %s from {IMAGE}
add f / add f /
run [ "$(cat /f)" = "hello" ] run [ "$(cat /f)" = "hello" ]
add f /abc add f /abc
@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
{"f", "hello"}, {"f", "hello"},
{"d/ga", "bu"}, {"d/ga", "bu"},
}, },
nil,
}, },
{ {
` `
from %s from {IMAGE}
add http://{SERVERADDR}/x /a/b/c
run [ "$(cat /a/b/c)" = "hello" ]
add http://{SERVERADDR}/x?foo=bar /
run [ "$(cat /x)" = "hello" ]
add http://{SERVERADDR}/x /d/
run [ "$(cat /d/x)" = "hello" ]
add http://{SERVERADDR} /e
run [ "$(cat /e)" = "blah" ]
`,
nil,
[][2]string{{"/x", "hello"}, {"/", "blah"}},
},
{
`
from {IMAGE}
env FOO BAR env FOO BAR
run [ "$FOO" = "BAR" ] run [ "$FOO" = "BAR" ]
`, `,
nil, nil,
},
{
`
from %s
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil, nil,
}, },
{ {
` `
from %s from {IMAGE}
ENTRYPOINT /bin/echo
CMD Hello world
`,
nil,
nil,
},
{
`
from {IMAGE}
VOLUME /test VOLUME /test
CMD Hello world CMD Hello world
`, `,
nil, nil,
nil,
}, },
} }
// FIXME: test building with 2 successive overlapping ADD commands // FIXME: test building with 2 successive overlapping ADD commands
func constructDockerfile(template string, ip net.IP, port string) string {
serverAddr := fmt.Sprintf("%s:%s", ip, port)
replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr)
return replacer.Replace(template)
}
func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
mux := http.NewServeMux()
for _, file := range files {
name, contents := file[0], file[1]
mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(contents))
})
}
// This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote
// connections (from the container).
listener, err := net.Listen("tcp", ":0")
if err != nil {
return nil, err
}
s := httptest.NewUnstartedServer(mux)
s.Listener = listener
s.Start()
return s, nil
}
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
for _, ctx := range testContexts { for _, ctx := range testContexts {
buildImage(ctx, t) buildImage(ctx, t)
@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
pullingPool: make(map[string]struct{}), pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}),
} }
buildfile := NewBuildFile(srv, ioutil.Discard, false)
id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t)) httpServer, err := mkTestingFileServer(context.remoteFiles)
if err != nil {
t.Fatal(err)
}
defer httpServer.Close()
idx := strings.LastIndex(httpServer.URL, ":")
if idx < 0 {
t.Fatalf("could not get port from test http server address %s", httpServer.URL)
}
port := httpServer.URL[idx+1:]
ip := runtime.networkManager.bridgeNetwork.IP
dockerfile := constructDockerfile(context.dockerfile, ip, port)
buildfile := NewBuildFile(srv, ioutil.Discard, false)
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
func TestVolume(t *testing.T) { func TestVolume(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
volume /test volume /test
cmd Hello world cmd Hello world
`, nil}, t) `, nil, nil}, t)
if len(img.Config.Volumes) == 0 { if len(img.Config.Volumes) == 0 {
t.Fail() t.Fail()
@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
func TestBuildMaintainer(t *testing.T) { func TestBuildMaintainer(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
maintainer dockerio maintainer dockerio
`, nil}, t) `, nil, nil}, t)
if img.Author != "dockerio" { if img.Author != "dockerio" {
t.Fail() t.Fail()
@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
func TestBuildEnv(t *testing.T) { func TestBuildEnv(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
env port 4243 env port 4243
`, `,
nil}, t) nil, nil}, t)
if img.Config.Env[0] != "port=4243" { if img.Config.Env[0] != "port=4243" {
t.Fail() t.Fail()
@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
func TestBuildCmd(t *testing.T) { func TestBuildCmd(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
cmd ["/bin/echo", "Hello World"] cmd ["/bin/echo", "Hello World"]
`, `,
nil}, t) nil, nil}, t)
if img.Config.Cmd[0] != "/bin/echo" { if img.Config.Cmd[0] != "/bin/echo" {
t.Log(img.Config.Cmd[0]) t.Log(img.Config.Cmd[0])
@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
func TestBuildExpose(t *testing.T) { func TestBuildExpose(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
expose 4243 expose 4243
`, `,
nil}, t) nil, nil}, t)
if img.Config.PortSpecs[0] != "4243" { if img.Config.PortSpecs[0] != "4243" {
t.Fail() t.Fail()
@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
func TestBuildEntrypoint(t *testing.T) { func TestBuildEntrypoint(t *testing.T) {
img := buildImage(testContextTemplate{` img := buildImage(testContextTemplate{`
from %s from {IMAGE}
entrypoint ["/bin/echo"] entrypoint ["/bin/echo"]
`, `,
nil}, t) nil, nil}, t)
if img.Config.Entrypoint[0] != "/bin/echo" { if img.Config.Entrypoint[0] != "/bin/echo" {
} }

View file

@ -152,6 +152,14 @@ destination container.
The copy obeys the following rules: The copy obeys the following rules:
* If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
then a file is downloaded from the URL and copied to ``<dest>``.
* If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
then the filename is inferred from the URL and the file is downloaded to
``<dest>/<filename>``. For instance, ``ADD http://example.com/foobar /``
would create the file ``/foobar``. The URL must have a nontrivial path
so that an appropriate filename can be discovered in this case
(``http://example.com`` will not work).
* If ``<src>`` is a directory, the entire directory is copied, * If ``<src>`` is a directory, the entire directory is copied,
including filesystem metadata. including filesystem metadata.
* If ``<src>``` is a tar archive in a recognized compression format * If ``<src>``` is a tar archive in a recognized compression format