terraform/remote/client.go

128 lines
3.1 KiB
Go
Raw Normal View History

2014-10-02 20:48:11 +02:00
package remote
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"github.com/hashicorp/terraform/terraform"
)
// RemoteStatePayload is used to return the remote state
// along with associated meta data when we do a remote fetch.
type RemoteStatePayload struct {
2014-10-03 20:14:12 +02:00
MD5 []byte
State []byte
2014-10-02 20:48:11 +02:00
}
// remoteStateClient is used to interact with a remote state store
// using the API
type remoteStateClient struct {
conf *terraform.RemoteState
}
// URL is used to return an appropriate URL to hit for the
// given server and remote name
func (r *remoteStateClient) URL() (*url.URL, error) {
2014-10-02 20:48:11 +02:00
// Get the base URL configuration
base, err := url.Parse(r.conf.Server)
2014-10-02 20:48:11 +02:00
if err != nil {
return nil, fmt.Errorf("Failed to parse remote server '%s': %v", r.conf.Server, err)
2014-10-02 20:48:11 +02:00
}
// Compute the full path by just appending the name
base.Path = path.Join(base.Path, r.conf.Name)
2014-10-02 20:48:11 +02:00
// Add the request token if any
if r.conf.AuthToken != "" {
2014-10-02 20:48:11 +02:00
values := base.Query()
values.Set("access_token", r.conf.AuthToken)
2014-10-02 20:48:11 +02:00
base.RawQuery = values.Encode()
}
return base, nil
}
// GetState is used to read the remote state
func (r *remoteStateClient) GetState() (*RemoteStatePayload, error) {
// Get the target URL
base, err := r.URL()
if err != nil {
return nil, err
}
2014-10-02 20:48:11 +02:00
// Request the url
resp, err := http.Get(base.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Handle the common status codes
switch resp.StatusCode {
case http.StatusOK:
// Handled after
case http.StatusNoContent:
return nil, nil
case http.StatusNotFound:
return nil, nil
case http.StatusUnauthorized:
return nil, fmt.Errorf("Remote server requires authentication")
case http.StatusForbidden:
2014-10-02 21:22:06 +02:00
return nil, fmt.Errorf("Invalid authentication")
2014-10-02 20:48:11 +02:00
case http.StatusInternalServerError:
return nil, fmt.Errorf("Remote server reporting internal error")
default:
2014-10-02 21:22:06 +02:00
return nil, fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
2014-10-02 20:48:11 +02:00
}
// Read in the body
buf := bytes.NewBuffer(nil)
if _, err := io.Copy(buf, resp.Body); err != nil {
return nil, fmt.Errorf("Failed to read remote state: %v", err)
}
// Create the payload
payload := &RemoteStatePayload{
2014-10-03 20:14:12 +02:00
State: buf.Bytes(),
2014-10-02 20:48:11 +02:00
}
// Check if this is Consul
if raw := resp.Header.Get("X-Consul-Index"); raw != "" {
// Check if we used the ?raw query param, otherwise decode
if _, ok := base.Query()["raw"]; !ok {
type kv struct {
Value []byte
}
var values []*kv
if err := json.Unmarshal(buf.Bytes(), &values); err != nil {
return nil, fmt.Errorf("Failed to decode Consul response: %v", err)
}
// Setup the reader to pull the value from Consul
2014-10-03 20:14:12 +02:00
payload.State = values[0].Value
2014-10-02 20:48:11 +02:00
}
}
// Check for the MD5
if raw := resp.Header.Get("Content-MD5"); raw != "" {
md5, err := base64.StdEncoding.DecodeString(raw)
if err != nil {
return nil, fmt.Errorf("Failed to decode Content-MD5 '%s': %v", raw, err)
}
payload.MD5 = md5
2014-10-03 20:14:12 +02:00
} else {
2014-10-02 20:48:11 +02:00
// Generate the MD5
2014-10-03 20:14:12 +02:00
hash := md5.Sum(payload.State)
2014-10-02 20:48:11 +02:00
payload.MD5 = hash[:md5.Size]
}
return payload, nil
}