github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/remote/http.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  )
    12  
    13  // HTTPRemoteClient implements the RemoteClient interface
    14  // for an HTTP compatible server.
    15  type HTTPRemoteClient struct {
    16  	// url is the URL that we GET / POST / DELETE to
    17  	url *url.URL
    18  }
    19  
    20  func NewHTTPRemoteClient(conf map[string]string) (*HTTPRemoteClient, error) {
    21  	client := &HTTPRemoteClient{}
    22  	if err := client.validateConfig(conf); err != nil {
    23  		return nil, err
    24  	}
    25  	return client, nil
    26  }
    27  
    28  func (c *HTTPRemoteClient) validateConfig(conf map[string]string) error {
    29  	urlRaw, ok := conf["address"]
    30  	if !ok || urlRaw == "" {
    31  		return fmt.Errorf("missing 'address' configuration")
    32  	}
    33  	url, err := url.Parse(urlRaw)
    34  	if err != nil {
    35  		return fmt.Errorf("failed to parse url: %v", err)
    36  	}
    37  	if url.Scheme != "http" && url.Scheme != "https" {
    38  		return fmt.Errorf("invalid url: %s", url)
    39  	}
    40  	c.url = url
    41  	return nil
    42  }
    43  
    44  func (c *HTTPRemoteClient) GetState() (*RemoteStatePayload, error) {
    45  	// Request the url
    46  	resp, err := http.Get(c.url.String())
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	defer resp.Body.Close()
    51  
    52  	// Handle the common status codes
    53  	switch resp.StatusCode {
    54  	case http.StatusOK:
    55  		// Handled after
    56  	case http.StatusNoContent:
    57  		return nil, nil
    58  	case http.StatusNotFound:
    59  		return nil, nil
    60  	case http.StatusUnauthorized:
    61  		return nil, ErrRequireAuth
    62  	case http.StatusForbidden:
    63  		return nil, ErrInvalidAuth
    64  	case http.StatusInternalServerError:
    65  		return nil, ErrRemoteInternal
    66  	default:
    67  		return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
    68  	}
    69  
    70  	// Read in the body
    71  	buf := bytes.NewBuffer(nil)
    72  	if _, err := io.Copy(buf, resp.Body); err != nil {
    73  		return nil, fmt.Errorf("Failed to read remote state: %v", err)
    74  	}
    75  
    76  	// Create the payload
    77  	payload := &RemoteStatePayload{
    78  		State: buf.Bytes(),
    79  	}
    80  
    81  	// Check for the MD5
    82  	if raw := resp.Header.Get("Content-MD5"); raw != "" {
    83  		md5, err := base64.StdEncoding.DecodeString(raw)
    84  		if err != nil {
    85  			return nil, fmt.Errorf("Failed to decode Content-MD5 '%s': %v", raw, err)
    86  		}
    87  		payload.MD5 = md5
    88  
    89  	} else {
    90  		// Generate the MD5
    91  		hash := md5.Sum(payload.State)
    92  		payload.MD5 = hash[:md5.Size]
    93  	}
    94  
    95  	return payload, nil
    96  }
    97  
    98  func (c *HTTPRemoteClient) PutState(state []byte, force bool) error {
    99  	// Copy the target URL
   100  	base := new(url.URL)
   101  	*base = *c.url
   102  
   103  	// Generate the MD5
   104  	hash := md5.Sum(state)
   105  	b64 := base64.StdEncoding.EncodeToString(hash[:md5.Size])
   106  
   107  	// Set the force query parameter if needed
   108  	if force {
   109  		values := base.Query()
   110  		values.Set("force", "true")
   111  		base.RawQuery = values.Encode()
   112  	}
   113  
   114  	// Make the HTTP client and request
   115  	req, err := http.NewRequest("POST", base.String(), bytes.NewReader(state))
   116  	if err != nil {
   117  		return fmt.Errorf("Failed to make HTTP request: %v", err)
   118  	}
   119  
   120  	// Prepare the request
   121  	req.Header.Set("Content-Type", "application/octet-stream")
   122  	req.Header.Set("Content-MD5", b64)
   123  	req.ContentLength = int64(len(state))
   124  
   125  	// Make the request
   126  	resp, err := http.DefaultClient.Do(req)
   127  	if err != nil {
   128  		return fmt.Errorf("Failed to upload state: %v", err)
   129  	}
   130  	defer resp.Body.Close()
   131  
   132  	// Handle the error codes
   133  	switch resp.StatusCode {
   134  	case http.StatusOK:
   135  		return nil
   136  	case http.StatusConflict:
   137  		return ErrConflict
   138  	case http.StatusPreconditionFailed:
   139  		return ErrServerNewer
   140  	case http.StatusUnauthorized:
   141  		return ErrRequireAuth
   142  	case http.StatusForbidden:
   143  		return ErrInvalidAuth
   144  	case http.StatusInternalServerError:
   145  		return ErrRemoteInternal
   146  	default:
   147  		return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
   148  	}
   149  }
   150  
   151  func (c *HTTPRemoteClient) DeleteState() error {
   152  	// Make the HTTP request
   153  	req, err := http.NewRequest("DELETE", c.url.String(), nil)
   154  	if err != nil {
   155  		return fmt.Errorf("Failed to make HTTP request: %v", err)
   156  	}
   157  
   158  	// Make the request
   159  	resp, err := http.DefaultClient.Do(req)
   160  	if err != nil {
   161  		return fmt.Errorf("Failed to delete state: %v", err)
   162  	}
   163  	defer resp.Body.Close()
   164  
   165  	// Handle the error codes
   166  	switch resp.StatusCode {
   167  	case http.StatusOK:
   168  		return nil
   169  	case http.StatusNoContent:
   170  		return nil
   171  	case http.StatusNotFound:
   172  		return nil
   173  	case http.StatusUnauthorized:
   174  		return ErrRequireAuth
   175  	case http.StatusForbidden:
   176  		return ErrInvalidAuth
   177  	case http.StatusInternalServerError:
   178  		return ErrRemoteInternal
   179  	default:
   180  		return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
   181  	}
   182  }