github.com/marinho/drone@v0.2.1-0.20140504195434-d3ba962e89a7/pkg/build/docker/client.go (about)

     1  package docker
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net"
    11  	"net/http"
    12  	"net/http/httputil"
    13  	"os"
    14  	"strings"
    15  
    16  	"github.com/dotcloud/docker/pkg/term"
    17  	"github.com/dotcloud/docker/utils"
    18  )
    19  
    20  const (
    21  	APIVERSION        = 1.9
    22  	DEFAULTHTTPPORT   = 4243
    23  	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
    24  	DEFAULTPROTOCOL   = "unix"
    25  	DEFAULTTAG        = "latest"
    26  	VERSION           = "0.8.0"
    27  )
    28  
    29  // Enables verbose logging to the Terminal window
    30  var Logging = true
    31  
    32  // New creates an instance of the Docker Client
    33  func New() *Client {
    34  	c := &Client{}
    35  
    36  	c.setHost(DEFAULTUNIXSOCKET)
    37  
    38  	c.Images = &ImageService{c}
    39  	c.Containers = &ContainerService{c}
    40  	return c
    41  }
    42  
    43  type Client struct {
    44  	proto string
    45  	addr  string
    46  
    47  	Images     *ImageService
    48  	Containers *ContainerService
    49  }
    50  
    51  var (
    52  	// Returned if the specified resource does not exist.
    53  	ErrNotFound = errors.New("Not Found")
    54  
    55  	// Returned if the caller attempts to make a call or modify a resource
    56  	// for which the caller is not authorized.
    57  	//
    58  	// The request was a valid request, the caller's authentication credentials
    59  	// succeeded but those credentials do not grant the caller permission to
    60  	// access the resource.
    61  	ErrForbidden = errors.New("Forbidden")
    62  
    63  	// Returned if the call requires authentication and either the credentials
    64  	// provided failed or no credentials were provided.
    65  	ErrNotAuthorized = errors.New("Unauthorized")
    66  
    67  	// Returned if the caller submits a badly formed request. For example,
    68  	// the caller can receive this return if you forget a required parameter.
    69  	ErrBadRequest = errors.New("Bad Request")
    70  )
    71  
    72  func (c *Client) setHost(defaultUnixSocket string) {
    73  	c.proto = DEFAULTPROTOCOL
    74  	c.addr = defaultUnixSocket
    75  
    76  	if os.Getenv("DOCKER_HOST") != "" {
    77  		pieces := strings.Split(os.Getenv("DOCKER_HOST"), "://")
    78  		if len(pieces) == 2 {
    79  			c.proto = pieces[0]
    80  			c.addr = pieces[1]
    81  		} else if len(pieces) == 1 {
    82  			c.addr = pieces[0]
    83  		}
    84  	} else {
    85  		// if the default socket doesn't exist then
    86  		// we'll try to connect to the default tcp address
    87  		if _, err := os.Stat(defaultUnixSocket); err != nil {
    88  			c.proto = "tcp"
    89  			c.addr = "0.0.0.0:4243"
    90  		}
    91  	}
    92  }
    93  
    94  // helper function used to make HTTP requests to the Docker daemon.
    95  func (c *Client) do(method, path string, in, out interface{}) error {
    96  	// if data input is provided, serialize to JSON
    97  	var payload io.Reader
    98  	if in != nil {
    99  		buf, err := json.Marshal(in)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		payload = bytes.NewBuffer(buf)
   104  	}
   105  
   106  	// create the request
   107  	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), payload)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	// set the appropariate headers
   113  	req.Header = http.Header{}
   114  	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
   115  	req.Header.Set("Content-Type", "application/json")
   116  
   117  	// dial the host server
   118  	req.Host = c.addr
   119  	dial, err := net.Dial(c.proto, c.addr)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	// make the request
   125  	conn := httputil.NewClientConn(dial, nil)
   126  	resp, err := conn.Do(req)
   127  	defer conn.Close()
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// Read the bytes from the body (make sure we defer close the body)
   133  	defer resp.Body.Close()
   134  	body, err := ioutil.ReadAll(resp.Body)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	// Check for an http error status (ie not 200 StatusOK)
   140  	switch resp.StatusCode {
   141  	case 404:
   142  		return ErrNotFound
   143  	case 403:
   144  		return ErrForbidden
   145  	case 401:
   146  		return ErrNotAuthorized
   147  	case 400:
   148  		return ErrBadRequest
   149  	}
   150  
   151  	// Unmarshall the JSON response
   152  	if out != nil {
   153  		return json.Unmarshal(body, out)
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func (c *Client) hijack(method, path string, setRawTerminal bool, out io.Writer) error {
   160  	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
   166  	req.Header.Set("Content-Type", "plain/text")
   167  	req.Host = c.addr
   168  
   169  	dial, err := net.Dial(c.proto, c.addr)
   170  	if err != nil {
   171  		if strings.Contains(err.Error(), "connection refused") {
   172  			return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
   173  		}
   174  		return err
   175  	}
   176  	clientconn := httputil.NewClientConn(dial, nil)
   177  	defer clientconn.Close()
   178  
   179  	// Server hijacks the connection, error 'connection closed' expected
   180  	clientconn.Do(req)
   181  
   182  	// Hijack the connection to read / write
   183  	rwc, br := clientconn.Hijack()
   184  	defer rwc.Close()
   185  
   186  	// launch a goroutine to copy the stream
   187  	// of build output to the writer.
   188  	errStdout := make(chan error, 1)
   189  	go func() {
   190  		var err error
   191  		if setRawTerminal {
   192  			_, err = io.Copy(out, br)
   193  		} else {
   194  			_, err = utils.StdCopy(out, out, br)
   195  		}
   196  
   197  		errStdout <- err
   198  	}()
   199  
   200  	// wait for a response
   201  	if err := <-errStdout; err != nil {
   202  		return err
   203  	}
   204  	return nil
   205  }
   206  
   207  func (c *Client) stream(method, path string, in io.Reader, out io.Writer, headers http.Header) error {
   208  	if (method == "POST" || method == "PUT") && in == nil {
   209  		in = bytes.NewReader(nil)
   210  	}
   211  
   212  	// setup the request
   213  	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	// set default headers
   219  	req.Header = headers
   220  	req.Header.Set("User-Agent", "Docker-Client/0.6.4")
   221  	req.Header.Set("Content-Type", "plain/text")
   222  
   223  	// dial the host server
   224  	req.Host = c.addr
   225  	dial, err := net.Dial(c.proto, c.addr)
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	// make the request
   231  	conn := httputil.NewClientConn(dial, nil)
   232  	resp, err := conn.Do(req)
   233  	defer conn.Close()
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	// make sure we defer close the body
   239  	defer resp.Body.Close()
   240  
   241  	// Check for an http error status (ie not 200 StatusOK)
   242  	switch resp.StatusCode {
   243  	case 404:
   244  		return ErrNotFound
   245  	case 403:
   246  		return ErrForbidden
   247  	case 401:
   248  		return ErrNotAuthorized
   249  	case 400:
   250  		return ErrBadRequest
   251  	}
   252  
   253  	// If no output we exit now with no errors
   254  	if out == nil {
   255  		return nil
   256  	}
   257  
   258  	// copy the output stream to the writer
   259  	if resp.Header.Get("Content-Type") == "application/json" {
   260  		var terminalFd = os.Stdin.Fd()
   261  		var isTerminal = term.IsTerminal(terminalFd)
   262  
   263  		// it may not make sense to put this code here, but it works for
   264  		// us at the moment, and I don't feel like refactoring
   265  		return utils.DisplayJSONMessagesStream(resp.Body, out, terminalFd, isTerminal)
   266  	}
   267  	// otherwise plain text
   268  	if _, err := io.Copy(out, resp.Body); err != nil {
   269  		return err
   270  	}
   271  
   272  	return nil
   273  }