Signed-off-by: John Howard <jhoward@microsoft.com>
| ... | ... |
@@ -21,6 +21,7 @@ import ( |
| 21 | 21 |
"github.com/docker/docker/graph/tags" |
| 22 | 22 |
"github.com/docker/docker/pkg/archive" |
| 23 | 23 |
"github.com/docker/docker/pkg/fileutils" |
| 24 |
+ "github.com/docker/docker/pkg/httputils" |
|
| 24 | 25 |
"github.com/docker/docker/pkg/jsonmessage" |
| 25 | 26 |
flag "github.com/docker/docker/pkg/mflag" |
| 26 | 27 |
"github.com/docker/docker/pkg/parsers" |
| ... | ... |
@@ -188,12 +189,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 188 | 188 |
} |
| 189 | 189 |
} |
| 190 | 190 |
|
| 191 |
- // windows: show error message about modified file permissions |
|
| 192 |
- // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. |
|
| 193 |
- if runtime.GOOS == "windows" {
|
|
| 194 |
- fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) |
|
| 195 |
- } |
|
| 196 |
- |
|
| 197 | 191 |
var body io.Reader |
| 198 | 192 |
// Setup an upload progress bar |
| 199 | 193 |
// FIXME: ProgressReader shouldn't be this annoying to use |
| ... | ... |
@@ -298,7 +293,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 298 | 298 |
out: cli.out, |
| 299 | 299 |
headers: headers, |
| 300 | 300 |
} |
| 301 |
- err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
|
| 301 |
+ |
|
| 302 |
+ serverResp, err := cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts)
|
|
| 303 |
+ |
|
| 304 |
+ // Windows: show error message about modified file permissions. |
|
| 305 |
+ if runtime.GOOS == "windows" {
|
|
| 306 |
+ h, err := httputils.ParseServerHeader(serverResp.header.Get("Server"))
|
|
| 307 |
+ if err == nil {
|
|
| 308 |
+ if h.OS != "windows" {
|
|
| 309 |
+ fmt.Fprintln(cli.err, `SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) |
|
| 310 |
+ } |
|
| 311 |
+ } |
|
| 312 |
+ } |
|
| 313 |
+ |
|
| 302 | 314 |
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
| 303 | 315 |
// If no error code is set, default to 1 |
| 304 | 316 |
if jerr.Code == 0 {
|
| ... | ... |
@@ -52,7 +52,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
| 52 | 52 |
out: out, |
| 53 | 53 |
headers: map[string][]string{"X-Registry-Auth": registryAuthHeader},
|
| 54 | 54 |
} |
| 55 |
- if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
|
| 55 |
+ if _, err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil {
|
|
| 56 | 56 |
return err |
| 57 | 57 |
} |
| 58 | 58 |
return nil |
| ... | ... |
@@ -55,7 +55,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
| 55 | 55 |
rawTerminal: true, |
| 56 | 56 |
out: cli.out, |
| 57 | 57 |
} |
| 58 |
- if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
|
| 58 |
+ if _, err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil {
|
|
| 59 | 59 |
return err |
| 60 | 60 |
} |
| 61 | 61 |
return nil |
| ... | ... |
@@ -38,7 +38,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
| 38 | 38 |
rawTerminal: true, |
| 39 | 39 |
out: output, |
| 40 | 40 |
} |
| 41 |
- if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
|
| 41 |
+ if _, err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil {
|
|
| 42 | 42 |
return err |
| 43 | 43 |
} |
| 44 | 44 |
|
| ... | ... |
@@ -34,7 +34,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
| 34 | 34 |
in: input, |
| 35 | 35 |
out: cli.out, |
| 36 | 36 |
} |
| 37 |
- if err := cli.stream("POST", "/images/load", sopts); err != nil {
|
|
| 37 |
+ if _, err := cli.stream("POST", "/images/load", sopts); err != nil {
|
|
| 38 | 38 |
return err |
| 39 | 39 |
} |
| 40 | 40 |
return nil |
| ... | ... |
@@ -41,7 +41,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
| 41 | 41 |
|
| 42 | 42 |
if len(cmd.Args()) == 1 {
|
| 43 | 43 |
image := cmd.Arg(0) |
| 44 |
- if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
|
| 44 |
+ if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
|
| 45 | 45 |
return err |
| 46 | 46 |
} |
| 47 | 47 |
} else {
|
| ... | ... |
@@ -49,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
| 49 | 49 |
for _, arg := range cmd.Args() {
|
| 50 | 50 |
v.Add("names", arg)
|
| 51 | 51 |
} |
| 52 |
- if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
|
| 52 |
+ if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
|
| 53 | 53 |
return err |
| 54 | 54 |
} |
| 55 | 55 |
} |
| ... | ... |
@@ -33,6 +33,12 @@ var ( |
| 33 | 33 |
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
| 34 | 34 |
) |
| 35 | 35 |
|
| 36 |
+type serverResponse struct {
|
|
| 37 |
+ body io.ReadCloser |
|
| 38 |
+ header http.Header |
|
| 39 |
+ statusCode int |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 36 | 42 |
// HTTPClient creates a new HTTP client with the cli's client transport instance. |
| 37 | 43 |
func (cli *DockerCli) HTTPClient() *http.Client {
|
| 38 | 44 |
return &http.Client{Transport: cli.transport}
|
| ... | ... |
@@ -48,14 +54,20 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) {
|
| 48 | 48 |
return params, nil |
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 |
-func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
|
| 51 |
+func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (*serverResponse, error) {
|
|
| 52 |
+ |
|
| 53 |
+ serverResp := &serverResponse{
|
|
| 54 |
+ body: nil, |
|
| 55 |
+ statusCode: -1, |
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 52 | 58 |
expectedPayload := (method == "POST" || method == "PUT") |
| 53 | 59 |
if expectedPayload && in == nil {
|
| 54 | 60 |
in = bytes.NewReader([]byte{})
|
| 55 | 61 |
} |
| 56 | 62 |
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.Version, path), in)
|
| 57 | 63 |
if err != nil {
|
| 58 |
- return nil, nil, -1, err |
|
| 64 |
+ return serverResp, err |
|
| 59 | 65 |
} |
| 60 | 66 |
|
| 61 | 67 |
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers |
| ... | ... |
@@ -79,33 +91,34 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m |
| 79 | 79 |
} |
| 80 | 80 |
|
| 81 | 81 |
resp, err := cli.HTTPClient().Do(req) |
| 82 |
- statusCode := -1 |
|
| 83 | 82 |
if resp != nil {
|
| 84 |
- statusCode = resp.StatusCode |
|
| 83 |
+ serverResp.statusCode = resp.StatusCode |
|
| 85 | 84 |
} |
| 86 | 85 |
if err != nil {
|
| 87 | 86 |
if strings.Contains(err.Error(), "connection refused") {
|
| 88 |
- return nil, nil, statusCode, errConnectionRefused |
|
| 87 |
+ return serverResp, errConnectionRefused |
|
| 89 | 88 |
} |
| 90 | 89 |
|
| 91 | 90 |
if cli.tlsConfig == nil {
|
| 92 |
- return nil, nil, statusCode, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err)
|
|
| 91 |
+ return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err)
|
|
| 93 | 92 |
} |
| 94 |
- return nil, nil, statusCode, fmt.Errorf("An error occurred trying to connect: %v", err)
|
|
| 93 |
+ return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
|
|
| 95 | 94 |
} |
| 96 | 95 |
|
| 97 |
- if statusCode < 200 || statusCode >= 400 {
|
|
| 96 |
+ if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
|
|
| 98 | 97 |
body, err := ioutil.ReadAll(resp.Body) |
| 99 | 98 |
if err != nil {
|
| 100 |
- return nil, nil, statusCode, err |
|
| 99 |
+ return serverResp, err |
|
| 101 | 100 |
} |
| 102 | 101 |
if len(body) == 0 {
|
| 103 |
- return nil, nil, statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL)
|
|
| 102 |
+ return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
|
|
| 104 | 103 |
} |
| 105 |
- return nil, nil, statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
|
| 104 |
+ return serverResp, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
|
| 106 | 105 |
} |
| 107 | 106 |
|
| 108 |
- return resp.Body, resp.Header, statusCode, nil |
|
| 107 |
+ serverResp.body = resp.Body |
|
| 108 |
+ serverResp.header = resp.Header |
|
| 109 |
+ return serverResp, nil |
|
| 109 | 110 |
} |
| 110 | 111 |
|
| 111 | 112 |
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
| ... | ... |
@@ -119,13 +132,13 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade |
| 119 | 119 |
} |
| 120 | 120 |
|
| 121 | 121 |
// begin the request |
| 122 |
- body, hdr, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{
|
|
| 122 |
+ serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
|
|
| 123 | 123 |
"X-Registry-Auth": registryAuthHeader, |
| 124 | 124 |
}) |
| 125 | 125 |
if err == nil && out != nil {
|
| 126 | 126 |
// If we are streaming output, complete the stream since |
| 127 | 127 |
// errors may not appear until later. |
| 128 |
- err = cli.streamBody(body, hdr.Get("Content-Type"), true, out, nil)
|
|
| 128 |
+ err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
|
|
| 129 | 129 |
} |
| 130 | 130 |
if err != nil {
|
| 131 | 131 |
// Since errors in a stream appear after status 200 has been written, |
| ... | ... |
@@ -133,10 +146,10 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade |
| 133 | 133 |
if strings.Contains(err.Error(), "Authentication is required") || |
| 134 | 134 |
strings.Contains(err.Error(), "Status 401") || |
| 135 | 135 |
strings.Contains(err.Error(), "status code 401") {
|
| 136 |
- statusCode = http.StatusUnauthorized |
|
| 136 |
+ serverResp.statusCode = http.StatusUnauthorized |
|
| 137 | 137 |
} |
| 138 | 138 |
} |
| 139 |
- return body, statusCode, err |
|
| 139 |
+ return serverResp.body, serverResp.statusCode, err |
|
| 140 | 140 |
} |
| 141 | 141 |
|
| 142 | 142 |
// Resolve the Auth config relevant for this server |
| ... | ... |
@@ -166,8 +179,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st
|
| 166 | 166 |
headers["Content-Type"] = []string{"application/json"}
|
| 167 | 167 |
} |
| 168 | 168 |
|
| 169 |
- body, hdr, statusCode, err := cli.clientRequest(method, path, params, headers) |
|
| 170 |
- return body, hdr, statusCode, err |
|
| 169 |
+ serverResp, err := cli.clientRequest(method, path, params, headers) |
|
| 170 |
+ return serverResp.body, serverResp.header, serverResp.statusCode, err |
|
| 171 | 171 |
} |
| 172 | 172 |
|
| 173 | 173 |
type streamOpts struct {
|
| ... | ... |
@@ -178,12 +191,12 @@ type streamOpts struct {
|
| 178 | 178 |
headers map[string][]string |
| 179 | 179 |
} |
| 180 | 180 |
|
| 181 |
-func (cli *DockerCli) stream(method, path string, opts *streamOpts) error {
|
|
| 182 |
- body, hdr, _, err := cli.clientRequest(method, path, opts.in, opts.headers) |
|
| 181 |
+func (cli *DockerCli) stream(method, path string, opts *streamOpts) (*serverResponse, error) {
|
|
| 182 |
+ serverResp, err := cli.clientRequest(method, path, opts.in, opts.headers) |
|
| 183 | 183 |
if err != nil {
|
| 184 |
- return err |
|
| 184 |
+ return serverResp, err |
|
| 185 | 185 |
} |
| 186 |
- return cli.streamBody(body, hdr.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
|
|
| 186 |
+ return serverResp, cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), opts.rawTerminal, opts.out, opts.err)
|
|
| 187 | 187 |
} |
| 188 | 188 |
|
| 189 | 189 |
func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error {
|
| ... | ... |
@@ -1502,6 +1502,8 @@ func makeHttpHandler(logging bool, localMethod string, localRoute string, handle |
| 1502 | 1502 |
return |
| 1503 | 1503 |
} |
| 1504 | 1504 |
|
| 1505 |
+ w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
|
| 1506 |
+ |
|
| 1505 | 1507 |
if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil {
|
| 1506 | 1508 |
logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err)
|
| 1507 | 1509 |
httpError(w, err) |
| ... | ... |
@@ -1,8 +1,11 @@ |
| 1 | 1 |
package httputils |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "errors" |
|
| 4 | 5 |
"fmt" |
| 5 | 6 |
"net/http" |
| 7 |
+ "regexp" |
|
| 8 |
+ "strings" |
|
| 6 | 9 |
|
| 7 | 10 |
"github.com/docker/docker/pkg/jsonmessage" |
| 8 | 11 |
) |
| ... | ... |
@@ -25,3 +28,31 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
|
| 25 | 25 |
Code: res.StatusCode, |
| 26 | 26 |
} |
| 27 | 27 |
} |
| 28 |
+ |
|
| 29 |
+type ServerHeader struct {
|
|
| 30 |
+ App string // docker |
|
| 31 |
+ Ver string // 1.8.0-dev |
|
| 32 |
+ OS string // windows or linux |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// parseServerHeader extracts pieces from am HTTP server header |
|
| 36 |
+// which is in the format "docker/version (os)" eg docker/1.8.0-dev (windows) |
|
| 37 |
+func ParseServerHeader(hdr string) (*ServerHeader, error) {
|
|
| 38 |
+ re := regexp.MustCompile(`.*\((.+)\).*$`) |
|
| 39 |
+ r := &ServerHeader{}
|
|
| 40 |
+ if matches := re.FindStringSubmatch(hdr); matches != nil {
|
|
| 41 |
+ r.OS = matches[1] |
|
| 42 |
+ parts := strings.Split(hdr, "/") |
|
| 43 |
+ if len(parts) != 2 {
|
|
| 44 |
+ return nil, errors.New("Bad header: '/' missing")
|
|
| 45 |
+ } |
|
| 46 |
+ r.App = parts[0] |
|
| 47 |
+ v := strings.Split(parts[1], " ") |
|
| 48 |
+ if len(v) != 2 {
|
|
| 49 |
+ return nil, errors.New("Bad header: Expected single space")
|
|
| 50 |
+ } |
|
| 51 |
+ r.Ver = v[0] |
|
| 52 |
+ return r, nil |
|
| 53 |
+ } |
|
| 54 |
+ return nil, errors.New("Bad header: Failed regex match")
|
|
| 55 |
+} |