| ... | ... |
@@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org> |
| 15 | 15 |
Bruno Bigras <bigras.bruno@gmail.com> |
| 16 | 16 |
Caleb Spare <cespare@gmail.com> |
| 17 | 17 |
Charles Hooper <charles.hooper@dotcloud.com> |
| 18 |
+Daniel Gasienica <daniel@gasienica.ch> |
|
| 18 | 19 |
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> |
| 19 | 20 |
Daniel Robinson <gottagetmac@gmail.com> |
| 20 | 21 |
Daniel Von Fange <daniel@leancoder.com> |
| ... | ... |
@@ -1,5 +1,14 @@ |
| 1 | 1 |
# Changelog |
| 2 | 2 |
|
| 3 |
+## 0.3.4 (2013-05-30) |
|
| 4 |
+ + Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile |
|
| 5 |
+ + Builder: 'docker build -t FOO' applies the tag FOO to the newly built container. |
|
| 6 |
+ + Runtime: interactive TTYs correctly handle window resize |
|
| 7 |
+ * Runtime: fix how configuration is merged between layers |
|
| 8 |
+ + Remote API: split stdout and stderr on 'docker run' |
|
| 9 |
+ + Remote API: optionally listen on a different IP and port (use at your own risk) |
|
| 10 |
+ * Documentation: improved install instructions. |
|
| 11 |
+ |
|
| 3 | 12 |
## 0.3.3 (2013-05-23) |
| 4 | 13 |
- Registry: Fix push regression |
| 5 | 14 |
- Various bugfixes |
| ... | ... |
@@ -47,6 +47,8 @@ func httpError(w http.ResponseWriter, err error) {
|
| 47 | 47 |
http.Error(w, err.Error(), http.StatusBadRequest) |
| 48 | 48 |
} else if strings.HasPrefix(err.Error(), "Conflict") {
|
| 49 | 49 |
http.Error(w, err.Error(), http.StatusConflict) |
| 50 |
+ } else if strings.HasPrefix(err.Error(), "Impossible") {
|
|
| 51 |
+ http.Error(w, err.Error(), http.StatusNotAcceptable) |
|
| 50 | 52 |
} else {
|
| 51 | 53 |
http.Error(w, err.Error(), http.StatusInternalServerError) |
| 52 | 54 |
} |
| ... | ... |
@@ -69,7 +71,16 @@ func getBoolParam(value string) (bool, error) {
|
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 | 71 |
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 72 |
- b, err := json.Marshal(srv.registry.GetAuthConfig(false)) |
|
| 72 |
+ // FIXME: Handle multiple login at once |
|
| 73 |
+ // FIXME: return specific error code if config file missing? |
|
| 74 |
+ authConfig, err := auth.LoadConfig(srv.runtime.root) |
|
| 75 |
+ if err != nil {
|
|
| 76 |
+ if err != auth.ErrConfigFileMissing {
|
|
| 77 |
+ return err |
|
| 78 |
+ } |
|
| 79 |
+ authConfig = &auth.AuthConfig{}
|
|
| 80 |
+ } |
|
| 81 |
+ b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
|
|
| 73 | 82 |
if err != nil {
|
| 74 | 83 |
return err |
| 75 | 84 |
} |
| ... | ... |
@@ -78,11 +89,19 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques |
| 78 | 78 |
} |
| 79 | 79 |
|
| 80 | 80 |
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 81 |
+ // FIXME: Handle multiple login at once |
|
| 81 | 82 |
config := &auth.AuthConfig{}
|
| 82 | 83 |
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
|
| 83 | 84 |
return err |
| 84 | 85 |
} |
| 85 |
- authConfig := srv.registry.GetAuthConfig(true) |
|
| 86 |
+ |
|
| 87 |
+ authConfig, err := auth.LoadConfig(srv.runtime.root) |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ if err != auth.ErrConfigFileMissing {
|
|
| 90 |
+ return err |
|
| 91 |
+ } |
|
| 92 |
+ authConfig = &auth.AuthConfig{}
|
|
| 93 |
+ } |
|
| 86 | 94 |
if config.Username == authConfig.Username {
|
| 87 | 95 |
config.Password = authConfig.Password |
| 88 | 96 |
} |
| ... | ... |
@@ -92,7 +111,6 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque |
| 92 | 92 |
if err != nil {
|
| 93 | 93 |
return err |
| 94 | 94 |
} |
| 95 |
- srv.registry.ResetClient(newAuthConfig) |
|
| 96 | 95 |
|
| 97 | 96 |
if status != "" {
|
| 98 | 97 |
b, err := json.Marshal(&ApiAuth{Status: status})
|
| ... | ... |
@@ -298,16 +316,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht |
| 298 | 298 |
tag := r.Form.Get("tag")
|
| 299 | 299 |
repo := r.Form.Get("repo")
|
| 300 | 300 |
|
| 301 |
+ if version > 1.0 {
|
|
| 302 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 303 |
+ } |
|
| 304 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 301 | 305 |
if image != "" { //pull
|
| 302 | 306 |
registry := r.Form.Get("registry")
|
| 303 |
- if version > 1.0 {
|
|
| 304 |
- w.Header().Set("Content-Type", "application/json")
|
|
| 305 |
- } |
|
| 306 |
- if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
|
|
| 307 |
+ if err := srv.ImagePull(image, tag, registry, w, sf); err != nil {
|
|
| 308 |
+ if sf.Used() {
|
|
| 309 |
+ w.Write(sf.FormatError(err)) |
|
| 310 |
+ return nil |
|
| 311 |
+ } |
|
| 307 | 312 |
return err |
| 308 | 313 |
} |
| 309 | 314 |
} else { //import
|
| 310 |
- if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
|
|
| 315 |
+ if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
|
|
| 316 |
+ if sf.Used() {
|
|
| 317 |
+ w.Write(sf.FormatError(err)) |
|
| 318 |
+ return nil |
|
| 319 |
+ } |
|
| 311 | 320 |
return err |
| 312 | 321 |
} |
| 313 | 322 |
} |
| ... | ... |
@@ -343,10 +370,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht |
| 343 | 343 |
return fmt.Errorf("Missing parameter")
|
| 344 | 344 |
} |
| 345 | 345 |
name := vars["name"] |
| 346 |
- |
|
| 347 |
- imgId, err := srv.ImageInsert(name, url, path, w) |
|
| 346 |
+ if version > 1.0 {
|
|
| 347 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 348 |
+ } |
|
| 349 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 350 |
+ imgId, err := srv.ImageInsert(name, url, path, w, sf) |
|
| 348 | 351 |
if err != nil {
|
| 349 |
- return err |
|
| 352 |
+ if sf.Used() {
|
|
| 353 |
+ w.Write(sf.FormatError(err)) |
|
| 354 |
+ return nil |
|
| 355 |
+ } |
|
| 350 | 356 |
} |
| 351 | 357 |
b, err := json.Marshal(&ApiId{Id: imgId})
|
| 352 | 358 |
if err != nil {
|
| ... | ... |
@@ -366,8 +399,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http |
| 366 | 366 |
return fmt.Errorf("Missing parameter")
|
| 367 | 367 |
} |
| 368 | 368 |
name := vars["name"] |
| 369 |
- |
|
| 370 |
- if err := srv.ImagePush(name, registry, w); err != nil {
|
|
| 369 |
+ if version > 1.0 {
|
|
| 370 |
+ w.Header().Set("Content-Type", "application/json")
|
|
| 371 |
+ } |
|
| 372 |
+ sf := utils.NewStreamFormatter(version > 1.0) |
|
| 373 |
+ if err := srv.ImagePush(name, registry, w, sf); err != nil {
|
|
| 374 |
+ if sf.Used() {
|
|
| 375 |
+ w.Write(sf.FormatError(err)) |
|
| 376 |
+ return nil |
|
| 377 |
+ } |
|
| 371 | 378 |
return err |
| 372 | 379 |
} |
| 373 | 380 |
return nil |
| ... | ... |
@@ -652,6 +692,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ |
| 652 | 652 |
if err := r.ParseMultipartForm(4096); err != nil {
|
| 653 | 653 |
return err |
| 654 | 654 |
} |
| 655 |
+ remote := r.FormValue("t")
|
|
| 656 |
+ tag := "" |
|
| 657 |
+ if strings.Contains(remote, ":") {
|
|
| 658 |
+ remoteParts := strings.Split(remote, ":") |
|
| 659 |
+ tag = remoteParts[1] |
|
| 660 |
+ remote = remoteParts[0] |
|
| 661 |
+ } |
|
| 655 | 662 |
|
| 656 | 663 |
dockerfile, _, err := r.FormFile("Dockerfile")
|
| 657 | 664 |
if err != nil {
|
| ... | ... |
@@ -666,8 +713,10 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ |
| 666 | 666 |
} |
| 667 | 667 |
|
| 668 | 668 |
b := NewBuildFile(srv, utils.NewWriteFlusher(w)) |
| 669 |
- if _, err := b.Build(dockerfile, context); err != nil {
|
|
| 669 |
+ if id, err := b.Build(dockerfile, context); err != nil {
|
|
| 670 | 670 |
fmt.Fprintf(w, "Error build: %s\n", err) |
| 671 |
+ } else if remote != "" {
|
|
| 672 |
+ srv.runtime.repositories.Set(remote, tag, id, false) |
|
| 671 | 673 |
} |
| 672 | 674 |
return nil |
| 673 | 675 |
} |
| ... | ... |
@@ -26,8 +26,7 @@ func TestGetAuth(t *testing.T) {
|
| 26 | 26 |
defer nuke(runtime) |
| 27 | 27 |
|
| 28 | 28 |
srv := &Server{
|
| 29 |
- runtime: runtime, |
|
| 30 |
- registry: registry.NewRegistry(runtime.root), |
|
| 29 |
+ runtime: runtime, |
|
| 31 | 30 |
} |
| 32 | 31 |
|
| 33 | 32 |
r := httptest.NewRecorder() |
| ... | ... |
@@ -56,7 +55,7 @@ func TestGetAuth(t *testing.T) {
|
| 56 | 56 |
t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
|
| 57 | 57 |
} |
| 58 | 58 |
|
| 59 |
- newAuthConfig := srv.registry.GetAuthConfig(false) |
|
| 59 |
+ newAuthConfig := registry.NewRegistry(runtime.root).GetAuthConfig(false) |
|
| 60 | 60 |
if newAuthConfig.Username != authConfig.Username || |
| 61 | 61 |
newAuthConfig.Email != authConfig.Email {
|
| 62 | 62 |
t.Fatalf("The auth configuration hasn't been set correctly")
|
| ... | ... |
@@ -247,8 +246,7 @@ func TestGetImagesSearch(t *testing.T) {
|
| 247 | 247 |
defer nuke(runtime) |
| 248 | 248 |
|
| 249 | 249 |
srv := &Server{
|
| 250 |
- runtime: runtime, |
|
| 251 |
- registry: registry.NewRegistry(runtime.root), |
|
| 250 |
+ runtime: runtime, |
|
| 252 | 251 |
} |
| 253 | 252 |
|
| 254 | 253 |
r := httptest.NewRecorder() |
| ... | ... |
@@ -504,15 +502,16 @@ func TestPostAuth(t *testing.T) {
|
| 504 | 504 |
defer nuke(runtime) |
| 505 | 505 |
|
| 506 | 506 |
srv := &Server{
|
| 507 |
- runtime: runtime, |
|
| 508 |
- registry: registry.NewRegistry(runtime.root), |
|
| 507 |
+ runtime: runtime, |
|
| 509 | 508 |
} |
| 510 | 509 |
|
| 511 |
- authConfigOrig := &auth.AuthConfig{
|
|
| 510 |
+ config := &auth.AuthConfig{
|
|
| 512 | 511 |
Username: "utest", |
| 513 | 512 |
Email: "utest@yopmail.com", |
| 514 | 513 |
} |
| 515 |
- srv.registry.ResetClient(authConfigOrig) |
|
| 514 |
+ |
|
| 515 |
+ authStr := auth.EncodeAuth(config) |
|
| 516 |
+ auth.SaveConfig(runtime.root, authStr, config.Email) |
|
| 516 | 517 |
|
| 517 | 518 |
r := httptest.NewRecorder() |
| 518 | 519 |
if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
|
| ... | ... |
@@ -524,7 +523,7 @@ func TestPostAuth(t *testing.T) {
|
| 524 | 524 |
t.Fatal(err) |
| 525 | 525 |
} |
| 526 | 526 |
|
| 527 |
- if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email {
|
|
| 527 |
+ if authConfig.Username != config.Username || authConfig.Email != config.Email {
|
|
| 528 | 528 |
t.Errorf("The retrieve auth mismatch with the one set.")
|
| 529 | 529 |
} |
| 530 | 530 |
} |
| ... | ... |
@@ -54,6 +54,9 @@ func Tar(path string, compression Compression) (io.Reader, error) {
|
| 54 | 54 |
func Untar(archive io.Reader, path string) error {
|
| 55 | 55 |
cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
|
| 56 | 56 |
cmd.Stdin = archive |
| 57 |
+ // Hardcode locale environment for predictable outcome regardless of host configuration. |
|
| 58 |
+ // (see https://github.com/dotcloud/docker/issues/355) |
|
| 59 |
+ cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
|
|
| 57 | 60 |
output, err := cmd.CombinedOutput() |
| 58 | 61 |
if err != nil {
|
| 59 | 62 |
return fmt.Errorf("%s: %s", err, output)
|
| ... | ... |
@@ -3,6 +3,7 @@ package auth |
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/base64" |
| 5 | 5 |
"encoding/json" |
| 6 |
+ "errors" |
|
| 6 | 7 |
"fmt" |
| 7 | 8 |
"io/ioutil" |
| 8 | 9 |
"net/http" |
| ... | ... |
@@ -17,6 +18,12 @@ const CONFIGFILE = ".dockercfg" |
| 17 | 17 |
// the registry server we want to login against |
| 18 | 18 |
const INDEX_SERVER = "https://index.docker.io/v1" |
| 19 | 19 |
|
| 20 |
+//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/" |
|
| 21 |
+ |
|
| 22 |
+var ( |
|
| 23 |
+ ErrConfigFileMissing error = errors.New("The Auth config file is missing")
|
|
| 24 |
+) |
|
| 25 |
+ |
|
| 20 | 26 |
type AuthConfig struct {
|
| 21 | 27 |
Username string `json:"username"` |
| 22 | 28 |
Password string `json:"password"` |
| ... | ... |
@@ -75,7 +82,7 @@ func DecodeAuth(authStr string) (*AuthConfig, error) {
|
| 75 | 75 |
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
| 76 | 76 |
confFile := path.Join(rootPath, CONFIGFILE) |
| 77 | 77 |
if _, err := os.Stat(confFile); err != nil {
|
| 78 |
- return &AuthConfig{}, fmt.Errorf("The Auth config file is missing")
|
|
| 78 |
+ return nil, ErrConfigFileMissing |
|
| 79 | 79 |
} |
| 80 | 80 |
b, err := ioutil.ReadFile(confFile) |
| 81 | 81 |
if err != nil {
|
| ... | ... |
@@ -97,7 +104,7 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
|
| 97 | 97 |
} |
| 98 | 98 |
|
| 99 | 99 |
// save the auth config |
| 100 |
-func saveConfig(rootPath, authStr string, email string) error {
|
|
| 100 |
+func SaveConfig(rootPath, authStr string, email string) error {
|
|
| 101 | 101 |
confFile := path.Join(rootPath, CONFIGFILE) |
| 102 | 102 |
if len(email) == 0 {
|
| 103 | 103 |
os.Remove(confFile) |
| ... | ... |
@@ -161,7 +168,9 @@ func Login(authConfig *AuthConfig) (string, error) {
|
| 161 | 161 |
status = "Login Succeeded\n" |
| 162 | 162 |
storeConfig = true |
| 163 | 163 |
} else if resp.StatusCode == 401 {
|
| 164 |
- saveConfig(authConfig.rootPath, "", "") |
|
| 164 |
+ if err := SaveConfig(authConfig.rootPath, "", ""); err != nil {
|
|
| 165 |
+ return "", err |
|
| 166 |
+ } |
|
| 165 | 167 |
return "", fmt.Errorf("Wrong login/password, please try again")
|
| 166 | 168 |
} else {
|
| 167 | 169 |
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
|
| ... | ... |
@@ -175,7 +184,9 @@ func Login(authConfig *AuthConfig) (string, error) {
|
| 175 | 175 |
} |
| 176 | 176 |
if storeConfig {
|
| 177 | 177 |
authStr := EncodeAuth(authConfig) |
| 178 |
- saveConfig(authConfig.rootPath, authStr, authConfig.Email) |
|
| 178 |
+ if err := SaveConfig(authConfig.rootPath, authStr, authConfig.Email); err != nil {
|
|
| 179 |
+ return "", err |
|
| 180 |
+ } |
|
| 179 | 181 |
} |
| 180 | 182 |
return status, nil |
| 181 | 183 |
} |
| ... | ... |
@@ -32,8 +32,6 @@ type buildFile struct {
|
| 32 | 32 |
tmpContainers map[string]struct{}
|
| 33 | 33 |
tmpImages map[string]struct{}
|
| 34 | 34 |
|
| 35 |
- needCommit bool |
|
| 36 |
- |
|
| 37 | 35 |
out io.Writer |
| 38 | 36 |
} |
| 39 | 37 |
|
| ... | ... |
@@ -63,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
|
| 63 | 63 |
remote = name |
| 64 | 64 |
} |
| 65 | 65 |
|
| 66 |
- if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
|
|
| 66 |
+ if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false)); err != nil {
|
|
| 67 | 67 |
return err |
| 68 | 68 |
} |
| 69 | 69 |
|
| ... | ... |
@@ -81,9 +79,8 @@ func (b *buildFile) CmdFrom(name string) error {
|
| 81 | 81 |
} |
| 82 | 82 |
|
| 83 | 83 |
func (b *buildFile) CmdMaintainer(name string) error {
|
| 84 |
- b.needCommit = true |
|
| 85 | 84 |
b.maintainer = name |
| 86 |
- return nil |
|
| 85 |
+ return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
|
|
| 87 | 86 |
} |
| 88 | 87 |
|
| 89 | 88 |
func (b *buildFile) CmdRun(args string) error {
|
| ... | ... |
@@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error {
|
| 95 | 95 |
return err |
| 96 | 96 |
} |
| 97 | 97 |
|
| 98 |
- cmd, env := b.config.Cmd, b.config.Env |
|
| 98 |
+ cmd := b.config.Cmd |
|
| 99 | 99 |
b.config.Cmd = nil |
| 100 | 100 |
MergeConfig(b.config, config) |
| 101 | 101 |
|
| 102 |
- if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
|
|
| 102 |
+ utils.Debugf("Command to be executed: %v", b.config.Cmd)
|
|
| 103 |
+ |
|
| 104 |
+ if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
|
| 103 | 105 |
return err |
| 104 | 106 |
} else if cache != nil {
|
| 105 |
- utils.Debugf("Use cached version")
|
|
| 107 |
+ utils.Debugf("[BUILDER] Use cached version")
|
|
| 106 | 108 |
b.image = cache.Id |
| 107 | 109 |
return nil |
| 110 |
+ } else {
|
|
| 111 |
+ utils.Debugf("[BUILDER] Cache miss")
|
|
| 108 | 112 |
} |
| 109 | 113 |
|
| 110 | 114 |
cid, err := b.run() |
| 111 | 115 |
if err != nil {
|
| 112 | 116 |
return err |
| 113 | 117 |
} |
| 114 |
- b.config.Cmd, b.config.Env = cmd, env |
|
| 115 |
- return b.commit(cid) |
|
| 118 |
+ if err := b.commit(cid, cmd, "run"); err != nil {
|
|
| 119 |
+ return err |
|
| 120 |
+ } |
|
| 121 |
+ b.config.Cmd = cmd |
|
| 122 |
+ return nil |
|
| 116 | 123 |
} |
| 117 | 124 |
|
| 118 | 125 |
func (b *buildFile) CmdEnv(args string) error {
|
| 119 |
- b.needCommit = true |
|
| 120 | 126 |
tmp := strings.SplitN(args, " ", 2) |
| 121 | 127 |
if len(tmp) != 2 {
|
| 122 | 128 |
return fmt.Errorf("Invalid ENV format")
|
| ... | ... |
@@ -131,60 +134,34 @@ func (b *buildFile) CmdEnv(args string) error {
|
| 131 | 131 |
} |
| 132 | 132 |
} |
| 133 | 133 |
b.config.Env = append(b.config.Env, key+"="+value) |
| 134 |
- return nil |
|
| 134 |
+ return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
|
|
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 | 137 |
func (b *buildFile) CmdCmd(args string) error {
|
| 138 |
- b.needCommit = true |
|
| 139 | 138 |
var cmd []string |
| 140 | 139 |
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
| 141 | 140 |
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
| 142 |
- b.config.Cmd = []string{"/bin/sh", "-c", args}
|
|
| 143 |
- } else {
|
|
| 144 |
- b.config.Cmd = cmd |
|
| 141 |
+ cmd = []string{"/bin/sh", "-c", args}
|
|
| 142 |
+ } |
|
| 143 |
+ if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
|
|
| 144 |
+ return err |
|
| 145 | 145 |
} |
| 146 |
+ b.config.Cmd = cmd |
|
| 146 | 147 |
return nil |
| 147 | 148 |
} |
| 148 | 149 |
|
| 149 | 150 |
func (b *buildFile) CmdExpose(args string) error {
|
| 150 | 151 |
ports := strings.Split(args, " ") |
| 151 | 152 |
b.config.PortSpecs = append(ports, b.config.PortSpecs...) |
| 152 |
- return nil |
|
| 153 |
+ return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
|
|
| 153 | 154 |
} |
| 154 | 155 |
|
| 155 | 156 |
func (b *buildFile) CmdInsert(args string) error {
|
| 156 |
- if b.image == "" {
|
|
| 157 |
- return fmt.Errorf("Please provide a source image with `from` prior to insert")
|
|
| 158 |
- } |
|
| 159 |
- tmp := strings.SplitN(args, " ", 2) |
|
| 160 |
- if len(tmp) != 2 {
|
|
| 161 |
- return fmt.Errorf("Invalid INSERT format")
|
|
| 162 |
- } |
|
| 163 |
- sourceUrl := strings.Trim(tmp[0], " ") |
|
| 164 |
- destPath := strings.Trim(tmp[1], " ") |
|
| 165 |
- |
|
| 166 |
- file, err := utils.Download(sourceUrl, b.out) |
|
| 167 |
- if err != nil {
|
|
| 168 |
- return err |
|
| 169 |
- } |
|
| 170 |
- defer file.Body.Close() |
|
| 171 |
- |
|
| 172 |
- b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
|
|
| 173 |
- cid, err := b.run() |
|
| 174 |
- if err != nil {
|
|
| 175 |
- return err |
|
| 176 |
- } |
|
| 177 |
- |
|
| 178 |
- container := b.runtime.Get(cid) |
|
| 179 |
- if container == nil {
|
|
| 180 |
- return fmt.Errorf("An error occured while creating the container")
|
|
| 181 |
- } |
|
| 182 |
- |
|
| 183 |
- if err := container.Inject(file.Body, destPath); err != nil {
|
|
| 184 |
- return err |
|
| 185 |
- } |
|
| 157 |
+ return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
|
|
| 158 |
+} |
|
| 186 | 159 |
|
| 187 |
- return b.commit(cid) |
|
| 160 |
+func (b *buildFile) CmdCopy(args string) error {
|
|
| 161 |
+ return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
|
|
| 188 | 162 |
} |
| 189 | 163 |
|
| 190 | 164 |
func (b *buildFile) CmdAdd(args string) error {
|
| ... | ... |
@@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error {
|
| 193 | 193 |
} |
| 194 | 194 |
tmp := strings.SplitN(args, " ", 2) |
| 195 | 195 |
if len(tmp) != 2 {
|
| 196 |
- return fmt.Errorf("Invalid INSERT format")
|
|
| 196 |
+ return fmt.Errorf("Invalid ADD format")
|
|
| 197 | 197 |
} |
| 198 | 198 |
orig := strings.Trim(tmp[0], " ") |
| 199 | 199 |
dest := strings.Trim(tmp[1], " ") |
| 200 | 200 |
|
| 201 |
- b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
|
|
| 201 |
+ cmd := b.config.Cmd |
|
| 202 |
+ b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
|
|
| 202 | 203 |
cid, err := b.run() |
| 203 | 204 |
if err != nil {
|
| 204 | 205 |
return err |
| ... | ... |
@@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error {
|
| 208 | 208 |
if container == nil {
|
| 209 | 209 |
return fmt.Errorf("Error while creating the container (CmdAdd)")
|
| 210 | 210 |
} |
| 211 |
- |
|
| 212 |
- if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
|
|
| 211 |
+ if err := container.EnsureMounted(); err != nil {
|
|
| 213 | 212 |
return err |
| 214 | 213 |
} |
| 214 |
+ defer container.Unmount() |
|
| 215 | 215 |
|
| 216 | 216 |
origPath := path.Join(b.context, orig) |
| 217 |
- destPath := path.Join(container.rwPath(), dest) |
|
| 217 |
+ destPath := path.Join(container.RootfsPath(), dest) |
|
| 218 | 218 |
|
| 219 | 219 |
fi, err := os.Stat(origPath) |
| 220 | 220 |
if err != nil {
|
| 221 | 221 |
return err |
| 222 | 222 |
} |
| 223 | 223 |
if fi.IsDir() {
|
| 224 |
+ if err := os.MkdirAll(destPath, 0700); err != nil {
|
|
| 225 |
+ return err |
|
| 226 |
+ } |
|
| 227 |
+ |
|
| 224 | 228 |
files, err := ioutil.ReadDir(path.Join(b.context, orig)) |
| 225 | 229 |
if err != nil {
|
| 226 | 230 |
return err |
| ... | ... |
@@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error {
|
| 231 | 231 |
} |
| 232 | 232 |
} |
| 233 | 233 |
} else {
|
| 234 |
+ if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
|
|
| 235 |
+ return err |
|
| 236 |
+ } |
|
| 234 | 237 |
if err := utils.CopyDirectory(origPath, destPath); err != nil {
|
| 235 | 238 |
return err |
| 236 | 239 |
} |
| 237 | 240 |
} |
| 238 |
- |
|
| 239 |
- return b.commit(cid) |
|
| 241 |
+ if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
|
|
| 242 |
+ return err |
|
| 243 |
+ } |
|
| 244 |
+ b.config.Cmd = cmd |
|
| 245 |
+ return nil |
|
| 240 | 246 |
} |
| 241 | 247 |
|
| 242 | 248 |
func (b *buildFile) run() (string, error) {
|
| ... | ... |
@@ -265,20 +253,30 @@ func (b *buildFile) run() (string, error) {
|
| 265 | 265 |
return c.Id, nil |
| 266 | 266 |
} |
| 267 | 267 |
|
| 268 |
-func (b *buildFile) commit(id string) error {
|
|
| 268 |
+// Commit the container <id> with the autorun command <autoCmd> |
|
| 269 |
+func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
|
| 269 | 270 |
if b.image == "" {
|
| 270 | 271 |
return fmt.Errorf("Please provide a source image with `from` prior to commit")
|
| 271 | 272 |
} |
| 272 | 273 |
b.config.Image = b.image |
| 273 | 274 |
if id == "" {
|
| 274 |
- cmd := b.config.Cmd |
|
| 275 |
- b.config.Cmd = []string{"true"}
|
|
| 275 |
+ b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
|
|
| 276 |
+ |
|
| 277 |
+ if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
|
|
| 278 |
+ return err |
|
| 279 |
+ } else if cache != nil {
|
|
| 280 |
+ utils.Debugf("[BUILDER] Use cached version")
|
|
| 281 |
+ b.image = cache.Id |
|
| 282 |
+ return nil |
|
| 283 |
+ } else {
|
|
| 284 |
+ utils.Debugf("[BUILDER] Cache miss")
|
|
| 285 |
+ } |
|
| 286 |
+ |
|
| 276 | 287 |
if cid, err := b.run(); err != nil {
|
| 277 | 288 |
return err |
| 278 | 289 |
} else {
|
| 279 | 290 |
id = cid |
| 280 | 291 |
} |
| 281 |
- b.config.Cmd = cmd |
|
| 282 | 292 |
} |
| 283 | 293 |
|
| 284 | 294 |
container := b.runtime.Get(id) |
| ... | ... |
@@ -286,20 +284,20 @@ func (b *buildFile) commit(id string) error {
|
| 286 | 286 |
return fmt.Errorf("An error occured while creating the container")
|
| 287 | 287 |
} |
| 288 | 288 |
|
| 289 |
+ // Note: Actually copy the struct |
|
| 290 |
+ autoConfig := *b.config |
|
| 291 |
+ autoConfig.Cmd = autoCmd |
|
| 289 | 292 |
// Commit the container |
| 290 |
- image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil) |
|
| 293 |
+ image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig) |
|
| 291 | 294 |
if err != nil {
|
| 292 | 295 |
return err |
| 293 | 296 |
} |
| 294 | 297 |
b.tmpImages[image.Id] = struct{}{}
|
| 295 | 298 |
b.image = image.Id |
| 296 |
- b.needCommit = false |
|
| 297 | 299 |
return nil |
| 298 | 300 |
} |
| 299 | 301 |
|
| 300 | 302 |
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
| 301 |
- defer b.clearTmp(b.tmpContainers, b.tmpImages) |
|
| 302 |
- |
|
| 303 | 303 |
if context != nil {
|
| 304 | 304 |
name, err := ioutil.TempDir("/tmp", "docker-build")
|
| 305 | 305 |
if err != nil {
|
| ... | ... |
@@ -337,6 +335,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
| 337 | 337 |
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
| 338 | 338 |
if !exists {
|
| 339 | 339 |
fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction)) |
| 340 |
+ continue |
|
| 340 | 341 |
} |
| 341 | 342 |
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
| 342 | 343 |
if ret != nil {
|
| ... | ... |
@@ -345,22 +344,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
| 345 | 345 |
|
| 346 | 346 |
fmt.Fprintf(b.out, "===> %v\n", b.image) |
| 347 | 347 |
} |
| 348 |
- if b.needCommit {
|
|
| 349 |
- if err := b.commit(""); err != nil {
|
|
| 350 |
- return "", err |
|
| 351 |
- } |
|
| 352 |
- } |
|
| 353 | 348 |
if b.image != "" {
|
| 354 |
- // The build is successful, keep the temporary containers and images |
|
| 355 |
- for i := range b.tmpImages {
|
|
| 356 |
- delete(b.tmpImages, i) |
|
| 357 |
- } |
|
| 358 |
- fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image) |
|
| 349 |
+ fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image) |
|
| 359 | 350 |
return b.image, nil |
| 360 | 351 |
} |
| 361 |
- for i := range b.tmpContainers {
|
|
| 362 |
- delete(b.tmpContainers, i) |
|
| 363 |
- } |
|
| 364 | 352 |
return "", fmt.Errorf("An error occured during the build\n")
|
| 365 | 353 |
} |
| 366 | 354 |
|
| ... | ... |
@@ -17,6 +17,7 @@ import ( |
| 17 | 17 |
"net/url" |
| 18 | 18 |
"os" |
| 19 | 19 |
"os/signal" |
| 20 |
+ "path" |
|
| 20 | 21 |
"path/filepath" |
| 21 | 22 |
"reflect" |
| 22 | 23 |
"strconv" |
| ... | ... |
@@ -27,7 +28,7 @@ import ( |
| 27 | 27 |
"unicode" |
| 28 | 28 |
) |
| 29 | 29 |
|
| 30 |
-const VERSION = "0.3.3" |
|
| 30 |
+const VERSION = "0.3.4" |
|
| 31 | 31 |
|
| 32 | 32 |
var ( |
| 33 | 33 |
GIT_COMMIT string |
| ... | ... |
@@ -73,37 +74,37 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
| 73 | 73 |
} |
| 74 | 74 |
} |
| 75 | 75 |
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
|
| 76 |
- for cmd, description := range map[string]string{
|
|
| 77 |
- "attach": "Attach to a running container", |
|
| 78 |
- "build": "Build a container from a Dockerfile", |
|
| 79 |
- "commit": "Create a new image from a container's changes", |
|
| 80 |
- "diff": "Inspect changes on a container's filesystem", |
|
| 81 |
- "export": "Stream the contents of a container as a tar archive", |
|
| 82 |
- "history": "Show the history of an image", |
|
| 83 |
- "images": "List images", |
|
| 84 |
- "import": "Create a new filesystem image from the contents of a tarball", |
|
| 85 |
- "info": "Display system-wide information", |
|
| 86 |
- "insert": "Insert a file in an image", |
|
| 87 |
- "inspect": "Return low-level information on a container", |
|
| 88 |
- "kill": "Kill a running container", |
|
| 89 |
- "login": "Register or Login to the docker registry server", |
|
| 90 |
- "logs": "Fetch the logs of a container", |
|
| 91 |
- "port": "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT", |
|
| 92 |
- "ps": "List containers", |
|
| 93 |
- "pull": "Pull an image or a repository from the docker registry server", |
|
| 94 |
- "push": "Push an image or a repository to the docker registry server", |
|
| 95 |
- "restart": "Restart a running container", |
|
| 96 |
- "rm": "Remove a container", |
|
| 97 |
- "rmi": "Remove an image", |
|
| 98 |
- "run": "Run a command in a new container", |
|
| 99 |
- "search": "Search for an image in the docker index", |
|
| 100 |
- "start": "Start a stopped container", |
|
| 101 |
- "stop": "Stop a running container", |
|
| 102 |
- "tag": "Tag an image into a repository", |
|
| 103 |
- "version": "Show the docker version information", |
|
| 104 |
- "wait": "Block until a container stops, then print its exit code", |
|
| 76 |
+ for _, command := range [][2]string{
|
|
| 77 |
+ {"attach", "Attach to a running container"},
|
|
| 78 |
+ {"build", "Build a container from a Dockerfile"},
|
|
| 79 |
+ {"commit", "Create a new image from a container's changes"},
|
|
| 80 |
+ {"diff", "Inspect changes on a container's filesystem"},
|
|
| 81 |
+ {"export", "Stream the contents of a container as a tar archive"},
|
|
| 82 |
+ {"history", "Show the history of an image"},
|
|
| 83 |
+ {"images", "List images"},
|
|
| 84 |
+ {"import", "Create a new filesystem image from the contents of a tarball"},
|
|
| 85 |
+ {"info", "Display system-wide information"},
|
|
| 86 |
+ {"insert", "Insert a file in an image"},
|
|
| 87 |
+ {"inspect", "Return low-level information on a container"},
|
|
| 88 |
+ {"kill", "Kill a running container"},
|
|
| 89 |
+ {"login", "Register or Login to the docker registry server"},
|
|
| 90 |
+ {"logs", "Fetch the logs of a container"},
|
|
| 91 |
+ {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
|
| 92 |
+ {"ps", "List containers"},
|
|
| 93 |
+ {"pull", "Pull an image or a repository from the docker registry server"},
|
|
| 94 |
+ {"push", "Push an image or a repository to the docker registry server"},
|
|
| 95 |
+ {"restart", "Restart a running container"},
|
|
| 96 |
+ {"rm", "Remove a container"},
|
|
| 97 |
+ {"rmi", "Remove an image"},
|
|
| 98 |
+ {"run", "Run a command in a new container"},
|
|
| 99 |
+ {"search", "Search for an image in the docker index"},
|
|
| 100 |
+ {"start", "Start a stopped container"},
|
|
| 101 |
+ {"stop", "Stop a running container"},
|
|
| 102 |
+ {"tag", "Tag an image into a repository"},
|
|
| 103 |
+ {"version", "Show the docker version information"},
|
|
| 104 |
+ {"wait", "Block until a container stops, then print its exit code"},
|
|
| 105 | 105 |
} {
|
| 106 |
- help += fmt.Sprintf(" %-10.10s%s\n", cmd, description)
|
|
| 106 |
+ help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1])
|
|
| 107 | 107 |
} |
| 108 | 108 |
fmt.Println(help) |
| 109 | 109 |
return nil |
| ... | ... |
@@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
|
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 | 132 |
func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 133 |
- cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
|
|
| 134 |
- fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
|
|
| 133 |
+ cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
|
|
| 134 |
+ tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
|
| 135 | 135 |
if err := cmd.Parse(args); err != nil {
|
| 136 | 136 |
return nil |
| 137 | 137 |
} |
| 138 |
+ if cmd.NArg() != 1 {
|
|
| 139 |
+ cmd.Usage() |
|
| 140 |
+ return nil |
|
| 141 |
+ } |
|
| 138 | 142 |
|
| 139 | 143 |
var ( |
| 140 |
- file io.ReadCloser |
|
| 141 | 144 |
multipartBody io.Reader |
| 142 |
- err error |
|
| 145 |
+ file io.ReadCloser |
|
| 146 |
+ contextPath string |
|
| 143 | 147 |
) |
| 144 | 148 |
|
| 145 | 149 |
// Init the needed component for the Multipart |
| ... | ... |
@@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 148 | 148 |
w := multipart.NewWriter(buff) |
| 149 | 149 |
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
|
| 150 | 150 |
|
| 151 |
- // Create a FormFile multipart for the Dockerfile |
|
| 152 |
- if *fileName == "-" {
|
|
| 151 |
+ compression := Bzip2 |
|
| 152 |
+ |
|
| 153 |
+ if cmd.Arg(0) == "-" {
|
|
| 153 | 154 |
file = os.Stdin |
| 154 | 155 |
} else {
|
| 155 |
- file, err = os.Open(*fileName) |
|
| 156 |
- if err != nil {
|
|
| 156 |
+ // Send Dockerfile from arg/Dockerfile (deprecate later) |
|
| 157 |
+ if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil {
|
|
| 157 | 158 |
return err |
| 159 |
+ } else {
|
|
| 160 |
+ file = f |
|
| 158 | 161 |
} |
| 159 |
- defer file.Close() |
|
| 160 |
- } |
|
| 161 |
- if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil {
|
|
| 162 |
- return err |
|
| 163 |
- } else {
|
|
| 164 |
- io.Copy(wField, file) |
|
| 165 |
- } |
|
| 166 |
- multipartBody = io.MultiReader(multipartBody, boundary) |
|
| 167 |
- |
|
| 168 |
- compression := Bzip2 |
|
| 169 |
- |
|
| 170 |
- // Create a FormFile multipart for the context if needed |
|
| 171 |
- if cmd.Arg(0) != "" {
|
|
| 162 |
+ // Send context from arg |
|
| 163 |
+ // Create a FormFile multipart for the context if needed |
|
| 172 | 164 |
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage? |
| 173 | 165 |
context, err := Tar(cmd.Arg(0), compression) |
| 174 | 166 |
if err != nil {
|
| ... | ... |
@@ -183,19 +180,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 183 | 183 |
return err |
| 184 | 184 |
} else {
|
| 185 | 185 |
// FIXME: Find a way to have a progressbar for the upload too |
| 186 |
- io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false)) |
|
| 186 |
+ sf := utils.NewStreamFormatter(false) |
|
| 187 |
+ io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
|
|
| 187 | 188 |
} |
| 188 |
- |
|
| 189 | 189 |
multipartBody = io.MultiReader(multipartBody, boundary) |
| 190 | 190 |
} |
| 191 |
+ // Create a FormFile multipart for the Dockerfile |
|
| 192 |
+ if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil {
|
|
| 193 |
+ return err |
|
| 194 |
+ } else {
|
|
| 195 |
+ io.Copy(wField, file) |
|
| 196 |
+ } |
|
| 197 |
+ multipartBody = io.MultiReader(multipartBody, boundary) |
|
| 191 | 198 |
|
| 199 |
+ v := &url.Values{}
|
|
| 200 |
+ v.Set("t", *tag)
|
|
| 192 | 201 |
// Send the multipart request with correct content-type |
| 193 |
- req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody)
|
|
| 202 |
+ req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
|
|
| 194 | 203 |
if err != nil {
|
| 195 | 204 |
return err |
| 196 | 205 |
} |
| 197 | 206 |
req.Header.Set("Content-Type", w.FormDataContentType())
|
| 198 |
- if cmd.Arg(0) != "" {
|
|
| 207 |
+ if contextPath != "" {
|
|
| 199 | 208 |
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
|
| 200 | 209 |
fmt.Println("Uploading Context...")
|
| 201 | 210 |
} |
| ... | ... |
@@ -366,12 +372,10 @@ func (cli *DockerCli) CmdWait(args ...string) error {
|
| 366 | 366 |
// 'docker version': show version information |
| 367 | 367 |
func (cli *DockerCli) CmdVersion(args ...string) error {
|
| 368 | 368 |
cmd := Subcmd("version", "", "Show the docker version information.")
|
| 369 |
- fmt.Println(len(args)) |
|
| 370 | 369 |
if err := cmd.Parse(args); err != nil {
|
| 371 | 370 |
return nil |
| 372 | 371 |
} |
| 373 | 372 |
|
| 374 |
- fmt.Println(cmd.NArg()) |
|
| 375 | 373 |
if cmd.NArg() > 0 {
|
| 376 | 374 |
cmd.Usage() |
| 377 | 375 |
return nil |
| ... | ... |
@@ -882,9 +886,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
| 882 | 882 |
for _, out := range outs {
|
| 883 | 883 |
if !*quiet {
|
| 884 | 884 |
if *noTrunc {
|
| 885 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) |
|
| 885 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) |
|
| 886 | 886 |
} else {
|
| 887 |
- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) |
|
| 887 |
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) |
|
| 888 | 888 |
} |
| 889 | 889 |
} else {
|
| 890 | 890 |
if *noTrunc {
|
| ... | ... |
@@ -996,12 +1000,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
| 996 | 996 |
return nil |
| 997 | 997 |
} |
| 998 | 998 |
|
| 999 |
- v := url.Values{}
|
|
| 1000 |
- v.Set("logs", "1")
|
|
| 1001 |
- v.Set("stdout", "1")
|
|
| 1002 |
- v.Set("stderr", "1")
|
|
| 1003 |
- |
|
| 1004 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
|
|
| 999 |
+ if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
|
|
| 1000 |
+ return err |
|
| 1001 |
+ } |
|
| 1002 |
+ if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
|
|
| 1005 | 1003 |
return err |
| 1006 | 1004 |
} |
| 1007 | 1005 |
return nil |
| ... | ... |
@@ -1028,15 +1030,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 1028 | 1028 |
return err |
| 1029 | 1029 |
} |
| 1030 | 1030 |
|
| 1031 |
+ splitStderr := container.Config.Tty |
|
| 1032 |
+ |
|
| 1033 |
+ connections := 1 |
|
| 1034 |
+ if splitStderr {
|
|
| 1035 |
+ connections += 1 |
|
| 1036 |
+ } |
|
| 1037 |
+ chErrors := make(chan error, connections) |
|
| 1038 |
+ cli.monitorTtySize(cmd.Arg(0)) |
|
| 1039 |
+ if splitStderr {
|
|
| 1040 |
+ go func() {
|
|
| 1041 |
+ chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
|
|
| 1042 |
+ }() |
|
| 1043 |
+ } |
|
| 1031 | 1044 |
v := url.Values{}
|
| 1032 | 1045 |
v.Set("stream", "1")
|
| 1033 |
- v.Set("stdout", "1")
|
|
| 1034 |
- v.Set("stderr", "1")
|
|
| 1035 | 1046 |
v.Set("stdin", "1")
|
| 1036 |
- |
|
| 1037 |
- cli.monitorTtySize(cmd.Arg(0)) |
|
| 1038 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
|
|
| 1039 |
- return err |
|
| 1047 |
+ v.Set("stdout", "1")
|
|
| 1048 |
+ if !splitStderr {
|
|
| 1049 |
+ v.Set("stderr", "1")
|
|
| 1050 |
+ } |
|
| 1051 |
+ go func() {
|
|
| 1052 |
+ chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
|
|
| 1053 |
+ }() |
|
| 1054 |
+ for connections > 0 {
|
|
| 1055 |
+ err := <-chErrors |
|
| 1056 |
+ if err != nil {
|
|
| 1057 |
+ return err |
|
| 1058 |
+ } |
|
| 1059 |
+ connections -= 1 |
|
| 1040 | 1060 |
} |
| 1041 | 1061 |
return nil |
| 1042 | 1062 |
} |
| ... | ... |
@@ -1200,19 +1222,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1200 | 1200 |
fmt.Fprintln(os.Stderr, "WARNING: ", warning) |
| 1201 | 1201 |
} |
| 1202 | 1202 |
|
| 1203 |
- v := url.Values{}
|
|
| 1204 |
- v.Set("logs", "1")
|
|
| 1205 |
- v.Set("stream", "1")
|
|
| 1203 |
+ splitStderr := !config.Tty |
|
| 1206 | 1204 |
|
| 1207 |
- if config.AttachStdin {
|
|
| 1208 |
- v.Set("stdin", "1")
|
|
| 1205 |
+ connections := 0 |
|
| 1206 |
+ if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
|
|
| 1207 |
+ connections += 1 |
|
| 1209 | 1208 |
} |
| 1210 |
- if config.AttachStdout {
|
|
| 1211 |
- v.Set("stdout", "1")
|
|
| 1212 |
- } |
|
| 1213 |
- if config.AttachStderr {
|
|
| 1214 |
- v.Set("stderr", "1")
|
|
| 1215 |
- |
|
| 1209 |
+ if splitStderr && config.AttachStderr {
|
|
| 1210 |
+ connections += 1 |
|
| 1216 | 1211 |
} |
| 1217 | 1212 |
|
| 1218 | 1213 |
//start the container |
| ... | ... |
@@ -1221,10 +1238,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 1221 | 1221 |
return err |
| 1222 | 1222 |
} |
| 1223 | 1223 |
|
| 1224 |
- if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
| 1224 |
+ if connections > 0 {
|
|
| 1225 |
+ chErrors := make(chan error, connections) |
|
| 1225 | 1226 |
cli.monitorTtySize(out.Id) |
| 1226 |
- if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
|
|
| 1227 |
- return err |
|
| 1227 |
+ |
|
| 1228 |
+ if splitStderr && config.AttachStderr {
|
|
| 1229 |
+ go func() {
|
|
| 1230 |
+ chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
|
|
| 1231 |
+ }() |
|
| 1232 |
+ } |
|
| 1233 |
+ |
|
| 1234 |
+ v := url.Values{}
|
|
| 1235 |
+ v.Set("logs", "1")
|
|
| 1236 |
+ v.Set("stream", "1")
|
|
| 1237 |
+ |
|
| 1238 |
+ if config.AttachStdin {
|
|
| 1239 |
+ v.Set("stdin", "1")
|
|
| 1240 |
+ } |
|
| 1241 |
+ if config.AttachStdout {
|
|
| 1242 |
+ v.Set("stdout", "1")
|
|
| 1243 |
+ } |
|
| 1244 |
+ if !splitStderr && config.AttachStderr {
|
|
| 1245 |
+ v.Set("stderr", "1")
|
|
| 1246 |
+ } |
|
| 1247 |
+ go func() {
|
|
| 1248 |
+ chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
|
|
| 1249 |
+ }() |
|
| 1250 |
+ for connections > 0 {
|
|
| 1251 |
+ err := <-chErrors |
|
| 1252 |
+ if err != nil {
|
|
| 1253 |
+ return err |
|
| 1254 |
+ } |
|
| 1255 |
+ connections -= 1 |
|
| 1228 | 1256 |
} |
| 1229 | 1257 |
} |
| 1230 | 1258 |
if !config.AttachStdout && !config.AttachStderr {
|
| ... | ... |
@@ -1334,13 +1379,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e |
| 1334 | 1334 |
} |
| 1335 | 1335 |
|
| 1336 | 1336 |
if resp.Header.Get("Content-Type") == "application/json" {
|
| 1337 |
- type Message struct {
|
|
| 1338 |
- Status string `json:"status,omitempty"` |
|
| 1339 |
- Progress string `json:"progress,omitempty"` |
|
| 1340 |
- } |
|
| 1341 | 1337 |
dec := json.NewDecoder(resp.Body) |
| 1342 | 1338 |
for {
|
| 1343 |
- var m Message |
|
| 1339 |
+ var m utils.JsonMessage |
|
| 1344 | 1340 |
if err := dec.Decode(&m); err == io.EOF {
|
| 1345 | 1341 |
break |
| 1346 | 1342 |
} else if err != nil {
|
| ... | ... |
@@ -1348,6 +1389,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e |
| 1348 | 1348 |
} |
| 1349 | 1349 |
if m.Progress != "" {
|
| 1350 | 1350 |
fmt.Fprintf(out, "Downloading %s\r", m.Progress) |
| 1351 |
+ } else if m.Error != "" {
|
|
| 1352 |
+ return fmt.Errorf(m.Error) |
|
| 1351 | 1353 |
} else {
|
| 1352 | 1354 |
fmt.Fprintf(out, "%s\n", m.Status) |
| 1353 | 1355 |
} |
| ... | ... |
@@ -1360,7 +1403,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e |
| 1360 | 1360 |
return nil |
| 1361 | 1361 |
} |
| 1362 | 1362 |
|
| 1363 |
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
|
| 1363 |
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
|
|
| 1364 | 1364 |
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
|
| 1365 | 1365 |
if err != nil {
|
| 1366 | 1366 |
return err |
| ... | ... |
@@ -1378,20 +1421,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
|
| 1378 | 1378 |
defer rwc.Close() |
| 1379 | 1379 |
|
| 1380 | 1380 |
receiveStdout := utils.Go(func() error {
|
| 1381 |
- _, err := io.Copy(os.Stdout, br) |
|
| 1381 |
+ _, err := io.Copy(out, br) |
|
| 1382 | 1382 |
return err |
| 1383 | 1383 |
}) |
| 1384 | 1384 |
|
| 1385 |
- if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
|
|
| 1385 |
+ if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
|
|
| 1386 | 1386 |
if oldState, err := term.SetRawTerminal(); err != nil {
|
| 1387 | 1387 |
return err |
| 1388 | 1388 |
} else {
|
| 1389 | 1389 |
defer term.RestoreTerminal(oldState) |
| 1390 | 1390 |
} |
| 1391 | 1391 |
} |
| 1392 |
- |
|
| 1393 | 1392 |
sendStdin := utils.Go(func() error {
|
| 1394 |
- _, err := io.Copy(rwc, os.Stdin) |
|
| 1393 |
+ _, err := io.Copy(rwc, in) |
|
| 1395 | 1394 |
if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
|
| 1396 | 1395 |
fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err) |
| 1397 | 1396 |
} |
| ... | ... |
@@ -2,18 +2,15 @@ |
| 2 | 2 |
set -e |
| 3 | 3 |
|
| 4 | 4 |
# these should match the names found at http://www.debian.org/releases/ |
| 5 |
-stableSuite='squeeze' |
|
| 6 |
-testingSuite='wheezy' |
|
| 5 |
+stableSuite='wheezy' |
|
| 6 |
+testingSuite='jessie' |
|
| 7 | 7 |
unstableSuite='sid' |
| 8 | 8 |
|
| 9 |
-# if suite is equal to this, it gets the "latest" tag |
|
| 10 |
-latestSuite="$testingSuite" |
|
| 11 |
- |
|
| 12 | 9 |
variant='minbase' |
| 13 | 10 |
include='iproute,iputils-ping' |
| 14 | 11 |
|
| 15 | 12 |
repo="$1" |
| 16 |
-suite="${2:-$latestSuite}"
|
|
| 13 |
+suite="${2:-$stableSuite}"
|
|
| 17 | 14 |
mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
|
| 18 | 15 |
|
| 19 | 16 |
if [ ! "$repo" ]; then |
| ... | ... |
@@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -) |
| 41 | 41 |
# tag suite |
| 42 | 42 |
docker tag $img $repo $suite |
| 43 | 43 |
|
| 44 |
-if [ "$suite" = "$latestSuite" ]; then |
|
| 45 |
- # tag latest |
|
| 46 |
- docker tag $img $repo latest |
|
| 47 |
-fi |
|
| 48 |
- |
|
| 49 | 44 |
# test the image |
| 50 | 45 |
docker run -i -t $repo:$suite echo success |
| 51 | 46 |
|
| 52 |
-# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag |
|
| 53 |
-if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then |
|
| 54 |
- # tag the specific version |
|
| 47 |
+if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then |
|
| 48 |
+ # tag latest |
|
| 49 |
+ docker tag $img $repo latest |
|
| 50 |
+ |
|
| 51 |
+ # tag the specific debian release version |
|
| 55 | 52 |
ver=$(docker run $repo:$suite cat /etc/debian_version) |
| 56 | 53 |
docker tag $img $repo $ver |
| 57 | 54 |
fi |
| ... | ... |
@@ -6,6 +6,7 @@ SPHINXOPTS = |
| 6 | 6 |
SPHINXBUILD = sphinx-build |
| 7 | 7 |
PAPER = |
| 8 | 8 |
BUILDDIR = _build |
| 9 |
+PYTHON = python |
|
| 9 | 10 |
|
| 10 | 11 |
# Internal variables. |
| 11 | 12 |
PAPEROPT_a4 = -D latex_paper_size=a4 |
| ... | ... |
@@ -38,6 +39,7 @@ help: |
| 38 | 38 |
# @echo " linkcheck to check all external links for integrity" |
| 39 | 39 |
# @echo " doctest to run all doctests embedded in the documentation (if enabled)" |
| 40 | 40 |
@echo " docs to build the docs and copy the static files to the outputdir" |
| 41 |
+ @echo " server to serve the docs in your browser under \`http://localhost:8000\`" |
|
| 41 | 42 |
@echo " publish to publish the app to dotcloud" |
| 42 | 43 |
|
| 43 | 44 |
clean: |
| ... | ... |
@@ -49,6 +51,8 @@ docs: |
| 49 | 49 |
@echo |
| 50 | 50 |
@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html." |
| 51 | 51 |
|
| 52 |
+server: |
|
| 53 |
+ @cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000 |
|
| 52 | 54 |
|
| 53 | 55 |
site: |
| 54 | 56 |
cp -r website $(BUILDDIR)/ |
| ... | ... |
@@ -14,20 +14,22 @@ Installation |
| 14 | 14 |
------------ |
| 15 | 15 |
|
| 16 | 16 |
* Work in your own fork of the code, we accept pull requests. |
| 17 |
-* Install sphinx: ``pip install sphinx`` |
|
| 18 |
-* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain`` |
|
| 17 |
+* Install sphinx: `pip install sphinx` |
|
| 18 |
+ * Mac OS X: `[sudo] pip-2.7 install sphinx`) |
|
| 19 |
+* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain` |
|
| 20 |
+ * Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain` |
|
| 19 | 21 |
* If pip is not available you can probably install it using your favorite package manager as **python-pip** |
| 20 | 22 |
|
| 21 | 23 |
Usage |
| 22 | 24 |
----- |
| 23 |
-* change the .rst files with your favorite editor to your liking |
|
| 24 |
-* run *make docs* to clean up old files and generate new ones |
|
| 25 |
-* your static website can now be found in the _build dir |
|
| 26 |
-* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000' |
|
| 25 |
+* Change the `.rst` files with your favorite editor to your liking. |
|
| 26 |
+* Run `make docs` to clean up old files and generate new ones. |
|
| 27 |
+* Your static website can now be found in the `_build` directory. |
|
| 28 |
+* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser. |
|
| 27 | 29 |
|
| 28 |
-Working using github's file editor |
|
| 30 |
+Working using GitHub's file editor |
|
| 29 | 31 |
---------------------------------- |
| 30 |
-Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows |
|
| 32 |
+Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows |
|
| 31 | 33 |
you to preview your changes right online. Just be carefull not to create many commits. |
| 32 | 34 |
|
| 33 | 35 |
Images |
| ... | ... |
@@ -72,4 +74,4 @@ Guides on using sphinx |
| 72 | 72 |
|
| 73 | 73 |
* Code examples |
| 74 | 74 |
|
| 75 |
- Start without $, so it's easy to copy and paste. |
|
| 76 | 75 |
\ No newline at end of file |
| 76 |
+ Start without $, so it's easy to copy and paste. |
| ... | ... |
@@ -15,10 +15,17 @@ Docker Remote API |
| 15 | 15 |
- Default port in the docker deamon is 4243 |
| 16 | 16 |
- 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 |
| 17 | 17 |
|
| 18 |
-2. Endpoints |
|
| 18 |
+2. Version |
|
| 19 |
+========== |
|
| 20 |
+ |
|
| 21 |
+The current verson of the API is 1.1 |
|
| 22 |
+Calling /images/<name>/insert is the same as calling /v1.1/images/<name>/insert |
|
| 23 |
+You can still call an old version of the api using /v1.0/images/<name>/insert |
|
| 24 |
+ |
|
| 25 |
+3. Endpoints |
|
| 19 | 26 |
============ |
| 20 | 27 |
|
| 21 |
-2.1 Containers |
|
| 28 |
+3.1 Containers |
|
| 22 | 29 |
-------------- |
| 23 | 30 |
|
| 24 | 31 |
List containers |
| ... | ... |
@@ -132,6 +139,7 @@ Create a container |
| 132 | 132 |
:jsonparam config: the container's configuration |
| 133 | 133 |
:statuscode 201: no error |
| 134 | 134 |
:statuscode 404: no such container |
| 135 |
+ :statuscode 406: impossible to attach (container not running) |
|
| 135 | 136 |
:statuscode 500: server error |
| 136 | 137 |
|
| 137 | 138 |
|
| ... | ... |
@@ -459,7 +467,7 @@ Remove a container |
| 459 | 459 |
:statuscode 500: server error |
| 460 | 460 |
|
| 461 | 461 |
|
| 462 |
-2.2 Images |
|
| 462 |
+3.2 Images |
|
| 463 | 463 |
---------- |
| 464 | 464 |
|
| 465 | 465 |
List Images |
| ... | ... |
@@ -548,7 +556,19 @@ Create an image |
| 548 | 548 |
|
| 549 | 549 |
POST /images/create?fromImage=base HTTP/1.1 |
| 550 | 550 |
|
| 551 |
- **Example response**: |
|
| 551 |
+ **Example response v1.1**: |
|
| 552 |
+ |
|
| 553 |
+ .. sourcecode:: http |
|
| 554 |
+ |
|
| 555 |
+ HTTP/1.1 200 OK |
|
| 556 |
+ Content-Type: application/json |
|
| 557 |
+ |
|
| 558 |
+ {"status":"Pulling..."}
|
|
| 559 |
+ {"progress":"1/? (n/a)"}
|
|
| 560 |
+ {"error":"Invalid..."}
|
|
| 561 |
+ ... |
|
| 562 |
+ |
|
| 563 |
+ **Example response v1.0**: |
|
| 552 | 564 |
|
| 553 | 565 |
.. sourcecode:: http |
| 554 | 566 |
|
| ... | ... |
@@ -579,7 +599,19 @@ Insert a file in a image |
| 579 | 579 |
|
| 580 | 580 |
POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 |
| 581 | 581 |
|
| 582 |
- **Example response**: |
|
| 582 |
+ **Example response v1.1**: |
|
| 583 |
+ |
|
| 584 |
+ .. sourcecode:: http |
|
| 585 |
+ |
|
| 586 |
+ HTTP/1.1 200 OK |
|
| 587 |
+ Content-Type: application/json |
|
| 588 |
+ |
|
| 589 |
+ {"status":"Inserting..."}
|
|
| 590 |
+ {"progress":"1/? (n/a)"}
|
|
| 591 |
+ {"error":"Invalid..."}
|
|
| 592 |
+ ... |
|
| 593 |
+ |
|
| 594 |
+ **Example response v1.0**: |
|
| 583 | 595 |
|
| 584 | 596 |
.. sourcecode:: http |
| 585 | 597 |
|
| ... | ... |
@@ -694,7 +726,19 @@ Push an image on the registry |
| 694 | 694 |
|
| 695 | 695 |
POST /images/test/push HTTP/1.1 |
| 696 | 696 |
|
| 697 |
- **Example response**: |
|
| 697 |
+ **Example response v1.1**: |
|
| 698 |
+ |
|
| 699 |
+ .. sourcecode:: http |
|
| 700 |
+ |
|
| 701 |
+ HTTP/1.1 200 OK |
|
| 702 |
+ Content-Type: application/json |
|
| 703 |
+ |
|
| 704 |
+ {"status":"Pushing..."}
|
|
| 705 |
+ {"progress":"1/? (n/a)"}
|
|
| 706 |
+ {"error":"Invalid..."}
|
|
| 707 |
+ ... |
|
| 708 |
+ |
|
| 709 |
+ **Example response v1.0**: |
|
| 698 | 710 |
|
| 699 | 711 |
.. sourcecode:: http |
| 700 | 712 |
|
| ... | ... |
@@ -817,7 +861,7 @@ Search images |
| 817 | 817 |
:statuscode 500: server error |
| 818 | 818 |
|
| 819 | 819 |
|
| 820 |
-2.3 Misc |
|
| 820 |
+3.3 Misc |
|
| 821 | 821 |
-------- |
| 822 | 822 |
|
| 823 | 823 |
Build an image from Dockerfile via stdin |
| ... | ... |
@@ -843,6 +887,7 @@ Build an image from Dockerfile via stdin |
| 843 | 843 |
|
| 844 | 844 |
{{ STREAM }}
|
| 845 | 845 |
|
| 846 |
+ :query t: tag to be applied to the resulting image in case of success |
|
| 846 | 847 |
:statuscode 200: no error |
| 847 | 848 |
:statuscode 500: server error |
| 848 | 849 |
|
| ... | ... |
@@ -246,7 +246,6 @@ The Index has two main purposes (along with its fancy social features): |
| 246 | 246 |
|
| 247 | 247 |
- Resolve short names (to avoid passing absolute URLs all the time) |
| 248 | 248 |
- username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/ |
| 249 |
- - team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/ |
|
| 250 | 249 |
- Authenticate a user as a repos owner (for a central referenced repository) |
| 251 | 250 |
|
| 252 | 251 |
3.1 Without an Index |
| ... | ... |
@@ -2,12 +2,27 @@ |
| 2 | 2 |
:description: Build a new image from the Dockerfile passed via stdin |
| 3 | 3 |
:keywords: build, docker, container, documentation |
| 4 | 4 |
|
| 5 |
-======================================================== |
|
| 6 |
-``build`` -- Build a container from Dockerfile via stdin |
|
| 7 |
-======================================================== |
|
| 5 |
+================================================ |
|
| 6 |
+``build`` -- Build a container from a Dockerfile |
|
| 7 |
+================================================ |
|
| 8 | 8 |
|
| 9 | 9 |
:: |
| 10 | 10 |
|
| 11 |
- Usage: docker build - |
|
| 12 |
- Example: cat Dockerfile | docker build - |
|
| 13 |
- Build a new image from the Dockerfile passed via stdin |
|
| 11 |
+ Usage: docker build [OPTIONS] PATH | - |
|
| 12 |
+ Build a new container image from the source code at PATH |
|
| 13 |
+ -t="": Tag to be applied to the resulting image in case of success. |
|
| 14 |
+ |
|
| 15 |
+Examples |
|
| 16 |
+-------- |
|
| 17 |
+ |
|
| 18 |
+.. code-block:: bash |
|
| 19 |
+ |
|
| 20 |
+ docker build . |
|
| 21 |
+ |
|
| 22 |
+This will take the local Dockerfile |
|
| 23 |
+ |
|
| 24 |
+.. code-block:: bash |
|
| 25 |
+ |
|
| 26 |
+ docker build - |
|
| 27 |
+ |
|
| 28 |
+This will read a Dockerfile form Stdin without context |
| 14 | 29 |
deleted file mode 100644 |
| ... | ... |
@@ -1,25 +0,0 @@ |
| 1 |
-:title: Building Blocks |
|
| 2 |
-:description: An introduction to docker and standard containers? |
|
| 3 |
-:keywords: containers, lxc, concepts, explanation |
|
| 4 |
- |
|
| 5 |
- |
|
| 6 |
-Building blocks |
|
| 7 |
-=============== |
|
| 8 |
- |
|
| 9 |
-.. _images: |
|
| 10 |
- |
|
| 11 |
-Images |
|
| 12 |
-An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository |
|
| 13 |
- |
|
| 14 |
-Images are stored on your local file system under /var/lib/docker/graph |
|
| 15 |
- |
|
| 16 |
- |
|
| 17 |
-.. _containers: |
|
| 18 |
- |
|
| 19 |
-Containers |
|
| 20 |
-A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance. |
|
| 21 |
- |
|
| 22 |
-Containers are stored on your local file system under /var/lib/docker/containers |
|
| 23 |
- |
| ... | ... |
@@ -5,8 +5,8 @@ |
| 5 | 5 |
Introduction |
| 6 | 6 |
============ |
| 7 | 7 |
|
| 8 |
-Docker - The Linux container runtime |
|
| 8 |
+Docker -- The Linux container runtime |
|
| 9 |
+------------------------------------- |
|
| 9 | 10 |
|
| 10 | 11 |
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers. |
| 11 | 12 |
|
| ... | ... |
@@ -1,8 +1,8 @@ |
| 1 |
-:title: Setting up a dev environment |
|
| 1 |
+:title: Setting Up a Dev Environment |
|
| 2 | 2 |
:description: Guides on how to contribute to docker |
| 3 | 3 |
:keywords: Docker, documentation, developers, contributing, dev environment |
| 4 | 4 |
|
| 5 |
-Setting up a dev environment |
|
| 5 |
+Setting Up a Dev Environment |
|
| 6 | 6 |
============================ |
| 7 | 7 |
|
| 8 | 8 |
Instructions that have been verified to work on Ubuntu 12.10, |
| ... | ... |
@@ -1,6 +1,6 @@ |
| 1 | 1 |
:title: Docker Examples |
| 2 | 2 |
:description: Examples on how to use Docker |
| 3 |
-:keywords: docker, hello world, examples |
|
| 3 |
+:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples |
|
| 4 | 4 |
|
| 5 | 5 |
|
| 6 | 6 |
|
| ... | ... |
@@ -16,6 +16,7 @@ Contents: |
| 16 | 16 |
hello_world |
| 17 | 17 |
hello_world_daemon |
| 18 | 18 |
python_web_app |
| 19 |
+ nodejs_web_app |
|
| 19 | 20 |
running_redis_service |
| 20 | 21 |
running_ssh_service |
| 21 | 22 |
couchdb_data_volumes |
| 22 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,236 @@ |
| 0 |
+:title: Running a Node.js app on CentOS |
|
| 1 |
+:description: Installing and running a Node.js app on CentOS |
|
| 2 |
+:keywords: docker, example, package installation, node, centos |
|
| 3 |
+ |
|
| 4 |
+.. _nodejs_web_app: |
|
| 5 |
+ |
|
| 6 |
+Node.js Web App |
|
| 7 |
+=============== |
|
| 8 |
+ |
|
| 9 |
+.. include:: example_header.inc |
|
| 10 |
+ |
|
| 11 |
+The goal of this example is to show you how you can build your own docker images |
|
| 12 |
+from a parent image using a ``Dockerfile`` . We will do that by making a simple |
|
| 13 |
+Node.js hello world web application running on CentOS. You can get the full |
|
| 14 |
+source code at https://github.com/gasi/docker-node-hello. |
|
| 15 |
+ |
|
| 16 |
+Create Node.js app |
|
| 17 |
+ |
|
| 18 |
+First, create a ``package.json`` file that describes your app and its |
|
| 19 |
+dependencies: |
|
| 20 |
+ |
|
| 21 |
+.. code-block:: json |
|
| 22 |
+ |
|
| 23 |
+ {
|
|
| 24 |
+ "name": "docker-centos-hello", |
|
| 25 |
+ "private": true, |
|
| 26 |
+ "version": "0.0.1", |
|
| 27 |
+ "description": "Node.js Hello World app on CentOS using docker", |
|
| 28 |
+ "author": "Daniel Gasienica <daniel@gasienica.ch>", |
|
| 29 |
+ "dependencies": {
|
|
| 30 |
+ "express": "3.2.4" |
|
| 31 |
+ } |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+Then, create an ``index.js`` file that defines a web app using the |
|
| 35 |
+`Express.js <http://expressjs.com/>`_ framework: |
|
| 36 |
+ |
|
| 37 |
+.. code-block:: javascript |
|
| 38 |
+ |
|
| 39 |
+ var express = require('express');
|
|
| 40 |
+ |
|
| 41 |
+ // Constants |
|
| 42 |
+ var PORT = 8080; |
|
| 43 |
+ |
|
| 44 |
+ // App |
|
| 45 |
+ var app = express(); |
|
| 46 |
+ app.get('/', function (req, res) {
|
|
| 47 |
+ res.send('Hello World\n');
|
|
| 48 |
+ }); |
|
| 49 |
+ |
|
| 50 |
+ app.listen(PORT) |
|
| 51 |
+ console.log('Running on http://localhost:' + PORT);
|
|
| 52 |
+ |
|
| 53 |
+ |
|
| 54 |
+In the next steps, we’ll look at how you can run this app inside a CentOS |
|
| 55 |
+container using docker. First, you’ll need to build a docker image of your app. |
|
| 56 |
+ |
|
| 57 |
+Creating a ``Dockerfile`` |
|
| 58 |
+ |
|
| 59 |
+Create an empty file called ``Dockerfile``: |
|
| 60 |
+ |
|
| 61 |
+.. code-block:: bash |
|
| 62 |
+ |
|
| 63 |
+ touch Dockerfile |
|
| 64 |
+ |
|
| 65 |
+Open the ``Dockerfile`` in your favorite text editor and add the following line |
|
| 66 |
+that defines the version of docker the image requires to build |
|
| 67 |
+(this example uses docker 0.3.4): |
|
| 68 |
+ |
|
| 69 |
+.. code-block:: bash |
|
| 70 |
+ |
|
| 71 |
+ # DOCKER-VERSION 0.3.4 |
|
| 72 |
+ |
|
| 73 |
+Next, define the parent image you want to use to build your own image on top of. |
|
| 74 |
+Here, we’ll use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``) |
|
| 75 |
+available on the `docker index`_: |
|
| 76 |
+ |
|
| 77 |
+.. code-block:: bash |
|
| 78 |
+ |
|
| 79 |
+ FROM centos:6.4 |
|
| 80 |
+ |
|
| 81 |
+Since we’re building a Node.js app, you’ll have to install Node.js as well as |
|
| 82 |
+npm on your CentOS image. Node.js is required to run your app and npm to install |
|
| 83 |
+your app’s dependencies defined in ``package.json``. |
|
| 84 |
+To install the right package for CentOS, we’ll use the instructions from the |
|
| 85 |
+`Node.js wiki`_: |
|
| 86 |
+ |
|
| 87 |
+.. code-block:: bash |
|
| 88 |
+ |
|
| 89 |
+ # Enable EPEL for Node.js |
|
| 90 |
+ RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm |
|
| 91 |
+ # Install Node.js and npm |
|
| 92 |
+ RUN yum install -y npm-1.2.17-5.el6 |
|
| 93 |
+ |
|
| 94 |
+To bundle your app’s source code inside the docker image, use the ``ADD`` |
|
| 95 |
+command: |
|
| 96 |
+ |
|
| 97 |
+.. code-block:: bash |
|
| 98 |
+ |
|
| 99 |
+ # Bundle app source |
|
| 100 |
+ ADD . /src |
|
| 101 |
+ |
|
| 102 |
+Install your app dependencies using npm: |
|
| 103 |
+ |
|
| 104 |
+.. code-block:: bash |
|
| 105 |
+ |
|
| 106 |
+ # Install app dependencies |
|
| 107 |
+ RUN cd /src; npm install |
|
| 108 |
+ |
|
| 109 |
+Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command to have it |
|
| 110 |
+mapped by the docker daemon: |
|
| 111 |
+ |
|
| 112 |
+.. code-block:: bash |
|
| 113 |
+ |
|
| 114 |
+ EXPOSE 8080 |
|
| 115 |
+ |
|
| 116 |
+Last but not least, define the command to run your app using ``CMD`` which |
|
| 117 |
+defines your runtime, i.e. ``node``, and the path to our app, i.e. |
|
| 118 |
+``src/index.js`` (see the step where we added the source to the container): |
|
| 119 |
+ |
|
| 120 |
+.. code-block:: bash |
|
| 121 |
+ |
|
| 122 |
+ CMD ["node", "/src/index.js"] |
|
| 123 |
+ |
|
| 124 |
+Your ``Dockerfile`` should now look like this: |
|
| 125 |
+ |
|
| 126 |
+.. code-block:: bash |
|
| 127 |
+ |
|
| 128 |
+ |
|
| 129 |
+ # DOCKER-VERSION 0.3.4 |
|
| 130 |
+ FROM centos:6.4 |
|
| 131 |
+ |
|
| 132 |
+ # Enable EPEL for Node.js |
|
| 133 |
+ RUN rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm |
|
| 134 |
+ # Install Node.js and npm |
|
| 135 |
+ RUN yum install -y npm-1.2.17-5.el6 |
|
| 136 |
+ |
|
| 137 |
+ # Bundle app source |
|
| 138 |
+ ADD . /src |
|
| 139 |
+ # Install app dependencies |
|
| 140 |
+ RUN cd /src; npm install |
|
| 141 |
+ |
|
| 142 |
+ EXPOSE 8080 |
|
| 143 |
+ CMD ["node", "/src/index.js"] |
|
| 144 |
+ |
|
| 145 |
+ |
|
| 146 |
+Building your image |
|
| 147 |
+ |
|
| 148 |
+Go to the directory that has your ``Dockerfile`` and run the following command |
|
| 149 |
+to build a docker image. The ``-t`` flag let’s you tag your image so it’s easier |
|
| 150 |
+to find later using the ``docker images`` command: |
|
| 151 |
+ |
|
| 152 |
+.. code-block:: bash |
|
| 153 |
+ |
|
| 154 |
+ docker build -t <your username>/centos-node-hello . |
|
| 155 |
+ |
|
| 156 |
+Your image will now be listed by docker: |
|
| 157 |
+ |
|
| 158 |
+.. code-block:: bash |
|
| 159 |
+ |
|
| 160 |
+ docker images |
|
| 161 |
+ |
|
| 162 |
+ > # Example |
|
| 163 |
+ > REPOSITORY TAG ID CREATED |
|
| 164 |
+ > centos 6.4 539c0211cd76 8 weeks ago |
|
| 165 |
+ > gasi/centos-node-hello latest d64d3505b0d2 2 hours ago |
|
| 166 |
+ |
|
| 167 |
+ |
|
| 168 |
+Run the image |
|
| 169 |
+ |
|
| 170 |
+Running your image with ``-d`` runs the container in detached mode, leaving the |
|
| 171 |
+container running in the background. Run the image you previously built: |
|
| 172 |
+ |
|
| 173 |
+.. code-block:: bash |
|
| 174 |
+ |
|
| 175 |
+ docker run -d <your username>/centos-node-hello |
|
| 176 |
+ |
|
| 177 |
+Print the output of your app: |
|
| 178 |
+ |
|
| 179 |
+.. code-block:: bash |
|
| 180 |
+ |
|
| 181 |
+ # Get container ID |
|
| 182 |
+ docker ps |
|
| 183 |
+ |
|
| 184 |
+ # Print app output |
|
| 185 |
+ docker logs <container id> |
|
| 186 |
+ |
|
| 187 |
+ > # Example |
|
| 188 |
+ > Running on http://localhost:8080 |
|
| 189 |
+ |
|
| 190 |
+ |
|
| 191 |
+Test |
|
| 192 |
+ |
|
| 193 |
+To test your app, get the the port of your app that docker mapped: |
|
| 194 |
+ |
|
| 195 |
+.. code-block:: bash |
|
| 196 |
+ |
|
| 197 |
+ docker ps |
|
| 198 |
+ |
|
| 199 |
+ > # Example |
|
| 200 |
+ > ID IMAGE COMMAND ... PORTS |
|
| 201 |
+ > ecce33b30ebf gasi/centos-node-hello:latest node /src/index.js 49160->8080 |
|
| 202 |
+ |
|
| 203 |
+In the example above, docker mapped the ``8080`` port of the container to |
|
| 204 |
+``49160``. |
|
| 205 |
+ |
|
| 206 |
+Now you can call your app using ``curl`` (install if needed via: |
|
| 207 |
+``sudo apt-get install curl``): |
|
| 208 |
+ |
|
| 209 |
+.. code-block:: bash |
|
| 210 |
+ |
|
| 211 |
+ curl -i localhost:49160 |
|
| 212 |
+ |
|
| 213 |
+ > HTTP/1.1 200 OK |
|
| 214 |
+ > X-Powered-By: Express |
|
| 215 |
+ > Content-Type: text/html; charset=utf-8 |
|
| 216 |
+ > Content-Length: 12 |
|
| 217 |
+ > Date: Sun, 02 Jun 2013 03:53:22 GMT |
|
| 218 |
+ > Connection: keep-alive |
|
| 219 |
+ > |
|
| 220 |
+ > Hello World |
|
| 221 |
+ |
|
| 222 |
+We hope this tutorial helped you get up and running with Node.js and CentOS on |
|
| 223 |
+docker. You can get the full source code at |
|
| 224 |
+https://github.com/gasi/docker-node-hello. |
|
| 225 |
+ |
|
| 226 |
+Continue to :ref:`running_redis_service`. |
|
| 227 |
+ |
|
| 228 |
+ |
|
| 229 |
+.. _Node.js wiki: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#rhelcentosscientific-linux-6 |
|
| 230 |
+.. _docker index: https://index.docker.io/ |
| ... | ... |
@@ -4,8 +4,8 @@ |
| 4 | 4 |
|
| 5 | 5 |
.. _running_redis_service: |
| 6 | 6 |
|
| 7 |
-Create a redis service |
|
| 8 |
-====================== |
|
| 7 |
+Redis Service |
|
| 8 |
+============= |
|
| 9 | 9 |
|
| 10 | 10 |
.. include:: example_header.inc |
| 11 | 11 |
|
| ... | ... |
@@ -34,7 +34,7 @@ Snapshot the installation |
| 34 | 34 |
|
| 35 | 35 |
.. code-block:: bash |
| 36 | 36 |
|
| 37 |
- docker ps -a # grab the container id (this will be the last one in the list) |
|
| 37 |
+ docker ps -a # grab the container id (this will be the first one in the list) |
|
| 38 | 38 |
docker commit <container_id> <your username>/redis |
| 39 | 39 |
|
| 40 | 40 |
Run the service |
| ... | ... |
@@ -4,8 +4,8 @@ |
| 4 | 4 |
|
| 5 | 5 |
.. _running_ssh_service: |
| 6 | 6 |
|
| 7 |
-Create an ssh daemon service |
|
| 8 |
-============================ |
|
| 7 |
+SSH Daemon Service |
|
| 8 |
+================== |
|
| 9 | 9 |
|
| 10 | 10 |
.. include:: example_header.inc |
| 11 | 11 |
|
| ... | ... |
@@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea. |
| 20 | 20 |
<div style="margin-top:10px;"> |
| 21 | 21 |
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe> |
| 22 | 22 |
</div> |
| 23 |
- |
|
| 24 |
- |
|
| 23 |
+ |
|
| 25 | 24 |
You can also get this sshd container by using |
| 26 | 25 |
:: |
| 27 | 26 |
|
| ... | ... |
@@ -30,3 +29,49 @@ You can also get this sshd container by using |
| 30 | 30 |
|
| 31 | 31 |
The password is 'screencast' |
| 32 | 32 |
|
| 33 |
+**Video's Transcription:** |
|
| 34 |
+ |
|
| 35 |
+.. code-block:: bash |
|
| 36 |
+ |
|
| 37 |
+ # Hello! We are going to try and install openssh on a container and run it as a servic |
|
| 38 |
+ # let's pull base to get a base ubuntu image. |
|
| 39 |
+ $ docker pull base |
|
| 40 |
+ # I had it so it was quick |
|
| 41 |
+ # now let's connect using -i for interactive and with -t for terminal |
|
| 42 |
+ # we execute /bin/bash to get a prompt. |
|
| 43 |
+ $ docker run -i -t base /bin/bash |
|
| 44 |
+ # now let's commit it |
|
| 45 |
+ # which container was it? |
|
| 46 |
+ $ docker ps -a |more |
|
| 47 |
+ $ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd |
|
| 48 |
+ # I gave the name dhrp/sshd for the container |
|
| 49 |
+ # now we can run it again |
|
| 50 |
+ $ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode |
|
| 51 |
+ # is it running? |
|
| 52 |
+ $ docker ps |
|
| 53 |
+ # yes! |
|
| 54 |
+ # let's stop it |
|
| 55 |
+ $ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562 |
|
| 56 |
+ $ docker ps |
|
| 57 |
+ # and reconnect, but now open a port to it |
|
| 58 |
+ $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D |
|
| 59 |
+ $ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22 |
|
| 60 |
+ # it has now given us a port to connect to |
|
| 61 |
+ # we have to connect using a public ip of our host |
|
| 62 |
+ $ hostname |
|
| 63 |
+ $ ifconfig |
|
| 64 |
+ $ ssh root@192.168.33.10 -p 49153 |
|
| 65 |
+ # Ah! forgot to set root passwd |
|
| 66 |
+ $ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd |
|
| 67 |
+ $ docker ps -a |
|
| 68 |
+ $ docker run -i -t dhrp/sshd /bin/bash |
|
| 69 |
+ $ passwd |
|
| 70 |
+ $ exit |
|
| 71 |
+ $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd |
|
| 72 |
+ $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D |
|
| 73 |
+ $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22 |
|
| 74 |
+ $ ifconfig |
|
| 75 |
+ $ ssh root@192.168.33.10 -p 49154 |
|
| 76 |
+ # Thanks for watching, Thatcher thatcher@dotcloud.com |
|
| 77 |
+ |
|
| 78 |
+ |
| ... | ... |
@@ -7,8 +7,8 @@ |
| 7 | 7 |
Introduction |
| 8 | 8 |
============ |
| 9 | 9 |
|
| 10 |
-Docker - The Linux container runtime |
|
| 10 |
+Docker -- The Linux container runtime |
|
| 11 |
+------------------------------------- |
|
| 11 | 12 |
|
| 12 | 13 |
Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers. |
| 13 | 14 |
|
| ... | ... |
@@ -67,3 +67,21 @@ To start on system boot: |
| 67 | 67 |
:: |
| 68 | 68 |
|
| 69 | 69 |
sudo systemctl enable docker |
| 70 |
+ |
|
| 71 |
+Network Configuration |
|
| 72 |
+--------------------- |
|
| 73 |
+ |
|
| 74 |
+IPv4 packet forwarding is disabled by default on Arch, so internet access from inside |
|
| 75 |
+the container may not work. |
|
| 76 |
+ |
|
| 77 |
+To enable the forwarding, run as root on the host system: |
|
| 78 |
+ |
|
| 79 |
+:: |
|
| 80 |
+ |
|
| 81 |
+ sysctl net.ipv4.ip_forward=1 |
|
| 82 |
+ |
|
| 83 |
+And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.conf**: |
|
| 84 |
+ |
|
| 85 |
+:: |
|
| 86 |
+ |
|
| 87 |
+ net.ipv4.ip_forward=1 |
| ... | ... |
@@ -125,8 +125,14 @@ curl was installed within the image. |
| 125 | 125 |
.. note:: |
| 126 | 126 |
The path must include the file name. |
| 127 | 127 |
|
| 128 |
-.. note:: |
|
| 129 |
- This instruction has temporarily disabled |
|
| 128 |
+2.8 ADD |
|
| 129 |
+------- |
|
| 130 |
+ |
|
| 131 |
+ ``ADD <src> <dest>`` |
|
| 132 |
+ |
|
| 133 |
+The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path |
|
| 134 |
+of the container. |
|
| 135 |
+The context must be set in order to use this instruction. (see examples) |
|
| 130 | 136 |
|
| 131 | 137 |
3. Dockerfile Examples |
| 132 | 138 |
====================== |
| ... | ... |
@@ -4,8 +4,8 @@ |
| 4 | 4 |
|
| 5 | 5 |
.. _working_with_the_repository: |
| 6 | 6 |
|
| 7 |
-Working with the repository |
|
| 8 |
-============================ |
|
| 7 |
+Working with the Repository |
|
| 8 |
+=========================== |
|
| 9 | 9 |
|
| 10 | 10 |
|
| 11 | 11 |
Top-level repositories and user repositories |
| ... | ... |
@@ -14,9 +14,9 @@ Top-level repositories and user repositories |
| 14 | 14 |
Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind |
| 15 | 15 |
Docker, and user repositories. |
| 16 | 16 |
|
| 17 |
-* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can |
|
| 17 |
+* Top-level repositories can easily be recognized by not having a ``/`` (slash) in their name. These repositories can |
|
| 18 | 18 |
generally be trusted. |
| 19 |
-* User repositories always come in the form of <username>/<repo_name>. This is what your published images will look like. |
|
| 19 |
+* User repositories always come in the form of ``<username>/<repo_name>``. This is what your published images will look like. |
|
| 20 | 20 |
* User images are not checked, it is therefore up to you whether or not you trust the creator of this image. |
| 21 | 21 |
|
| 22 | 22 |
|
| ... | ... |
@@ -270,7 +270,7 @@ |
| 270 | 270 |
<li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li> |
| 271 | 271 |
<li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li> |
| 272 | 272 |
<li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li> |
| 273 |
- <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li> |
|
| 273 |
+ <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li> |
|
| 274 | 274 |
<li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li> |
| 275 | 275 |
<li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li> |
| 276 | 276 |
<li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li> |
| ... | ... |
@@ -107,6 +107,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut |
| 107 | 107 |
DockerVersion: VERSION, |
| 108 | 108 |
Author: author, |
| 109 | 109 |
Config: config, |
| 110 |
+ Architecture: "x86_64", |
|
| 110 | 111 |
} |
| 111 | 112 |
if container != nil {
|
| 112 | 113 |
img.Parent = container.Image |
| ... | ... |
@@ -165,7 +166,8 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output |
| 165 | 165 |
if err != nil {
|
| 166 | 166 |
return nil, err |
| 167 | 167 |
} |
| 168 |
- return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root) |
|
| 168 |
+ sf := utils.NewStreamFormatter(false) |
|
| 169 |
+ return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
|
|
| 169 | 170 |
} |
| 170 | 171 |
|
| 171 | 172 |
// Mktemp creates a temporary sub-directory inside the graph's filesystem. |
| ... | ... |
@@ -1,23 +1,31 @@ |
| 1 | 1 |
# This will build a container capable of producing an official binary build of docker and |
| 2 | 2 |
# uploading it to S3 |
| 3 |
+from ubuntu:12.04 |
|
| 3 | 4 |
maintainer Solomon Hykes <solomon@dotcloud.com> |
| 4 |
-from ubuntu:12.10 |
|
| 5 |
+# Workaround the upstart issue |
|
| 6 |
+run dpkg-divert --local --rename --add /sbin/initctl |
|
| 7 |
+run ln -s /bin/true /sbin/initctl |
|
| 8 |
+# Enable universe and gophers PPA |
|
| 9 |
+run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties |
|
| 10 |
+run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe" |
|
| 11 |
+run add-apt-repository -y ppa:gophers/go/ubuntu |
|
| 5 | 12 |
run apt-get update |
| 13 |
+# Packages required to checkout, build and upload docker |
|
| 6 | 14 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd |
| 7 | 15 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl |
| 8 |
-# Packages required to checkout and build docker |
|
| 9 | 16 |
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz |
| 10 | 17 |
run tar -C /usr/local -xzf /go.tar.gz |
| 11 |
-run echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc |
|
| 12 |
-run echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile |
|
| 18 |
+run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc |
|
| 19 |
+run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile |
|
| 13 | 20 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git |
| 14 | 21 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential |
| 15 | 22 |
# Packages required to build an ubuntu package |
| 23 |
+run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable |
|
| 16 | 24 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper |
| 17 | 25 |
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev |
| 18 |
-copy fake_initctl /usr/local/bin/initctl |
|
| 19 | 26 |
run apt-get install -y -q devscripts |
| 20 |
-add . /src |
|
| 27 |
+# Copy dockerbuilder files into the container |
|
| 28 |
+add . /src |
|
| 21 | 29 |
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder |
| 22 | 30 |
run cp /src/s3cfg /.s3cfg |
| 23 | 31 |
cmd ["dockerbuilder"] |
| ... | ... |
@@ -2,7 +2,7 @@ |
| 2 | 2 |
set -x |
| 3 | 3 |
set -e |
| 4 | 4 |
|
| 5 |
-export PATH=$PATH:/usr/local/go/bin |
|
| 5 |
+export PATH=/usr/local/go/bin:$PATH |
|
| 6 | 6 |
|
| 7 | 7 |
PACKAGE=github.com/dotcloud/docker |
| 8 | 8 |
|
| ... | ... |
@@ -36,5 +36,6 @@ else |
| 36 | 36 |
fi |
| 37 | 37 |
|
| 38 | 38 |
if [ -z "$NO_UBUNTU" ]; then |
| 39 |
+ export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'` |
|
| 39 | 40 |
(cd packaging/ubuntu && make ubuntu) |
| 40 | 41 |
fi |
| 0 | 2 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,5 @@ |
| 0 |
+# Docker project infrastructure |
|
| 1 |
+ |
|
| 2 |
+This directory holds all information about the technical infrastructure of the docker project; servers, dns, email, and all the corresponding tools and configuration. |
|
| 3 |
+ |
|
| 4 |
+Obviously credentials should not be stored in this repo, but how to obtain and use them should be documented here. |
| ... | ... |
@@ -1,3 +1,14 @@ |
| 1 |
+lxc-docker (0.3.4-1) UNRELEASED; urgency=low |
|
| 2 |
+ - Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile |
|
| 3 |
+ - Builder: 'docker build -t FOO' applies the tag FOO to the newly built container. |
|
| 4 |
+ - Runtime: interactive TTYs correctly handle window resize |
|
| 5 |
+ - Runtime: fix how configuration is merged between layers |
|
| 6 |
+ - Remote API: split stdout and stderr on 'docker run' |
|
| 7 |
+ - Remote API: optionally listen on a different IP and port (use at your own risk) |
|
| 8 |
+ - Documentation: improved install instructions. |
|
| 9 |
+ |
|
| 10 |
+ -- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700 |
|
| 11 |
+ |
|
| 1 | 12 |
lxc-docker (0.3.2-1) UNRELEASED; urgency=low |
| 2 | 13 |
- Runtime: Store the actual archive on commit |
| 3 | 14 |
- Registry: Improve the checksum process |
| ... | ... |
@@ -1,3 +1,15 @@ |
| 1 |
+lxc-docker (0.3.4-1) precise; urgency=low |
|
| 2 |
+ - Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile |
|
| 3 |
+ - Builder: 'docker build -t FOO' applies the tag FOO to the newly built container. |
|
| 4 |
+ - Runtime: interactive TTYs correctly handle window resize |
|
| 5 |
+ - Runtime: fix how configuration is merged between layers |
|
| 6 |
+ - Remote API: split stdout and stderr on 'docker run' |
|
| 7 |
+ - Remote API: optionally listen on a different IP and port (use at your own risk) |
|
| 8 |
+ - Documentation: improved install instructions. |
|
| 9 |
+ |
|
| 10 |
+ -- dotCloud <ops@dotcloud.com> Thu, 30 May 2013 00:00:00 -0700 |
|
| 11 |
+ |
|
| 12 |
+ |
|
| 1 | 13 |
lxc-docker (0.3.3-1) precise; urgency=low |
| 2 | 14 |
- Registry: Fix push regression |
| 3 | 15 |
- Various bugfixes |
| ... | ... |
@@ -330,6 +330,9 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat |
| 330 | 330 |
if validate {
|
| 331 | 331 |
suffix = "images" |
| 332 | 332 |
} |
| 333 |
+ |
|
| 334 |
+ utils.Debugf("Image list pushed to index:\n%s\n", imgListJson)
|
|
| 335 |
+ |
|
| 333 | 336 |
req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson))
|
| 334 | 337 |
if err != nil {
|
| 335 | 338 |
return nil, err |
| ... | ... |
@@ -2,13 +2,15 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 |
- "github.com/dotcloud/docker/registry" |
|
| 6 | 5 |
"github.com/dotcloud/docker/utils" |
| 7 | 6 |
"io" |
| 8 | 7 |
"io/ioutil" |
| 8 |
+ "log" |
|
| 9 | 9 |
"net" |
| 10 | 10 |
"os" |
| 11 | 11 |
"os/user" |
| 12 |
+ "strconv" |
|
| 13 |
+ "strings" |
|
| 12 | 14 |
"sync" |
| 13 | 15 |
"testing" |
| 14 | 16 |
"time" |
| ... | ... |
@@ -63,11 +65,10 @@ func init() {
|
| 63 | 63 |
|
| 64 | 64 |
// Create the "Server" |
| 65 | 65 |
srv := &Server{
|
| 66 |
- runtime: runtime, |
|
| 67 |
- registry: registry.NewRegistry(runtime.root), |
|
| 66 |
+ runtime: runtime, |
|
| 68 | 67 |
} |
| 69 | 68 |
// Retrieve the Image |
| 70 |
- if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
|
|
| 69 |
+ if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false)); err != nil {
|
|
| 71 | 70 |
panic(err) |
| 72 | 71 |
} |
| 73 | 72 |
} |
| ... | ... |
@@ -279,24 +280,50 @@ func TestGet(t *testing.T) {
|
| 279 | 279 |
|
| 280 | 280 |
} |
| 281 | 281 |
|
| 282 |
-// Run a container with a TCP port allocated, and test that it can receive connections on localhost |
|
| 283 |
-func TestAllocatePortLocalhost(t *testing.T) {
|
|
| 284 |
- runtime, err := newTestRuntime() |
|
| 285 |
- if err != nil {
|
|
| 286 |
- t.Fatal(err) |
|
| 287 |
- } |
|
| 282 |
+func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
|
|
| 283 |
+ strPort := strconv.Itoa(port) |
|
| 288 | 284 |
container, err := NewBuilder(runtime).Create(&Config{
|
| 289 | 285 |
Image: GetTestImage(runtime).Id, |
| 290 |
- Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
|
|
| 291 |
- PortSpecs: []string{"5555"},
|
|
| 286 |
+ Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
|
|
| 287 |
+ PortSpecs: []string{strPort},
|
|
| 292 | 288 |
}, |
| 293 | 289 |
) |
| 294 | 290 |
if err != nil {
|
| 295 |
- t.Fatal(err) |
|
| 291 |
+ return nil, err |
|
| 296 | 292 |
} |
| 297 | 293 |
if err := container.Start(); err != nil {
|
| 294 |
+ if strings.Contains(err.Error(), "address already in use") {
|
|
| 295 |
+ return nil, nil |
|
| 296 |
+ } |
|
| 297 |
+ return nil, err |
|
| 298 |
+ } |
|
| 299 |
+ return container, nil |
|
| 300 |
+} |
|
| 301 |
+ |
|
| 302 |
+// Run a container with a TCP port allocated, and test that it can receive connections on localhost |
|
| 303 |
+func TestAllocatePortLocalhost(t *testing.T) {
|
|
| 304 |
+ runtime, err := newTestRuntime() |
|
| 305 |
+ if err != nil {
|
|
| 298 | 306 |
t.Fatal(err) |
| 299 | 307 |
} |
| 308 |
+ port := 5554 |
|
| 309 |
+ |
|
| 310 |
+ var container *Container |
|
| 311 |
+ for {
|
|
| 312 |
+ port += 1 |
|
| 313 |
+ log.Println("Trying port", port)
|
|
| 314 |
+ t.Log("Trying port", port)
|
|
| 315 |
+ container, err = findAvailalblePort(runtime, port) |
|
| 316 |
+ if container != nil {
|
|
| 317 |
+ break |
|
| 318 |
+ } |
|
| 319 |
+ if err != nil {
|
|
| 320 |
+ t.Fatal(err) |
|
| 321 |
+ } |
|
| 322 |
+ log.Println("Port", port, "already in use")
|
|
| 323 |
+ t.Log("Port", port, "already in use")
|
|
| 324 |
+ } |
|
| 325 |
+ |
|
| 300 | 326 |
defer container.Kill() |
| 301 | 327 |
|
| 302 | 328 |
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
| ... | ... |
@@ -310,7 +337,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
|
| 310 | 310 |
|
| 311 | 311 |
conn, err := net.Dial("tcp",
|
| 312 | 312 |
fmt.Sprintf( |
| 313 |
- "localhost:%s", container.NetworkSettings.PortMapping["5555"], |
|
| 313 |
+ "localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)], |
|
| 314 | 314 |
), |
| 315 | 315 |
) |
| 316 | 316 |
if err != nil {
|
| ... | ... |
@@ -50,7 +50,8 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 | 52 |
func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
|
| 53 |
- results, err := srv.registry.SearchRepositories(term) |
|
| 53 |
+ |
|
| 54 |
+ results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term) |
|
| 54 | 55 |
if err != nil {
|
| 55 | 56 |
return nil, err |
| 56 | 57 |
} |
| ... | ... |
@@ -68,7 +69,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
|
| 68 | 68 |
return outs, nil |
| 69 | 69 |
} |
| 70 | 70 |
|
| 71 |
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
|
|
| 71 |
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
|
|
| 72 | 72 |
out = utils.NewWriteFlusher(out) |
| 73 | 73 |
img, err := srv.runtime.repositories.LookupImage(name) |
| 74 | 74 |
if err != nil {
|
| ... | ... |
@@ -92,7 +93,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e |
| 92 | 92 |
return "", err |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
- if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
|
|
| 95 |
+ if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
|
|
| 96 | 96 |
return "", err |
| 97 | 97 |
} |
| 98 | 98 |
// FIXME: Handle custom repo, tag comment, author |
| ... | ... |
@@ -100,7 +101,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e |
| 100 | 100 |
if err != nil {
|
| 101 | 101 |
return "", err |
| 102 | 102 |
} |
| 103 |
- fmt.Fprintf(out, "%s\n", img.Id) |
|
| 103 |
+ out.Write(sf.FormatStatus(img.Id)) |
|
| 104 | 104 |
return img.ShortId(), nil |
| 105 | 105 |
} |
| 106 | 106 |
|
| ... | ... |
@@ -292,8 +293,8 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
|
| 292 | 292 |
return nil |
| 293 | 293 |
} |
| 294 | 294 |
|
| 295 |
-func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string, json bool) error {
|
|
| 296 |
- history, err := srv.registry.GetRemoteHistory(imgId, registry, token) |
|
| 295 |
+func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
|
| 296 |
+ history, err := r.GetRemoteHistory(imgId, endpoint, token) |
|
| 297 | 297 |
if err != nil {
|
| 298 | 298 |
return err |
| 299 | 299 |
} |
| ... | ... |
@@ -302,8 +303,8 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri |
| 302 | 302 |
// FIXME: Launch the getRemoteImage() in goroutines |
| 303 | 303 |
for _, id := range history {
|
| 304 | 304 |
if !srv.runtime.graph.Exists(id) {
|
| 305 |
- fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
|
|
| 306 |
- imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token) |
|
| 305 |
+ out.Write(sf.FormatStatus("Pulling %s metadata", id))
|
|
| 306 |
+ imgJson, err := r.GetRemoteImageJson(id, endpoint, token) |
|
| 307 | 307 |
if err != nil {
|
| 308 | 308 |
// FIXME: Keep goging in case of error? |
| 309 | 309 |
return err |
| ... | ... |
@@ -314,12 +315,12 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri |
| 314 | 314 |
} |
| 315 | 315 |
|
| 316 | 316 |
// Get the layer |
| 317 |
- fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
|
|
| 318 |
- layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token) |
|
| 317 |
+ out.Write(sf.FormatStatus("Pulling %s fs layer", id))
|
|
| 318 |
+ layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token) |
|
| 319 | 319 |
if err != nil {
|
| 320 | 320 |
return err |
| 321 | 321 |
} |
| 322 |
- if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
|
|
| 322 |
+ if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
|
|
| 323 | 323 |
return err |
| 324 | 324 |
} |
| 325 | 325 |
} |
| ... | ... |
@@ -327,9 +328,9 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri |
| 327 | 327 |
return nil |
| 328 | 328 |
} |
| 329 | 329 |
|
| 330 |
-func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json bool) error {
|
|
| 331 |
- fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
|
|
| 332 |
- repoData, err := srv.registry.GetRepositoryData(remote) |
|
| 330 |
+func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
|
|
| 331 |
+ out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
|
|
| 332 |
+ repoData, err := r.GetRepositoryData(remote) |
|
| 333 | 333 |
if err != nil {
|
| 334 | 334 |
return err |
| 335 | 335 |
} |
| ... | ... |
@@ -341,7 +342,7 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b |
| 341 | 341 |
} |
| 342 | 342 |
|
| 343 | 343 |
utils.Debugf("Retrieving the tag list")
|
| 344 |
- tagsList, err := srv.registry.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens) |
|
| 344 |
+ tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens) |
|
| 345 | 345 |
if err != nil {
|
| 346 | 346 |
return err |
| 347 | 347 |
} |
| ... | ... |
@@ -365,11 +366,11 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b |
| 365 | 365 |
utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
|
| 366 | 366 |
continue |
| 367 | 367 |
} |
| 368 |
- fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
|
|
| 368 |
+ out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.Id, img.Tag, remote))
|
|
| 369 | 369 |
success := false |
| 370 | 370 |
for _, ep := range repoData.Endpoints {
|
| 371 |
- if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
|
|
| 372 |
- fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
|
|
| 371 |
+ if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
|
|
| 372 |
+ out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
|
|
| 373 | 373 |
continue |
| 374 | 374 |
} |
| 375 | 375 |
success = true |
| ... | ... |
@@ -394,16 +395,17 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b |
| 394 | 394 |
return nil |
| 395 | 395 |
} |
| 396 | 396 |
|
| 397 |
-func (srv *Server) ImagePull(name, tag, registry string, out io.Writer, json bool) error {
|
|
| 397 |
+func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
|
|
| 398 |
+ r := registry.NewRegistry(srv.runtime.root) |
|
| 398 | 399 |
out = utils.NewWriteFlusher(out) |
| 399 |
- if registry != "" {
|
|
| 400 |
- if err := srv.pullImage(out, name, registry, nil, json); err != nil {
|
|
| 400 |
+ if endpoint != "" {
|
|
| 401 |
+ if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
|
|
| 401 | 402 |
return err |
| 402 | 403 |
} |
| 403 | 404 |
return nil |
| 404 | 405 |
} |
| 405 | 406 |
|
| 406 |
- if err := srv.pullRepository(out, name, tag, json); err != nil {
|
|
| 407 |
+ if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
|
|
| 407 | 408 |
return err |
| 408 | 409 |
} |
| 409 | 410 |
|
| ... | ... |
@@ -476,53 +478,52 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat |
| 476 | 476 |
return imgList, nil |
| 477 | 477 |
} |
| 478 | 478 |
|
| 479 |
-func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[string]string) error {
|
|
| 479 |
+func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
|
|
| 480 | 480 |
out = utils.NewWriteFlusher(out) |
| 481 |
- fmt.Fprintf(out, "Processing checksums\n") |
|
| 481 |
+ out.Write(sf.FormatStatus("Processing checksums"))
|
|
| 482 | 482 |
imgList, err := srv.getImageList(localRepo) |
| 483 | 483 |
if err != nil {
|
| 484 | 484 |
return err |
| 485 | 485 |
} |
| 486 |
- fmt.Fprintf(out, "Sending image list\n") |
|
| 486 |
+ out.Write(sf.FormatStatus("Sending image list"))
|
|
| 487 | 487 |
|
| 488 |
- repoData, err := srv.registry.PushImageJsonIndex(name, imgList, false) |
|
| 488 |
+ repoData, err := r.PushImageJsonIndex(name, imgList, false) |
|
| 489 | 489 |
if err != nil {
|
| 490 | 490 |
return err |
| 491 | 491 |
} |
| 492 | 492 |
|
| 493 |
- // FIXME: Send only needed images |
|
| 494 | 493 |
for _, ep := range repoData.Endpoints {
|
| 495 |
- fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo)) |
|
| 494 |
+ out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
|
|
| 496 | 495 |
// For each image within the repo, push them |
| 497 | 496 |
for _, elem := range imgList {
|
| 498 | 497 |
if _, exists := repoData.ImgList[elem.Id]; exists {
|
| 499 |
- fmt.Fprintf(out, "Image %s already on registry, skipping\n", name) |
|
| 498 |
+ out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
|
|
| 500 | 499 |
continue |
| 501 | 500 |
} |
| 502 |
- if err := srv.pushImage(out, name, elem.Id, ep, repoData.Tokens); err != nil {
|
|
| 501 |
+ if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens, sf); err != nil {
|
|
| 503 | 502 |
// FIXME: Continue on error? |
| 504 | 503 |
return err |
| 505 | 504 |
} |
| 506 |
- fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
|
|
| 507 |
- if err := srv.registry.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
|
|
| 505 |
+ out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.Id, ep+"/users/"+name+"/"+elem.Tag))
|
|
| 506 |
+ if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
|
|
| 508 | 507 |
return err |
| 509 | 508 |
} |
| 510 | 509 |
} |
| 511 | 510 |
} |
| 512 | 511 |
|
| 513 |
- if _, err := srv.registry.PushImageJsonIndex(name, imgList, true); err != nil {
|
|
| 512 |
+ if _, err := r.PushImageJsonIndex(name, imgList, true); err != nil {
|
|
| 514 | 513 |
return err |
| 515 | 514 |
} |
| 516 | 515 |
return nil |
| 517 | 516 |
} |
| 518 | 517 |
|
| 519 |
-func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []string) error {
|
|
| 518 |
+func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
|
|
| 520 | 519 |
out = utils.NewWriteFlusher(out) |
| 521 | 520 |
jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json")) |
| 522 | 521 |
if err != nil {
|
| 523 | 522 |
return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
|
| 524 | 523 |
} |
| 525 |
- fmt.Fprintf(out, "Pushing %s\r\n", imgId) |
|
| 524 |
+ out.Write(sf.FormatStatus("Pushing %s", imgId))
|
|
| 526 | 525 |
|
| 527 | 526 |
// Make sure we have the image's checksum |
| 528 | 527 |
checksum, err := srv.getChecksum(imgId) |
| ... | ... |
@@ -535,9 +536,9 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st |
| 535 | 535 |
} |
| 536 | 536 |
|
| 537 | 537 |
// Send the json |
| 538 |
- if err := srv.registry.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
|
|
| 538 |
+ if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
|
|
| 539 | 539 |
if err == registry.ErrAlreadyExists {
|
| 540 |
- fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id) |
|
| 540 |
+ out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.Id))
|
|
| 541 | 541 |
return nil |
| 542 | 542 |
} |
| 543 | 543 |
return err |
| ... | ... |
@@ -570,20 +571,22 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st |
| 570 | 570 |
} |
| 571 | 571 |
|
| 572 | 572 |
// Send the layer |
| 573 |
- if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
|
|
| 573 |
+ if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "%v/%v (%v)"), sf), ep, token); err != nil {
|
|
| 574 | 574 |
return err |
| 575 | 575 |
} |
| 576 | 576 |
return nil |
| 577 | 577 |
} |
| 578 | 578 |
|
| 579 |
-func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
|
|
| 579 |
+func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
|
|
| 580 | 580 |
out = utils.NewWriteFlusher(out) |
| 581 | 581 |
img, err := srv.runtime.graph.Get(name) |
| 582 |
+ r := registry.NewRegistry(srv.runtime.root) |
|
| 583 |
+ |
|
| 582 | 584 |
if err != nil {
|
| 583 |
- fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name])) |
|
| 585 |
+ out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
|
|
| 584 | 586 |
// If it fails, try to get the repository |
| 585 | 587 |
if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
|
| 586 |
- if err := srv.pushRepository(out, name, localRepo); err != nil {
|
|
| 588 |
+ if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
|
|
| 587 | 589 |
return err |
| 588 | 590 |
} |
| 589 | 591 |
return nil |
| ... | ... |
@@ -591,14 +594,14 @@ func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
|
| 591 | 591 |
|
| 592 | 592 |
return err |
| 593 | 593 |
} |
| 594 |
- fmt.Fprintf(out, "The push refers to an image: [%s]\n", name) |
|
| 595 |
- if err := srv.pushImage(out, name, img.Id, registry, nil); err != nil {
|
|
| 594 |
+ out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
|
|
| 595 |
+ if err := srv.pushImage(r, out, name, img.Id, endpoint, nil, sf); err != nil {
|
|
| 596 | 596 |
return err |
| 597 | 597 |
} |
| 598 | 598 |
return nil |
| 599 | 599 |
} |
| 600 | 600 |
|
| 601 |
-func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
|
|
| 601 |
+func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
|
|
| 602 | 602 |
var archive io.Reader |
| 603 | 603 |
var resp *http.Response |
| 604 | 604 |
|
| ... | ... |
@@ -607,21 +610,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write |
| 607 | 607 |
} else {
|
| 608 | 608 |
u, err := url.Parse(src) |
| 609 | 609 |
if err != nil {
|
| 610 |
- fmt.Fprintf(out, "Error: %s\n", err) |
|
| 610 |
+ return err |
|
| 611 | 611 |
} |
| 612 | 612 |
if u.Scheme == "" {
|
| 613 | 613 |
u.Scheme = "http" |
| 614 | 614 |
u.Host = src |
| 615 | 615 |
u.Path = "" |
| 616 | 616 |
} |
| 617 |
- fmt.Fprintf(out, "Downloading from %s\n", u) |
|
| 617 |
+ out.Write(sf.FormatStatus("Downloading from %s", u))
|
|
| 618 | 618 |
// Download with curl (pretty progress bar) |
| 619 | 619 |
// If curl is not available, fallback to http.Get() |
| 620 | 620 |
resp, err = utils.Download(u.String(), out) |
| 621 | 621 |
if err != nil {
|
| 622 | 622 |
return err |
| 623 | 623 |
} |
| 624 |
- archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false) |
|
| 624 |
+ archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
|
|
| 625 | 625 |
} |
| 626 | 626 |
img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) |
| 627 | 627 |
if err != nil {
|
| ... | ... |
@@ -633,7 +636,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write |
| 633 | 633 |
return err |
| 634 | 634 |
} |
| 635 | 635 |
} |
| 636 |
- fmt.Fprintf(out, "%s\n", img.ShortId()) |
|
| 636 |
+ out.Write(sf.FormatStatus(img.ShortId())) |
|
| 637 | 637 |
return nil |
| 638 | 638 |
} |
| 639 | 639 |
|
| ... | ... |
@@ -883,7 +886,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std |
| 883 | 883 |
if container == nil {
|
| 884 | 884 |
return fmt.Errorf("No such container: %s", name)
|
| 885 | 885 |
} |
| 886 |
- |
|
| 887 | 886 |
//logs |
| 888 | 887 |
if logs {
|
| 889 | 888 |
if stdout {
|
| ... | ... |
@@ -909,6 +911,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std |
| 909 | 909 |
if container.State.Ghost {
|
| 910 | 910 |
return fmt.Errorf("Impossible to attach to a ghost container")
|
| 911 | 911 |
} |
| 912 |
+ if !container.State.Running {
|
|
| 913 |
+ return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
|
| 914 |
+ } |
|
| 912 | 915 |
|
| 913 | 916 |
var ( |
| 914 | 917 |
cStdin io.ReadCloser |
| ... | ... |
@@ -967,14 +972,12 @@ func NewServer(autoRestart bool) (*Server, error) {
|
| 967 | 967 |
return nil, err |
| 968 | 968 |
} |
| 969 | 969 |
srv := &Server{
|
| 970 |
- runtime: runtime, |
|
| 971 |
- registry: registry.NewRegistry(runtime.root), |
|
| 970 |
+ runtime: runtime, |
|
| 972 | 971 |
} |
| 973 | 972 |
runtime.srv = srv |
| 974 | 973 |
return srv, nil |
| 975 | 974 |
} |
| 976 | 975 |
|
| 977 | 976 |
type Server struct {
|
| 978 |
- runtime *Runtime |
|
| 979 |
- registry *registry.Registry |
|
| 977 |
+ runtime *Runtime |
|
| 980 | 978 |
} |
| ... | ... |
@@ -4,6 +4,7 @@ import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"crypto/sha256" |
| 6 | 6 |
"encoding/hex" |
| 7 |
+ "encoding/json" |
|
| 7 | 8 |
"errors" |
| 8 | 9 |
"fmt" |
| 9 | 10 |
"index/suffixarray" |
| ... | ... |
@@ -69,7 +70,7 @@ type progressReader struct {
|
| 69 | 69 |
readProgress int // How much has been read so far (bytes) |
| 70 | 70 |
lastUpdate int // How many bytes read at least update |
| 71 | 71 |
template string // Template to print. Default "%v/%v (%v)" |
| 72 |
- json bool |
|
| 72 |
+ sf *StreamFormatter |
|
| 73 | 73 |
} |
| 74 | 74 |
|
| 75 | 75 |
func (r *progressReader) Read(p []byte) (n int, err error) {
|
| ... | ... |
@@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
| 93 | 93 |
} |
| 94 | 94 |
// Send newline when complete |
| 95 | 95 |
if err != nil {
|
| 96 |
- fmt.Fprintf(r.output, FormatStatus("", r.json))
|
|
| 96 |
+ r.output.Write(r.sf.FormatStatus(""))
|
|
| 97 | 97 |
} |
| 98 | 98 |
|
| 99 | 99 |
return read, err |
| ... | ... |
@@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
|
| 101 | 101 |
func (r *progressReader) Close() error {
|
| 102 | 102 |
return io.ReadCloser(r.reader).Close() |
| 103 | 103 |
} |
| 104 |
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
|
|
| 105 |
- if template == "" {
|
|
| 106 |
- template = "%v/%v (%v)\r" |
|
| 104 |
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
|
|
| 105 |
+ tpl := string(template) |
|
| 106 |
+ if tpl == "" {
|
|
| 107 |
+ tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
|
|
| 107 | 108 |
} |
| 108 |
- return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
|
|
| 109 |
+ return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
|
|
| 109 | 110 |
} |
| 110 | 111 |
|
| 111 | 112 |
// HumanDuration returns a human-readable approximation of a duration |
| ... | ... |
@@ -564,16 +566,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
| 564 | 564 |
return &WriteFlusher{w: w, flusher: flusher}
|
| 565 | 565 |
} |
| 566 | 566 |
|
| 567 |
-func FormatStatus(str string, json bool) string {
|
|
| 568 |
- if json {
|
|
| 569 |
- return "{\"status\" : \"" + str + "\"}"
|
|
| 567 |
+type JsonMessage struct {
|
|
| 568 |
+ Status string `json:"status,omitempty"` |
|
| 569 |
+ Progress string `json:"progress,omitempty"` |
|
| 570 |
+ Error string `json:"error,omitempty"` |
|
| 571 |
+} |
|
| 572 |
+ |
|
| 573 |
+type StreamFormatter struct {
|
|
| 574 |
+ json bool |
|
| 575 |
+ used bool |
|
| 576 |
+} |
|
| 577 |
+ |
|
| 578 |
+func NewStreamFormatter(json bool) *StreamFormatter {
|
|
| 579 |
+ return &StreamFormatter{json, false}
|
|
| 580 |
+} |
|
| 581 |
+ |
|
| 582 |
+func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
|
|
| 583 |
+ sf.used = true |
|
| 584 |
+ str := fmt.Sprintf(format, a...) |
|
| 585 |
+ if sf.json {
|
|
| 586 |
+ b, err := json.Marshal(&JsonMessage{Status:str});
|
|
| 587 |
+ if err != nil {
|
|
| 588 |
+ return sf.FormatError(err) |
|
| 589 |
+ } |
|
| 590 |
+ return b |
|
| 570 | 591 |
} |
| 571 |
- return str + "\r\n" |
|
| 592 |
+ return []byte(str + "\r\n") |
|
| 572 | 593 |
} |
| 573 | 594 |
|
| 574 |
-func FormatProgress(str string, json bool) string {
|
|
| 575 |
- if json {
|
|
| 576 |
- return "{\"progress\" : \"" + str + "\"}"
|
|
| 595 |
+func (sf *StreamFormatter) FormatError(err error) []byte {
|
|
| 596 |
+ sf.used = true |
|
| 597 |
+ if sf.json {
|
|
| 598 |
+ if b, err := json.Marshal(&JsonMessage{Error:err.Error()}); err == nil {
|
|
| 599 |
+ return b |
|
| 600 |
+ } |
|
| 601 |
+ return []byte("{\"error\":\"format error\"}")
|
|
| 577 | 602 |
} |
| 578 |
- return "Downloading " + str + "\r" |
|
| 603 |
+ return []byte("Error: " + err.Error() + "\r\n")
|
|
| 604 |
+} |
|
| 605 |
+ |
|
| 606 |
+func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
|
|
| 607 |
+ sf.used = true |
|
| 608 |
+ if sf.json {
|
|
| 609 |
+ b, err := json.Marshal(&JsonMessage{Progress:str})
|
|
| 610 |
+ if err != nil {
|
|
| 611 |
+ return nil |
|
| 612 |
+ } |
|
| 613 |
+ return b |
|
| 614 |
+ } |
|
| 615 |
+ return []byte(action + " " + str + "\r") |
|
| 616 |
+} |
|
| 617 |
+ |
|
| 618 |
+func (sf *StreamFormatter) Used() bool {
|
|
| 619 |
+ return sf.used |
|
| 579 | 620 |
} |