providers/google: first pass

This commit is contained in:
Mitchell Hashimoto 2014-08-25 11:48:20 -07:00
commit 80e2023e6b
8 changed files with 432 additions and 0 deletions

115
config.go Normal file
View File

@ -0,0 +1,115 @@
package google
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"code.google.com/p/goauth2/oauth"
"code.google.com/p/goauth2/oauth/jwt"
"code.google.com/p/google-api-go-client/compute/v1"
)
const clientScopes string = "https://www.googleapis.com/auth/compute"
// Config is the configuration structure used to instantiate the Google
// provider.
type Config struct {
AccountFile string
ClientSecretsFile string
clientCompute *compute.Service
}
func (c *Config) loadAndValidate() error {
var account accountFile
var secrets clientSecretsFile
// TODO: validation that it isn't blank
if c.AccountFile == "" {
c.AccountFile = os.Getenv("GOOGLE_ACCOUNT_FILE")
}
if c.ClientSecretsFile == "" {
c.ClientSecretsFile = os.Getenv("GOOGLE_CLIENT_FILE")
}
if err := loadJSON(&account, c.AccountFile); err != nil {
return fmt.Errorf(
"Error loading account file '%s': %s",
c.AccountFile,
err)
}
if err := loadJSON(&secrets, c.ClientSecretsFile); err != nil {
return fmt.Errorf(
"Error loading client secrets file '%s': %s",
c.ClientSecretsFile,
err)
}
// Get the token for use in our requests
log.Printf("[INFO] Requesting Google token...")
log.Printf("[INFO] -- Email: %s", account.ClientEmail)
log.Printf("[INFO] -- Scopes: %s", clientScopes)
log.Printf("[INFO] -- Private Key Length: %d", len(account.PrivateKey))
log.Printf("[INFO] -- Token URL: %s", secrets.Web.TokenURI)
jwtTok := jwt.NewToken(
account.ClientEmail,
clientScopes,
[]byte(account.PrivateKey))
jwtTok.ClaimSet.Aud = secrets.Web.TokenURI
token, err := jwtTok.Assert(new(http.Client))
if err != nil {
return fmt.Errorf("Error retrieving auth token: %s", err)
}
// Instantiate the transport to communicate to Google
transport := &oauth.Transport{
Config: &oauth.Config{
ClientId: account.ClientId,
Scope: clientScopes,
TokenURL: secrets.Web.TokenURI,
AuthURL: secrets.Web.AuthURI,
},
Token: token,
}
log.Printf("[INFO] Instantiating GCE client...")
c.clientCompute, err = compute.New(transport.Client())
if err != nil {
return err
}
return nil
}
// accountFile represents the structure of the account file JSON file.
type accountFile struct {
PrivateKeyId string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
}
// clientSecretsFile represents the structure of the client secrets JSON file.
type clientSecretsFile struct {
Web struct {
AuthURI string `json:"auth_uri"`
ClientEmail string `json:"client_email"`
ClientId string `json:"client_id"`
TokenURI string `json:"token_uri"`
}
}
func loadJSON(result interface{}, path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
dec := json.NewDecoder(f)
return dec.Decode(result)
}

41
config_test.go Normal file
View File

