github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/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 // HTTPRemoteClient implements the RemoteClient interface 14 // for an HTTP compatible server. 15 type HTTPRemoteClient struct { 16 // url is the URL that we GET / POST / DELETE to 17 url *url.URL 18 } 19 20 func NewHTTPRemoteClient(conf map[string]string) (*HTTPRemoteClient, error) { 21 client := &HTTPRemoteClient{} 22 if err := client.validateConfig(conf); err != nil { 23 return nil, err 24 } 25 return client, nil 26 } 27 28 func (c *HTTPRemoteClient) validateConfig(conf map[string]string) error { 29 urlRaw, ok := conf["address"] 30 if !ok || urlRaw == "" { 31 return fmt.Errorf("missing 'address' configuration") 32 } 33 url, err := url.Parse(urlRaw) 34 if err != nil { 35 return fmt.Errorf("failed to parse url: %v", err) 36 } 37 if url.Scheme != "http" && url.Scheme != "https" { 38 return fmt.Errorf("invalid url: %s", url) 39 } 40 c.url = url 41 return nil 42 } 43 44 func (c *HTTPRemoteClient) GetState() (*RemoteStatePayload, error) { 45 // Request the url 46 resp, err := http.Get(c.url.String()) 47 if err != nil { 48 return nil, err 49 } 50 defer resp.Body.Close() 51 52 // Handle the common status codes 53 switch resp.StatusCode { 54 case http.StatusOK: 55 // Handled after 56 case http.StatusNoContent: 57 return nil, nil 58 case http.StatusNotFound: 59 return nil, nil 60 case http.StatusUnauthorized: 61 return nil, ErrRequireAuth 62 case http.StatusForbidden: 63 return nil, ErrInvalidAuth 64 case http.StatusInternalServerError: 65 return nil, ErrRemoteInternal 66 default: 67 return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 68 } 69 70 // Read in the body 71 buf := bytes.NewBuffer(nil) 72 if _, err := io.Copy(buf, resp.Body); err != nil { 73 return nil, fmt.Errorf("Failed to read remote state: %v", err) 74 } 75 76 // Create the payload 77 payload := &RemoteStatePayload{ 78 State: buf.Bytes(), 79 } 80 81 // Check for the MD5 82 if raw := resp.Header.Get("Content-MD5"); raw != "" { 83 md5, err := base64.StdEncoding.DecodeString(raw) 84 if err != nil { 85 return nil, fmt.Errorf("Failed to decode Content-MD5 '%s': %v", raw, err) 86 } 87 payload.MD5 = md5 88 89 } else { 90 // Generate the MD5 91 hash := md5.Sum(payload.State) 92 payload.MD5 = hash[:md5.Size] 93 } 94 95 return payload, nil 96 } 97 98 func (c *HTTPRemoteClient) PutState(state []byte, force bool) error { 99 // Copy the target URL 100 base := new(url.URL) 101 *base = *c.url 102 103 // Generate the MD5 104 hash := md5.Sum(state) 105 b64 := base64.StdEncoding.EncodeToString(hash[:md5.Size]) 106 107 // Set the force query parameter if needed 108 if force { 109 values := base.Query() 110 values.Set("force", "true") 111 base.RawQuery = values.Encode() 112 } 113 114 // Make the HTTP client and request 115 req, err := http.NewRequest("POST", base.String(), bytes.NewReader(state)) 116 if err != nil { 117 return fmt.Errorf("Failed to make HTTP request: %v", err) 118 } 119 120 // Prepare the request 121 req.Header.Set("Content-Type", "application/octet-stream") 122 req.Header.Set("Content-MD5", b64) 123 req.ContentLength = int64(len(state)) 124 125 // Make the request 126 resp, err := http.DefaultClient.Do(req) 127 if err != nil { 128 return fmt.Errorf("Failed to upload state: %v", err) 129 } 130 defer resp.Body.Close() 131 132 // Handle the error codes 133 switch resp.StatusCode { 134 case http.StatusOK: 135 return nil 136 case http.StatusConflict: 137 return ErrConflict 138 case http.StatusPreconditionFailed: 139 return ErrServerNewer 140 case http.StatusUnauthorized: 141 return ErrRequireAuth 142 case http.StatusForbidden: 143 return ErrInvalidAuth 144 case http.StatusInternalServerError: 145 return ErrRemoteInternal 146 default: 147 return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 148 } 149 } 150 151 func (c *HTTPRemoteClient) DeleteState() error { 152 // Make the HTTP request 153 req, err := http.NewRequest("DELETE", c.url.String(), nil) 154 if err != nil { 155 return fmt.Errorf("Failed to make HTTP request: %v", err) 156 } 157 158 // Make the request 159 resp, err := http.DefaultClient.Do(req) 160 if err != nil { 161 return fmt.Errorf("Failed to delete state: %v", err) 162 } 163 defer resp.Body.Close() 164 165 // Handle the error codes 166 switch resp.StatusCode { 167 case http.StatusOK: 168 return nil 169 case http.StatusNoContent: 170 return nil 171 case http.StatusNotFound: 172 return nil 173 case http.StatusUnauthorized: 174 return ErrRequireAuth 175 case http.StatusForbidden: 176 return ErrInvalidAuth 177 case http.StatusInternalServerError: 178 return ErrRemoteInternal 179 default: 180 return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode) 181 } 182 }