Преглед изворни кода

Merge branch 'master' of ssh://github.com/dotcloud/docker

Solomon Hykes пре 12 година
родитељ
комит
432e18990b

+ 1 - 0
AUTHORS

@@ -42,6 +42,7 @@ Ken Cochrane <kencochrane@gmail.com>
 Kevin J. Lynagh <kevin@keminglabs.com>
 Kevin J. Lynagh <kevin@keminglabs.com>
 Louis Opter <kalessin@kalessin.fr>
 Louis Opter <kalessin@kalessin.fr>
 Maxim Treskin <zerthurd@gmail.com>
 Maxim Treskin <zerthurd@gmail.com>
+Michael Crosby <crosby.michael@gmail.com>
 Mikhail Sobolev <mss@mawhrin.net>
 Mikhail Sobolev <mss@mawhrin.net>
 Nate Jones <nate@endot.org>
 Nate Jones <nate@endot.org>
 Nelson Chen <crazysim@gmail.com>
 Nelson Chen <crazysim@gmail.com>

+ 1 - 1
README.md

@@ -251,7 +251,7 @@ Note
 ----
 ----
 
 
 We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
 We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
-Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/master/docs/README.md
+Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md
 
 
 Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
 Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
 
 

+ 34 - 4
api.go

@@ -703,9 +703,18 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
 	return nil
 	return nil
 }
 }
 
 
-func ListenAndServe(addr string, srv *Server, logging bool) error {
+func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	w.WriteHeader(http.StatusOK)
+	return nil
+}
+func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
+	w.Header().Add("Access-Control-Allow-Origin", "*")
+	w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
+	w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
+}
+
+func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 	r := mux.NewRouter()
 	r := mux.NewRouter()
-	log.Printf("Listening for HTTP on %s\n", addr)
 
 
 	m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
 	m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
 		"GET": {
 		"GET": {
@@ -745,6 +754,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
 			"/containers/{name:.*}": deleteContainers,
 			"/containers/{name:.*}": deleteContainers,
 			"/images/{name:.*}":     deleteImages,
 			"/images/{name:.*}":     deleteImages,
 		},
 		},
+		"OPTIONS": {
+			"": optionsHandler,
+		},
 	}
 	}
 
 
 	for method, routes := range m {
 	for method, routes := range m {
@@ -769,6 +781,9 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
 				if err != nil {
 				if err != nil {
 					version = APIVERSION
 					version = APIVERSION
 				}
 				}
+				if srv.enableCors {
+					writeCorsHeaders(w, r)
+				}
 				if version == 0 || version > APIVERSION {
 				if version == 0 || version > APIVERSION {
 					w.WriteHeader(http.StatusNotFound)
 					w.WriteHeader(http.StatusNotFound)
 					return
 					return
@@ -777,9 +792,24 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
 					httpError(w, err)
 					httpError(w, err)
 				}
 				}
 			}
 			}
-			r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
-			r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
+
+			if localRoute == "" {
+				r.Methods(localMethod).HandlerFunc(f)
+			} else {
+				r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
+				r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
+			}
 		}
 		}
 	}
 	}
+	return r, nil
+}
+
+func ListenAndServe(addr string, srv *Server, logging bool) error {
+	log.Printf("Listening for HTTP on %s\n", addr)
+
+	r, err := createRouter(srv, logging)
+	if err != nil {
+		return err
+	}
 	return http.ListenAndServe(addr, r)
 	return http.ListenAndServe(addr, r)
 }
 }

+ 67 - 0
api_test.go

@@ -1239,6 +1239,73 @@ func TestDeleteContainers(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func TestOptionsRoute(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime, enableCors: true}
+
+	r := httptest.NewRecorder()
+	router, err := createRouter(srv, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req, err := http.NewRequest("OPTIONS", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	router.ServeHTTP(r, req)
+	if r.Code != http.StatusOK {
+		t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
+	}
+}
+
+func TestGetEnabledCors(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime, enableCors: true}
+
+	r := httptest.NewRecorder()
+
+	router, err := createRouter(srv, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req, err := http.NewRequest("GET", "/version", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	router.ServeHTTP(r, req)
+	if r.Code != http.StatusOK {
+		t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code)
+	}
+
+	allowOrigin := r.Header().Get("Access-Control-Allow-Origin")
+	allowHeaders := r.Header().Get("Access-Control-Allow-Headers")
+	allowMethods := r.Header().Get("Access-Control-Allow-Methods")
+
+	if allowOrigin != "*" {
+		t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin)
+	}
+	if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept" {
+		t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept\", %s found.", allowHeaders)
+	}
+	if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" {
+		t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods)
+	}
+}
+
 func TestDeleteImages(t *testing.T) {
 func TestDeleteImages(t *testing.T) {
 	//FIXME: Implement this test
 	//FIXME: Implement this test
 	t.Log("Test not implemented")
 	t.Log("Test not implemented")

+ 4 - 3
docker/docker.go

@@ -33,6 +33,7 @@ func main() {
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
 	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
 	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
+	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
 	flag.Parse()
 	flag.Parse()
 	if *bridgeName != "" {
 	if *bridgeName != "" {
 		docker.NetworkBridgeIface = *bridgeName
 		docker.NetworkBridgeIface = *bridgeName
@@ -65,7 +66,7 @@ func main() {
 			flag.Usage()
 			flag.Usage()
 			return
 			return
 		}
 		}
-		if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil {
+		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors); err != nil {
 			log.Fatal(err)
 			log.Fatal(err)
 			os.Exit(-1)
 			os.Exit(-1)
 		}
 		}
@@ -104,7 +105,7 @@ func removePidFile(pidfile string) {
 	}
 	}
 }
 }
 
 
