github.com/ctrox/terraform@v0.11.12-beta1/state/remote/http.go (about) 1 package remote 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "crypto/tls" 7 "encoding/base64" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "strconv" 15 16 "github.com/hashicorp/terraform/state" 17 ) 18 19 func httpFactory(conf map[string]string) (Client, error) { 20 address, ok := conf["address"] 21 if !ok { 22 return nil, fmt.Errorf("missing 'address' configuration") 23 } 24 25 updateURL, err := url.Parse(address) 26 if err != nil { 27 return nil, fmt.Errorf("failed to parse address URL: %s", err) 28 } 29 if updateURL.Scheme != "http" && updateURL.Scheme != "https" { 30 return nil, fmt.Errorf("address must be HTTP or HTTPS") 31 } 32 updateMethod, ok := conf["update_method"] 33 if !ok { 34 updateMethod = "POST" 35 } 36 37 var lockURL *url.URL 38 if lockAddress, ok := conf["lock_address"]; ok { 39 var err error 40 lockURL, err = url.Parse(lockAddress) 41 if err != nil { 42 return nil, fmt.Errorf("failed to parse lockAddress URL: %s", err) 43 } 44 if lockURL.Scheme != "http" && lockURL.Scheme != "https" { 45 return nil, fmt.Errorf("lockAddress must be HTTP or HTTPS") 46 } 47 } else { 48 lockURL = nil 49 } 50 lockMethod, ok := conf["lock_method"] 51 if !ok { 52 lockMethod = "LOCK" 53 } 54 55 var unlockURL *url.URL 56 if unlockAddress, ok := conf["unlock_address"]; ok { 57 var err error 58 unlockURL, err = url.Parse(unlockAddress) 59 if err != nil { 60 return nil, fmt.Errorf("failed to parse unlockAddress URL: %s", err) 61 } 62 if unlockURL.Scheme != "http" && unlockURL.Scheme != "https" { 63 return nil, fmt.Errorf("unlockAddress must be HTTP or HTTPS") 64 } 65 } else { 66 unlockURL = nil 67 } 68 unlockMethod, ok := conf["unlock_method"] 69 if !ok { 70 unlockMethod = "UNLOCK" 71 } 72 73 client := &http.Client{} 74 if skipRaw, ok := conf["skip_cert_verification"]; ok { 75 skip, err := strconv.ParseBool(skipRaw) 76 if err != nil { 77 return nil, fmt.Errorf("skip_cert_verification must be boolean") 78 } 79 if skip { 80 // Replace the client with one that ignores TLS verification 81 client = &http.Client{ 82 Transport: &http.Transport{ 83 TLSClientConfig: &tls.Config{ 84 InsecureSkipVerify: true, 85 }, 86 }, 87 } 88 } 89 } 90 91 ret := &HTTPClient{ 92 URL: updateURL, 93 UpdateMethod: updateMethod, 94 95 LockURL: lockURL, 96 LockMethod: lockMethod, 97 UnlockURL: unlockURL, 98 UnlockMethod: unlockMethod, 99 100 Username: conf["username"], 101 Password: conf["password"], 102 103 // accessible only for testing use 104 Client: client, 105 } 106 107 return ret, nil 108 } 109 110 // HTTPClient is a remote client that stores data in Consul or HTTP REST. 111 type HTTPClient struct { 112 // Update & Retrieve 113 URL *url.URL 114 UpdateMethod string 115 116 // Locking 117 LockURL *url.URL 118 LockMethod string 119 UnlockURL *url.URL 120 UnlockMethod string 121 122 // HTTP 123 Client *http.Client 124 Username string 125 Password string 126 127 lockID string 128 jsonLockInfo []byte 129 } 130 131 func (c *HTTPClient) httpRequest(method string, url *url.URL, data *[]byte, what string) (*http.Response, error) { 132 // If we have data we need a reader 133 var reader io.Reader = nil 134 if data != nil { 135 reader = bytes.NewReader(*data) 136 } 137 138 // Create the request 139 req, err := http.NewRequest(method, url.String(), reader) 140 if err != nil { 141 return nil, fmt.Errorf("Failed to make %s HTTP request: %s", what, err) 142 } 143 // Setup basic auth 144 if c.Username != "" { 145 req.SetBasicAuth(c.Username, c.Password) 146 } 147 148 // Work with data/body 149 if data != nil { 150 req.Header.Set("Content-Type", "application/json") 151 req.ContentLength = int64(len(*data)) 152 153 // Generate the MD5 154 hash := md5.Sum(*data) 155 b64 := base64.StdEncoding.EncodeToString(hash[:]) 156 req.Header.Set("Content-MD5", b64) 157 } 158 159 // Make the request 160 resp, err := c.Client.Do(req) 161 if err != nil { 162 return nil, fmt.Errorf("Failed to %s: %v", what, err) 163 } 164 165 return resp, nil 166 } 167 168 func (c *HTTPClient) Lock(info *state.LockInfo) (string, error) { 169 if c.LockURL == nil { 170 return "", nil 171 } 172 c.lockID = "" 173 174 jsonLockInfo := info.Marshal() 175 resp, err := c.httpRequest(c.LockMethod, c.LockURL, &jsonLockInfo, "lock") 176 if err != nil { 177 return "", err 178 } 179 defer resp.Body.Close() 180 181 switch resp.StatusCode { 182 case http.StatusOK: 183 c.lockID = info.ID 184 c.jsonLockInfo = jsonLockInfo 185 return info.ID, nil 186 case http.StatusUnauthorized: 187 return "", fmt.Errorf("HTTP remote state endpoint requires auth") 188 case http.StatusForbidden: 189 return "", fmt.Errorf("HTTP remote state endpoint invalid auth") 190 case http.StatusConflict, http.StatusLocked: 191 defer resp.Body.Close() 192 body, err := ioutil.ReadAll(resp.Body) 193 if err != nil { 194 return "", fmt.Errorf("HTTP remote state already locked, failed to read body") 195 } 196 existing := state.LockInfo{} 197 err = json.Unmarshal(body, &existing) 198 if err != nil { 199 return "", fmt.Errorf("HTTP remote state already locked, failed to unmarshal body") 200 } 201 return "", fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID) 202 default: 203 return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 204 } 205 } 206 207 func (c *HTTPClient) Unlock(id string) error { 208 if c.UnlockURL == nil { 209 return nil 210 } 211 212 resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock") 213 if err != nil { 214 return err 215 } 216 defer resp.Body.Close() 217 218 switch resp.StatusCode { 219 case http.StatusOK: 220 return nil 221 default: 222 return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 223 } 224 } 225 226 func (c *HTTPClient) Get() (*Payload, error) { 227 resp, err := c.httpRequest("GET", c.URL, nil, "get state") 228 if err != nil { 229 return nil, err 230 } 231 defer resp.Body.Close() 232 233 // Handle the common status codes 234 switch resp.StatusCode { 235 case http.StatusOK: 236 // Handled after 237 case http.StatusNoContent: 238 return nil, nil 239 case http.StatusNotFound: 240 return nil, nil 241 case http.StatusUnauthorized: 242 return nil, fmt.Errorf("HTTP remote state endpoint requires auth") 243 case http.StatusForbidden: 244 return nil, fmt.Errorf("HTTP remote state endpoint invalid auth") 245 case http.StatusInternalServerError: 246 return nil, fmt.Errorf("HTTP remote state internal server error") 247 default: 248 return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 249 } 250 251 // Read in the body 252 buf := bytes.NewBuffer(nil) 253 if _, err := io.Copy(buf, resp.Body); err != nil { 254 return nil, fmt.Errorf("Failed to read remote state: %s", err) 255 } 256 257 // Create the payload 258 payload := &Payload{ 259 Data: buf.Bytes(), 260 } 261 262 // If there was no data, then return nil 263 if len(payload.Data) == 0 { 264 return nil, nil 265 } 266 267 // Check for the MD5 268 if raw := resp.Header.Get("Content-MD5"); raw != "" { 269 md5, err := base64.StdEncoding.DecodeString(raw) 270 if err != nil { 271 return nil, fmt.Errorf( 272 "Failed to decode Content-MD5 '%s': %s", raw, err) 273 } 274 275 payload.MD5 = md5 276 } else { 277 // Generate the MD5 278 hash := md5.Sum(payload.Data) 279 payload.MD5 = hash[:] 280 } 281 282 return payload, nil 283 } 284 285 func (c *HTTPClient) Put(data []byte) error { 286 // Copy the target URL 287 base := *c.URL 288 289 if c.lockID != "" { 290 query := base.Query() 291 query.Set("ID", c.lockID) 292 base.RawQuery = query.Encode() 293 } 294 295 /* 296 // Set the force query parameter if needed 297 if force { 298 values := base.Query() 299 values.Set("force", "true") 300 base.RawQuery = values.Encode() 301 } 302 */ 303 304 var method string = "POST" 305 if c.UpdateMethod != "" { 306 method = c.UpdateMethod 307 } 308 resp, err := c.httpRequest(method, &base, &data, "upload state") 309 if err != nil { 310 return err 311 } 312 defer resp.Body.Close() 313 314 // Handle the error codes 315 switch resp.StatusCode { 316 case http.StatusOK, http.StatusCreated, http.StatusNoContent: 317 return nil 318 default: 319 return fmt.Errorf("HTTP error: %d", resp.StatusCode) 320 } 321 } 322 323 func (c *HTTPClient) Delete() error { 324 // Make the request 325 resp, err := c.httpRequest("DELETE", c.URL, nil, "delete state") 326 if err != nil { 327 return err 328 } 329 defer resp.Body.Close() 330 331 // Handle the error codes 332 switch resp.StatusCode { 333 case http.StatusOK: 334 return nil 335 default: 336 return fmt.Errorf("HTTP error: %d", resp.StatusCode) 337 } 338 }