Merge pull request #235 from hashicorp/f-gce
Google Compute Engine Provider
This commit is contained in:
commit
3934c1b106
|
@ -0,0 +1,123 @@
|
||||||
|
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
|
||||||
|
Project string
|
||||||
|
Region 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 c.Project == "" {
|
||||||
|
c.Project = os.Getenv("GOOGLE_PROJECT")
|
||||||
|
}
|
||||||
|
if c.Region == "" {
|
||||||
|
c.Region = os.Getenv("GOOGLE_REGION")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readImage finds the image with the given name.
|
||||||
|
func readImage(c *Config, name string) (*compute.Image, error) {
|
||||||
|
// First, always try ourselves first.
|
||||||
|
image, err := c.clientCompute.Images.Get(c.Project, name).Do()
|
||||||
|
if err == nil && image != nil && image.SelfLink != "" {
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a map of names to the project name where a public image is
|
||||||
|
// hosted. GCE doesn't have an API to simply look up an image without
|
||||||
|
// a project so we do this jank thing.
|
||||||
|
imageMap := map[string]string{
|
||||||
|
"centos": "centos-cloud",
|
||||||
|
"coreos": "coreos-cloud",
|
||||||
|
"debian": "debian-cloud",
|
||||||
|
"opensuse": "opensuse-cloud",
|
||||||
|
"rhel": "rhel-cloud",
|
||||||
|
"sles": "suse-cloud",
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we match a lookup for an alternate project, then try that next.
|
||||||
|
// If not, we return the error.
|
||||||
|
var project string
|
||||||
|
for k, v := range imageMap {
|
||||||
|
if strings.Contains(name, k) {
|
||||||
|
project = v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if project == "" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.clientCompute.Images.Get(project, name).Do()
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OperationWaitType is an enum specifying what type of operation
|
||||||
|
// we're waiting on.
|
||||||
|
type OperationWaitType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
OperationWaitInvalid OperationWaitType = iota
|
||||||
|
OperationWaitGlobal
|
||||||
|
OperationWaitRegion
|
||||||
|
OperationWaitZone
|
||||||
|
)
|
||||||
|
|
||||||
|
type OperationWaiter struct {
|
||||||
|
Service *compute.Service
|
||||||
|
Op *compute.Operation
|
||||||
|
Project string
|
||||||
|
Region string
|
||||||
|
Zone string
|
||||||
|
Type OperationWaitType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *OperationWaiter) RefreshFunc() resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
var op *compute.Operation
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch w.Type {
|
||||||
|
case OperationWaitGlobal:
|
||||||
|
op, err = w.Service.GlobalOperations.Get(
|
||||||
|
w.Project, w.Op.Name).Do()
|
||||||
|
case OperationWaitRegion:
|
||||||
|
op, err = w.Service.RegionOperations.Get(
|
||||||
|
w.Project, w.Region, w.Op.Name).Do()
|
||||||
|
case OperationWaitZone:
|
||||||
|
op, err = w.Service.ZoneOperations.Get(
|
||||||
|
w.Project, w.Zone, w.Op.Name).Do()
|
||||||
|
default:
|
||||||
|
return nil, "bad-type", fmt.Errorf(
|
||||||
|
"Invalid wait type: %#v", w.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return op, op.Status, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *OperationWaiter) Conf() *resource.StateChangeConf {
|
||||||
|
return &resource.StateChangeConf{
|
||||||
|
Pending: []string{"PENDING", "RUNNING"},
|
||||||
|
Target: "DONE",
|
||||||
|
Refresh: w.RefreshFunc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperationError wraps compute.OperationError and implements the
|
||||||
|
// error interface so it can be returned.
|
||||||
|
type OperationError compute.OperationError
|
||||||
|
|
||||||
|
func (e OperationError) Error() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for _, err := range e.Errors {
|
||||||
|
buf.WriteString(err.Message + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
|
||||||
|
"project": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"region": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"google_compute_address": resourceComputeAddress(),
|
||||||
|
"google_compute_disk": resourceComputeDisk(),
|
||||||
|
"google_compute_firewall": resourceComputeFirewall(),
|
||||||
|
"google_compute_instance": resourceComputeInstance(),
|
||||||
|
"google_compute_network": resourceComputeNetwork(),
|
||||||
|
"google_compute_route": resourceComputeRoute(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: providerConfigure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||||
|
config := Config{
|
||||||
|
AccountFile: d.Get("account_file").(string),
|
||||||
|
ClientSecretsFile: d.Get("client_secrets_file").(string),
|
||||||
|
Project: d.Get("project").(string),
|
||||||
|
Region: d.Get("region").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.loadAndValidate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeAddress() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeAddressCreate,
|
||||||
|
Read: resourceComputeAddressRead,
|
||||||
|
Delete: resourceComputeAddressDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeAddressCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Build the address parameter
|
||||||
|
addr := &compute.Address{Name: d.Get("name").(string)}
|
||||||
|
log.Printf("[DEBUG] Address insert request: %#v", addr)
|
||||||
|
op, err := config.clientCompute.Addresses.Insert(
|
||||||
|
config.Project, config.Region, addr).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(addr.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Region: config.Region,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for address to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeAddressRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeAddressRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
addr, err := config.clientCompute.Addresses.Get(
|
||||||
|
config.Project, config.Region, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("address", addr.Address)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeAddressDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the address
|
||||||
|
op, err := config.clientCompute.Addresses.Delete(
|
||||||
|
config.Project, config.Region, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Region: config.Region,
|
||||||
|
Type: OperationWaitRegion,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for address to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeAddress_basic(t *testing.T) {
|
||||||
|
var addr compute.Address
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeAddressDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeAddress_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeAddressExists(
|
||||||
|
"google_compute_address.foobar", &addr),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeAddressDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_address" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Addresses.Get(
|
||||||
|
config.Project, config.Region, rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Address still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeAddressExists(n string, addr *compute.Address) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Addresses.Get(
|
||||||
|
config.Project, config.Region, rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Addr not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*addr = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeAddress_basic = `
|
||||||
|
resource "google_compute_address" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
}`
|
|
@ -0,0 +1,157 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeDisk() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeDiskCreate,
|
||||||
|
Read: resourceComputeDiskRead,
|
||||||
|
Delete: resourceComputeDiskDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"image": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"size": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeDiskCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Build the disk parameter
|
||||||
|
disk := &compute.Disk{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
SizeGb: int64(d.Get("size").(int)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we were given a source image, load that.
|
||||||
|
if v, ok := d.GetOk("image"); ok {
|
||||||
|
log.Printf("[DEBUG] Loading image: %s", v.(string))
|
||||||
|
image, err := readImage(config, v.(string))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading image '%s': %s",
|
||||||
|
v.(string), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
disk.SourceImage = image.SelfLink
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := config.clientCompute.Disks.Insert(
|
||||||
|
config.Project, d.Get("zone").(string), disk).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating disk: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(disk.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: d.Get("zone").(string),
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for disk to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeDiskRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeDiskRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Disks.Get(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading disk: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeDiskDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the disk
|
||||||
|
op, err := config.clientCompute.Disks.Delete(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting disk: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: d.Get("zone").(string),
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for disk to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeDisk_basic(t *testing.T) {
|
||||||
|
var disk compute.Disk
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeDiskDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeDisk_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeDiskExists(
|
||||||
|
"google_compute_disk.foobar", &disk),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeDiskDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_disk" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Disks.Get(
|
||||||
|
config.Project, rs.Attributes["zone"], rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Disk still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeDiskExists(n string, disk *compute.Disk) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Disks.Get(
|
||||||
|
config.Project, rs.Attributes["zone"], rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Disk not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*disk = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeDisk_basic = `
|
||||||
|
resource "google_compute_disk" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
size = 50
|
||||||
|
zone = "us-central1-a"
|
||||||
|
}`
|
|
@ -0,0 +1,292 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeFirewall() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeFirewallCreate,
|
||||||
|
Read: resourceComputeFirewallRead,
|
||||||
|
Update: resourceComputeFirewallUpdate,
|
||||||
|
Delete: resourceComputeFirewallDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"network": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"allow": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"protocol": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ports": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: resourceComputeFirewallAllowHash,
|
||||||
|
},
|
||||||
|
|
||||||
|
"source_ranges": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"source_tags": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFirewallAllowHash(v interface{}) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
|
||||||
|
|
||||||
|
// We need to make sure to sort the strings below so that we always
|
||||||
|
// generate the same hash code no matter what is in the set.
|
||||||
|
if v, ok := m["ports"]; ok {
|
||||||
|
vs := v.(*schema.Set).List()
|
||||||
|
s := make([]string, len(vs))
|
||||||
|
for i, raw := range vs {
|
||||||
|
s[i] = raw.(string)
|
||||||
|
}
|
||||||
|
sort.Strings(s)
|
||||||
|
|
||||||
|
for _, v := range s {
|
||||||
|
buf.WriteString(fmt.Sprintf("%s-", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashcode.String(buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFirewallCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
firewall, err := resourceFirewall(d, meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := config.clientCompute.Firewalls.Insert(
|
||||||
|
config.Project, firewall).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating firewall: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(firewall.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for firewall to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeFirewallRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFirewallRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Firewalls.Get(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading firewall: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
firewall, err := resourceFirewall(d, meta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := config.clientCompute.Firewalls.Update(
|
||||||
|
config.Project, d.Id(), firewall).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating firewall: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for firewall to update: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeFirewallRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeFirewallDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the firewall
|
||||||
|
op, err := config.clientCompute.Firewalls.Delete(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting firewall: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for firewall to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceFirewall(
|
||||||
|
d *schema.ResourceData,
|
||||||
|
meta interface{}) (*compute.Firewall, error) {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Look up the network to attach the firewall to
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, d.Get("network").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the list of allowed entries
|
||||||
|
var allowed []*compute.FirewallAllowed
|
||||||
|
if v := d.Get("allow").(*schema.Set); v.Len() > 0 {
|
||||||
|
allowed = make([]*compute.FirewallAllowed, 0, v.Len())
|
||||||
|
for _, v := range v.List() {
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
|
||||||
|
var ports []string
|
||||||
|
if v := m["ports"].(*schema.Set); v.Len() > 0 {
|
||||||
|
ports = make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
ports[i] = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed = append(allowed, &compute.FirewallAllowed{
|
||||||
|
IPProtocol: m["protocol"].(string),
|
||||||
|
Ports: ports,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the list of sources
|
||||||
|
var sourceRanges, sourceTags []string
|
||||||
|
if v := d.Get("source_ranges").(*schema.Set); v.Len() > 0 {
|
||||||
|
sourceRanges = make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
sourceRanges[i] = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v := d.Get("source_tags").(*schema.Set); v.Len() > 0 {
|
||||||
|
sourceTags = make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
sourceTags[i] = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the firewall parameter
|
||||||
|
return &compute.Firewall{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
Network: network.SelfLink,
|
||||||
|
Allowed: allowed,
|
||||||
|
SourceRanges: sourceRanges,
|
||||||
|
SourceTags: sourceTags,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeFirewall_basic(t *testing.T) {
|
||||||
|
var firewall compute.Firewall
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeFirewallDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeFirewall_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeFirewallExists(
|
||||||
|
"google_compute_firewall.foobar", &firewall),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeFirewall_update(t *testing.T) {
|
||||||
|
var firewall compute.Firewall
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeFirewallDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeFirewall_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeFirewallExists(
|
||||||
|
"google_compute_firewall.foobar", &firewall),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeFirewall_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeFirewallExists(
|
||||||
|
"google_compute_firewall.foobar", &firewall),
|
||||||
|
testAccCheckComputeFirewallPorts(
|
||||||
|
&firewall, "80-255"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeFirewallDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_firewall" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Firewalls.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Firewall still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeFirewallExists(n string, firewall *compute.Firewall) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Firewalls.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Firewall not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*firewall = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeFirewallPorts(
|
||||||
|
firewall *compute.Firewall, ports string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if len(firewall.Allowed) == 0 {
|
||||||
|
return fmt.Errorf("no allowed rules")
|
||||||
|
}
|
||||||
|
|
||||||
|
if firewall.Allowed[0].Ports[0] != ports {
|
||||||
|
return fmt.Errorf("bad: %#v", firewall.Allowed[0].Ports)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeFirewall_basic = `
|
||||||
|
resource "google_compute_network" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_firewall" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
network = "${google_compute_network.foobar.name}"
|
||||||
|
source_tags = ["foo"]
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "icmp"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccComputeFirewall_update = `
|
||||||
|
resource "google_compute_network" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_firewall" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
network = "${google_compute_network.foobar.name}"
|
||||||
|
source_tags = ["foo"]
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "tcp"
|
||||||
|
ports = ["80-255"]
|
||||||
|
}
|
||||||
|
}`
|
|
@ -0,0 +1,462 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeInstance() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeInstanceCreate,
|
||||||
|
Read: resourceComputeInstanceRead,
|
||||||
|
Update: resourceComputeInstanceUpdate,
|
||||||
|
Delete: resourceComputeInstanceDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"description": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"machine_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"disk": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
// TODO(mitchellh): one of image or disk is required
|
||||||
|
|
||||||
|
"disk": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"image": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"network": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"source": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"internal_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"metadata": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"tags": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"metadata_fingerprint": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"tags_fingerprint": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Get the zone
|
||||||
|
log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string))
|
||||||
|
zone, err := config.clientCompute.Zones.Get(
|
||||||
|
config.Project, d.Get("zone").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading zone '%s': %s", d.Get("zone").(string), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the machine type
|
||||||
|
log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
|
||||||
|
machineType, err := config.clientCompute.MachineTypes.Get(
|
||||||
|
config.Project, zone.Name, d.Get("machine_type").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading machine type: %s",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the list of disks
|
||||||
|
disksCount := d.Get("disk.#").(int)
|
||||||
|
disks := make([]*compute.AttachedDisk, 0, disksCount)
|
||||||
|
for i := 0; i < disksCount; i++ {
|
||||||
|
prefix := fmt.Sprintf("disk.%d", i)
|
||||||
|
|
||||||
|
var sourceLink string
|
||||||
|
|
||||||
|
// Load up the disk for this disk if specified
|
||||||
|
if v, ok := d.GetOk(prefix + ".disk"); ok {
|
||||||
|
diskName := v.(string)
|
||||||
|
disk, err := config.clientCompute.Disks.Get(
|
||||||
|
config.Project, zone.Name, diskName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading disk '%s': %s",
|
||||||
|
diskName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceLink = disk.SelfLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load up the image for this disk if specified
|
||||||
|
if v, ok := d.GetOk(prefix + ".image"); ok {
|
||||||
|
imageName := v.(string)
|
||||||
|
image, err := readImage(config, imageName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading image '%s': %s",
|
||||||
|
imageName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceLink = image.SelfLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the disk
|
||||||
|
var disk compute.AttachedDisk
|
||||||
|
disk.Type = "PERSISTENT"
|
||||||
|
disk.Mode = "READ_WRITE"
|
||||||
|
disk.Boot = i == 0
|
||||||
|
disk.AutoDelete = true
|
||||||
|
disk.InitializeParams = &compute.AttachedDiskInitializeParams{
|
||||||
|
SourceImage: sourceLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
disks = append(disks, &disk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the list of networks
|
||||||
|
networksCount := d.Get("network.#").(int)
|
||||||
|
networks := make([]*compute.NetworkInterface, 0, networksCount)
|
||||||
|
for i := 0; i < networksCount; i++ {
|
||||||
|
prefix := fmt.Sprintf("network.%d", i)
|
||||||
|
// Load up the name of this network
|
||||||
|
networkName := d.Get(prefix + ".source").(string)
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, networkName).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Error loading network '%s': %s",
|
||||||
|
networkName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the disk
|
||||||
|
var iface compute.NetworkInterface
|
||||||
|
iface.AccessConfigs = []*compute.AccessConfig{
|
||||||
|
&compute.AccessConfig{
|
||||||
|
Type: "ONE_TO_ONE_NAT",
|
||||||
|
NatIP: d.Get(prefix + ".address").(string),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iface.Network = network.SelfLink
|
||||||
|
|
||||||
|
networks = append(networks, &iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the instance information
|
||||||
|
instance := compute.Instance{
|
||||||
|
Description: d.Get("description").(string),
|
||||||
|
Disks: disks,
|
||||||
|
MachineType: machineType.SelfLink,
|
||||||
|
Metadata: resourceInstanceMetadata(d),
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
NetworkInterfaces: networks,
|
||||||
|
Tags: resourceInstanceTags(d),
|
||||||
|
/*
|
||||||
|
ServiceAccounts: []*compute.ServiceAccount{
|
||||||
|
&compute.ServiceAccount{
|
||||||
|
Email: "default",
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/userinfo.email",
|
||||||
|
"https://www.googleapis.com/auth/compute",
|
||||||
|
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Requesting instance creation")
|
||||||
|
op, err := config.clientCompute.Instances.Insert(
|
||||||
|
config.Project, zone.Name, &instance).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the ID now
|
||||||
|
d.SetId(instance.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: zone.Name,
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Delay = 10 * time.Second
|
||||||
|
state.Timeout = 10 * time.Minute
|
||||||
|
state.MinTimeout = 2 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for instance to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeInstanceRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
instance, err := config.clientCompute.Instances.Get(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the networks
|
||||||
|
for i, iface := range instance.NetworkInterfaces {
|
||||||
|
prefix := fmt.Sprintf("network.%d", i)
|
||||||
|
d.Set(prefix+".name", iface.Name)
|
||||||
|
d.Set(prefix+".internal_address", iface.NetworkIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the metadata fingerprint if there is one.
|
||||||
|
if instance.Metadata != nil {
|
||||||
|
d.Set("metadata_fingerprint", instance.Metadata.Fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the tags fingerprint if there is one.
|
||||||
|
if instance.Tags != nil {
|
||||||
|
d.Set("tags_fingerprint", instance.Tags.Fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// If the Metadata has changed, then update that.
|
||||||
|
if d.HasChange("metadata") {
|
||||||
|
metadata := resourceInstanceMetadata(d)
|
||||||
|
op, err := config.clientCompute.Instances.SetMetadata(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id(), metadata).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating metadata: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: d.Get("zone").(string),
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Delay = 1 * time.Second
|
||||||
|
state.Timeout = 5 * time.Minute
|
||||||
|
state.MinTimeout = 2 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for metadata to update: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasChange("tags") {
|
||||||
|
tags := resourceInstanceTags(d)
|
||||||
|
op, err := config.clientCompute.Instances.SetTags(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id(), tags).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error updating tags: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: d.Get("zone").(string),
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Delay = 1 * time.Second
|
||||||
|
state.Timeout = 5 * time.Minute
|
||||||
|
state.MinTimeout = 2 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for tags to update: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeInstanceRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
op, err := config.clientCompute.Instances.Delete(
|
||||||
|
config.Project, d.Get("zone").(string), d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Zone: d.Get("zone").(string),
|
||||||
|
Type: OperationWaitZone,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Delay = 5 * time.Second
|
||||||
|
state.Timeout = 5 * time.Minute
|
||||||
|
state.MinTimeout = 2 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for instance to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata {
|
||||||
|
var metadata *compute.Metadata
|
||||||
|
if v := d.Get("metadata").([]interface{}); len(v) > 0 {
|
||||||
|
m := new(compute.Metadata)
|
||||||
|
m.Items = make([]*compute.MetadataItems, 0, len(v))
|
||||||
|
for _, v := range v {
|
||||||
|
for k, v := range v.(map[string]interface{}) {
|
||||||
|
m.Items = append(m.Items, &compute.MetadataItems{
|
||||||
|
Key: k,
|
||||||
|
Value: v.(string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the fingerprint. If the metadata has never been set before
|
||||||
|
// then this will just be blank.
|
||||||
|
m.Fingerprint = d.Get("metadata_fingerprint").(string)
|
||||||
|
|
||||||
|
metadata = m
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
|
||||||
|
// Calculate the tags
|
||||||
|
var tags *compute.Tags
|
||||||
|
if v := d.Get("tags"); v != nil {
|
||||||
|
vs := v.(*schema.Set).List()
|
||||||
|
tags = new(compute.Tags)
|
||||||
|
tags.Items = make([]string, len(vs))
|
||||||
|
for i, v := range v.(*schema.Set).List() {
|
||||||
|
tags.Items[i] = v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
tags.Fingerprint = d.Get("tags_fingerprint").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeInstance_basic(t *testing.T) {
|
||||||
|
var instance compute.Instance
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceTag(&instance, "foo"),
|
||||||
|
testAccCheckComputeInstanceMetadata(&instance, "foo", "bar"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeInstance_IP(t *testing.T) {
|
||||||
|
var instance compute.Instance
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_ip,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceNetwork(&instance),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccComputeInstance_update(t *testing.T) {
|
||||||
|
var instance compute.Instance
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeInstanceDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeInstance_update,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeInstanceExists(
|
||||||
|
"google_compute_instance.foobar", &instance),
|
||||||
|
testAccCheckComputeInstanceMetadata(
|
||||||
|
&instance, "bar", "baz"),
|
||||||
|
testAccCheckComputeInstanceTag(&instance, "baz"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_instance" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Instances.Get(
|
||||||
|
config.Project, rs.Attributes["zone"], rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Instance still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceExists(n string, instance *compute.Instance) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Instances.Get(
|
||||||
|
config.Project, rs.Attributes["zone"], rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Instance not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*instance = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceMetadata(
|
||||||
|
instance *compute.Instance,
|
||||||
|
k string, v string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if instance.Metadata == nil {
|
||||||
|
return fmt.Errorf("no metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range instance.Metadata.Items {
|
||||||
|
if k != item.Key {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v == item.Value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("bad value for %s: %s", k, item.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("metadata not found: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceNetwork(instance *compute.Instance) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
for _, i := range instance.NetworkInterfaces {
|
||||||
|
for _, c := range i.AccessConfigs {
|
||||||
|
if c.NatIP == "" {
|
||||||
|
return fmt.Errorf("no NAT IP")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeInstanceTag(instance *compute.Instance, n string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
if instance.Tags == nil {
|
||||||
|
return fmt.Errorf("no tags")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range instance.Tags.Items {
|
||||||
|
if k == n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("tag not found: %s", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeInstance_basic = `
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
tags = ["foo", "bar"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
source = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccComputeInstance_update = `
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
tags = ["baz"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
source = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
bar = "baz"
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccComputeInstance_ip = `
|
||||||
|
resource "google_compute_address" "foo" {
|
||||||
|
name = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
tags = ["foo", "bar"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
source = "default"
|
||||||
|
address = "${google_compute_address.foo.address}"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}`
|
|
@ -0,0 +1,137 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeNetwork() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeNetworkCreate,
|
||||||
|
Read: resourceComputeNetworkRead,
|
||||||
|
Delete: resourceComputeNetworkDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"ipv4_range": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"gateway_ipv4": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeNetworkCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Build the network parameter
|
||||||
|
network := &compute.Network{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
IPv4Range: d.Get("ipv4_range").(string),
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Network insert request: %#v", network)
|
||||||
|
op, err := config.clientCompute.Networks.Insert(
|
||||||
|
config.Project, network).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(network.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for network to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeNetworkRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeNetworkRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("gateway_ipv4", network.GatewayIPv4)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeNetworkDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the network
|
||||||
|
op, err := config.clientCompute.Networks.Delete(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for network to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeNetwork_basic(t *testing.T) {
|
||||||
|
var network compute.Network
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeNetworkDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeNetwork_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeNetworkExists(
|
||||||
|
"google_compute_network.foobar", &network),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeNetworkDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_network" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Network still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeNetworkExists(n string, network *compute.Network) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Network not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*network = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeNetwork_basic = `
|
||||||
|
resource "google_compute_network" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}`
|
|
@ -0,0 +1,234 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"code.google.com/p/google-api-go-client/googleapi"
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceComputeRoute() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceComputeRouteCreate,
|
||||||
|
Read: resourceComputeRouteRead,
|
||||||
|
Delete: resourceComputeRouteDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"dest_range": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"network": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"next_hop_ip": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"next_hop_instance": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"next_hop_instance_zone": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"next_hop_gateway": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"next_hop_network": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"priority": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"tags": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
Set: func(v interface{}) int {
|
||||||
|
return hashcode.String(v.(string))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeRouteCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Look up the network to attach the route to
|
||||||
|
network, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, d.Get("network").(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next hop data
|
||||||
|
var nextHopInstance, nextHopIp, nextHopNetwork, nextHopGateway string
|
||||||
|
if v, ok := d.GetOk("next_hop_ip"); ok {
|
||||||
|
nextHopIp = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("next_hop_gateway"); ok {
|
||||||
|
nextHopGateway = v.(string)
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("next_hop_instance"); ok {
|
||||||
|
nextInstance, err := config.clientCompute.Instances.Get(
|
||||||
|
config.Project,
|
||||||
|
d.Get("next_hop_instance_zone").(string),
|
||||||
|
v.(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextHopInstance = nextInstance.SelfLink
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("next_hop_network"); ok {
|
||||||
|
nextNetwork, err := config.clientCompute.Networks.Get(
|
||||||
|
config.Project, v.(string)).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading network: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextHopNetwork = nextNetwork.SelfLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
var tags []string
|
||||||
|
if v := d.Get("tags").(*schema.Set); v.Len() > 0 {
|
||||||
|
tags = make([]string, v.Len())
|
||||||
|
for i, v := range v.List() {
|
||||||
|
tags[i] = v.(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the route parameter
|
||||||
|
route := &compute.Route{
|
||||||
|
Name: d.Get("name").(string),
|
||||||
|
DestRange: d.Get("dest_range").(string),
|
||||||
|
Network: network.SelfLink,
|
||||||
|
NextHopInstance: nextHopInstance,
|
||||||
|
NextHopIp: nextHopIp,
|
||||||
|
NextHopNetwork: nextHopNetwork,
|
||||||
|
NextHopGateway: nextHopGateway,
|
||||||
|
Priority: int64(d.Get("priority").(int)),
|
||||||
|
Tags: tags,
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Route insert request: %#v", route)
|
||||||
|
op, err := config.clientCompute.Routes.Insert(
|
||||||
|
config.Project, route).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating route: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It probably maybe worked, so store the ID now
|
||||||
|
d.SetId(route.Name)
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for route to create: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// The resource didn't actually create
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceComputeRouteRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeRouteRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Routes.Get(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
|
||||||
|
// The resource doesn't exist anymore
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Error reading route: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceComputeRouteDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
config := meta.(*Config)
|
||||||
|
|
||||||
|
// Delete the route
|
||||||
|
op, err := config.clientCompute.Routes.Delete(
|
||||||
|
config.Project, d.Id()).Do()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting route: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the operation to complete
|
||||||
|
w := &OperationWaiter{
|
||||||
|
Service: config.clientCompute,
|
||||||
|
Op: op,
|
||||||
|
Project: config.Project,
|
||||||
|
Type: OperationWaitGlobal,
|
||||||
|
}
|
||||||
|
state := w.Conf()
|
||||||
|
state.Timeout = 2 * time.Minute
|
||||||
|
state.MinTimeout = 1 * time.Second
|
||||||
|
opRaw, err := state.WaitForState()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for route to delete: %s", err)
|
||||||
|
}
|
||||||
|
op = opRaw.(*compute.Operation)
|
||||||
|
if op.Error != nil {
|
||||||
|
// Return the error
|
||||||
|
return OperationError(*op.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.google.com/p/google-api-go-client/compute/v1"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccComputeRoute_basic(t *testing.T) {
|
||||||
|
var route compute.Route
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckComputeRouteDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccComputeRoute_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckComputeRouteExists(
|
||||||
|
"google_compute_route.foobar", &route),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeRouteDestroy(s *terraform.State) error {
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "google_compute_route" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := config.clientCompute.Routes.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Route still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckComputeRouteExists(n string, route *compute.Route) 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 ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
|
found, err := config.clientCompute.Routes.Get(
|
||||||
|
config.Project, rs.ID).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if found.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Route not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*route = *found
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccComputeRoute_basic = `
|
||||||
|
resource "google_compute_network" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_route" "foobar" {
|
||||||
|
name = "terraform-test"
|
||||||
|
dest_range = "15.0.0.0/24"
|
||||||
|
network = "${google_compute_network.foobar.name}"
|
||||||
|
next_hop_ip = "10.0.1.5"
|
||||||
|
priority = 100
|
||||||
|
}`
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"private_key_id": "foo",
|
||||||
|
"private_key": "bar",
|
||||||
|
"client_email": "foo@bar.com",
|
||||||
|
"client_id": "id@foo.com",
|
||||||
|
"type": "service_account"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Provider: Google Cloud"
|
||||||
|
sidebar_current: "docs-google-index"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Google Cloud Provider
|
||||||
|
|
||||||
|
The Google Cloud provider is used to interact with
|
||||||
|
[Google Cloud services](https://cloud.google.com/). The provider needs
|
||||||
|
to be configured with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the Google Cloud provider
|
||||||
|
provider "google" {
|
||||||
|
account_file = "account.json"
|
||||||
|
client_secrets_file = "client_secrets.json"
|
||||||
|
project = "my-gce-project"
|
||||||
|
region = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a new instance
|
||||||
|
resource "google_compute_instance" "default" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
The following keys can be used to configure the provider.
|
||||||
|
|
||||||
|
* `account_file` - (Required) Path to the JSON file used to describe
|
||||||
|
your account credentials, downloaded from Google Cloud Console. More
|
||||||
|
details on retrieving this file are below.
|
||||||
|
|
||||||
|
* `client_secrets_file` - (Required) Path to the JSON file containing
|
||||||
|
the secrets for your account, downloaded from Google Cloud Console.
|
||||||
|
More details on retrieving this file are below.
|
||||||
|
|
||||||
|
* `project` - (Required) The name of the project to apply any resources to.
|
||||||
|
|
||||||
|
* `region` - (Required) The region to operate under.
|
||||||
|
|
||||||
|
## Authentication JSON Files
|
||||||
|
|
||||||
|
Authenticating with Google Cloud services requires two separate JSON
|
||||||
|
files: one which we call the _account file_ and the _client secrets file_.
|
||||||
|
|
||||||
|
Both of these files are downloaded directly from the
|
||||||
|
[Google Developers Console](https://console.developers.google.com). To make
|
||||||
|
the process more straightforwarded, it is documented here.
|
||||||
|
|
||||||
|
1. Log into the [Google Developers Console](https://console.developers.google.com)
|
||||||
|
and select a project.
|
||||||
|
|
||||||
|
2. Under the "APIs & Auth" section, click "Credentials."
|
||||||
|
|
||||||
|
3. Click the "Download JSON" button under the "Compute Engine and App Engine"
|
||||||
|
account in the OAuth section. The file should start with "client\_secrets".
|
||||||
|
This is your _client secrets file_.
|
||||||
|
|
||||||
|
4. Create a new OAuth client ID and select "Service Account" as the type
|
||||||
|
of account. Once created, a JSON file should be downloaded. This is your
|
||||||
|
_account file_.
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_address"
|
||||||
|
sidebar_current: "docs-google-resource-address"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_address
|
||||||
|
|
||||||
|
Creates a static IP address resource for Google Compute Engine.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_address" "default" {
|
||||||
|
name = "test-address"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `address` - The IP address that was allocated.
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_disk"
|
||||||
|
sidebar_current: "docs-google-resource-disk"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_disk
|
||||||
|
|
||||||
|
Creates a new persistent disk within GCE, based on another disk.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_disk" "default" {
|
||||||
|
name = "test-disk"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
image = "debian7-wheezy"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `zone` - (Required) The zone where this disk will be available.
|
||||||
|
|
||||||
|
* `image` - (Optional) The machine image to base this disk off of.
|
||||||
|
|
||||||
|
* `size` - (Optional) The size of the image in gigabytes. If not specified,
|
||||||
|
it will inherit the size of its base image.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `zone` - The zone where the resource is located.
|
||||||
|
* `image` - The name of the image the disk is based off of.
|
||||||
|
* `size` - The size of the disk in gigabytes.
|
|
@ -0,0 +1,62 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_firewall"
|
||||||
|
sidebar_current: "docs-google-resource-firewall"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_firewall
|
||||||
|
|
||||||
|
Manages a firewall resource within GCE.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_firewall" "default" {
|
||||||
|
name = "test"
|
||||||
|
network = "${google_compute_network.other.name}"
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "icmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
allow {
|
||||||
|
protocol = "tcp"
|
||||||
|
ports = ["80", "8080", "1000-2000"]
|
||||||
|
}
|
||||||
|
|
||||||
|
source_tags = ["web"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `network` - (Required) The name of the network to attach this firewall to.
|
||||||
|
|
||||||
|
* `allow` - (Required) Can be specified multiple times for each allow
|
||||||
|
rule. Each allow block supports fields documented below.
|
||||||
|
|
||||||
|
* `source_ranges` - (Optional) A list of source CIDR ranges that this
|
||||||
|
firewall applies to.
|
||||||
|
|
||||||
|
* `source_tags` - (Optional) A list of tags that this firewall applies to.
|
||||||
|
|
||||||
|
The `allow` block supports:
|
||||||
|
|
||||||
|
* `protocol` - (Required) The name of the protocol to allow.
|
||||||
|
|
||||||
|
* `ports` - (Optional) List of ports and/or port ranges to allow. This can
|
||||||
|
only be specified if the protocol is TCP or UDP.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `network` - The network that this resource is attached to.
|
||||||
|
* `source_ranges` - The CIDR block ranges this firewall applies to.
|
||||||
|
* `source_tags` - The tags that this firewall applies to.
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_instance"
|
||||||
|
sidebar_current: "docs-google-resource-instance"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_instance
|
||||||
|
|
||||||
|
Manages a VM instance resource within GCE.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_instance" "default" {
|
||||||
|
name = "test"
|
||||||
|
machine_type = "n1-standard-1"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
tags = ["foo", "bar"]
|
||||||
|
|
||||||
|
disk {
|
||||||
|
image = "debian-7-wheezy-v20140814"
|
||||||
|
}
|
||||||
|
|
||||||
|
network {
|
||||||
|
source = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `description` - (Optional) A brief description of this resource.
|
||||||
|
|
||||||
|
* `machine_type` - (Required) The machine type to create.
|
||||||
|
|
||||||
|
* `zone` - (Required) The zone that the machine should be created in.
|
||||||
|
|
||||||
|
* `disk` - (Required) Disks to attach to the instance. This can be specified
|
||||||
|
multiple times for multiple disks. Structure is documented below.
|
||||||
|
|
||||||
|
* `metadata` - (Optional) Metadata key/value pairs to make available from
|
||||||
|
within the instance.
|
||||||
|
|
||||||
|
* `network` - (Required) Networks to attach to the instance. This can be
|
||||||
|
specified multiple times for multiple networks. Structure is documented
|
||||||
|
below.
|
||||||
|
|
||||||
|
* `tags` - (Optional) Tags to attach to the instance.
|
||||||
|
|
||||||
|
The `disk` block supports:
|
||||||
|
|
||||||
|
* `disk` - (Required if image not set) The name of the disk (such as
|
||||||
|
those managed by `google_compute_disk`) to attach.
|
||||||
|
|
||||||
|
* `image` - (Required if disk not set) The name of the image to base
|
||||||
|
this disk off of.
|
||||||
|
|
||||||
|
The `network` block supports:
|
||||||
|
|
||||||
|
* `source` - (Required) The name of the network to attach this interface to.
|
||||||
|
|
||||||
|
* `address` - (Optional) The IP address of a reserved IP address to assign
|
||||||
|
to this interface.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `machine_type` - The type of machine.
|
||||||
|
* `zone` - The zone the machine lives in.
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_network"
|
||||||
|
sidebar_current: "docs-google-resource-network"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_network
|
||||||
|
|
||||||
|
Manages a network within GCE.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_network" "default" {
|
||||||
|
name = "test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `ipv4_range` - (Required) The IPv4 address range that machines in this
|
||||||
|
network are assigned to, represented as a CIDR block.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `ipv4_range` - The CIDR block of this network.
|
||||||
|
* `gateway_ipv4` - The IPv4 address of the gateway.
|
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
layout: "google"
|
||||||
|
page_title: "Google: google_compute_route"
|
||||||
|
sidebar_current: "docs-google-resource-route"
|
||||||
|
---
|
||||||
|
|
||||||
|
# google\_compute\_route
|
||||||
|
|
||||||
|
Manages a network route within GCE.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "google_compute_network" "foobar" {
|
||||||
|
name = "test"
|
||||||
|
ipv4_range = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_route" "foobar" {
|
||||||
|
name = "test"
|
||||||
|
dest_range = "15.0.0.0/24"
|
||||||
|
network = "${google_compute_network.foobar.name}"
|
||||||
|
next_hop_ip = "10.0.1.5"
|
||||||
|
priority = 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) A unique name for the resource, required by GCE.
|
||||||
|
Changing this forces a new resource to be created.
|
||||||
|
|
||||||
|
* `dest_range` - (Required) The destination IPv4 address range that this
|
||||||
|
route applies to.
|
||||||
|
|
||||||
|
* `network` - (Required) The name of the network to attach this route to.
|
||||||
|
|
||||||
|
* `next_hop_ip` - (Optional) The IP address of the next hop if this route
|
||||||
|
is matched.
|
||||||
|
|
||||||
|
* `next_hop_instance` - (Optional) The name of the VM instance to route to
|
||||||
|
if this route is matched.
|
||||||
|
|
||||||
|
* `next_hop_instance_zone` - (Optional) The zone of the instance specified
|
||||||
|
in `next_hop_instance`.
|
||||||
|
|
||||||
|
* `next_hop_gateway` - (Optional) The name of the internet gateway to route
|
||||||
|
to if this route is matched.
|
||||||
|
|
||||||
|
* `next_hop_network` - (Optional) The name of the network to route to if this
|
||||||
|
route is matched.
|
||||||
|
|
||||||
|
* `priority` - (Required) The priority of this route, used to break ties.
|
||||||
|
|
||||||
|
* `tags` - (Optional) The tags that this route applies to.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `name` - The name of the resource.
|
||||||
|
* `dest_range` - The detination CIDR block of this route.
|
||||||
|
* `network` - The name of the network of this route.
|
||||||
|
* `next_hop_ip` - The IP address of the next hop, if available.
|
||||||
|
* `next_hop_instance` - The name of the instance of the next hop, if available.
|
||||||
|
* `next_hop_instance_zone` - The zone of the next hop instance, if available.
|
||||||
|
* `next_hop_gateway` - The name of the next hop gateway, if available.
|
||||||
|
* `next_hop_network` - The name of the next hop network, if available.
|
||||||
|
* `priority` - The priority of this route.
|
||||||
|
* `tags` - The tags this route applies to.
|
|
@ -96,6 +96,10 @@
|
||||||
<a href="/docs/providers/dnsimple/index.html">DNSimple</a>
|
<a href="/docs/providers/dnsimple/index.html">DNSimple</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-providers-google") %>>
|
||||||
|
<a href="/docs/providers/google/index.html">Google Cloud</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-providers-heroku") %>>
|
<li<%= sidebar_current("docs-providers-heroku") %>>
|
||||||
<a href="/docs/providers/heroku/index.html">Heroku</a>
|
<a href="/docs/providers/heroku/index.html">Heroku</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-index") %>>
|
||||||
|
<a href="/docs/providers/google/index.html">google Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource") %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-google-resource-address") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_address.html">google_compute_address</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource-disk") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_disk.html">google_compute_disk</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource-firewall") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_firewall.html">google_compute_firewall</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource-instance") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_instance.html">google_compute_instance</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource-network") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_network.html">google_compute_network</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-google-resource-route") %>>
|
||||||
|
<a href="/docs/providers/google/r/compute_route.html">google_compute_route</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
|
@ -9,6 +9,7 @@ body.page-sub{
|
||||||
body.layout-consul,
|
body.layout-consul,
|
||||||
body.layout-dnsimple,
|
body.layout-dnsimple,
|
||||||
body.layout-cloudflare,
|
body.layout-cloudflare,
|
||||||
|
body.layout-google,
|
||||||
body.layout-heroku,
|
body.layout-heroku,
|
||||||
body.layout-mailgun,
|
body.layout-mailgun,
|
||||||
body.layout-digitalocean,
|
body.layout-digitalocean,
|
||||||
|
|
|
@ -1617,6 +1617,7 @@ body.page-sub {
|
||||||
body.layout-consul,
|
body.layout-consul,
|
||||||
body.layout-dnsimple,
|
body.layout-dnsimple,
|
||||||
body.layout-cloudflare,
|
body.layout-cloudflare,
|
||||||
|
body.layout-google,
|
||||||
body.layout-heroku,
|
body.layout-heroku,
|
||||||
body.layout-mailgun,
|
body.layout-mailgun,
|
||||||
body.layout-digitalocean,
|
body.layout-digitalocean,
|
||||||
|
@ -1630,6 +1631,7 @@ body.layout-intro {
|
||||||
body.layout-consul > .container .col-md-8[role=main],
|
body.layout-consul > .container .col-md-8[role=main],
|
||||||
body.layout-dnsimple > .container .col-md-8[role=main],
|
body.layout-dnsimple > .container .col-md-8[role=main],
|
||||||
body.layout-cloudflare > .container .col-md-8[role=main],
|
body.layout-cloudflare > .container .col-md-8[role=main],
|
||||||
|
body.layout-google > .container .col-md-8[role=main],
|
||||||
body.layout-heroku > .container .col-md-8[role=main],
|
body.layout-heroku > .container .col-md-8[role=main],
|
||||||
body.layout-mailgun > .container .col-md-8[role=main],
|
body.layout-mailgun > .container .col-md-8[role=main],
|
||||||
body.layout-digitalocean > .container .col-md-8[role=main],
|
body.layout-digitalocean > .container .col-md-8[role=main],
|
||||||
|
@ -1644,6 +1646,7 @@ body.layout-intro > .container .col-md-8[role=main] {
|
||||||
body.layout-consul > .container .col-md-8[role=main] > div,
|
body.layout-consul > .container .col-md-8[role=main] > div,
|
||||||
body.layout-dnsimple > .container .col-md-8[role=main] > div,
|
body.layout-dnsimple > .container .col-md-8[role=main] > div,
|
||||||
body.layout-cloudflare > .container .col-md-8[role=main] > div,
|
body.layout-cloudflare > .container .col-md-8[role=main] > div,
|
||||||
|
body.layout-google > .container .col-md-8[role=main] > div,
|
||||||
body.layout-heroku > .container .col-md-8[role=main] > div,
|
body.layout-heroku > .container .col-md-8[role=main] > div,
|
||||||
body.layout-mailgun > .container .col-md-8[role=main] > div,
|
body.layout-mailgun > .container .col-md-8[role=main] > div,
|
||||||
body.layout-digitalocean > .container .col-md-8[role=main] > div,
|
body.layout-digitalocean > .container .col-md-8[role=main] > div,
|
||||||
|
|
Loading…
Reference in New Issue