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