2014-03-28 19:21:55 -04:00
package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
gosignal "os/signal"
"strconv"
"strings"
"syscall"
2014-09-15 07:50:22 -04:00
"time"
2014-03-28 19:21:55 -04:00
2014-07-24 18:19:50 -04:00
"github.com/docker/docker/api"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/engine"
2014-07-24 16:37:44 -04:00
"github.com/docker/docker/pkg/log"
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"
"github.com/docker/docker/utils"
2014-03-28 19:21:55 -04:00
)
var (
ErrConnectionRefused = errors . New ( "Cannot connect to the Docker daemon. Is 'docker -d' running on this host?" )
)
2014-05-01 20:25:10 -04:00
func ( cli * DockerCli ) HTTPClient ( ) * http . Client {
tr := & http . Transport {
2014-05-01 20:40:13 -04:00
TLSClientConfig : cli . tlsConfig ,
2014-05-01 20:25:10 -04:00
Dial : func ( network , addr string ) ( net . Conn , error ) {
2014-09-15 07:50:22 -04:00
// Why 32? See issue 8035
return net . DialTimeout ( cli . proto , cli . addr , 32 * time . Second )
2014-05-01 20:25:10 -04:00
} ,
}
2014-10-10 19:58:49 -04:00
if cli . proto == "unix" {
// XXX workaround for net/http Transport which caches connections, but is
// intended for tcp connections, not unix sockets.
tr . DisableKeepAlives = true
// no need in compressing for local communications
tr . DisableCompression = true
}
2014-05-01 20:25:10 -04:00
return & http . Client { Transport : tr }
}
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 {
if env , ok := data . ( engine . Env ) ; ok {
if err := env . Encode ( params ) ; err != nil {
2014-09-09 01:51:53 -04:00
return nil , err
2014-03-28 19:21:55 -04:00
}
} else {
buf , err := json . Marshal ( data )
if err != nil {
2014-09-09 01:51:53 -04:00
return nil , err
2014-03-28 19:21:55 -04:00
}
if _ , err := params . Write ( buf ) ; err != nil {
2014-09-09 01:51:53 -04:00
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
2014-09-09 01:51:53 -04:00
func ( cli * DockerCli ) call ( method , path string , data interface { } , passAuthInfo bool ) ( io . ReadCloser , int , error ) {
2014-09-10 20:06:59 -04:00
params , err := cli . encodeData ( data )
2014-09-09 01:51:53 -04:00
if err != nil {
return nil , - 1 , err
}
2014-05-02 15:49:12 -04:00
req , err := http . NewRequest ( method , fmt . Sprintf ( "/v%s%s" , api . APIVERSION , path ) , params )
2014-03-28 19:21:55 -04:00
if err != nil {
return nil , - 1 , err
}
if passAuthInfo {
cli . LoadConfigFile ( )
// Resolve the Auth config relevant for this server
authConfig := cli . configFile . ResolveAuthConfig ( registry . IndexServerAddress ( ) )
getHeaders := func ( authConfig registry . AuthConfig ) ( map [ string ] [ ] string , error ) {
buf , err := json . Marshal ( authConfig )
if err != nil {
return nil , err
}
registryAuthHeader := [ ] string {
base64 . URLEncoding . EncodeToString ( buf ) ,
}
return map [ string ] [ ] string { "X-Registry-Auth" : registryAuthHeader } , nil
}
if headers , err := getHeaders ( authConfig ) ; err == nil && headers != nil {
for k , v := range headers {
req . Header [ k ] = v
}
}
}
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
2014-03-28 19:21:55 -04:00
if data != nil {
req . Header . Set ( "Content-Type" , "application/json" )
} else if method == "POST" {
req . Header . Set ( "Content-Type" , "plain/text" )
}
2014-05-01 20:25:10 -04:00
resp , err := cli . HTTPClient ( ) . Do ( req )
2014-03-28 19:21:55 -04:00
if err != nil {
if strings . Contains ( err . Error ( ) , "connection refused" ) {
return nil , - 1 , ErrConnectionRefused
}
return nil , - 1 , err
}
if resp . StatusCode < 200 || resp . StatusCode >= 400 {
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return nil , - 1 , err
}
if len ( body ) == 0 {
return nil , resp . StatusCode , fmt . Errorf ( "Error: request returned %s for API route and version %s, check if the server supports the requested API version" , http . StatusText ( resp . StatusCode ) , req . URL )
}
2014-06-17 16:07:34 -04:00
return nil , resp . 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
2014-05-01 20:25:10 -04:00
return resp . Body , resp . StatusCode , nil
2014-03-28 19:21:55 -04:00
}
func ( cli * DockerCli ) stream ( method , path string , in io . Reader , out io . Writer , headers map [ string ] [ ] string ) error {
2014-04-02 15:26:06 -04:00
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 {
2014-03-28 19:21:55 -04:00
if ( method == "POST" || method == "PUT" ) && in == nil {
in = bytes . NewReader ( [ ] byte { } )
}
2014-10-06 09:43:09 -04: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 {
return err
}
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
2014-03-28 19:21:55 -04:00
if method == "POST" {
req . Header . Set ( "Content-Type" , "plain/text" )
}
if headers != nil {
for k , v := range headers {
req . Header [ k ] = v
}
}
2014-05-01 20:25:10 -04:00
resp , err := cli . HTTPClient ( ) . Do ( req )
2014-03-28 19:21:55 -04:00
if err != nil {
if strings . Contains ( err . Error ( ) , "connection refused" ) {
return fmt . Errorf ( "Cannot connect to the Docker daemon. Is 'docker -d' running on this host?" )
}
return err
}
defer resp . Body . Close ( )
if resp . StatusCode < 200 || resp . StatusCode >= 400 {
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return err
}
if len ( body ) == 0 {
return fmt . Errorf ( "Error :%s" , http . StatusText ( resp . StatusCode ) )
}
return fmt . Errorf ( "Error: %s" , bytes . TrimSpace ( body ) )
}
2014-10-09 16:17:47 -04:00
if api . MatchesContentType ( resp . Header . Get ( "Content-Type" ) , "application/json" ) {
2014-10-09 16:20:38 -04:00
return utils . DisplayJSONMessagesStream ( resp . 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
if setRawTerminal {
_ , err = io . Copy ( stdout , resp . Body )
} else {
2014-09-17 11:04:56 -04:00
_ , err = stdcopy . StdCopy ( stdout , stderr , resp . Body )
2014-04-02 15:26:06 -04:00
}
2014-07-24 16:37:44 -04:00
log . 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?"
}
if _ , _ , err := readBody ( cli . call ( "POST" , path + v . Encode ( ) , nil , false ) ) ; err != nil {
2014-07-24 16:37:44 -04:00
log . Debugf ( "Error resize: %s" , err )
2014-03-28 19:21:55 -04:00
}
}
func waitForExit ( cli * DockerCli , containerId string ) ( int , error ) {
stream , _ , err := cli . call ( "POST" , "/containers/" + containerId + "/wait" , nil , false )
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.
func getExitCode ( cli * DockerCli , containerId string ) ( bool , int , error ) {
2014-06-04 18:11:11 -04:00
steam , _ , err := cli . call ( "GET" , "/containers/" + containerId + "/json" , nil , false )
2014-03-28 19:21:55 -04: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 18:11:11 -04:00
var result engine . Env
if err := result . Decode ( steam ) ; err != nil {
2014-03-28 19:21:55 -04:00
return false , - 1 , err
}
2014-06-04 18:11:11 -04:00
state := result . GetSubEnv ( "State" )
return state . GetBool ( "Running" ) , state . GetInt ( "ExitCode" ) , nil
2014-03-28 19:21:55 -04: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
sigchan := make ( chan os . Signal , 1 )
gosignal . Notify ( sigchan , syscall . SIGWINCH )
go func ( ) {
for _ = range sigchan {
2014-09-16 02:43:43 -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 {
2014-07-24 16:37:44 -04:00
log . 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
}