github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 }