github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/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 }