2014-03-28 23:21:55 +00:00
package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
gosignal "os/signal"
2015-03-26 15:52:16 -07:00
"runtime"
2014-03-28 23:21:55 +00:00
"strconv"
"strings"
2015-03-26 15:52:16 -07:00
"time"
2014-03-28 23:21:55 +00:00
2015-03-26 23:22:04 +01:00
"github.com/Sirupsen/logrus"
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/api"
2015-02-04 21:22:38 +00:00
"github.com/docker/docker/autogen/dockerversion"
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/engine"
2015-03-17 19:18:41 -07:00
"github.com/docker/docker/pkg/jsonmessage"
2014-11-13 10:40:22 -08:00
"github.com/docker/docker/pkg/signal"
2014-09-17 18:04:56 +03:00
"github.com/docker/docker/pkg/stdcopy"
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
2014-03-28 23:21:55 +00:00
)
var (
ErrConnectionRefused = errors . New ( "Cannot connect to the Docker daemon. Is 'docker -d' running on this host?" )
)
2014-05-02 00:25:10 +00:00
func ( cli * DockerCli ) HTTPClient ( ) * http . Client {
2014-10-12 22:26:42 -04:00
return & http . Client { Transport : cli . transport }
2014-05-02 00:25:10 +00:00
}
2014-09-11 00:06:59 +00:00
func ( cli * DockerCli ) encodeData ( data interface { } ) ( * bytes . Buffer , error ) {
2014-03-28 23:21:55 +00:00
params := bytes . NewBuffer ( nil )
if data != nil {
if env , ok := data . ( engine . Env ) ; ok {
if err := env . Encode ( params ) ; err != nil {
2014-09-09 05:51:53 +00:00
return nil , err
2014-03-28 23:21:55 +00:00
}
} else {
buf , err := json . Marshal ( data )
if err != nil {
2014-09-09 05:51:53 +00:00
return nil , err
2014-03-28 23:21:55 +00:00
}
if _ , err := params . Write ( buf ) ; err != nil {
2014-09-09 05:51:53 +00:00
return nil , err
2014-03-28 23:21:55 +00:00
}
}
}
2014-09-09 05:51:53 +00:00
return params , nil
}
2014-03-28 23:21:55 +00:00
2015-01-12 19:56:01 +00:00
func ( cli * DockerCli ) clientRequest ( method , path string , in io . Reader , headers map [ string ] [ ] string ) ( io . ReadCloser , string , int , error ) {
expectedPayload := ( method == "POST" || method == "PUT" )
if expectedPayload && in == nil {
in = bytes . NewReader ( [ ] byte { } )
2014-09-09 05:51:53 +00:00
}
2015-01-12 19:56:01 +00:00
req , err := http . NewRequest ( method , fmt . Sprintf ( "/v%s%s" , api . APIVERSION , path ) , in )
2014-03-28 23:21:55 +00:00
if err != nil {
2015-01-12 19:56:01 +00:00
return nil , "" , - 1 , err
2014-03-28 23:21:55 +00:00
}
req . Header . Set ( "User-Agent" , "Docker-Client/" + dockerversion . VERSION )
2014-05-02 19:49:12 +00:00
req . URL . Host = cli . addr
req . URL . Scheme = cli . scheme
2015-01-12 19:56:01 +00:00
if headers != nil {
for k , v := range headers {
req . Header [ k ] = v
}
}
if expectedPayload && req . Header . Get ( "Content-Type" ) == "" {
2015-01-07 11:19:14 -05:00
req . Header . Set ( "Content-Type" , "text/plain" )
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
2014-05-02 00:25:10 +00:00
resp , err := cli . HTTPClient ( ) . Do ( req )
2015-01-12 19:56:01 +00:00
statusCode := - 1
if resp != nil {
statusCode = resp . StatusCode
}
2014-03-28 23:21:55 +00:00
if err != nil {
if strings . Contains ( err . Error ( ) , "connection refused" ) {
2015-01-12 19:56:01 +00:00
return nil , "" , statusCode , ErrConnectionRefused
2014-03-28 23:21:55 +00:00
}
2014-10-01 13:17:29 -07:00
if cli . tlsConfig == nil {
2015-01-12 19:56:01 +00:00
return nil , "" , statusCode , fmt . Errorf ( "%v. Are you trying to connect to a TLS-enabled daemon without TLS?" , err )
2014-10-01 13:17:29 -07:00
}
2015-01-12 19:56:01 +00:00
return nil , "" , statusCode , fmt . Errorf ( "An error occurred trying to connect: %v" , err )
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
if statusCode < 200 || statusCode >= 400 {
2014-03-28 23:21:55 +00:00
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2015-01-12 19:56:01 +00:00
return nil , "" , statusCode , err
2014-03-28 23:21:55 +00:00
}
if len ( body ) == 0 {
2015-01-12 19:56:01 +00:00
return 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 )
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
return nil , "" , statusCode , fmt . Errorf ( "Error response from daemon: %s" , bytes . TrimSpace ( body ) )
2014-03-28 23:21:55 +00:00
}
2014-09-16 06:43:43 +00:00
2015-01-12 19:56:01 +00:00
return resp . Body , resp . Header . Get ( "Content-Type" ) , statusCode , nil
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
func ( cli * DockerCli ) clientRequestAttemptLogin ( method , path string , in io . Reader , out io . Writer , index * registry . IndexInfo , cmdName string ) ( io . ReadCloser , int , error ) {
cmdAttempt := func ( authConfig registry . AuthConfig ) ( io . ReadCloser , int , error ) {
buf , err := json . Marshal ( authConfig )
if err != nil {
return nil , - 1 , err
}
registryAuthHeader := [ ] string {
base64 . URLEncoding . EncodeToString ( buf ) ,
}
2014-04-02 23:26:06 +04:00
2015-01-12 19:56:01 +00:00
// begin the request
body , contentType , statusCode , err := cli . clientRequest ( method , path , in , map [ string ] [ ] string {
"X-Registry-Auth" : registryAuthHeader ,
} )
if err == nil && out != nil {
// If we are streaming output, complete the stream since
// errors may not appear until later.
err = cli . streamBody ( body , contentType , true , out , nil )
}
if err != nil {
// Since errors in a stream appear after status 200 has been written,
// we may need to change the status code.
if strings . Contains ( err . Error ( ) , "Authentication is required" ) ||
strings . Contains ( err . Error ( ) , "Status 401" ) ||
strings . Contains ( err . Error ( ) , "status code 401" ) {
statusCode = http . StatusUnauthorized
}
}
return body , statusCode , err
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
// Resolve the Auth config relevant for this server
authConfig := cli . configFile . ResolveAuthConfig ( index )
body , statusCode , err := cmdAttempt ( authConfig )
if statusCode == http . StatusUnauthorized {
fmt . Fprintf ( cli . out , "\nPlease login prior to %s:\n" , cmdName )
if err = cli . CmdLogin ( index . GetAuthConfigKey ( ) ) ; err != nil {
return nil , - 1 , err
}
authConfig = cli . configFile . ResolveAuthConfig ( index )
return cmdAttempt ( authConfig )
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
return body , statusCode , err
}
func ( cli * DockerCli ) call ( method , path string , data interface { } , headers map [ string ] [ ] string ) ( io . ReadCloser , int , error ) {
params , err := cli . encodeData ( data )
if err != nil {
return nil , - 1 , err
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
if data != nil {
if headers == nil {
headers = make ( map [ string ] [ ] string )
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
headers [ "Content-Type" ] = [ ] string { "application/json" }
2014-03-28 23:21:55 +00:00
}
2015-01-12 19:56:01 +00:00
body , _ , statusCode , err := cli . clientRequest ( method , path , params , headers )
return body , statusCode , err
}
func ( cli * DockerCli ) stream ( method , path string , in io . Reader , out io . Writer , headers map [ string ] [ ] string ) error {
return cli . streamHelper ( method , path , true , in , out , nil , headers )
}
func ( cli * DockerCli ) streamHelper ( method , path string , setRawTerminal bool , in io . Reader , stdout , stderr io . Writer , headers map [ string ] [ ] string ) error {
body , contentType , _ , err := cli . clientRequest ( method , path , in , headers )
2014-03-28 23:21:55 +00:00
if err != nil {
return err
}
2015-01-12 19:56:01 +00:00
return cli . streamBody ( body , contentType , setRawTerminal , stdout , stderr )
}
2014-03-28 23:21:55 +00:00
2015-01-12 19:56:01 +00:00
func ( cli * DockerCli ) streamBody ( body io . ReadCloser , contentType string , setRawTerminal bool , stdout , stderr io . Writer ) error {
defer body . Close ( )
2014-03-28 23:21:55 +00:00
2015-01-12 19:56:01 +00:00
if api . MatchesContentType ( contentType , "application/json" ) {
return jsonmessage . DisplayJSONMessagesStream ( body , stdout , cli . outFd , cli . isTerminalOut )
2014-03-28 23:21:55 +00:00
}
2014-04-02 23:26:06 +04:00
if stdout != nil || stderr != nil {
// When TTY is ON, use regular copy
2015-01-12 19:56:01 +00:00
var err error
2014-04-02 23:26:06 +04:00
if setRawTerminal {
2015-01-12 19:56:01 +00:00
_ , err = io . Copy ( stdout , body )
2014-04-02 23:26:06 +04:00
} else {
2015-01-12 19:56:01 +00:00
_ , err = stdcopy . StdCopy ( stdout , stderr , body )
2014-04-02 23:26:06 +04:00
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[stream] End of stdout" )
2014-03-28 23:21:55 +00:00
return err
}
return nil
}
2014-09-16 06:43:43 +00:00
func ( cli * DockerCli ) resizeTty ( id string , isExec bool ) {
2014-03-28 23:21:55 +00:00
height , width := cli . getTtySize ( )
if height == 0 && width == 0 {
return
}
v := url . Values { }
v . Set ( "h" , strconv . Itoa ( height ) )
v . Set ( "w" , strconv . Itoa ( width ) )
2014-09-16 06:43:43 +00:00
path := ""
if ! isExec {
path = "/containers/" + id + "/resize?"
} else {
path = "/exec/" + id + "/resize?"
}
2015-01-12 19:56:01 +00:00
if _ , _ , err := readBody ( cli . call ( "POST" , path + v . Encode ( ) , nil , nil ) ) ; err != nil {
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Error resize: %s" , err )
2014-03-28 23:21:55 +00:00
}
}
2015-03-25 19:31:29 -07:00
func waitForExit ( cli * DockerCli , containerID string ) ( int , error ) {
2015-01-12 19:56:01 +00:00
stream , _ , err := cli . call ( "POST" , "/containers/" + containerID + "/wait" , nil , nil )
2014-03-28 23:21:55 +00:00
if err != nil {
return - 1 , err
}
var out engine . Env
if err := out . Decode ( stream ) ; err != nil {
return - 1 , err
}
return out . GetInt ( "StatusCode" ) , nil
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
2015-03-25 19:31:29 -07:00
func getExitCode ( cli * DockerCli , containerID string ) ( bool , int , error ) {
2015-01-12 19:56:01 +00:00
stream , _ , err := cli . call ( "GET" , "/containers/" + containerID + "/json" , nil , nil )
2014-03-28 23:21:55 +00:00
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
return false , - 1 , err
}
return false , - 1 , nil
}
2014-06-04 15:11:11 -07:00
var result engine . Env
2014-11-18 10:49:01 -08:00
if err := result . Decode ( stream ) ; err != nil {
2014-03-28 23:21:55 +00:00
return false , - 1 , err
}
2014-06-04 15:11:11 -07:00
state := result . GetSubEnv ( "State" )
return state . GetBool ( "Running" ) , state . GetInt ( "ExitCode" ) , nil
2014-03-28 23:21:55 +00:00
}
2014-11-17 15:50:09 -08:00
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
2015-03-25 19:31:29 -07:00
func getExecExitCode ( cli * DockerCli , execID string ) ( bool , int , error ) {
2015-01-12 19:56:01 +00:00
stream , _ , err := cli . call ( "GET" , "/exec/" + execID + "/json" , nil , nil )
2014-11-17 15:50:09 -08:00
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
return false , - 1 , err
}
return false , - 1 , nil
}
var result engine . Env
if err := result . Decode ( stream ) ; err != nil {
return false , - 1 , err
}
return result . GetBool ( "Running" ) , result . GetInt ( "ExitCode" ) , nil
}
2014-09-16 06:43:43 +00:00
func ( cli * DockerCli ) monitorTtySize ( id string , isExec bool ) error {
cli . resizeTty ( id , isExec )
2014-03-28 23:21:55 +00:00
2015-03-26 15:52:16 -07:00
if runtime . GOOS == "windows" {
go func ( ) {
2015-04-06 14:31:42 -07:00
prevH , prevW := cli . getTtySize ( )
2015-03-26 15:52:16 -07:00
for {
time . Sleep ( time . Millisecond * 250 )
2015-04-06 14:31:42 -07:00
h , w := cli . getTtySize ( )
2015-03-26 15:52:16 -07:00
if prevW != w || prevH != h {
cli . resizeTty ( id , isExec )
}
prevH = h
2015-04-06 14:31:42 -07:00
prevW = w
2015-03-26 15:52:16 -07:00
}
} ( )
} else {
sigchan := make ( chan os . Signal , 1 )
gosignal . Notify ( sigchan , signal . SIGWINCH )
go func ( ) {
for _ = range sigchan {
cli . resizeTty ( id , isExec )
}
} ( )
}
2014-03-28 23:21:55 +00:00
return nil
}
func ( cli * DockerCli ) getTtySize ( ) ( int , int ) {
2014-09-10 15:35:48 +02:00
if ! cli . isTerminalOut {
2014-03-28 23:21:55 +00:00
return 0 , 0
}
2014-09-10 15:35:48 +02:00
ws , err := term . GetWinsize ( cli . outFd )
2014-03-28 23:21:55 +00:00
if err != nil {
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Error getting size: %s" , err )
2014-03-28 23:21:55 +00:00
if ws == nil {
return 0 , 0
}
}
return int ( ws . Height ) , int ( ws . Width )
}
func readBody ( stream io . ReadCloser , statusCode int , err error ) ( [ ] byte , int , error ) {
if stream != nil {
defer stream . Close ( )
}
if err != nil {
return nil , statusCode , err
}
body , err := ioutil . ReadAll ( stream )
if err != nil {
return nil , - 1 , err
}
return body , statusCode , nil
}