github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/state/remote/gcs.go (about)

     1  package remote
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"net/http"
    10  	"os"
    11  	"runtime"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/terraform/helper/pathorcontents"
    15  	"github.com/hashicorp/terraform/terraform"
    16  	"golang.org/x/net/context"
    17  	"golang.org/x/oauth2"
    18  	"golang.org/x/oauth2/google"
    19  	"golang.org/x/oauth2/jwt"
    20  	"google.golang.org/api/googleapi"
    21  	"google.golang.org/api/storage/v1"
    22  )
    23  
    24  // accountFile represents the structure of the credentials JSON
    25  type accountFile struct {
    26  	PrivateKeyId string `json:"private_key_id"`
    27  	PrivateKey   string `json:"private_key"`
    28  	ClientEmail  string `json:"client_email"`
    29  	ClientId     string `json:"client_id"`
    30  }
    31  
    32  func parseJSON(result interface{}, contents string) error {
    33  	r := strings.NewReader(contents)
    34  	dec := json.NewDecoder(r)
    35  
    36  	return dec.Decode(result)
    37  }
    38  
    39  type GCSClient struct {
    40  	bucket        string
    41  	path          string
    42  	clientStorage *storage.Service
    43  	context       context.Context
    44  }
    45  
    46  func gcsFactory(conf map[string]string) (Client, error) {
    47  	var account accountFile
    48  	var client *http.Client
    49  	clientScopes := []string{
    50  		"https://www.googleapis.com/auth/devstorage.full_control",
    51  	}
    52  
    53  	bucketName, ok := conf["bucket"]
    54  	if !ok {
    55  		return nil, fmt.Errorf("missing 'bucket' configuration")
    56  	}
    57  
    58  	pathName, ok := conf["path"]
    59  	if !ok {
    60  		return nil, fmt.Errorf("missing 'path' configuration")
    61  	}
    62  
    63  	credentials, ok := conf["credentials"]
    64  	if !ok {
    65  		credentials = os.Getenv("GOOGLE_CREDENTIALS")
    66  	}
    67  
    68  	if credentials != "" {
    69  		contents, _, err := pathorcontents.Read(credentials)
    70  		if err != nil {
    71  			return nil, fmt.Errorf("Error loading credentials: %s", err)
    72  		}
    73  
    74  		// Assume account_file is a JSON string
    75  		if err := parseJSON(&account, contents); err != nil {
    76  			return nil, fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
    77  		}
    78  
    79  		// Get the token for use in our requests
    80  		log.Printf("[INFO] Requesting Google token...")
    81  		log.Printf("[INFO]   -- Email: %s", account.ClientEmail)
    82  		log.Printf("[INFO]   -- Scopes: %s", clientScopes)
    83  		log.Printf("[INFO]   -- Private Key Length: %d", len(account.PrivateKey))
    84  
    85  		conf := jwt.Config{
    86  			Email:      account.ClientEmail,
    87  			PrivateKey: []byte(account.PrivateKey),
    88  			Scopes:     clientScopes,
    89  			TokenURL:   "https://accounts.google.com/o/oauth2/token",
    90  		}
    91  
    92  		client = conf.Client(oauth2.NoContext)
    93  
    94  	} else {
    95  		log.Printf("[INFO] Authenticating using DefaultClient")
    96  		err := error(nil)
    97  		client, err = google.DefaultClient(oauth2.NoContext, clientScopes...)
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  	}
   102  	versionString := terraform.Version
   103  	userAgent := fmt.Sprintf(
   104  		"(%s %s) Terraform/%s", runtime.GOOS, runtime.GOARCH, versionString)
   105  
   106  	log.Printf("[INFO] Instantiating Google Storage Client...")
   107  	clientStorage, err := storage.New(client)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	clientStorage.UserAgent = userAgent
   112  
   113  	return &GCSClient{
   114  		clientStorage: clientStorage,
   115  		bucket:        bucketName,
   116  		path:          pathName,
   117  	}, nil
   118  
   119  }
   120  
   121  func (c *GCSClient) Get() (*Payload, error) {
   122  	// Read the object from bucket.
   123  	log.Printf("[INFO] Reading %s/%s", c.bucket, c.path)
   124  
   125  	resp, err := c.clientStorage.Objects.Get(c.bucket, c.path).Download()
   126  	if err != nil {
   127  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   128  			log.Printf("[INFO] %s/%s not found", c.bucket, c.path)
   129  
   130  			return nil, nil
   131  		}
   132  
   133  		return nil, fmt.Errorf("[WARN] Error retrieving object %s/%s: %s", c.bucket, c.path, err)
   134  	}
   135  	defer resp.Body.Close()
   136  
   137  	var buf []byte
   138  	w := bytes.NewBuffer(buf)
   139  	n, err := io.Copy(w, resp.Body)
   140  	if err != nil {
   141  		log.Fatalf("[WARN] error buffering %q: %v", c.path, err)
   142  	}
   143  	log.Printf("[INFO] Downloaded %d bytes", n)
   144  
   145  	payload := &Payload{
   146  		Data: w.Bytes(),
   147  	}
   148  
   149  	// If there was no data, then return nil
   150  	if len(payload.Data) == 0 {
   151  		return nil, nil
   152  	}
   153  
   154  	return payload, nil
   155  }
   156  
   157  func (c *GCSClient) Put(data []byte) error {
   158  	log.Printf("[INFO] Writing %s/%s", c.bucket, c.path)
   159  
   160  	r := bytes.NewReader(data)
   161  	_, err := c.clientStorage.Objects.Insert(c.bucket, &storage.Object{Name: c.path}).Media(r).Do()
   162  	if err != nil {
   163  		return err
   164  	}
   165  
   166  	return nil
   167  }
   168  
   169  func (c *GCSClient) Delete() error {
   170  	log.Printf("[INFO] Deleting %s/%s", c.bucket, c.path)
   171  
   172  	err := c.clientStorage.Objects.Delete(c.bucket, c.path).Do()
   173  	return err
   174  
   175  }