github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/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  	return &HTTPClient{
    48  		URL:    url,
    49  		Client: client,
    50  	}, nil
    51  }
    52  
    53  // HTTPClient is a remote client that stores data in Consul or HTTP REST.
    54  type HTTPClient struct {
    55  	URL    *url.URL
    56  	Client *http.Client
    57  }
    58  
    59  func (c *HTTPClient) Get() (*Payload, error) {
    60  	resp, err := c.Client.Get(c.URL.String())
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	defer resp.Body.Close()
    65  
    66  	// Handle the common status codes
    67  	switch resp.StatusCode {
    68  	case http.StatusOK:
    69  		// Handled after
    70  	case http.StatusNoContent:
    71  		return nil, nil
    72  	case http.StatusNotFound:
    73  		return nil, nil
    74  	case http.StatusUnauthorized:
    75  		return nil, fmt.Errorf("HTTP remote state endpoint requires auth")
    76  	case http.StatusForbidden:
    77  		return nil, fmt.Errorf("HTTP remote state endpoint invalid auth")
    78  	case http.StatusInternalServerError:
    79  		return nil, fmt.Errorf("HTTP remote state internal server error")
    80  	default:
    81  		return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
    82  	}
    83  
    84  	// Read in the body
    85  	buf := bytes.NewBuffer(nil)
    86  	if _, err := io.Copy(buf, resp.Body); err != nil {
    87  		return nil, fmt.Errorf("Failed to read remote state: %s", err)
    88  	}
    89  
    90  	// Create the payload
    91  	payload := &Payload{
    92  		Data: buf.Bytes(),
    93  	}
    94  
    95  	// If there was no data, then return nil
    96  	if len(payload.Data) == 0 {
    97  		return nil, nil
    98  	}
    99  
   100  	// Check for the MD5
   101  	if raw := resp.Header.Get("Content-MD5"); raw != "" {
   102  		md5, err := base64.StdEncoding.DecodeString(raw)
   103  		if err != nil {
   104  			return nil, fmt.Errorf(
   105  				"Failed to decode Content-MD5 '%s': %s", raw, err)
   106  		}
   107  
   108  		payload.MD5 = md5
   109  	} else {
   110  		// Generate the MD5
   111  		hash := md5.Sum(payload.Data)
   112  		payload.MD5 = hash[:]
   113  	}
   114  
   115  	return payload, nil
   116  }
   117  
   118  func (c *HTTPClient) Put(data []byte) error {
   119  	// Copy the target URL
   120  	base := *c.URL
   121  
   122  	// Generate the MD5
   123  	hash := md5.Sum(data)
   124  	b64 := base64.StdEncoding.EncodeToString(hash[:])
   125  
   126  	/*
   127  		// Set the force query parameter if needed
   128  		if force {
   129  			values := base.Query()
   130  			values.Set("force", "true")
   131  			base.RawQuery = values.Encode()
   132  		}
   133  	*/
   134  
   135  	req, err := http.NewRequest("POST", base.String(), bytes.NewReader(data))
   136  	if err != nil {
   137  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   138  	}
   139  
   140  	// Prepare the request
   141  	req.Header.Set("Content-Type", "application/json")
   142  	req.Header.Set("Content-MD5", b64)
   143  	req.ContentLength = int64(len(data))
   144  
   145  	// Make the request
   146  	resp, err := c.Client.Do(req)
   147  	if err != nil {
   148  		return fmt.Errorf("Failed to upload state: %v", err)
   149  	}
   150  	defer resp.Body.Close()
   151  
   152  	// Handle the error codes
   153  	switch resp.StatusCode {
   154  	case http.StatusOK:
   155  		return nil
   156  	default:
   157  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   158  	}
   159  }
   160  
   161  func (c *HTTPClient) Delete() error {
   162  	req, err := http.NewRequest("DELETE", c.URL.String(), nil)
   163  	if err != nil {
   164  		return fmt.Errorf("Failed to make HTTP request: %s", err)
   165  	}
   166  
   167  	// Make the request
   168  	resp, err := c.Client.Do(req)
   169  	if err != nil {
   170  		return fmt.Errorf("Failed to delete state: %s", err)
   171  	}
   172  	defer resp.Body.Close()
   173  
   174  	// Handle the error codes
   175  	switch resp.StatusCode {
   176  	case http.StatusOK:
   177  		return nil
   178  	default:
   179  		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
   180  	}
   181  }