-func daemon(pidfile, addr string, port int, autoRestart bool) error {
+func daemon(pidfile, addr string, port int, autoRestart, enableCors bool) error {
 	if addr != "127.0.0.1" {
 	if addr != "127.0.0.1" {
 		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
 		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
 	}
 	}
@@ -122,7 +123,7 @@ func daemon(pidfile, addr string, port int, autoRestart bool) error {
 		os.Exit(0)
 		os.Exit(0)
 	}()
 	}()
 
 
-	server, err := docker.NewServer(autoRestart)
+	server, err := docker.NewServer(autoRestart, enableCors)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 4 - 4
docs/Makefile

@@ -46,12 +46,11 @@ clean:
 	-rm -rf $(BUILDDIR)/*
 	-rm -rf $(BUILDDIR)/*
 
 
 docs:
 docs:
-	#-rm -rf $(BUILDDIR)/*
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	@echo
 	@echo
 	@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
 	@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
 
 
-server:
+server: docs
 	@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
 	@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
 
 
 site:
 site:
@@ -62,12 +61,13 @@ site:
 
 
 connect:
 connect:
 	@echo connecting dotcloud to www.docker.io website, make sure to use user 1
 	@echo connecting dotcloud to www.docker.io website, make sure to use user 1
-	@cd _build/website/ ; \
+	@echo or create your own "dockerwebsite" app
+	@cd $(BUILDDIR)/website/ ; \
 	dotcloud connect dockerwebsite ; \
 	dotcloud connect dockerwebsite ; \
 	dotcloud list
 	dotcloud list
 
 
 push:
 push:
-	@cd _build/website/ ; \
+	@cd $(BUILDDIR)/website/ ; \
 	dotcloud push
 	dotcloud push
 
 
 $(VERSIONS):
 $(VERSIONS):

+ 36 - 3
docs/sources/api/docker_remote_api.rst

@@ -839,9 +839,9 @@ Search images
 		}
 		}
 	   ]
 	   ]
 
 
-	   :query term: term to search
-	   :statuscode 200: no error
-	   :statuscode 500: server error
+	:query term: term to search
+	:statuscode 200: no error
+	:statuscode 500: server error
 
 
 
 
 3.3 Misc
 3.3 Misc
@@ -1056,3 +1056,36 @@ Here are the steps of 'docker run' :
 
 
 In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin,
 In this first version of the API, some of the endpoints, like /attach, /pull or /push uses hijacking to transport stdin,
 stdout and stderr on the same socket. This might change in the future.
 stdout and stderr on the same socket. This might change in the future.
+
+3.3 CORS Requests
+-----------------
+
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
+    
+    docker -d -H="192.168.1.9:4243" -api-enable-cors
+
+
+==================================
+Docker Remote API Client Libraries
+==================================
+
+These libraries have been not tested by the Docker Maintainers for
+compatibility. Please file issues with the library owners.  If you
+find more library implementations, please list them in Docker doc bugs
+and we will add the libraries here.
+
++----------------------+----------------+--------------------------------------------+
+| Language/Framework   | Name           | Repository                                 |
++======================+================+============================================+
+| Python               | docker-py      | https://github.com/dotcloud/docker-py      |
++----------------------+----------------+--------------------------------------------+
+| Ruby                 | docker-ruby    | https://github.com/ActiveState/docker-ruby |
++----------------------+----------------+--------------------------------------------+
+| Ruby                 | docker-client  | https://github.com/geku/docker-client      |
++----------------------+----------------+--------------------------------------------+
+| Javascript           | docker-js      | https://github.com/dgoujard/docker-js      |
++----------------------+----------------+--------------------------------------------+
+| Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  |
+| **WebUI**            |                |                                            |
++----------------------+----------------+--------------------------------------------+
+

+ 1 - 1
docs/sources/api/index.rst

@@ -5,7 +5,7 @@
 APIs
 APIs
 ====
 ====
 
 
-This following :
+Your programs and scripts can access Docker's functionality via these interfaces:
 
 
 .. toctree::
 .. toctree::
   :maxdepth: 3
   :maxdepth: 3

+ 1 - 1
docs/sources/contributing/contributing.rst

@@ -5,5 +5,5 @@
 Contributing to Docker
 Contributing to Docker
 ======================
 ======================
 
 
-Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`.
+Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
 
 

+ 5 - 2
docs/sources/faq.rst

@@ -35,13 +35,16 @@ Most frequently asked questions.
 
 
     You can find more answers on:
     You can find more answers on:
 
 
-    * `IRC: docker on freenode`_
+    * `Docker club mailinglist`_
+    * `IRC, docker on freenode`_
     * `Github`_
     * `Github`_
     * `Ask questions on Stackoverflow`_
     * `Ask questions on Stackoverflow`_
     * `Join the conversation on Twitter`_
     * `Join the conversation on Twitter`_
 
 
+
+    .. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club
     .. _the repo: http://www.github.com/dotcloud/docker
     .. _the repo: http://www.github.com/dotcloud/docker
-    .. _IRC: docker on freenode: docker on freenode: irc://chat.freenode.net#docker
+    .. _IRC, docker on freenode: irc://chat.freenode.net#docker
     .. _Github: http://www.github.com/dotcloud/docker
     .. _Github: http://www.github.com/dotcloud/docker
     .. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
     .. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
     .. _Join the conversation on Twitter: http://twitter.com/getdocker
     .. _Join the conversation on Twitter: http://twitter.com/getdocker

+ 1 - 1
docs/sources/use/builder.rst

@@ -18,7 +18,7 @@ steps and commit them along the way, giving you a final image.
 To use Docker Builder, assemble the steps into a text file (commonly referred to
 To use Docker Builder, assemble the steps into a text file (commonly referred to
 as a Dockerfile) and supply this to `docker build` on STDIN, like so:
 as a Dockerfile) and supply this to `docker build` on STDIN, like so:
 
 
-    ``docker build < Dockerfile``
+    ``docker build - < Dockerfile``
 
 
 Docker will run your steps one-by-one, committing the result if necessary, 
 Docker will run your steps one-by-one, committing the result if necessary, 
 before finally outputting the ID of your new image.
 before finally outputting the ID of your new image.

+ 25 - 13
server.go

@@ -330,8 +330,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
 	return nil
 	return nil
 }
 }
 
 
-func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
-	out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
+func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag string, sf *utils.StreamFormatter) error {
+	out.Write(sf.FormatStatus("Pulling repository %s from %s", local, auth.IndexServerAddress()))
 	repoData, err := r.GetRepositoryData(remote)
 	repoData, err := r.GetRepositoryData(remote)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -358,7 +358,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
 		// Otherwise, check that the tag exists and use only that one
 		// Otherwise, check that the tag exists and use only that one
 		id, exists := tagsList[askedTag]
 		id, exists := tagsList[askedTag]
 		if !exists {
 		if !exists {
-			return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, remote)
+			return fmt.Errorf("Tag %s not found in repositoy %s", askedTag, local)
 		}
 		}
 		repoData.ImgList[id].Tag = askedTag
 		repoData.ImgList[id].Tag = askedTag
 	}
 	}
@@ -386,7 +386,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, a
 		if askedTag != "" && tag != askedTag {
 		if askedTag != "" && tag != askedTag {
 			continue
 			continue
 		}
 		}
-		if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
+		if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -406,8 +406,12 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
 		}
 		}
 		return nil
 		return nil
 	}
 	}
-
-	if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
+	remote := name
+	parts := strings.Split(name, "/")
+	if len(parts) > 2 {
+		remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
+	}
+	if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -489,7 +493,13 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
 	}
 	}
 	out.Write(sf.FormatStatus("Sending image list"))
 	out.Write(sf.FormatStatus("Sending image list"))
 
 
-	repoData, err := r.PushImageJSONIndex(name, imgList, false)
+	srvName := name
+	parts := strings.Split(name, "/")
+	if len(parts) > 2 {
+		srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/")))
+	}
+
+	repoData, err := r.PushImageJSONIndex(srvName, imgList, false)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -506,14 +516,14 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri
 				// FIXME: Continue on error?
 				// FIXME: Continue on error?
 				return err
 				return err
 			}
 			}
-			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+name+"/"+elem.Tag))
-			if err := r.PushRegistryTag(name, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
+			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/users/"+srvName+"/"+elem.Tag))
+			if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	if _, err := r.PushImageJSONIndex(name, imgList, true); err != nil {
+	if _, err := r.PushImageJSONIndex(srvName, imgList, true); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -869,7 +879,7 @@ func (srv *Server) ImageInspect(name string) (*Image, error) {
 	return nil, fmt.Errorf("No such image: %s", name)
 	return nil, fmt.Errorf("No such image: %s", name)
 }
 }
 
 
-func NewServer(autoRestart bool) (*Server, error) {
+func NewServer(autoRestart, enableCors bool) (*Server, error) {
 	if runtime.GOARCH != "amd64" {
 	if runtime.GOARCH != "amd64" {
 		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
 		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
 	}
 	}
@@ -878,12 +888,14 @@ func NewServer(autoRestart bool) (*Server, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	srv := &Server{
 	srv := &Server{
-		runtime: runtime,
+		runtime:    runtime,
+		enableCors: enableCors,
 	}
 	}
 	runtime.srv = srv
 	runtime.srv = srv
 	return srv, nil
 	return srv, nil
 }
 }
 
 
 type Server struct {
 type Server struct {
-	runtime *Runtime
+	runtime    *Runtime
+	enableCors bool
 }
 }