github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/state/remote/http.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"crypto/tls"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  )
    14  
    15  func httpFactory(conf map[string]string) (Client, error) {
    16  	address, ok := conf["address"]
    17  	if !ok {
    18  		return nil, fmt.Errorf("missing 'address' configuration")
    19  	}
    20  
    21  	url, err := url.Parse(address)
    22  	if err != nil {
    23  		return nil, fmt.Errorf("failed to parse HTTP URL: %s", err)
    24  	}
    25  	if url.Scheme != "http" && url.Scheme != "https" {
    26  		return nil, fmt.Errorf("address must be HTTP or HTTPS")
    27  	}
    28  
    29  	client := &http.Client{}
    30  	if skipRaw, ok := conf["skip_cert_verification"]; ok {
    31  		skip, err := strconv.ParseBool(skipRaw)
    32  		if err != nil {
    33  			return nil, fmt.Errorf("skip_cert_verification must be boolean")
    34  		}
    35  		if skip {
    36  			// Replace the client with one that ignores TLS verification
    37  			client = &http.Client{
    38  				Transport: &http.Transport{
    39  					TLSClientConfig: &tls.Config{
    40  						InsecureSkipVerify: true,
    41  					},
    42  				},
    43  			}
    44  		}
    45  	}
    46  
    47  	ret := &HTTPClient{
    48  		URL:    url,
    49  		Client: client,
    50  	}
    51  	if username, ok := conf["username"]; ok && username != "" {
    52  		ret.Username = username
    53  	}
    54  	if password, ok := conf["password"]; ok && password != "" {
    55  		ret.Password = password
    56  	}
    57  	return ret, nil
    58  }
    59  
    60  // HTTPClient is a remote client that stores data in Consul or HTTP REST.
    61  type HTTPClient struct {
    62  	URL      *url.URL
    63  	Client   *http.Client
    64  	Username string
    65  	Password string
    66  }
    67  
    68  func (c *HTTPClient) Get() (*Payload, error) {
    69  	req, err := http.NewRequest("GET", c.URL.String(), nil)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// Prepare the request
    75  	if c.Username != "" {
    76  		req.SetBasicAuth(c.Username, c.Password)
    77  	}
    78  
    79  	// Make the request
    80  	resp, err := c.Client.Do(req)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	defer resp.Body.Close()
    85  
    86  	// Handle the common status codes
    87  	switch resp.StatusCode {
    88  	case http.StatusOK:
    89  		// Handled after
    90  	case http.StatusNoContent:
    91  		return nil, nil
    92  	case http.StatusNotFound:
    93  		return nil, nil
    94  	case http.StatusUnauthorized:
    95  		return nil, fmt.Errorf("HTTP remote state endpoint requires auth")
    96  	case http.StatusForbidden:
    97  		return nil, fmt.Errorf("HTTP remote state endpoint invalid auth")
    98  	case http.StatusInternalServerError:
    99  		return nil, fmt.Errorf("HTTP remote state internal server error")
   100  	default:
   101  		return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
   102  	}
   103  
   104  	// Read in the body
   105  	buf := bytes.NewBuffer(nil)
   106  	if _, err := io.Copy(buf, resp.Body); err != nil {
   107  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
   108  	}
   109  
   110  	// Create the payload
   111  	payload := &Payload{
   112  		Data: buf.Bytes(),
   113  	}
   114  
   115  	// If there was no data, then return nil
   116  	if len(payload.Data) == 0 {
   117  		return nil, nil
   118  	}
   119  
   120  	// Check for the MD5
   121  	if raw := resp.Header.Get("Content-MD5"); raw != "" {
   122  		md5, err := base64.StdEncoding.DecodeString(raw)
   123  		if err != nil {
   124  			return nil, fmt.Errorf(
   125  				"Failed to decode Content-MD5 '%s': %s", raw, err)
   126  		}
   127  
   128  		payload.MD5 = md5
   129  	} else {
   130  		// Generate the MD5
   131  		hash := md5.Sum(payload.Data)
   132  		payload.MD5 = hash[:]
   133  	}
   134  
   135  	return payload, nil
   136  }
   137  
   138  func (c *HTTPClient) Put(data []byte) error {
   139  	// Copy the target URL
   140  	base := *c.URL
   141  
   142  	// Generate the MD5
   143  	hash := md5.Sum(data)
   144  	b64 := base64.StdEncoding.EncodeToString(hash[:])
   145  
   146  	/*
   147  		// Set the force query parameter if needed
   148  		if force {
   149  			values := base.Query()
   150  			values.Set("force", "true")
   151  			base.RawQuery = values.Encode()
   152  		}
   153  	*/
   154  
   155  	req, err := http.NewRequest("POST", base.String(), bytes.NewReader(data))
   156  	if err != nil {
   157  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   158  	}
   159  
   160  	// Prepare the request
   161  	req.Header.Set("Content-Type", "application/json")
   162  	req.Header.Set("Content-MD5", b64)
   163  	req.ContentLength = int64(len(data))
   164  	if c.Username != "" {
   165  		req.SetBasicAuth(c.Username, c.Password)
   166  	}
   167  
   168  	// Make the request
   169  	resp, err := c.Client.Do(req)
   170  	if err != nil {
   171  		return fmt.Errorf("Failed to upload state: %v", err)
   172  	}
   173  	defer resp.Body.Close()
   174  
   175  	// Handle the error codes
   176  	switch resp.StatusCode {
   177  	case http.StatusOK:
   178  		return nil
   179  	default:
   180  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   181  	}
   182  }
   183  
   184  func (c *HTTPClient) Delete() error {
   185  	req, err := http.NewRequest("DELETE", c.URL.String(), nil)
   186  	if err != nil {
   187  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   188  	}
   189  
   190  	// Prepare the request
   191  	if c.Username != "" {
   192  		req.SetBasicAuth(c.Username, c.Password)
   193  	}
   194  
   195  	// Make the request
   196  	resp, err := c.Client.Do(req)
   197  	if err != nil {
   198  		return fmt.Errorf("Failed to delete state: %s", err)
   199  	}
   200  	defer resp.Body.Close()
   201  
   202  	// Handle the error codes
   203  	switch resp.StatusCode {
   204  	case http.StatusOK:
   205  		return nil
   206  	default:
   207  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   208  	}
   209  }