123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- package docker
- import (
- "fmt"
- "github.com/dotcloud/docker/archive"
- "io/ioutil"
- "net"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- )
- // mkTestContext generates a build context from the contents of the provided dockerfile.
- // This context is suitable for use as an argument to BuildFile.Build()
- func mkTestContext(dockerfile string, files [][2]string, t *testing.T) archive.Archive {
- context, err := mkBuildContext(dockerfile, files)
- if err != nil {
- t.Fatal(err)
- }
- return context
- }
- // A testContextTemplate describes a build context and how to test it
- type testContextTemplate struct {
- // Contents of the Dockerfile
- dockerfile string
- // Additional files in the context, eg [][2]string{"./passwd", "gordon"}
- 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 new docker runtime will be created and torn down for each context.
- var testContexts = []testContextTemplate{
- {
- `
- from {IMAGE}
- run sh -c 'echo root:testpass > /tmp/passwd'
- run mkdir -p /var/run/sshd
- run [ "$(cat /tmp/passwd)" = "root:testpass" ]
- run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
- `,
- nil,
- nil,
- },
- // Exactly the same as above, except uses a line split with a \ to test
- // multiline support.
- {
- `
- from {IMAGE}
- run sh -c 'echo root:testpass \
- > /tmp/passwd'
- run mkdir -p /var/run/sshd
- run [ "$(cat /tmp/passwd)" = "root:testpass" ]
- run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
- `,
- nil,
- nil,
- },
- // Line containing literal "\n"
- {
- `
- from {IMAGE}
- run sh -c 'echo root:testpass > /tmp/passwd'
- run echo "foo \n bar"; echo "baz"
- run mkdir -p /var/run/sshd
- run [ "$(cat /tmp/passwd)" = "root:testpass" ]
- run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
- `,
- nil,
- nil,
- },
- {
- `
- from {IMAGE}
- add foo /usr/lib/bla/bar
- 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"}},
- [][2]string{{"/baz", "world!"}},
- },
- {
- `
- from {IMAGE}
- add f /
- run [ "$(cat /f)" = "hello" ]
- add f /abc
- run [ "$(cat /abc)" = "hello" ]
- add f /x/y/z
- run [ "$(cat /x/y/z)" = "hello" ]
- add f /x/y/d/
- run [ "$(cat /x/y/d/f)" = "hello" ]
- add d /
- run [ "$(cat /ga)" = "bu" ]
- add d /somewhere
- run [ "$(cat /somewhere/ga)" = "bu" ]
- add d /anotherplace/
- run [ "$(cat /anotherplace/ga)" = "bu" ]
- add d /somewheeeere/over/the/rainbooow
- run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
- `,
- [][2]string{
- {"f", "hello"},
- {"d/ga", "bu"},
- },
- nil,
- },
- {
- `
- 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
- run [ "$FOO" = "BAR" ]
- `,
- nil,
- nil,
- },
- {
- `
- from {IMAGE}
- ENTRYPOINT /bin/echo
- CMD Hello world
- `,
- nil,
- nil,
- },
- {
- `
- from {IMAGE}
- VOLUME /test
- CMD Hello world
- `,
- nil,
- nil,
- },
- {
- `
- from {IMAGE}
- env FOO /foo/baz
- env BAR /bar
- env BAZ $BAR
- env FOOPATH $PATH:$FOO
- run [ "$BAR" = "$BAZ" ]
- run [ "$FOOPATH" = "$PATH:/foo/baz" ]
- `,
- nil,
- nil,
- },
- {
- `
- from {IMAGE}
- env FOO /bar
- env TEST testdir
- env BAZ /foobar
- add testfile $BAZ/
- add $TEST $FOO
- run [ "$(cat /foobar/testfile)" = "test1" ]
- run [ "$(cat /bar/withfile)" = "test2" ]
- `,
- [][2]string{
- {"testfile", "test1"},
- {"testdir/withfile", "test2"},
- },
- nil,
- },
- }
- // 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) {
- for _, ctx := range testContexts {
- buildImage(ctx, t, nil, true)
- }
- }
- func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
- if srv == nil {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv = &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- }
- 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 := srv.runtime.networkManager.bridgeNetwork.IP
- dockerfile := constructDockerfile(context.dockerfile, ip, port)
- buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache, false)
- id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
- if err != nil {
- t.Fatal(err)
- }
- img, err := srv.ImageInspect(id)
- if err != nil {
- t.Fatal(err)
- }
- return img
- }
- func TestVolume(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- volume /test
- cmd Hello world
- `, nil, nil}, t, nil, true)
- if len(img.Config.Volumes) == 0 {
- t.Fail()
- }
- for key := range img.Config.Volumes {
- if key != "/test" {
- t.Fail()
- }
- }
- }
- func TestBuildMaintainer(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- maintainer dockerio
- `, nil, nil}, t, nil, true)
- if img.Author != "dockerio" {
- t.Fail()
- }
- }
- func TestBuildUser(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- user dockerio
- `, nil, nil}, t, nil, true)
- if img.Config.User != "dockerio" {
- t.Fail()
- }
- }
- func TestBuildEnv(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- env port 4243
- `,
- nil, nil}, t, nil, true)
- hasEnv := false
- for _, envVar := range img.Config.Env {
- if envVar == "port=4243" {
- hasEnv = true
- break
- }
- }
- if !hasEnv {
- t.Fail()
- }
- }
- func TestBuildCmd(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- cmd ["/bin/echo", "Hello World"]
- `,
- nil, nil}, t, nil, true)
- if img.Config.Cmd[0] != "/bin/echo" {
- t.Log(img.Config.Cmd[0])
- t.Fail()
- }
- if img.Config.Cmd[1] != "Hello World" {
- t.Log(img.Config.Cmd[1])
- t.Fail()
- }
- }
- func TestBuildExpose(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- expose 4243
- `,
- nil, nil}, t, nil, true)
- if img.Config.PortSpecs[0] != "4243" {
- t.Fail()
- }
- }
- func TestBuildEntrypoint(t *testing.T) {
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- entrypoint ["/bin/echo"]
- `,
- nil, nil}, t, nil, true)
- if img.Config.Entrypoint[0] != "/bin/echo" {
- }
- }
- // testing #1405 - config.Cmd does not get cleaned up if
- // utilizing cache
- func TestBuildEntrypointRunCleanup(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- run echo "hello"
- `,
- nil, nil}, t, srv, true)
- img = buildImage(testContextTemplate{`
- from {IMAGE}
- run echo "hello"
- add foo /foo
- entrypoint ["/bin/echo"]
- `,
- [][2]string{{"foo", "HEYO"}}, nil}, t, srv, true)
- if len(img.Config.Cmd) != 0 {
- t.Fail()
- }
- }
- func TestBuildImageWithCache(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- template := testContextTemplate{`
- from {IMAGE}
- maintainer dockerio
- `,
- nil, nil}
- img := buildImage(template, t, srv, true)
- imageId := img.ID
- img = nil
- img = buildImage(template, t, srv, true)
- if imageId != img.ID {
- t.Logf("Image ids should match: %s != %s", imageId, img.ID)
- t.Fail()
- }
- }
- func TestBuildImageWithoutCache(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- template := testContextTemplate{`
- from {IMAGE}
- maintainer dockerio
- `,
- nil, nil}
- img := buildImage(template, t, srv, true)
- imageId := img.ID
- img = nil
- img = buildImage(template, t, srv, false)
- if imageId == img.ID {
- t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
- t.Fail()
- }
- }
- func TestForbiddenContextPath(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- context := testContextTemplate{`
- from {IMAGE}
- maintainer dockerio
- add ../../ test/
- `,
- [][2]string{{"test.txt", "test1"}, {"other.txt", "other"}}, nil}
- 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 := srv.runtime.networkManager.bridgeNetwork.IP
- dockerfile := constructDockerfile(context.dockerfile, ip, port)
- buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
- _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
- if err == nil {
- t.Log("Error should not be nil")
- t.Fail()
- }
- if err.Error() != "Forbidden path: /" {
- t.Logf("Error message is not expected: %s", err.Error())
- t.Fail()
- }
- }
- func TestBuildADDFileNotFound(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- context := testContextTemplate{`
- from {IMAGE}
- add foo /usr/local/bar
- `,
- nil, nil}
- 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 := srv.runtime.networkManager.bridgeNetwork.IP
- dockerfile := constructDockerfile(context.dockerfile, ip, port)
- buildfile := NewBuildFile(srv, ioutil.Discard, false, true, false)
- _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
- if err == nil {
- t.Log("Error should not be nil")
- t.Fail()
- }
- if err.Error() != "foo: no such file or directory" {
- t.Logf("Error message is not expected: %s", err.Error())
- t.Fail()
- }
- }
- func TestBuildInheritance(t *testing.T) {
- runtime := mkRuntime(t)
- defer nuke(runtime)
- srv := &Server{
- runtime: runtime,
- pullingPool: make(map[string]struct{}),
- pushingPool: make(map[string]struct{}),
- }
- img := buildImage(testContextTemplate{`
- from {IMAGE}
- expose 4243
- `,
- nil, nil}, t, srv, true)
- img2 := buildImage(testContextTemplate{fmt.Sprintf(`
- from %s
- entrypoint ["/bin/echo"]
- `, img.ID),
- nil, nil}, t, srv, true)
- // from child
- if img2.Config.Entrypoint[0] != "/bin/echo" {
- t.Fail()
- }
- // from parent
- if img.Config.PortSpecs[0] != "4243" {
- t.Fail()
- }
- }
|