소스 검색

Add dynbinary and dyntest scripts for building/testing a separate static dockerinit binary

After a nice long brainstorming session with @shykes on IRC, we decided on using a SHA1 hash of dockerinit compiled into the dynamic docker binary to ensure that we always use the two in a perfect pair, and never mix and match.
Tianon Gravi 11 년 전
부모
커밋
21161dbd51
9개의 변경된 파일163개의 추가작업 그리고 8개의 파일을 삭제
  1. 10 0
      hack/PACKAGERS.md
  2. 3 0
      hack/make.sh
  3. 1 1
      hack/make/binary
  4. 15 0
      hack/make/dynbinary
  5. 41 0
      hack/make/dyntest
  6. 1 1
      hack/make/test
  7. 5 6
      runtime.go
  8. 20 0
      runtime_test.go
  9. 67 0
      utils/utils.go

+ 10 - 0
hack/PACKAGERS.md

@@ -90,6 +90,16 @@ You would do the users of your distro a disservice and "void the docker warranty
 A good comparison is Busybox: all distros package it as a statically linked binary, because it just
 A good comparison is Busybox: all distros package it as a statically linked binary, because it just
 makes sense. Docker is the same way.
 makes sense. Docker is the same way.
 
 
+If you *must* have a non-static Docker binary, please use:
+
+```bash
+./hack/make.sh dynbinary
+```
+
+This will create *./bundles/$VERSION/dynbinary/docker-$VERSION* and *./bundles/$VERSION/binary/dockerinit-$VERSION*.
+The first of these would usually be installed at */usr/bin/docker*, while the second must be installed
+at */usr/libexec/docker/dockerinit*.
+
 ## Testing Docker
 ## Testing Docker
 
 
 Before releasing your binary, make sure to run the tests! Run the following command with the source
 Before releasing your binary, make sure to run the tests! Run the following command with the source

+ 3 - 0
hack/make.sh

@@ -35,6 +35,8 @@ grep -q "$RESOLVCONF" /proc/mounts || {
 DEFAULT_BUNDLES=(
 DEFAULT_BUNDLES=(
 	binary
 	binary
 	test
 	test
+	dynbinary
+	dyntest
 	ubuntu
 	ubuntu
 )
 )
 
 
@@ -46,6 +48,7 @@ fi
 
 
 # Use these flags when compiling the tests and final binary
 # Use these flags when compiling the tests and final binary
 LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w'
 LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w'
+LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"'
 BUILDFLAGS='-tags netgo'
 BUILDFLAGS='-tags netgo'
 
 
 bundle() {
 bundle() {

+ 1 - 1
hack/make/binary

@@ -2,5 +2,5 @@
 
 
 DEST=$1
 DEST=$1
 
 
-go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS ./docker
+go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS ./docker
 echo "Created binary: $DEST/docker-$VERSION"
 echo "Created binary: $DEST/docker-$VERSION"

+ 15 - 0
hack/make/dynbinary

@@ -0,0 +1,15 @@
+#!/bin/sh
+
+DEST=$1
+
+# dockerinit still needs to be a static binary, even if docker is dynamic
+CGO_ENABLED=0 go build -a -o $DEST/dockerinit-$VERSION -ldflags "$LDFLAGS -d" $BUILDFLAGS ./dockerinit
+echo "Created binary: $DEST/dockerinit-$VERSION"
+ln -sf dockerinit-$VERSION $DEST/dockerinit
+
+# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another
+export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)"
+# exported so that "dyntest" can easily access it later without recalculating it
+
+go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS ./docker
+echo "Created binary: $DEST/docker-$VERSION"

+ 41 - 0
hack/make/dyntest

@@ -0,0 +1,41 @@
+#!/bin/sh
+
+DEST=$1
+INIT=$DEST/../dynbinary/dockerinit-$VERSION
+
+set -e
+
+if [ ! -x "$INIT" ]; then
+	echo >&2 'error: dynbinary must be run before dyntest'
+	false
+fi
+
+# Run Docker's test suite, including sub-packages, and store their output as a bundle
+# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
+# You can use this to select certain tests to run, eg.
+# 
+# 	TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test
+#
+bundle_test() {
+	{
+		date
+		for test_dir in $(find_test_dirs); do (
+			set -x
+			cd $test_dir
+			export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
+			go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS
+		)  done
+	} 2>&1 | tee $DEST/test.log
+}
+
+
+# This helper function walks the current directory looking for directories
+# holding Go test files, and prints their paths on standard output, one per
+# line.
+find_test_dirs() {
+       find . -name '*_test.go' | grep -v '^./vendor' |
+               { while read f; do dirname $f; done; } |
+               sort -u
+}
+
+bundle_test

+ 1 - 1
hack/make/test

@@ -17,7 +17,7 @@ bundle_test() {
 			set -x
 			set -x
 			cd $test_dir
 			cd $test_dir
 			go test -i
 			go test -i
-			go test -v -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS $TESTFLAGS
+			go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
 		)  done
 		)  done
 	} 2>&1 | tee $DEST/test.log
 	} 2>&1 | tee $DEST/test.log
 }
 }

