github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/http/client.go (about) 1 package http 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/base64" 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net/http" 12 "net/url" 13 14 "github.com/hashicorp/go-retryablehttp" 15 "github.com/hashicorp/terraform/internal/states/remote" 16 "github.com/hashicorp/terraform/internal/states/statemgr" 17 ) 18 19 // httpClient is a remote client that stores data in Consul or HTTP REST. 20 type httpClient struct { 21 // Update & Retrieve 22 URL *url.URL 23 UpdateMethod string 24 25 // Locking 26 LockURL *url.URL 27 LockMethod string 28 UnlockURL *url.URL 29 UnlockMethod string 30 31 // HTTP 32 Client *retryablehttp.Client 33 Username string 34 Password string 35 36 lockID string 37 jsonLockInfo []byte 38 } 39 40 func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what string) (*http.Response, error) { 41 // If we have data we need a reader 42 var reader io.Reader = nil 43 if data != nil { 44 reader = bytes.NewReader(*data) 45 } 46 47 // Create the request 48 req, err := retryablehttp.NewRequest(method, url.String(), reader) 49 if err != nil { 50 return nil, fmt.Errorf("Failed to make %s HTTP request: %s", what, err) 51 } 52 // Set up basic auth 53 if c.Username != "" { 54 req.SetBasicAuth(c.Username, c.Password) 55 } 56 57 // Work with data/body 58 if data != nil { 59 req.Header.Set("Content-Type", "application/json") 60 req.ContentLength = int64(len(*data)) 61 62 // Generate the MD5 63 hash := md5.Sum(*data) 64 b64 := base64.StdEncoding.EncodeToString(hash[:]) 65 req.Header.Set("Content-MD5", b64) 66 } 67 68 // Make the request 69 resp, err := c.Client.Do(req) 70 if err != nil { 71 return nil, fmt.Errorf("Failed to %s: %v", what, err) 72 } 73 74 return resp, nil 75 } 76 77 func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) { 78 if c.LockURL == nil { 79 return "", nil 80 } 81 c.lockID = "" 82 83 jsonLockInfo := info.Marshal() 84 resp, err := c.httpRequest(c.LockMethod, c.LockURL, &jsonLockInfo, "lock") 85 if err != nil { 86 return "", err 87 } 88 defer resp.Body.Close() 89 90 switch resp.StatusCode { 91 case http.StatusOK: 92 c.lockID = info.ID 93 c.jsonLockInfo = jsonLockInfo 94 return info.ID, nil 95 case http.StatusUnauthorized: 96 return "", fmt.Errorf("HTTP remote state endpoint requires auth") 97 case http.StatusForbidden: 98 return "", fmt.Errorf("HTTP remote state endpoint invalid auth") 99 case http.StatusConflict, http.StatusLocked: 100 defer resp.Body.Close() 101 body, err := ioutil.ReadAll(resp.Body) 102 if err != nil { 103 return "", &statemgr.LockError{ 104 Info: info, 105 Err: fmt.Errorf("HTTP remote state already locked, failed to read body"), 106 } 107 } 108 existing := statemgr.LockInfo{} 109 err = json.Unmarshal(body, &existing) 110 if err != nil { 111 return "", &statemgr.LockError{ 112 Info: info, 113 Err: fmt.Errorf("HTTP remote state already locked, failed to unmarshal body"), 114 } 115 } 116 return "", &statemgr.LockError{ 117 Info: info, 118 Err: fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID), 119 } 120 default: 121 return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 122 } 123 } 124 125 func (c *httpClient) Unlock(id string) error { 126 if c.UnlockURL == nil { 127 return nil 128 } 129 130 resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock") 131 if err != nil { 132 return err 133 } 134 defer resp.Body.Close() 135 136 switch resp.StatusCode { 137 case http.StatusOK: 138 return nil 139 default: 140 return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 141 } 142 } 143 144 func (c *httpClient) Get() (*remote.Payload, error) { 145 resp, err := c.httpRequest("GET", c.URL, nil, "get state") 146 if err != nil { 147 return nil, err 148 } 149 defer resp.Body.Close() 150 151 // Handle the common status codes 152 switch resp.StatusCode { 153 case http.StatusOK: 154 // Handled after 155 case http.StatusNoContent: 156 return nil, nil 157 case http.StatusNotFound: 158 return nil, nil 159 case http.StatusUnauthorized: 160 return nil, fmt.Errorf("HTTP remote state endpoint requires auth") 161 case http.StatusForbidden: 162 return nil, fmt.Errorf("HTTP remote state endpoint invalid auth") 163 case http.StatusInternalServerError: 164 return nil, fmt.Errorf("HTTP remote state internal server error") 165 default: 166 return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 167 } 168 169 // Read in the body 170 buf := bytes.NewBuffer(nil) 171 if _, err := io.Copy(buf, resp.Body); err != nil { 172 return nil, fmt.Errorf("Failed to read remote state: %s", err) 173 } 174 175 // Create the payload 176 payload := &remote.Payload{ 177 Data: buf.Bytes(), 178 } 179 180 // If there was no data, then return nil 181 if len(payload.Data) == 0 { 182 return nil, nil 183 } 184 185 // Check for the MD5 186 if raw := resp.Header.Get("Content-MD5"); raw != "" { 187 md5, err := base64.StdEncoding.DecodeString(raw) 188 if err != nil { 189 return nil, fmt.Errorf( 190 "Failed to decode Content-MD5 '%s': %s", raw, err) 191 } 192 193 payload.MD5 = md5 194 } else { 195 // Generate the MD5 196 hash := md5.Sum(payload.Data) 197 payload.MD5 = hash[:] 198 } 199 200 return payload, nil 201 } 202 203 func (c *httpClient) Put(data []byte) error { 204 // Copy the target URL 205 base := *c.URL 206 207 if c.lockID != "" { 208 query := base.Query() 209 query.Set("ID", c.lockID) 210 base.RawQuery = query.Encode() 211 } 212 213 /* 214 // Set the force query parameter if needed 215 if force { 216 values := base.Query() 217 values.Set("force", "true") 218 base.RawQuery = values.Encode() 219 } 220 */ 221 222 var method string = "POST" 223 if c.UpdateMethod != "" { 224 method = c.UpdateMethod 225 } 226 resp, err := c.httpRequest(method, &base, &data, "upload state") 227 if err != nil { 228 return err 229 } 230 defer resp.Body.Close() 231 232 // Handle the error codes 233 switch resp.StatusCode { 234 case http.StatusOK, http.StatusCreated, http.StatusNoContent: 235 return nil 236 default: 237 return fmt.Errorf("HTTP error: %d", resp.StatusCode) 238 } 239 } 240 241 func (c *httpClient) Delete() error { 242 // Make the request 243 resp, err := c.httpRequest("DELETE", c.URL, nil, "delete state") 244 if err != nil { 245 return err 246 } 247 defer resp.Body.Close() 248 249 // Handle the error codes 250 switch resp.StatusCode { 251 case http.StatusOK: 252 return nil 253 default: 254 return fmt.Errorf("HTTP error: %d", resp.StatusCode) 255 } 256 }