2014-03-28 19:21:55 -04: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 18:52:16 -04:00
"runtime"
2014-03-28 19:21:55 -04:00
"strconv"
"strings"
2015-03-26 18:52:16 -04:00
"time"
2014-03-28 19:21:55 -04:00
2015-03-26 18:22:04 -04:00
"github.com/Sirupsen/logrus"
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/api"
2015-04-13 10:17:14 -04:00
"github.com/docker/docker/api/types"
2015-02-04 16:22:38 -05:00
"github.com/docker/docker/autogen/dockerversion"
2015-04-22 08:06:58 -04:00
"github.com/docker/docker/cliconfig"
2015-03-17 22:18:41 -04:00
"github.com/docker/docker/pkg/jsonmessage"
2014-11-13 13:40:22 -05:00
"github.com/docker/docker/pkg/signal"
2014-09-17 11:04:56 -04:00
"github.com/docker/docker/pkg/stdcopy"
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
2014-03-28 19:21:55 -04:00
)
var (
2015-04-22 02:45:18 -04:00
errConnectionRefused = errors . New ( "Cannot connect to the Docker daemon. Is 'docker -d' running on this host?" )
2014-03-28 19:21:55 -04:00
)
2015-04-22 02:45:18 -04:00
// HTTPClient creates a new HTP client with the cli's client transport instance.
2014-05-01 20:25:10 -04:00
func ( cli * DockerCli ) HTTPClient ( ) * http . Client {
2014-10-12 22:26:42 -04:00
return & http . Client { Transport : cli . transport }
2014-05-01 20:25:10 -04:00
}
2014-09-10 20:06:59 -04:00
func ( cli * DockerCli ) encodeData ( data interface { } ) ( * bytes . Buffer , error ) {
2014-03-28 19:21:55 -04:00
params := bytes . NewBuffer ( nil )
if data != nil {
2015-04-27 17:11:29 -04:00
if err := json . NewEncoder ( params ) . Encode ( data ) ; err != nil {
return nil , err
2014-03-28 19:21:55 -04:00
}
}
2014-09-09 01:51:53 -04:00
return params , nil
}
2014-03-28 19:21:55 -04:00
2015-01-12 14:56:01 -05: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 01:51:53 -04:00
}
2015-01-12 14:56:01 -05:00
req , err := http . NewRequest ( method , fmt . Sprintf ( "/v%s%s" , api . APIVERSION , path ) , in )
2014-03-28 19:21:55 -04:00
if err != nil {
2015-01-12 14:56:01 -05:00
return nil , "" , - 1 , err
2014-03-28 19:21:55 -04:00
}
2015-04-01 18:39:37 -04:00
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers
for k , v := range cli . configFile . HttpHeaders {
req . Header . Set ( k , v )
}
2014-03-28 19:21:55 -04:00
req . Header . Set ( "User-Agent" , "Docker-Client/" + dockerversion . VERSION )
2014-05-02 15:49:12 -04:00
req . URL . Host = cli . addr
req . URL . Scheme = cli . scheme
2015-01-12 14:56:01 -05: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 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
2014-05-01 20:25:10 -04:00
resp , err := cli . HTTPClient ( ) . Do ( req )
2015-01-12 14:56:01 -05:00
statusCode := - 1
if resp != nil {
statusCode = resp . StatusCode
}
2014-03-28 19:21:55 -04:00
if err != nil {
if strings . Contains ( err . Error ( ) , "connection refused" ) {
2015-04-22 02:45:18 -04:00
return nil , "" , statusCode , errConnectionRefused
2014-03-28 19:21:55 -04:00
}
2014-10-01 16:17:29 -04:00
if cli . tlsConfig == nil {
2015-01-12 14:56:01 -05:00
return nil , "" , statusCode , fmt . Errorf ( "%v. Are you trying to connect to a TLS-enabled daemon without TLS?" , err )
2014-10-01 16:17:29 -04:00
}
2015-01-12 14:56:01 -05:00
return nil , "" , statusCode , fmt . Errorf ( "An error occurred trying to connect: %v" , err )
2014-03-28 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
if statusCode < 200 || statusCode >= 400 {
2014-03-28 19:21:55 -04:00
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2015-01-12 14:56:01 -05:00
return nil , "" , statusCode , err
2014-03-28 19:21:55 -04:00
}
if len ( body ) == 0 {
2015-01-12 14:56:01 -05: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 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
return nil , "" , statusCode , fmt . Errorf ( "Error response from daemon: %s" , bytes . TrimSpace ( body ) )
2014-03-28 19:21:55 -04:00
}
2014-09-16 02:43:43 -04:00
2015-01-12 14:56:01 -05:00
return resp . Body , resp . Header . Get ( "Content-Type" ) , statusCode , nil
2014-03-28 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
func ( cli * DockerCli ) clientRequestAttemptLogin ( method , path string , in io . Reader , out io . Writer , index * registry . IndexInfo , cmdName string ) ( io . ReadCloser , int , error ) {
2015-04-22 08:06:58 -04:00
cmdAttempt := func ( authConfig cliconfig . AuthConfig ) ( io . ReadCloser , int , error ) {
2015-01-12 14:56:01 -05:00
buf , err := json . Marshal ( authConfig )
if err != nil {
return nil , - 1 , err
}
registryAuthHeader := [ ] string {
base64 . URLEncoding . EncodeToString ( buf ) ,
}
2014-04-02 15:26:06 -04:00
2015-01-12 14:56:01 -05: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 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
// Resolve the Auth config relevant for this server
2015-04-22 08:06:58 -04:00
authConfig := registry . ResolveAuthConfig ( cli . configFile , index )
2015-01-12 14:56:01 -05:00
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
}
2015-04-22 08:06:58 -04:00
authConfig = registry . ResolveAuthConfig ( cli . configFile , index )
2015-01-12 14:56:01 -05:00
return cmdAttempt ( authConfig )
2014-03-28 19:21:55 -04:00
}
2015-01-12 14:56:01 -05: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 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
if data != nil {
if headers == nil {
headers = make ( map [ string ] [ ] string )
2014-03-28 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
headers [ "Content-Type" ] = [ ] string { "application/json" }
2014-03-28 19:21:55 -04:00
}
2015-01-12 14:56:01 -05:00
body , _ , statusCode , err := cli . clientRequest ( method , path , params , headers )
return body , statusCode , err
}
2015-05-01 14:23:44 -04:00
type streamOpts struct {
rawTerminal bool
in io . Reader
out io . Writer
err io . Writer
headers map [ string ] [ ] string
2015-01-12 14:56:01 -05:00
}
2015-05-01 14:23:44 -04:00
func ( cli * DockerCli ) stream ( method , path string , opts * streamOpts ) error {
body , contentType , _ , err := cli . clientRequest ( method , path , opts . in , opts . headers )
2014-03-28 19:21:55 -04:00
if err != nil {
return err
}
2015-05-01 14:23:44 -04:00
return cli . streamBody ( body , contentType , opts . rawTerminal , opts . out , opts . err )
2015-01-12 14:56:01 -05:00
}
2014-03-28 19:21:55 -04:00
2015-05-01 14:23:44 -04:00
func ( cli * DockerCli ) streamBody ( body io . ReadCloser , contentType string , rawTerminal bool , stdout , stderr io . Writer ) error {
2015-01-12 14:56:01 -05:00
defer body . Close ( )
2014-03-28 19:21:55 -04:00
2015-01-12 14:56:01 -05:00
if api . MatchesContentType ( contentType , "application/json" ) {
return jsonmessage . DisplayJSONMessagesStream ( body , stdout , cli . outFd , cli . isTerminalOut )
2014-03-28 19:21:55 -04:00
}
2014-04-02 15:26:06 -04:00
if stdout != nil || stderr != nil {
// When TTY is ON, use regular copy
2015-01-12 14:56:01 -05:00
var err error
2015-05-01 14:23:44 -04:00
if rawTerminal {
2015-01-12 14:56:01 -05:00
_ , err = io . Copy ( stdout , body )
2014-04-02 15:26:06 -04:00
} else {
2015-01-12 14:56:01 -05:00
_ , err = stdcopy . StdCopy ( stdout , stderr , body )
2014-04-02 15:26:06 -04:00
}
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "[stream] End of stdout" )
2014-03-28 19:21:55 -04:00
return err
}
return nil
}
2014-09-16 02:43:43 -04:00
func ( cli * DockerCli ) resizeTty ( id string , isExec bool ) {
2014-03-28 19:21:55 -04: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 02:43:43 -04:00
path := ""
if ! isExec {
path = "/containers/" + id + "/resize?"
} else {
path = "/exec/" + id + "/resize?"
}
2015-01-12 14:56:01 -05:00
if _ , _ , err := readBody ( cli . call ( "POST" , path + v . Encode ( ) , nil , nil ) ) ; err != nil {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Error resize: %s" , err )
2014-03-28 19:21:55 -04:00
}
}
2015-03-25 22:31:29 -04:00
func waitForExit ( cli * DockerCli , containerID string ) ( int , error ) {
2015-01-12 14:56:01 -05:00
stream , _ , err := cli . call ( "POST" , "/containers/" + containerID + "/wait" , nil , nil )
2014-03-28 19:21:55 -04:00
if err != nil {
return - 1 , err
}
2015-04-13 10:17:14 -04:00
var res types . ContainerWaitResponse
if err := json . NewDecoder ( stream ) . Decode ( & res ) ; err != nil {
2014-03-28 19:21:55 -04:00
return - 1 , err
}
2015-04-13 10:17:14 -04:00
return res . StatusCode , nil
2014-03-28 19:21:55 -04:00
}
// getExitCode perform an inspect on the container. It returns
// the running state and the exit code.
2015-03-25 22:31:29 -04:00
func getExitCode ( cli * DockerCli , containerID string ) ( bool , int , error ) {
2015-01-12 14:56:01 -05:00
stream , _ , err := cli . call ( "GET" , "/containers/" + containerID + "/json" , nil , nil )
2014-03-28 19:21:55 -04:00
if err != nil {
// If we can't connect, then the daemon probably died.
2015-04-22 02:45:18 -04:00
if err != errConnectionRefused {
2014-03-28 19:21:55 -04:00
return false , - 1 , err
}
return false , - 1 , nil
}
2014-06-04 18:11:11 -04:00
2015-04-13 10:17:14 -04:00
var c types . ContainerJSON
if err := json . NewDecoder ( stream ) . Decode ( & c ) ; err != nil {
2014-03-28 19:21:55 -04:00
return false , - 1 , err
}
2014-06-04 18:11:11 -04:00
2015-04-13 10:17:14 -04:00
return c . State . Running , c . State . ExitCode , nil
2014-03-28 19:21:55 -04:00
}
2014-11-17 18:50:09 -05:00
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
2015-03-25 22:31:29 -04:00
func getExecExitCode ( cli * DockerCli , execID string ) ( bool , int , error ) {
2015-01-12 14:56:01 -05:00
stream , _ , err := cli . call ( "GET" , "/exec/" + execID + "/json" , nil , nil )
2014-11-17 18:50:09 -05:00
if err != nil {
// If we can't connect, then the daemon probably died.
2015-04-22 02:45:18 -04:00
if err != errConnectionRefused {
2014-11-17 18:50:09 -05:00
return false , - 1 , err
}
return false , - 1 , nil
}
2015-04-13 10:17:14 -04:00
//TODO: Should we reconsider having a type in api/types?
//this is a response to exex/id/json not container
var c struct {
Running bool
ExitCode int
}
if err := json . NewDecoder ( stream ) . Decode ( & c ) ; err != nil {
2014-11-17 18:50:09 -05:00
return false , - 1 , err
}
2015-04-13 10:17:14 -04:00
return c . Running , c . ExitCode , nil
2014-11-17 18:50:09 -05:00
}
2014-09-16 02:43:43 -04:00
func ( cli * DockerCli ) monitorTtySize ( id string , isExec bool ) error {
cli . resizeTty ( id , isExec )
2014-03-28 19:21:55 -04:00
2015-03-26 18:52:16 -04:00
if runtime . GOOS == "windows" {
go func ( ) {
2015-04-06 17:31:42 -04:00
prevH , prevW := cli . getTtySize ( )
2015-03-26 18:52:16 -04:00
for {
time . Sleep ( time . Millisecond * 250 )
2015-04-06 17:31:42 -04:00
h , w := cli . getTtySize ( )
2015-03-26 18:52:16 -04:00
if prevW != w || prevH != h {
cli . resizeTty ( id , isExec )
}
prevH = h
2015-04-06 17:31:42 -04:00
prevW = w
2015-03-26 18:52:16 -04:00
}
} ( )
} else {
sigchan := make ( chan os . Signal , 1 )
gosignal . Notify ( sigchan , signal . SIGWINCH )
go func ( ) {
2015-04-01 18:39:37 -04:00
for range sigchan {
2015-03-26 18:52:16 -04:00
cli . resizeTty ( id , isExec )
}
} ( )
}
2014-03-28 19:21:55 -04:00
return nil
}
func ( cli * DockerCli ) getTtySize ( ) ( int , int ) {
2014-09-10 09:35:48 -04:00
if ! cli . isTerminalOut {
2014-03-28 19:21:55 -04:00
return 0 , 0
}
2014-09-10 09:35:48 -04:00
ws , err := term . GetWinsize ( cli . outFd )
2014-03-28 19:21:55 -04:00
if err != nil {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Error getting size: %s" , err )
2014-03-28 19:21:55 -04: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
}