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