+ 5 - 6
runtime.go

@@ -38,12 +38,6 @@ type Runtime struct {
 	containerGraph *gograph.Database
 	containerGraph *gograph.Database
 }
 }
 
 
-var sysInitPath string
-
-func init() {
-	sysInitPath = utils.SelfPath()
-}
-
 // List returns an array of all containers registered in the runtime.
 // List returns an array of all containers registered in the runtime.
 func (runtime *Runtime) List() []*Container {
 func (runtime *Runtime) List() []*Container {
 	containers := new(History)
 	containers := new(History)
@@ -335,6 +329,11 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) {
 		return nil, nil, fmt.Errorf("No command specified")
 		return nil, nil, fmt.Errorf("No command specified")
 	}
 	}
 
 
+	sysInitPath := utils.DockerInitPath()
+	if sysInitPath == "" {
+		return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.")
+	}
+
 	// Generate id
 	// Generate id
 	id := GenerateID()
 	id := GenerateID()
 
 

+ 20 - 0
runtime_test.go

@@ -9,6 +9,7 @@ import (
 	"log"
 	"log"
 	"net"
 	"net"
 	"os"
 	"os"
+	"path/filepath"
 	"runtime"
 	"runtime"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -86,6 +87,25 @@ func init() {
 		log.Fatal("docker tests need to be run as root")
 		log.Fatal("docker tests need to be run as root")
 	}
 	}
 
 
+	// Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary)
+	if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" {
+		src, err := os.Open(dockerinit)
+		if err != nil {
+			log.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s\n", err)
+		}
+		defer src.Close()
+		dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555)
+		if err != nil {
+			log.Fatalf("Unable to create dockerinit in test directory: %s\n", err)
+		}
+		defer dst.Close()
+		if _, err := io.Copy(dst, src); err != nil {
+			log.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s\n", err)
+		}
+		dst.Close()
+		src.Close()
+	}
+
 	// Setup the base runtime, which will be duplicated for each test.
 	// Setup the base runtime, which will be duplicated for each test.
 	// (no tests are run directly in the base)
 	// (no tests are run directly in the base)
 	setupBaseImage()
 	setupBaseImage()

+ 67 - 0
utils/utils.go

@@ -2,6 +2,7 @@ package utils
 
 
 import (
 import (
 	"bytes"
 	"bytes"
+	"crypto/sha1"
 	"crypto/sha256"
 	"crypto/sha256"
 	"encoding/hex"
 	"encoding/hex"
 	"encoding/json"
 	"encoding/json"
@@ -21,6 +22,11 @@ import (
 	"time"
 	"time"
 )
 )
 
 
+var (
+	IAMSTATIC bool   // whether or not Docker itself was compiled statically via ./hack/make.sh binary
+	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
+)
+
 // ListOpts type
 // ListOpts type
 type ListOpts []string
 type ListOpts []string
 
 
@@ -190,6 +196,67 @@ func SelfPath() string {
 	return path
 	return path
 }
 }
 
 
+func dockerInitSha1(target string) string {
+	f, err := os.Open(target)
+	if err != nil {
+		return ""
+	}
+	defer f.Close()
+	h := sha1.New()
+	_, err = io.Copy(h, f)
+	if err != nil {
+		return ""
+	}
+	return hex.EncodeToString(h.Sum(nil))
+}
+
+func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this)
+	if IAMSTATIC {
+		if target == selfPath {
+			return true
+		}
+		targetFileInfo, err := os.Lstat(target)
+		if err != nil {
+			return false
+		}
+		selfPathFileInfo, err := os.Lstat(selfPath)
+		if err != nil {
+			return false
+		}
+		return os.SameFile(targetFileInfo, selfPathFileInfo)
+	}
+	return INITSHA1 != "" && dockerInitSha1(target) == INITSHA1
+}
+
+// Figure out the path of our dockerinit (which may be SelfPath())
+func DockerInitPath() string {
+	selfPath := SelfPath()
+	if isValidDockerInitPath(selfPath, selfPath) {
+		// if we're valid, don't bother checking anything else
+		return selfPath
+	}
+	var possibleInits = []string{
+		filepath.Join(filepath.Dir(selfPath), "dockerinit"),
+		// "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec."
+		"/usr/libexec/docker/dockerinit",
+		"/usr/local/libexec/docker/dockerinit",
+	}
+	for _, dockerInit := range possibleInits {
+		path, err := exec.LookPath(dockerInit)
+		if err == nil {
+			path, err = filepath.Abs(path)
+			if err != nil {
+				// LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail?
+				panic(err)
+			}
+			if isValidDockerInitPath(path, selfPath) {
+				return path
+			}
+		}
+	}
+	return ""
+}
+
 type NopWriter struct{}
 type NopWriter struct{}
 
 
 func (*NopWriter) Write(buf []byte) (int, error) {
 func (*NopWriter) Write(buf []byte) (int, error) {