@ -0,0 +1,41 @@
package google
import (
"reflect"
"testing"
)
func TestConfigLoadJSON_account(t *testing.T) {
var actual accountFile
if err := loadJSON(&actual, "./test-fixtures/fake_account.json"); err != nil {
t.Fatalf("err: %s", err)
}
expected := accountFile{
PrivateKeyId: "foo",
PrivateKey: "bar",
ClientEmail: "foo@bar.com",
ClientId: "id@foo.com",
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestConfigLoadJSON_client(t *testing.T) {
var actual clientSecretsFile
if err := loadJSON(&actual, "./test-fixtures/fake_client.json"); err != nil {
t.Fatalf("err: %s", err)
}
var expected clientSecretsFile
expected.Web.AuthURI = "https://accounts.google.com/o/oauth2/auth"
expected.Web.ClientEmail = "foo@developer.gserviceaccount.com"
expected.Web.ClientId = "foo.apps.googleusercontent.com"
expected.Web.TokenURI = "https://accounts.google.com/o/oauth2/token"
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}

41
provider.go Normal file
View File

@ -0,0 +1,41 @@
package google
import (
"github.com/hashicorp/terraform/helper/schema"
)
// Provider returns a terraform.ResourceProvider.
func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"account_file": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"client_secrets_file": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
ResourcesMap: map[string]*schema.Resource{
"google_compute_instance": resourceComputeInstance(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
AccountFile: d.Get("account_file").(string),
ClientSecretsFile: d.Get("client_secrets_file").(string),
}
if err := config.loadAndValidate(); err != nil {
return nil, err
}
return nil, nil
}

39
provider_test.go Normal file
View File

@ -0,0 +1,39 @@
package google
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider()
testAccProviders = map[string]terraform.ResourceProvider{
"google": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("GOOGLE_ACCOUNT_FILE"); v == "" {
t.Fatal("GOOGLE_ACCOUNT_FILE must be set for acceptance tests")
}
if v := os.Getenv("GOOGLE_CLIENT_FILE"); v == "" {
t.Fatal("GOOGLE_CLIENT_FILE must be set for acceptance tests")
}
}

View File

@ -0,0 +1,17 @@
package google
import(
"github.com/hashicorp/terraform/helper/schema"
)
func resourceComputeInstance() *schema.Resource {
return &schema.Resource{
Create: resourceComputeInstanceCreate,
Schema: map[string]*schema.Schema{},
}
}
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
return nil
}

View File

@ -0,0 +1,161 @@
package google
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccComputeInstance_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
//CheckDestroy: testAccCheckHerokuAppDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeInstance_basic,
/*
Check: resource.ComposeTestCheckFunc(
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
testAccCheckHerokuAppAttributes(&app),
resource.TestCheckResourceAttr(
"heroku_app.foobar", "name", "terraform-test-app"),
resource.TestCheckResourceAttr(
"heroku_app.foobar", "config_vars.0.FOO", "bar"),
),
*/
},
},
})
}
/*
func testAccCheckHerokuAppDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Client)
for _, rs := range s.Resources {
if rs.Type != "heroku_app" {
continue
}
_, err := client.AppInfo(rs.ID)
if err == nil {
return fmt.Errorf("App still exists")
}
}
return nil
}
func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Client)
if app.Region.Name != "us" {
return fmt.Errorf("Bad region: %s", app.Region.Name)
}
if app.Stack.Name != "cedar" {
return fmt.Errorf("Bad stack: %s", app.Stack.Name)
}
if app.Name != "terraform-test-app" {
return fmt.Errorf("Bad name: %s", app.Name)
}
vars, err := client.ConfigVarInfo(app.Name)
if err != nil {
return err
}
if vars["FOO"] != "bar" {
return fmt.Errorf("Bad config vars: %v", vars)
}
return nil
}
}
func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Client)
if app.Name != "terraform-test-renamed" {
return fmt.Errorf("Bad name: %s", app.Name)
}
vars, err := client.ConfigVarInfo(app.Name)
if err != nil {
return err
}
// Make sure we kept the old one
if vars["FOO"] != "bing" {
return fmt.Errorf("Bad config vars: %v", vars)
}
if vars["BAZ"] != "bar" {
return fmt.Errorf("Bad config vars: %v", vars)
}
return nil
}
}
func testAccCheckHerokuAppAttributesNoVars(app *heroku.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*heroku.Client)
if app.Name != "terraform-test-app" {
return fmt.Errorf("Bad name: %s", app.Name)
}
vars, err := client.ConfigVarInfo(app.Name)
if err != nil {
return err
}
if len(vars) != 0 {
return fmt.Errorf("vars exist: %v", vars)
}
return nil
}
}
func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.ID == "" {
return fmt.Errorf("No App Name is set")
}
client := testAccProvider.Meta().(*heroku.Client)
foundApp, err := client.AppInfo(rs.ID)
if err != nil {
return err
}
if foundApp.Name != rs.ID {
return fmt.Errorf("App not found")
}
*app = *foundApp
return nil
}
}
*/
const testAccComputeInstance_basic = `
resource "google_compute_instance" "foobar" {
}`

View File

@ -0,0 +1,7 @@
{
"private_key_id": "foo",
"private_key": "bar",
"client_email": "foo@bar.com",
"client_id": "id@foo.com",
"type": "service_account"
}

View File

@ -0,0 +1,11 @@
{
"web": {
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"client_secret": "foo",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"client_email": "foo@developer.gserviceaccount.com",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/foo@developer.gserviceaccount.com",
"client_id": "foo.apps.googleusercontent.com",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs"
}
}