mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
bump master
This commit is contained in:
commit
ca39f15fa3
43 changed files with 907 additions and 316 deletions
1
AUTHORS
1
AUTHORS
|
@ -76,6 +76,7 @@ Shawn Siefkas <shawn.siefkas@meredith.com>
|
|||
Silas Sewell <silas@sewell.org>
|
||||
Solomon Hykes <solomon@dotcloud.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Stefan Praszalowicz <stefan@greplin.com>
|
||||
Thatcher Peskens <thatcher@dotcloud.com>
|
||||
Thomas Bikeev <thomas.bikeev@mac.com>
|
||||
Thomas Hansen <thomas.hansen@gmail.com>
|
||||
|
|
6
Makefile
6
Makefile
|
@ -11,7 +11,7 @@ BUILD_DIR := $(CURDIR)/.gopath
|
|||
GOPATH ?= $(BUILD_DIR)
|
||||
export GOPATH
|
||||
|
||||
GO_OPTIONS ?=
|
||||
GO_OPTIONS ?= -a -ldflags='-w -d'
|
||||
ifeq ($(VERBOSE), 1)
|
||||
GO_OPTIONS += -v
|
||||
endif
|
||||
|
@ -80,10 +80,10 @@ test:
|
|||
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
|
||||
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
|
||||
# Do the test
|
||||
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS}
|
||||
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS}
|
||||
|
||||
testall: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
|
||||
@(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
|
119
api.go
119
api.go
|
@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) {
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if version > 1.1 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return nil
|
||||
}
|
||||
authConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = &auth.AuthConfig{}
|
||||
}
|
||||
b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeJSON(w, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := ""
|
||||
if version > 1.1 {
|
||||
status, err = auth.Login(authConfig, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil {
|
||||
if err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if authConfig.Username == localAuthConfig.Username {
|
||||
authConfig.Password = localAuthConfig.Password
|
||||
}
|
||||
|
||||
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
|
||||
status, err = auth.Login(newAuthConfig, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := auth.Login(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != "" {
|
||||
b, err := json.Marshal(&APIAuth{Status: status})
|
||||
|
@ -217,6 +178,64 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
|
|||
return nil
|
||||
}
|
||||
|
||||
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error {
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("JSON error")
|
||||
}
|
||||
_, err = wf.Write(b)
|
||||
if err != nil {
|
||||
// On error, evict the listener
|
||||
utils.Debugf("%s", err)
|
||||
srv.Lock()
|
||||
delete(srv.listeners, r.RemoteAddr)
|
||||
srv.Unlock()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
}
|
||||
listener := make(chan utils.JSONMessage)
|
||||
srv.Lock()
|
||||
srv.listeners[r.RemoteAddr] = listener
|
||||
srv.Unlock()
|
||||
since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
|
||||
if err != nil {
|
||||
since = 0
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
wf := utils.NewWriteFlusher(w)
|
||||
if since != 0 {
|
||||
// If since, send previous events that happened after the timestamp
|
||||
for _, event := range srv.events {
|
||||
if event.Time >= since {
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
event := <-listener
|
||||
err := sendEvent(wf, &event)
|
||||
if err != nil && err.Error() == "JSON error" {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
|
@ -429,16 +448,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
|
|||
|
||||
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
authConfig := &auth.AuthConfig{}
|
||||
if version > 1.1 {
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
|
||||
if err != nil && err != auth.ErrConfigFileMissing {
|
||||
return err
|
||||
}
|
||||
authConfig = localAuthConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parseForm(r); err != nil {
|
||||
return err
|
||||
|
@ -854,9 +865,9 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|||
|
||||
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
|
||||
"GET": {
|
||||
"/auth": getAuth,
|
||||
"/version": getVersion,
|
||||
"/events": getEvents,
|
||||
"/info": getInfo,
|
||||
"/version": getVersion,
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
|
|
|
@ -17,14 +17,15 @@ type APIImages struct {
|
|||
}
|
||||
|
||||
type APIInfo struct {
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
Debug bool
|
||||
Containers int
|
||||
Images int
|
||||
NFd int `json:",omitempty"`
|
||||
NGoroutines int `json:",omitempty"`
|
||||
MemoryLimit bool `json:",omitempty"`
|
||||
SwapLimit bool `json:",omitempty"`
|
||||
LXCVersion string `json:",omitempty"`
|
||||
NEventsListener int `json:",omitempty"`
|
||||
}
|
||||
|
||||
type APITop struct {
|
||||
|
|
38
api_test.go
38
api_test.go
|
@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetEvents(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
events: make([]utils.JSONMessage, 0, 64),
|
||||
listeners: make(map[string]chan utils.JSONMessage),
|
||||
}
|
||||
|
||||
srv.LogEvent("fakeaction", "fakeid")
|
||||
srv.LogEvent("fakeaction2", "fakeid")
|
||||
|
||||
req, err := http.NewRequest("GET", "/events?since=1", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
r := httptest.NewRecorder()
|
||||
setTimeout(t, "", 500*time.Millisecond, func() {
|
||||
if err := getEvents(srv, APIVERSION, r, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
for i := 0; i < 2; i++ {
|
||||
var jm utils.JSONMessage
|
||||
if err := dec.Decode(&jm); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jm != srv.events[i] {
|
||||
t.Fatalf("Event received it different than expected")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetImagesJSON(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
|
105
auth/auth.go
105
auth/auth.go
|
@ -25,19 +25,15 @@ var (
|
|||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Auth string `json:"auth"`
|
||||
Email string `json:"email"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
|
||||
return &AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
Email: email,
|
||||
rootPath: rootPath,
|
||||
}
|
||||
type ConfigFile struct {
|
||||
Configs map[string]AuthConfig `json:"configs,omitempty"`
|
||||
rootPath string
|
||||
}
|
||||
|
||||
func IndexServerAddress() string {
|
||||
|
@ -54,61 +50,84 @@ func encodeAuth(authConfig *AuthConfig) string {
|
|||
}
|
||||
|
||||
// decode the auth string
|
||||
func decodeAuth(authStr string) (*AuthConfig, error) {
|
||||
func decodeAuth(authStr string) (string, string, error) {
|
||||
decLen := base64.StdEncoding.DecodedLen(len(authStr))
|
||||
decoded := make([]byte, decLen)
|
||||
authByte := []byte(authStr)
|
||||
n, err := base64.StdEncoding.Decode(decoded, authByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", "", err
|
||||
}
|
||||
if n > decLen {
|
||||
return nil, fmt.Errorf("Something went wrong decoding auth config")
|
||||
return "", "", fmt.Errorf("Something went wrong decoding auth config")
|
||||
}
|
||||
arr := strings.Split(string(decoded), ":")
|
||||
if len(arr) != 2 {
|
||||
return nil, fmt.Errorf("Invalid auth configuration file")
|
||||
return "", "", fmt.Errorf("Invalid auth configuration file")
|
||||
}
|
||||
password := strings.Trim(arr[1], "\x00")
|
||||
return &AuthConfig{Username: arr[0], Password: password}, nil
|
||||
return arr[0], password, nil
|
||||
}
|
||||
|
||||
// load up the auth config information and return values
|
||||
// FIXME: use the internal golang config parser
|
||||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
func LoadConfig(rootPath string) (*ConfigFile, error) {
|
||||
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
|
||||
return &configFile, ErrConfigFileMissing
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
|
||||
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
|
||||
arr := strings.Split(string(b), "\n")
|
||||
if len(arr) < 2 {
|
||||
return nil, fmt.Errorf("The Auth config file is empty")
|
||||
}
|
||||
authConfig := AuthConfig{}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig.Email = origEmail[1]
|
||||
configFile.Configs[IndexServerAddress()] = authConfig
|
||||
} else {
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfig.Auth = ""
|
||||
configFile.Configs[k] = authConfig
|
||||
}
|
||||
}
|
||||
origAuth := strings.Split(arr[0], " = ")
|
||||
origEmail := strings.Split(arr[1], " = ")
|
||||
authConfig, err := decodeAuth(origAuth[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authConfig.Email = origEmail[1]
|
||||
authConfig.rootPath = rootPath
|
||||
return authConfig, nil
|
||||
return &configFile, nil
|
||||
}
|
||||
|
||||
// save the auth config
|
||||
func SaveConfig(authConfig *AuthConfig) error {
|
||||
confFile := path.Join(authConfig.rootPath, CONFIGFILE)
|
||||
if len(authConfig.Email) == 0 {
|
||||
func SaveConfig(configFile *ConfigFile) error {
|
||||
confFile := path.Join(configFile.rootPath, CONFIGFILE)
|
||||
if len(configFile.Configs) == 0 {
|
||||
os.Remove(confFile)
|
||||
return nil
|
||||
}
|
||||
lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
|
||||
b := []byte(lines)
|
||||
err := ioutil.WriteFile(confFile, b, 0600)
|
||||
for k, authConfig := range configFile.Configs {
|
||||
authConfig.Auth = encodeAuth(&authConfig)
|
||||
authConfig.Username = ""
|
||||
authConfig.Password = ""
|
||||
configFile.Configs[k] = authConfig
|
||||
}
|
||||
|
||||
b, err := json.Marshal(configFile.Configs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(confFile, b, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -116,8 +135,7 @@ func SaveConfig(authConfig *AuthConfig) error {
|
|||
}
|
||||
|
||||
// try to register/login to the registry server
|
||||
func Login(authConfig *AuthConfig, store bool) (string, error) {
|
||||
storeConfig := false
|
||||
func Login(authConfig *AuthConfig) (string, error) {
|
||||
client := &http.Client{}
|
||||
reqStatusCode := 0
|
||||
var status string
|
||||
|
@ -143,7 +161,6 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
|||
if reqStatusCode == 201 {
|
||||
status = "Account created. Please use the confirmation link we sent" +
|
||||
" to your e-mail to activate it."
|
||||
storeConfig = true
|
||||
} else if reqStatusCode == 403 {
|
||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
||||
"Please check your e-mail for a confirmation link.")
|
||||
|
@ -162,14 +179,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
|||
}
|
||||
if resp.StatusCode == 200 {
|
||||
status = "Login Succeeded"
|
||||
storeConfig = true
|
||||
} else if resp.StatusCode == 401 {
|
||||
if store {
|
||||
authConfig.Email = ""
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("Wrong login/password, please try again")
|
||||
} else {
|
||||
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
||||
|
@ -181,10 +191,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
|
|||
} else {
|
||||
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
|
||||
}
|
||||
if storeConfig && store {
|
||||
if err := SaveConfig(authConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ import (
|
|||
func TestEncodeAuth(t *testing.T) {
|
||||
newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
|
||||
authStr := encodeAuth(newAuthConfig)
|
||||
decAuthConfig, err := decodeAuth(authStr)
|
||||
decAuthConfig := &AuthConfig{}
|
||||
var err error
|
||||
decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -29,8 +31,8 @@ func TestEncodeAuth(t *testing.T) {
|
|||
func TestLogin(t *testing.T) {
|
||||
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
|
||||
defer os.Setenv("DOCKER_INDEX_URL", "")
|
||||
authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
|
||||
status, err := Login(authConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -49,8 +51,8 @@ func TestCreateAccount(t *testing.T) {
|
|||
}
|
||||
token := hex.EncodeToString(tokenBuffer)[:12]
|
||||
username := "ut" + token
|
||||
authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
|
||||
status, err := Login(authConfig, false)
|
||||
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+"+token+"@example.com"}
|
||||
status, err := Login(authConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -60,7 +62,7 @@ func TestCreateAccount(t *testing.T) {
|
|||
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
|
||||
}
|
||||
|
||||
status, err = Login(authConfig, false)
|
||||
status, err = Login(authConfig)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error but found nil instead")
|
||||
}
|
||||
|
|
21
buildfile.go
21
buildfile.go
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
|
@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
|
|||
origPath := path.Join(b.context, orig)
|
||||
destPath := path.Join(container.RootfsPath(), dest)
|
||||
// Preserve the trailing '/'
|
||||
if dest[len(dest)-1] == '/' {
|
||||
if strings.HasSuffix(dest, "/") {
|
||||
destPath = destPath + "/"
|
||||
}
|
||||
fi, err := os.Stat(origPath)
|
||||
|
|
|
@ -3,13 +3,17 @@ package docker
|
|||
import (
|
||||
"fmt"
|
||||
"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 {
|
||||
context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files)
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -22,6 +26,8 @@ type testContextTemplate struct {
|
|||
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.
|
||||
|
@ -29,27 +35,31 @@ type testContextTemplate struct {
|
|||
var testContexts = []testContextTemplate{
|
||||
{
|
||||
`
|
||||
from %s
|
||||
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,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
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 /
|
||||
run [ "$(cat /f)" = "hello" ]
|
||||
add f /abc
|
||||
|
@ -71,38 +81,86 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
|
|||
{"f", "hello"},
|
||||
{"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
|
||||
run [ "$FOO" = "BAR" ]
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
ENTRYPOINT /bin/echo
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from {IMAGE}
|
||||
VOLUME /test
|
||||
CMD Hello world
|
||||
`,
|
||||
nil,
|
||||
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)
|
||||
|
@ -121,9 +179,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
|||
pullingPool: 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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -137,10 +210,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
|
|||
|
||||
func TestVolume(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
volume /test
|
||||
cmd Hello world
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t)
|
||||
|
||||
if len(img.Config.Volumes) == 0 {
|
||||
t.Fail()
|
||||
|
@ -154,9 +227,9 @@ func TestVolume(t *testing.T) {
|
|||
|
||||
func TestBuildMaintainer(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
maintainer dockerio
|
||||
`, nil}, t)
|
||||
`, nil, nil}, t)
|
||||
|
||||
if img.Author != "dockerio" {
|
||||
t.Fail()
|
||||
|
@ -165,10 +238,10 @@ func TestBuildMaintainer(t *testing.T) {
|
|||
|
||||
func TestBuildEnv(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
env port 4243
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Env[0] != "port=4243" {
|
||||
t.Fail()
|
||||
|
@ -177,10 +250,10 @@ func TestBuildEnv(t *testing.T) {
|
|||
|
||||
func TestBuildCmd(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
cmd ["/bin/echo", "Hello World"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Cmd[0] != "/bin/echo" {
|
||||
t.Log(img.Config.Cmd[0])
|
||||
|
@ -194,10 +267,10 @@ func TestBuildCmd(t *testing.T) {
|
|||
|
||||
func TestBuildExpose(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
expose 4243
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.PortSpecs[0] != "4243" {
|
||||
t.Fail()
|
||||
|
@ -206,10 +279,10 @@ func TestBuildExpose(t *testing.T) {
|
|||
|
||||
func TestBuildEntrypoint(t *testing.T) {
|
||||
img := buildImage(testContextTemplate{`
|
||||
from %s
|
||||
from {IMAGE}
|
||||
entrypoint ["/bin/echo"]
|
||||
`,
|
||||
nil}, t)
|
||||
nil, nil}, t)
|
||||
|
||||
if img.Config.Entrypoint[0] != "/bin/echo" {
|
||||
}
|
||||
|
|
94
commands.go
94
commands.go
|
@ -78,6 +78,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|||
{"build", "Build a container from a Dockerfile"},
|
||||
{"commit", "Create a new image from a container's changes"},
|
||||
{"diff", "Inspect changes on a container's filesystem"},
|
||||
{"events", "Get real time events from the server"},
|
||||
{"export", "Stream the contents of a container as a tar archive"},
|
||||
{"history", "Show the history of an image"},
|
||||
{"images", "List images"},
|
||||
|
@ -185,6 +186,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
|
||||
isRemote = true
|
||||
} else {
|
||||
if _, err := os.Stat(cmd.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
context, err = Tar(cmd.Arg(0), Uncompressed)
|
||||
}
|
||||
var body io.Reader
|
||||
|
@ -310,16 +314,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
email string
|
||||
)
|
||||
|
||||
authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
|
||||
if !ok {
|
||||
authconfig = auth.AuthConfig{}
|
||||
}
|
||||
|
||||
if *flUsername == "" {
|
||||
fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username)
|
||||
fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username)
|
||||
username = readAndEchoString(cli.in, cli.out)
|
||||
if username == "" {
|
||||
username = cli.authConfig.Username
|
||||
username = authconfig.Username
|
||||
}
|
||||
} else {
|
||||
username = *flUsername
|
||||
}
|
||||
if username != cli.authConfig.Username {
|
||||
if username != authconfig.Username {
|
||||
if *flPassword == "" {
|
||||
fmt.Fprintf(cli.out, "Password: ")
|
||||
password = readString(cli.in, cli.out)
|
||||
|
@ -331,31 +340,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
}
|
||||
|
||||
if *flEmail == "" {
|
||||
fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
|
||||
fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email)
|
||||
email = readAndEchoString(cli.in, cli.out)
|
||||
if email == "" {
|
||||
email = cli.authConfig.Email
|
||||
email = authconfig.Email
|
||||
}
|
||||
} else {
|
||||
email = *flEmail
|
||||
}
|
||||
} else {
|
||||
password = cli.authConfig.Password
|
||||
email = cli.authConfig.Email
|
||||
password = authconfig.Password
|
||||
email = authconfig.Email
|
||||
}
|
||||
if oldState != nil {
|
||||
term.RestoreTerminal(cli.terminalFd, oldState)
|
||||
}
|
||||
cli.authConfig.Username = username
|
||||
cli.authConfig.Password = password
|
||||
cli.authConfig.Email = email
|
||||
authconfig.Username = username
|
||||
authconfig.Password = password
|
||||
authconfig.Email = email
|
||||
cli.configFile.Configs[auth.IndexServerAddress()] = authconfig
|
||||
|
||||
body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
|
||||
body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()])
|
||||
if statusCode == 401 {
|
||||
cli.authConfig.Username = ""
|
||||
cli.authConfig.Password = ""
|
||||
cli.authConfig.Email = ""
|
||||
auth.SaveConfig(cli.authConfig)
|
||||
delete(cli.configFile.Configs, auth.IndexServerAddress())
|
||||
auth.SaveConfig(cli.configFile)
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -365,10 +373,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||
var out2 APIAuth
|
||||
err = json.Unmarshal(body, &out2)
|
||||
if err != nil {
|
||||
auth.LoadConfig(os.Getenv("HOME"))
|
||||
cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
|
||||
return err
|
||||
}
|
||||
auth.SaveConfig(cli.authConfig)
|
||||
auth.SaveConfig(cli.configFile)
|
||||
if out2.Status != "" {
|
||||
fmt.Fprintf(cli.out, "%s\n", out2.Status)
|
||||
}
|
||||
|
@ -464,6 +472,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd)
|
||||
fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines)
|
||||
fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
|
||||
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
|
||||
}
|
||||
if !out.MemoryLimit {
|
||||
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
|
||||
|
@ -476,7 +485,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10")
|
||||
nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -800,10 +809,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
|||
// Custom repositories can have different rules, and we must also
|
||||
// allow pushing by image ID.
|
||||
if len(strings.SplitN(name, "/", 2)) == 1 {
|
||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
|
||||
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(cli.authConfig)
|
||||
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1053,6 +1062,29 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
since := cmd.String("since", "", "Show events previously created (used for polling).")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.NArg() != 0 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
if *since != "" {
|
||||
v.Set("since", *since)
|
||||
}
|
||||
|
||||
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
|
@ -1408,11 +1440,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||
|
||||
func (cli *DockerCli) checkIfLogged(action string) error {
|
||||
// If condition AND the login failed
|
||||
if cli.authConfig.Username == "" {
|
||||
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
|
||||
if err := cli.CmdLogin(""); err != nil {
|
||||
return err
|
||||
}
|
||||
if cli.authConfig.Username == "" {
|
||||
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
|
||||
return fmt.Errorf("Please login prior to %s. ('docker login')", action)
|
||||
}
|
||||
}
|
||||
|
@ -1507,19 +1539,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
|||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var m utils.JSONMessage
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
var jm utils.JSONMessage
|
||||
if err := dec.Decode(&jm); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
|
||||
} else if m.Error != "" {
|
||||
return fmt.Errorf(m.Error)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n", m.Status)
|
||||
}
|
||||
jm.Display(out)
|
||||
}
|
||||
} else {
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
|
@ -1668,11 +1694,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
|
|||
err = out
|
||||
}
|
||||
|
||||
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
|
||||
configFile, _ := auth.LoadConfig(os.Getenv("HOME"))
|
||||
return &DockerCli{
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
authConfig: authConfig,
|
||||
configFile: configFile,
|
||||
in: in,
|
||||
out: out,
|
||||
err: err,
|
||||
|
@ -1684,7 +1710,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
|
|||
type DockerCli struct {
|
||||
proto string
|
||||
addr string
|
||||
authConfig *auth.AuthConfig
|
||||
configFile *auth.ConfigFile
|
||||
in io.ReadCloser
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
|
|
|
@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
|||
f()
|
||||
c <- false
|
||||
}()
|
||||
if <-c {
|
||||
if <-c && msg != "" {
|
||||
t.Fatal(msg)
|
||||
}
|
||||
}
|
||||
|
|
100
container.go
100
container.go
|
@ -58,25 +58,26 @@ type Container struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
CpuShares int64 // CPU shares (relative weight vs. other containers)
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
Entrypoint []string
|
||||
Hostname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
CpuShares int64 // CPU shares (relative weight vs. other containers)
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
|
@ -94,6 +95,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if len(args) > 0 && args[0] != "--help" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
|
@ -105,6 +107,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
|
||||
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
|
@ -173,23 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
|
|||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
Entrypoint: entrypoint,
|
||||
Hostname: *flHostname,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: *flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: flEnv,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: *flVolumesFrom,
|
||||
Entrypoint: entrypoint,
|
||||
}
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
|
@ -510,8 +514,12 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
if err := container.EnsureMounted(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
if container.runtime.networkManager.disabled {
|
||||
container.Config.NetworkDisabled = true
|
||||
} else {
|
||||
if err := container.allocateNetwork(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the config is compatible with the current kernel
|
||||
|
@ -625,7 +633,9 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
}
|
||||
|
||||
// Networking
|
||||
params = append(params, "-g", container.network.Gateway.String())
|
||||
if !container.Config.NetworkDisabled {
|
||||
params = append(params, "-g", container.network.Gateway.String())
|
||||
}
|
||||
|
||||
// User
|
||||
if container.Config.User != "" {
|
||||
|
@ -640,6 +650,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
|||
params = append(params,
|
||||
"-e", "HOME=/",
|
||||
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"-e", "container=lxc",
|
||||
)
|
||||
|
||||
for _, elem := range container.Config.Env {
|
||||
|
@ -726,6 +737,10 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
|||
}
|
||||
|
||||
func (container *Container) allocateNetwork() error {
|
||||
if container.Config.NetworkDisabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
iface, err := container.runtime.networkManager.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -752,6 +767,9 @@ func (container *Container) allocateNetwork() error {
|
|||
}
|
||||
|
||||
func (container *Container) releaseNetwork() {
|
||||
if container.Config.NetworkDisabled {
|
||||
return
|
||||
}
|
||||
container.network.Release()
|
||||
container.network = nil
|
||||
container.NetworkSettings = &NetworkSettings{}
|
||||
|
@ -787,7 +805,9 @@ func (container *Container) monitor() {
|
|||
}
|
||||
}
|
||||
utils.Debugf("Process finished")
|
||||
|
||||
if container.runtime != nil && container.runtime.srv != nil {
|
||||
container.runtime.srv.LogEvent("die", container.ShortID())
|
||||
}
|
||||
exitCode := -1
|
||||
if container.cmd != nil {
|
||||
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
||||
|
|
|
@ -959,6 +959,7 @@ func TestEnv(t *testing.T) {
|
|||
goodEnv := []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"HOME=/",
|
||||
"container=lxc",
|
||||
}
|
||||
sort.Strings(goodEnv)
|
||||
if len(goodEnv) != len(actualEnv) {
|
||||
|
@ -1251,3 +1252,41 @@ func TestRestartWithVolumes(t *testing.T) {
|
|||
t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
|
||||
config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := NewBuilder(runtime).Create(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdout, err := c.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer runtime.Destroy(c)
|
||||
if err := c.Start(hc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.WaitTimeout(500 * time.Millisecond)
|
||||
c.Wait()
|
||||
output, err := ioutil.ReadAll(stdout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1)
|
||||
if len(interfaces) != 1 {
|
||||
t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces)
|
||||
}
|
||||
if interfaces[0] != "1: lo" {
|
||||
t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func main() {
|
|||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
||||
.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to
|
||||
.. document the REST API.
|
||||
|
||||
=================
|
||||
Docker Remote API
|
||||
=================
|
||||
|
@ -13,15 +16,23 @@ Docker Remote API
|
|||
|
||||
- The Remote API is replacing rcli
|
||||
- Default port in the docker deamon is 4243
|
||||
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
|
||||
- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push
|
||||
- The API tends to be REST, but for some complex commands, like attach
|
||||
or pull, the HTTP connection is hijacked to transport stdout stdin
|
||||
and stderr
|
||||
- Since API version 1.2, the auth configuration is now handled client
|
||||
side, so the client has to send the authConfig as POST in
|
||||
/images/(name)/push
|
||||
|
||||
2. Versions
|
||||
===========
|
||||
|
||||
The current verson of the API is 1.3
|
||||
Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
|
||||
You can still call an old version of the api using /v1.0/images/<name>/insert
|
||||
The current verson of the API is 1.3
|
||||
|
||||
Calling /images/<name>/insert is the same as calling
|
||||
/v1.3/images/<name>/insert
|
||||
|
||||
You can still call an old version of the api using
|
||||
/v1.0/images/<name>/insert
|
||||
|
||||
:doc:`docker_remote_api_v1.3`
|
||||
*****************************
|
||||
|
@ -29,19 +40,25 @@ You can still call an old version of the api using /v1.0/images/<name>/insert
|
|||
What's new
|
||||
----------
|
||||
|
||||
Listing processes (/top):
|
||||
.. http:get:: /containers/(id)/top
|
||||
|
||||
- List the processes inside a container
|
||||
**New!** List the processes running inside a container.
|
||||
|
||||
.. http:get:: /events:
|
||||
|
||||
**New!** Monitor docker's events via streaming or via polling
|
||||
|
||||
Builder (/build):
|
||||
|
||||
- Simplify the upload of the build context
|
||||
- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
|
||||
- Simply stream a tarball instead of multipart upload with 4
|
||||
intermediary buffers
|
||||
- Simpler, less memory usage, less disk usage and faster
|
||||
|
||||
.. Note::
|
||||
The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
|
||||
.. Warning::
|
||||
|
||||
The /build improvements are not reverse-compatible. Pre 1.3 clients
|
||||
will break on /build.
|
||||
|
||||
List containers (/containers/json):
|
||||
|
||||
|
@ -49,7 +66,8 @@ List containers (/containers/json):
|
|||
|
||||
Start containers (/containers/<id>/start):
|
||||
|
||||
- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls
|
||||
- You can now pass host-specific configuration (e.g. bind mounts) in
|
||||
the POST body for start calls
|
||||
|
||||
:doc:`docker_remote_api_v1.2`
|
||||
*****************************
|
||||
|
@ -60,14 +78,25 @@ What's new
|
|||
----------
|
||||
|
||||
The auth configuration is now handled by the client.
|
||||
The client should send it's authConfig as POST on each call of /images/(name)/push
|
||||
|
||||
.. http:get:: /auth is now deprecated
|
||||
.. http:post:: /auth only checks the configuration but doesn't store it on the server
|
||||
The client should send it's authConfig as POST on each call of
|
||||
/images/(name)/push
|
||||
|
||||
Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
|
||||
.. http:get:: /auth
|
||||
|
||||
.. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
|
||||
**Deprecated.**
|
||||
|
||||
.. http:post:: /auth
|
||||
|
||||
Only checks the configuration but doesn't store it on the server
|
||||
|
||||
Deleting an image is now improved, will only untag the image if it
|
||||
has chidren and remove all the untagged parents if has any.
|
||||
|
||||
.. http:post:: /images/<name>/delete
|
||||
|
||||
Now returns a JSON structure with the list of images
|
||||
deleted/untagged.
|
||||
|
||||
|
||||
:doc:`docker_remote_api_v1.1`
|
||||
|
@ -82,7 +111,7 @@ What's new
|
|||
.. http:post:: /images/(name)/insert
|
||||
.. http:post:: /images/(name)/push
|
||||
|
||||
Uses json stream instead of HTML hijack, it looks like this:
|
||||
Uses json stream instead of HTML hijack, it looks like this:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.0
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
@ -300,8 +305,8 @@ Start a container
|
|||
:statuscode 500: server error
|
||||
|
||||
|
||||
Stop a contaier
|
||||
***************
|
||||
Stop a container
|
||||
****************
|
||||
|
||||
.. http:post:: /containers/(id)/stop
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.1
|
||||
:description: API Documentation for Docker
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.2
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.. use orphan to suppress "WARNING: document isn't included in any toctree"
|
||||
.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata
|
||||
|
||||
:orphan:
|
||||
|
||||
:title: Remote API v1.3
|
||||
:description: API Documentation for Docker
|
||||
:keywords: API, Docker, rcli, REST, documentation
|
||||
|
@ -1054,6 +1059,36 @@ Create a new image from a container's changes
|
|||
:statuscode 500: server error
|
||||
|
||||
|
||||
Monitor Docker's events
|
||||
***********************
|
||||
|
||||
.. http:get:: /events
|
||||
|
||||
Get events from docker, either in real time via streaming, or via polling (using `since`)
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /events?since=1374067924
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{"status":"create","id":"dfdf82bd3881","time":1374067924}
|
||||
{"status":"start","id":"dfdf82bd3881","time":1374067924}
|
||||
{"status":"stop","id":"dfdf82bd3881","time":1374067966}
|
||||
{"status":"destroy","id":"dfdf82bd3881","time":1374067970}
|
||||
|
||||
:query since: timestamp used for polling
|
||||
:statuscode 200: no error
|
||||
:statuscode 500: server error
|
||||
|
||||
|
||||
3. Going further
|
||||
================
|
||||
|
||||
|
|
|
@ -452,7 +452,7 @@ User Register
|
|||
"username": "foobar"'}
|
||||
|
||||
:jsonparameter email: valid email address, that needs to be confirmed
|
||||
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
:jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_].
|
||||
:jsonparameter password: min 5 characters
|
||||
|
||||
**Example Response**:
|
||||
|
|
|
@ -367,7 +367,8 @@ POST /v1/users
|
|||
{"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
|
||||
|
||||
**Validation**:
|
||||
- **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
|
||||
- **username**: min 4 character, max 30 characters, must match the regular
|
||||
expression [a-z0-9\_].
|
||||
- **password**: min 5 characters
|
||||
|
||||
**Valid**: return HTTP 200
|
||||
|
@ -566,4 +567,4 @@ Next request::
|
|||
---------------------
|
||||
|
||||
- 1.0 : May 6th 2013 : initial release
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
-h="": Container host name
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-m=0: Memory limit (in bytes)
|
||||
-n=true: Enable networking for this container
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Stop a running container
|
||||
|
||||
-t=10: Number of seconds to wait for the container to stop before killing it.
|
||||
|
|
|
@ -37,5 +37,6 @@ Contents:
|
|||
start <command/start>
|
||||
stop <command/stop>
|
||||
tag <command/tag>
|
||||
top <command/top>
|
||||
version <command/version>
|
||||
wait <command/wait>
|
||||
wait <command/wait>
|
||||
|
|
|
@ -46,11 +46,13 @@ in a standard build environment.
|
|||
You can run an interactive session in the newly built container:
|
||||
|
||||
::
|
||||
|
||||
docker run -i -t docker bash
|
||||
|
||||
|
||||
To extract the binaries from the container:
|
||||
|
||||
::
|
||||
|
||||
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
:description: An overview of the Docker Documentation
|
||||
:keywords: containers, lxc, concepts, explanation
|
||||
|
||||
.. _introduction:
|
||||
|
||||
Welcome
|
||||
=======
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ For example:
|
|||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -H 0.0.0.0:5555 &
|
||||
sudo <path to>/docker -H 0.0.0.0:5555 -d &
|
||||
# Download a base image
|
||||
docker -H :5555 pull base
|
||||
|
||||
|
@ -61,7 +61,7 @@ on both tcp and a unix socket
|
|||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
|
||||
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d &
|
||||
# Download a base image
|
||||
docker pull base
|
||||
# OR
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
:title: Dockerfile Builder
|
||||
:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
|
||||
:keywords: builder, docker, Docker Builder, automation, image creation
|
||||
:title: Dockerfiles for Images
|
||||
:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
|
||||
:keywords: builder, docker, Dockerfile, automation, image creation
|
||||
|
||||
==================
|
||||
Dockerfile Builder
|
||||
|
@ -30,7 +30,7 @@ build succeeds:
|
|||
|
||||
``docker build -t shykes/myapp .``
|
||||
|
||||
Docker will run your steps one-by-one, committing the result if necessary,
|
||||
Docker will run your steps one-by-one, committing the result if necessary,
|
||||
before finally outputting the ID of your new image.
|
||||
|
||||
2. Format
|
||||
|
@ -43,7 +43,7 @@ The Dockerfile format is quite simple:
|
|||
# Comment
|
||||
INSTRUCTION arguments
|
||||
|
||||
The Instruction is not case-sensitive, however convention is for them to be
|
||||
The Instruction is not case-sensitive, however convention is for them to be
|
||||
UPPERCASE in order to distinguish them from arguments more easily.
|
||||
|
||||
Docker evaluates the instructions in a Dockerfile in order. **The first
|
||||
|
@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running
|
|||
the image. This is functionally equivalent to running ``docker commit
|
||||
-run '{"Cmd": <command>}'`` outside the builder.
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
|
||||
command and commits the result; `CMD` does not execute anything at
|
||||
build time, but specifies the intended command for the image.
|
||||
|
@ -131,7 +131,7 @@ value ``<value>``. This value will be passed to all future ``RUN``
|
|||
instructions. This is functionally equivalent to prefixing the command
|
||||
with ``<key>=<value>``
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
|
@ -152,16 +152,24 @@ destination container.
|
|||
|
||||
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,
|
||||
including filesystem metadata.
|
||||
* If ``<src>``` is a tar archive in a recognized compression format
|
||||
(identity, gzip, bzip2 or xz), it is unpacked as a directory.
|
||||
|
||||
When a directory is copied or unpacked, it has the same behavior as
|
||||
``tar -x``: the result is the union of
|
||||
``tar -x``: the result is the union of
|
||||
|
||||
1. whatever existed at the destination path and
|
||||
2. the contents of the source tree,
|
||||
2. the contents of the source tree,
|
||||
|
||||
with conflicts resolved in favor of 2) on a file-by-file basis.
|
||||
|
||||
|
@ -177,9 +185,9 @@ The copy obeys the following rules:
|
|||
with mode 0700, uid and gid 0.
|
||||
|
||||
3.8 ENTRYPOINT
|
||||
-------------
|
||||
--------------
|
||||
|
||||
``ENTRYPOINT /bin/echo``
|
||||
``ENTRYPOINT ["/bin/echo"]``
|
||||
|
||||
The ``ENTRYPOINT`` instruction adds an entry command that will not be
|
||||
overwritten when arguments are passed to docker run, unlike the
|
||||
|
@ -203,14 +211,14 @@ container created from the image.
|
|||
# Nginx
|
||||
#
|
||||
# VERSION 0.0.1
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com"
|
||||
|
||||
|
||||
# make sure the package repository is up to date
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
RUN apt-get install -y inotify-tools nginx apache2 openssh-server
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -218,12 +226,12 @@ container created from the image.
|
|||
# Firefox over VNC
|
||||
#
|
||||
# VERSION 0.3
|
||||
|
||||
|
||||
FROM ubuntu
|
||||
# make sure the package repository is up to date
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
|
||||
RUN apt-get update
|
||||
|
||||
|
||||
# Install vnc, xvfb in order to create a 'fake' display and firefox
|
||||
RUN apt-get install -y x11vnc xvfb firefox
|
||||
RUN mkdir /.vnc
|
||||
|
@ -231,7 +239,7 @@ container created from the image.
|
|||
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
|
||||
# Autostart firefox (might not be the best way, but it does the trick)
|
||||
RUN bash -c 'echo "firefox" >> /.bashrc'
|
||||
|
||||
|
||||
EXPOSE 5900
|
||||
CMD ["x11vnc", "-forever", "-usepw", "-create"]
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ your container to an image within your username namespace.
|
|||
|
||||
|
||||
Pushing a container to its repository
|
||||
------------------------------------
|
||||
-------------------------------------
|
||||
|
||||
In order to push an image to its repository you need to have committed
|
||||
your container to a named image (see above)
|
||||
|
|
13
docs/theme/docker/layout.html
vendored
13
docs/theme/docker/layout.html
vendored
|
@ -68,19 +68,18 @@
|
|||
|
||||
<div style="float: right" class="pull-right">
|
||||
<ul class="nav">
|
||||
<li id="nav-introduction"><a href="http://www.docker.io/">Introduction</a></li>
|
||||
<li id="nav-introduction"><a href="http://www.docker.io/">Home</a></li>
|
||||
<li id="nav-about"><a href="http://www.docker.io/">About</a></li>
|
||||
<li id="nav-community"><a href="http://www.docker.io/">Community</a></li>
|
||||
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
|
||||
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
|
||||
<li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
|
||||
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
|
||||
</ul>
|
||||
<!--<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">-->
|
||||
<!--<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>-->
|
||||
<!--<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
|
||||
<div style="margin-left: -12px; float: left;">
|
||||
<a href="http://www.docker.io"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
|
||||
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -96,7 +95,7 @@
|
|||
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
|
||||
<a href="http://github.com/dotcloud/docker/"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Fork us on Github</a>
|
||||
</div>
|
||||
<h1 class="pageheader">DOCUMENTATION</h1>
|
||||
<h1 class="pageheader"><a href="http://docs.docker.io/en/latest/" title="Documentation" style="color: white;">DOCUMENTATION</a></h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
10
docs/theme/docker/static/css/main.css
vendored
10
docs/theme/docker/static/css/main.css
vendored
|
@ -34,12 +34,12 @@ h4 {
|
|||
.navbar .nav li a {
|
||||
padding: 22px 15px 22px;
|
||||
}
|
||||
.navbar .brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
}
|
||||
.navbar-dotcloud .container {
|
||||
border-bottom: 2px #000000 solid;
|
||||
}
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
* http://www.jonsuh.com
|
||||
|
@ -82,7 +82,7 @@ h4 {
|
|||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
background-repeat: repeat-x;
|
||||
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
|
||||
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
|
||||
background-image: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
|
@ -301,7 +301,7 @@ section.header {
|
|||
height: 28px;
|
||||
line-height: 28px;
|
||||
background-color: #43484c;
|
||||
filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
|
||||
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
|
||||
background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
|
||||
|
|
10
docs/theme/docker/static/css/main.less
vendored
10
docs/theme/docker/static/css/main.less
vendored
|
@ -53,13 +53,6 @@ h1, h2, h3, h4 {
|
|||
padding: 22px 15px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
padding: 13px 10px 13px 28px ;
|
||||
// padding-left: 30px;
|
||||
|
||||
}
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
@ -67,6 +60,9 @@ h1, h2, h3, h4 {
|
|||
border-bottom: 2px @black solid;
|
||||
}
|
||||
|
||||
.inline-icon {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
|
||||
|
|
BIN
docs/theme/docker/static/img/docker-top-logo.png
vendored
Normal file
BIN
docs/theme/docker/static/img/docker-top-logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
docs/theme/docker/static/img/external-link-icon.png
vendored
Normal file
BIN
docs/theme/docker/static/img/external-link-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
91
hack/bootcamp/README.md
Normal file
91
hack/bootcamp/README.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
# Docker maintainer bootcamp
|
||||
|
||||
## Introduction: we need more maintainers
|
||||
|
||||
Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people,
|
||||
and its API is used by dozens of 3rd-party tools. Over 1,000 issues have been opened. As the first production deployments
|
||||
start going live, the growth will only accelerate.
|
||||
|
||||
Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker
|
||||
is going to live up to the expectations, we need more than that.
|
||||
|
||||
This document describes a *bootcamp* to guide and train volunteers interested in helping the project, either with individual
|
||||
contributions, maintainer work, or both.
|
||||
|
||||
This bootcamp is an experiment. If you decide to go through it, consider yourself an alpha-tester. You should expect quirks,
|
||||
and report them to us as you encounter them to help us smooth out the process.
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
The maintainer bootcamp is a 12-step program - one step for each of the maintainer's responsibilities. The aspiring maintainer must
|
||||
validate all 12 steps by 1) studying it, 2) practicing it, and 3) getting endorsed for it.
|
||||
|
||||
Steps are all equally important and can be validated in any order. Validating all 12 steps is a pre-requisite for becoming a core
|
||||
maintainer, but even 1 step will make you a better contributor!
|
||||
|
||||
### List of steps
|
||||
|
||||
#### 1) Be a power user
|
||||
|
||||
Use docker daily, build cool things with it, know its quirks inside and out.
|
||||
|
||||
|
||||
#### 2) Help users
|
||||
|
||||
Answer questions on irc, twitter, email, in person.
|
||||
|
||||
|
||||
#### 3) Manage the bug tracker
|
||||
|
||||
Help triage tickets - ask the right questions, find duplicates, reference relevant resources, know when to close a ticket when necessary, take the time to go over older tickets.
|
||||
|
||||
|
||||
#### 4) Improve the documentation
|
||||
|
||||
Follow the documentation from scratch regularly and make sure it is still up-to-date. Find and fix inconsistencies. Remove stale information. Find a frequently asked question that is not documented. Simplify the content and the form.
|
||||
|
||||
|
||||
#### 5) Evangelize the principles of docker
|
||||
|
||||
Understand what the underlying goals and principle of docker are. Explain design decisions based on what docker is, and what it is not. When someone is not using docker, find how docker can be valuable to them. If they are using docker, find how they can use it better.
|
||||
|
||||
|
||||
#### 6) Fix bugs
|
||||
|
||||
Self-explanatory. Contribute improvements to docker which solve defects. Bugfixes should be well-tested, and prioritized by impact to the user.
|
||||
|
||||
|
||||
#### 7) Improve the testing infrastructure
|
||||
|
||||
Automated testing is complicated and should be perpetually improved. Invest time to improve the current tooling. Refactor existing tests, create new ones, make testing more accessible to developers, add new testing capabilities (integration tests, mocking, stress test...), improve integration between tests and documentation...
|
||||
|
||||
|
||||
#### 8) Contribute features
|
||||
|
||||
Improve docker to do more things, or get better at doing the same things. Features should be well-tested, not break existing APIs, respect the project goals. They should make the user's life measurably better. Features should be discussed ahead of time to avoid wasting time and duplicating effort.
|
||||
|
||||
|
||||
#### 9) Refactor internals
|
||||
|
||||
Improve docker to repay technical debt. Simplify code layout, improve performance, add missing comments, reduce the number of files and functions, rename functions and variables to be more readable, go over FIXMEs, etc.
|
||||
|
||||
#### 10) Review and merge contributions
|
||||
|
||||
Review pull requests in a timely manner, review code in detail and offer feedback. Keep a high bar without being pedantic. Share the load of testing and merging pull requests.
|
||||
|
||||
#### 11) Release
|
||||
|
||||
Manage a release of docker from beginning to end. Tests, final review, tags, builds, upload to mirrors, distro packaging, etc.
|
||||
|
||||
#### 12) Train other maintainers
|
||||
|
||||
Contribute to training other maintainers. Give advice, delegate work, help organize the bootcamp. This also means contribute to the maintainer's manual, look for ways to improve the project organization etc.
|
||||
|
||||
### How to study a step
|
||||
|
||||
### How to practice a step
|
||||
|
||||
### How to get endorsed for a step
|
||||
|
||||
|
|
@ -13,6 +13,10 @@ lxc.utsname = {{.Id}}
|
|||
{{end}}
|
||||
#lxc.aa_profile = unconfined
|
||||
|
||||
{{if .Config.NetworkDisabled}}
|
||||
# network is disabled (-n=false)
|
||||
lxc.network.type = empty
|
||||
{{else}}
|
||||
# network configuration
|
||||
lxc.network.type = veth
|
||||
lxc.network.flags = up
|
||||
|
@ -20,6 +24,7 @@ lxc.network.link = {{.NetworkSettings.Bridge}}
|
|||
lxc.network.name = eth0
|
||||
lxc.network.mtu = 1500
|
||||
lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}}
|
||||
{{end}}
|
||||
|
||||
# root filesystem
|
||||
{{$ROOTFS := .RootfsPath}}
|
||||
|
|
52
network.go
52
network.go
|
@ -17,6 +17,7 @@ var NetworkBridgeIface string
|
|||
|
||||
const (
|
||||
DefaultNetworkBridge = "docker0"
|
||||
DisableNetworkBridge = "none"
|
||||
portRangeStart = 49153
|
||||
portRangeEnd = 65535
|
||||
)
|
||||
|
@ -111,10 +112,29 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
|
||||
// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
|
||||
// If it can't find an address which doesn't conflict, it will return an error.
|
||||
func CreateBridgeIface(ifaceName string) error {
|
||||
// FIXME: try more IP ranges
|
||||
// FIXME: try bigger ranges! /24 is too small.
|
||||
addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
|
||||
addrs := []string{
|
||||
// Here we don't follow the convention of using the 1st IP of the range for the gateway.
|
||||
// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
|
||||
// In theory this shouldn't matter - in practice there's bound to be a few scripts relying
|
||||
// on the internal addressing or other stupid things like that.
|
||||
// The shouldn't, but hey, let's not break them unless we really have to.
|
||||
"172.16.42.1/16",
|
||||
"10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive
|
||||
"10.1.42.1/16",
|
||||
"10.42.42.1/16",
|
||||
"172.16.42.1/24",
|
||||
"172.16.43.1/24",
|
||||
"172.16.44.1/24",
|
||||
"10.0.42.1/24",
|
||||
"10.0.43.1/24",
|
||||
"192.168.42.1/24",
|
||||
"192.168.43.1/24",
|
||||
"192.168.44.1/24",
|
||||
}
|
||||
|
||||
var ifaceAddr string
|
||||
for _, addr := range addrs {
|
||||
|
@ -453,10 +473,16 @@ type NetworkInterface struct {
|
|||
|
||||
manager *NetworkManager
|
||||
extPorts []*Nat
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// Allocate an external TCP port and map it to the interface
|
||||
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
|
||||
|
||||
if iface.disabled {
|
||||
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
|
||||
}
|
||||
|
||||
nat, err := parseNat(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -552,6 +578,11 @@ func parseNat(spec string) (*Nat, error) {
|
|||
|
||||
// Release: Network cleanup - release all resources
|
||||
func (iface *NetworkInterface) Release() {
|
||||
|
||||
if iface.disabled {
|
||||
return
|
||||
}
|
||||
|
||||
for _, nat := range iface.extPorts {
|
||||
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
|
||||
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
|
||||
|
@ -579,10 +610,17 @@ type NetworkManager struct {
|
|||
tcpPortAllocator *PortAllocator
|
||||
udpPortAllocator *PortAllocator
|
||||
portMapper *PortMapper
|
||||
|
||||
disabled bool
|
||||
}
|
||||
|
||||
// Allocate a network interface
|
||||
func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
|
||||
if manager.disabled {
|
||||
return &NetworkInterface{disabled: true}, nil
|
||||
}
|
||||
|
||||
ip, err := manager.ipAllocator.Acquire()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -596,6 +634,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
|||
}
|
||||
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
|
||||
if bridgeIface == DisableNetworkBridge {
|
||||
manager := &NetworkManager{
|
||||
disabled: true,
|
||||
}
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
// If the iface is not found, try to create it
|
||||
|
|
|
@ -17,12 +17,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
unitTestImageName = "docker-test-image"
|
||||
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
|
||||
unitTestNetworkBridge = "testdockbr0"
|
||||
unitTestStoreBase = "/var/lib/docker/unit-tests"
|
||||
testDaemonAddr = "127.0.0.1:4270"
|
||||
testDaemonProto = "tcp"
|
||||
unitTestImageName = "docker-test-image"
|
||||
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
|
||||
unitTestNetworkBridge = "testdockbr0"
|
||||
unitTestStoreBase = "/var/lib/docker/unit-tests"
|
||||
testDaemonAddr = "127.0.0.1:4270"
|
||||
testDaemonProto = "tcp"
|
||||
)
|
||||
|
||||
var globalRuntime *Runtime
|
||||
|
|
45
server.go
45
server.go
|
@ -19,6 +19,7 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (srv *Server) DockerVersion() APIVersion {
|
||||
|
@ -32,8 +33,9 @@ func (srv *Server) DockerVersion() APIVersion {
|
|||
func (srv *Server) ContainerKill(name string) error {
|
||||
if container := srv.runtime.Get(name); container != nil {
|
||||
if err := container.Kill(); err != nil {
|
||||
return fmt.Errorf("Error restarting container %s: %s", name, err)
|
||||
return fmt.Errorf("Error killing container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("kill", name)
|
||||
} else {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
@ -52,6 +54,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
|||
if _, err := io.Copy(out, data); err != nil {
|
||||
return err
|
||||
}
|
||||
srv.LogEvent("export", name)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
|
@ -217,14 +220,15 @@ func (srv *Server) DockerInfo() *APIInfo {
|
|||
}
|
||||
|
||||
return &APIInfo{
|
||||
Containers: len(srv.runtime.List()),
|
||||
Images: imgcount,
|
||||
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
|
||||
SwapLimit: srv.runtime.capabilities.SwapLimit,
|
||||
Debug: os.Getenv("DEBUG") != "",
|
||||
NFd: utils.GetTotalUsedFds(),
|
||||
NGoroutines: runtime.NumGoroutine(),
|
||||
LXCVersion: lxcVersion,
|
||||
Containers: len(srv.runtime.List()),
|
||||
Images: imgcount,
|
||||
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
|
||||
SwapLimit: srv.runtime.capabilities.SwapLimit,
|
||||
Debug: os.Getenv("DEBUG") != "",
|
||||
NFd: utils.GetTotalUsedFds(),
|
||||
NGoroutines: runtime.NumGoroutine(),
|
||||
LXCVersion: lxcVersion,
|
||||
NEventsListener: len(srv.events),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -819,6 +823,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
|
|||
}
|
||||
return "", err
|
||||
}
|
||||
srv.LogEvent("create", container.ShortID())
|
||||
return container.ShortID(), nil
|
||||
}
|
||||
|
||||
|
@ -827,6 +832,7 @@ func (srv *Server) ContainerRestart(name string, t int) error {
|
|||
if err := container.Restart(t); err != nil {
|
||||
return fmt.Errorf("Error restarting container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("restart", name)
|
||||
} else {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
@ -846,6 +852,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
|
|||
if err := srv.runtime.Destroy(container); err != nil {
|
||||
return fmt.Errorf("Error destroying container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("destroy", name)
|
||||
|
||||
if removeVolume {
|
||||
// Retrieve all volumes from all remaining containers
|
||||
|
@ -912,6 +919,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
|
|||
return err
|
||||
}
|
||||
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
|
||||
srv.LogEvent("delete", utils.TruncateID(id))
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
@ -955,6 +963,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
|
|||
}
|
||||
if tagDeleted {
|
||||
imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
|
||||
srv.LogEvent("untag", img.ShortID())
|
||||
}
|
||||
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
|
||||
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
|
||||
|
@ -1027,6 +1036,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
|
|||
if err := container.Start(hostConfig); err != nil {
|
||||
return fmt.Errorf("Error starting container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("start", name)
|
||||
} else {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
@ -1038,6 +1048,7 @@ func (srv *Server) ContainerStop(name string, t int) error {
|
|||
if err := container.Stop(t); err != nil {
|
||||
return fmt.Errorf("Error stopping container %s: %s", name, err)
|
||||
}
|
||||
srv.LogEvent("stop", name)
|
||||
} else {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
@ -1171,15 +1182,31 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (
|
|||
enableCors: enableCors,
|
||||
pullingPool: make(map[string]struct{}),
|
||||
pushingPool: make(map[string]struct{}),
|
||||
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
|
||||
listeners: make(map[string]chan utils.JSONMessage),
|
||||
}
|
||||
runtime.srv = srv
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (srv *Server) LogEvent(action, id string) {
|
||||
now := time.Now().Unix()
|
||||
jm := utils.JSONMessage{Status: action, ID: id, Time: now}
|
||||
srv.events = append(srv.events, jm)
|
||||
for _, c := range srv.listeners {
|
||||
select { // non blocking channel
|
||||
case c <- jm:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
runtime *Runtime
|
||||
enableCors bool
|
||||
pullingPool map[string]struct{}
|
||||
pushingPool map[string]struct{}
|
||||
events []utils.JSONMessage
|
||||
listeners map[string]chan utils.JSONMessage
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestContainerTagImageDelete(t *testing.T) {
|
||||
|
@ -163,3 +165,41 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLogEvent(t *testing.T) {
|
||||
runtime := mkRuntime(t)
|
||||
srv := &Server{
|
||||
runtime: runtime,
|
||||
events: make([]utils.JSONMessage, 0, 64),
|
||||
listeners: make(map[string]chan utils.JSONMessage),
|
||||
}
|
||||
|
||||
srv.LogEvent("fakeaction", "fakeid")
|
||||
|
||||
listener := make(chan utils.JSONMessage)
|
||||
srv.Lock()
|
||||
srv.listeners["test"] = listener
|
||||
srv.Unlock()
|
||||
|
||||
srv.LogEvent("fakeaction2", "fakeid")
|
||||
|
||||
if len(srv.events) != 2 {
|
||||
t.Fatalf("Expected 2 events, found %d", len(srv.events))
|
||||
}
|
||||
go func() {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
srv.LogEvent("fakeaction3", "fakeid")
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
srv.LogEvent("fakeaction4", "fakeid")
|
||||
}()
|
||||
|
||||
setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
|
||||
for i := 2; i < 4; i++ {
|
||||
event := <-listener
|
||||
if event != srv.events[i] {
|
||||
t.Fatalf("Event received it different than expected")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
|
2
utils.go
2
utils.go
|
@ -78,7 +78,7 @@ func MergeConfig(userConf, imageConf *Config) {
|
|||
imageNat, _ := parseNat(imagePortSpec)
|
||||
for _, userPortSpec := range userConf.PortSpecs {
|
||||
userNat, _ := parseNat(userPortSpec)
|
||||
if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend {
|
||||
if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -611,8 +611,27 @@ type JSONMessage struct {
|
|||
Status string `json:"status,omitempty"`
|
||||
Progress string `json:"progress,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
}
|
||||
|
||||
func (jm *JSONMessage) Display(out io.Writer) (error) {
|
||||
if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
|
||||
}
|
||||
if jm.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
|
||||
} else if jm.Error != "" {
|
||||
return fmt.Errorf(jm.Error)
|
||||
} else if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s\n", jm.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
type StreamFormatter struct {
|
||||
json bool
|
||||
used bool
|
||||
|
|
|
@ -155,7 +155,7 @@ func TestMergeConfig(t *testing.T) {
|
|||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"2222:3333", "3333:3333"},
|
||||
PortSpecs: []string{"3333:2222", "3333:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
@ -172,11 +172,11 @@ func TestMergeConfig(t *testing.T) {
|
|||
}
|
||||
|
||||
if len(configUser.PortSpecs) != 3 {
|
||||
t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs))
|
||||
t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs))
|
||||
}
|
||||
for _, portSpecs := range configUser.PortSpecs {
|
||||
if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" {
|
||||
t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs)
|
||||
if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" {
|
||||
t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs)
|
||||
}
|
||||
}
|
||||
if len(configUser.Env) != 3 {
|
||||
|
@ -197,3 +197,45 @@ func TestMergeConfig(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfigPublicPortNotHonored(t *testing.T) {
|
||||
volumesImage := make(map[string]struct{})
|
||||
volumesImage["/test1"] = struct{}{}
|
||||
volumesImage["/test2"] = struct{}{}
|
||||
configImage := &Config{
|
||||
Dns: []string{"1.1.1.1", "2.2.2.2"},
|
||||
PortSpecs: []string{"1111", "2222"},
|
||||
Env: []string{"VAR1=1", "VAR2=2"},
|
||||
Volumes: volumesImage,
|
||||
}
|
||||
|
||||
volumesUser := make(map[string]struct{})
|
||||
volumesUser["/test3"] = struct{}{}
|
||||
configUser := &Config{
|
||||
Dns: []string{"3.3.3.3"},
|
||||
PortSpecs: []string{"1111:3333"},
|
||||
Env: []string{"VAR2=3", "VAR3=3"},
|
||||
Volumes: volumesUser,
|
||||
}
|
||||
|
||||
MergeConfig(configUser, configImage)
|
||||
|
||||
contains := func(a []string, expect string) bool {
|
||||
for _, p := range a {
|
||||
if p == expect {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if !contains(configUser.PortSpecs, "2222") {
|
||||
t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !contains(configUser.PortSpecs, "1111:3333") {
|
||||
t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue