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"
"github.com/dotcloud/docker/api"
"github.com/dotcloud/docker/dockerversion"
"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/pkg/term"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
)
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 ) {
return net . Dial ( cli . proto , cli . addr )
} ,
}
return & http . Client { Transport : tr }
}
2014-03-28 19:21:55 -04:00
func ( cli * DockerCli ) call ( method , path string , data interface { } , passAuthInfo bool ) ( io . ReadCloser , int , error ) {
params := bytes . NewBuffer ( nil )
if data != nil {
if env , ok := data . ( engine . Env ) ; ok {
if err := env . Encode ( params ) ; err != nil {
return nil , - 1 , err
}
} else {
buf , err := json . Marshal ( data )
if err != nil {
return nil , - 1 , err
}
if _ , err := params . Write ( buf ) ; 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 )
}
return nil , resp . StatusCode , fmt . Errorf ( "Error: %s" , bytes . TrimSpace ( body ) )
}
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-05-01 20:25:10 -04:00
req , err := http . NewRequest ( method , fmt . Sprintf ( "http://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 ) )
}
if api . MatchesContentType ( resp . Header . Get ( "Content-Type" ) , "application/json" ) {
2014-04-02 15:26:06 -04:00
return utils . DisplayJSONMessagesStream ( resp . Body , stdout , cli . terminalFd , cli . isTerminal )
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 {
_ , err = utils . StdCopy ( stdout , stderr , resp . Body )
}
utils . Debugf ( "[stream] End of stdout" )
2014-03-28 19:21:55 -04:00
return err
}
return nil
}
func ( cli * DockerCli ) resizeTty ( id string ) {
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 ) )
if _ , _ , err := readBody ( cli . call ( "POST" , "/containers/" + id + "/resize?" + v . Encode ( ) , nil , false ) ) ; err != nil {
2014-03-31 14:08:46 -04:00
utils . 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 ) {
body , _ , err := readBody ( cli . call ( "GET" , "/containers/" + containerId + "/json" , nil , false ) )
if err != nil {
// If we can't connect, then the daemon probably died.
if err != ErrConnectionRefused {
return false , - 1 , err
}
return false , - 1 , nil
}
c := & api . Container { }
if err := json . Unmarshal ( body , c ) ; err != nil {
return false , - 1 , err
}
return c . State . Running , c . State . ExitCode , nil
}
func ( cli * DockerCli ) monitorTtySize ( id string ) error {
cli . resizeTty ( id )
sigchan := make ( chan os . Signal , 1 )
gosignal . Notify ( sigchan , syscall . SIGWINCH )
go func ( ) {
for _ = range sigchan {
cli . resizeTty ( id )
}
} ( )
return nil
}
func ( cli * DockerCli ) getTtySize ( ) ( int , int ) {
if ! cli . isTerminal {
return 0 , 0
}
ws , err := term . GetWinsize ( cli . terminalFd )
if err != nil {
2014-03-31 14:08:46 -04:00
utils . 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
}