github.com/ggiamarchi/terraform@v0.3.7-0.20150607194748-ed2a66a46a71/state/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  func httpFactory(conf map[string]string) (Client, error) {
    14  	address, ok := conf["address"]
    15  	if !ok {
    16  		return nil, fmt.Errorf("missing 'address' configuration")
    17  	}
    18  
    19  	url, err := url.Parse(address)
    20  	if err != nil {
    21  		return nil, fmt.Errorf("failed to parse HTTP URL: %s", err)
    22  	}
    23  	if url.Scheme != "http" && url.Scheme != "https" {
    24  		return nil, fmt.Errorf("address must be HTTP or HTTPS")
    25  	}
    26  
    27  	return &HTTPClient{
    28  		URL: url,
    29  	}, nil
    30  }
    31  
    32  // HTTPClient is a remote client that stores data in Consul.
    33  type HTTPClient struct {
    34  	URL *url.URL
    35  }
    36  
    37  func (c *HTTPClient) Get() (*Payload, error) {
    38  	resp, err := http.Get(c.URL.String())
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	defer resp.Body.Close()
    43  
    44  	// Handle the common status codes
    45  	switch resp.StatusCode {
    46  	case http.StatusOK:
    47  		// Handled after
    48  	case http.StatusNoContent:
    49  		return nil, nil
    50  	case http.StatusNotFound:
    51  		return nil, nil
    52  	case http.StatusUnauthorized:
    53  		return nil, fmt.Errorf("HTTP remote state endpoint requires auth")
    54  	case http.StatusForbidden:
    55  		return nil, fmt.Errorf("HTTP remote state endpoint invalid auth")
    56  	case http.StatusInternalServerError:
    57  		return nil, fmt.Errorf("HTTP remote state internal server error")
    58  	default:
    59  		return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
    60  	}
    61  
    62  	// Read in the body
    63  	buf := bytes.NewBuffer(nil)
    64  	if _, err := io.Copy(buf, resp.Body); err != nil {
    65  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
    66  	}
    67  
    68  	// Create the payload
    69  	payload := &Payload{
    70  		Data: buf.Bytes(),
    71  	}
    72  
    73  	// If there was no data, then return nil
    74  	if len(payload.Data) == 0 {
    75  		return nil, nil
    76  	}
    77  
    78  	// Check for the MD5
    79  	if raw := resp.Header.Get("Content-MD5"); raw != "" {
    80  		md5, err := base64.StdEncoding.DecodeString(raw)
    81  		if err != nil {
    82  			return nil, fmt.Errorf(
    83  				"Failed to decode Content-MD5 '%s': %s", raw, err)
    84  		}
    85  
    86  		payload.MD5 = md5
    87  	} else {
    88  		// Generate the MD5
    89  		hash := md5.Sum(payload.Data)
    90  		payload.MD5 = hash[:]
    91  	}
    92  
    93  	return payload, nil
    94  }
    95  
    96  func (c *HTTPClient) Put(data []byte) error {
    97  	// Copy the target URL
    98  	base := *c.URL
    99  
   100  	// Generate the MD5
   101  	hash := md5.Sum(data)
   102  	b64 := base64.StdEncoding.EncodeToString(hash[:])
   103  
   104  	/*
   105  		// Set the force query parameter if needed
   106  		if force {
   107  			values := base.Query()
   108  			values.Set("force", "true")
   109  			base.RawQuery = values.Encode()
   110  		}
   111  	*/
   112  
   113  	// Make the HTTP client and request
   114  	req, err := http.NewRequest("POST", base.String(), bytes.NewReader(data))
   115  	if err != nil {
   116  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   117  	}
   118  
   119  	// Prepare the request
   120  	req.Header.Set("Content-Type", "application/octet-stream")
   121  	req.Header.Set("Content-MD5", b64)
   122  	req.ContentLength = int64(len(data))
   123  
   124  	// Make the request
   125  	resp, err := http.DefaultClient.Do(req)
   126  	if err != nil {
   127  		return fmt.Errorf("Failed to upload state: %v", err)
   128  	}
   129  	defer resp.Body.Close()
   130  
   131  	// Handle the error codes
   132  	switch resp.StatusCode {
   133  	case http.StatusOK:
   134  		return nil
   135  	default:
   136  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   137  	}
   138  }
   139  
   140  func (c *HTTPClient) Delete() error {
   141  	// Make the HTTP request
   142  	req, err := http.NewRequest("DELETE", c.URL.String(), nil)
   143  	if err != nil {
   144  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   145  	}
   146  
   147  	// Make the request
   148  	resp, err := http.DefaultClient.Do(req)
   149  	if err != nil {
   150  		return fmt.Errorf("Failed to delete state: %s", err)
   151  	}
   152  	defer resp.Body.Close()
   153  
   154  	// Handle the error codes
   155  	switch resp.StatusCode {
   156  	case http.StatusOK:
   157  		return nil
   158  	default:
   159  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   160  	}
   161  }