Add generated BackendBucket resource + transport.go (#1064)

* Add generated BackendBucket resource

* add ForceNew back to name

* Add log statements back
This commit is contained in:
Dana Hoffman 2018-02-08 17:58:20 -08:00 committed by GitHub
parent 5e907c7186
commit 9900e6a4c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 421 additions and 74 deletions

View File

@ -1,3 +1,17 @@
// ----------------------------------------------------------------------------
//
// *** AUTO GENERATED CODE *** AUTO GENERATED CODE ***
//
// ----------------------------------------------------------------------------
//
// This file is automatically generated and manual changes will be
// clobbered when the file is regenerated.
//
// Please read more about how to change this file in
// .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------
package google
import (
@ -5,7 +19,7 @@ import (
"log"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/compute/v1"
compute "google.golang.org/api/compute/v1"
)
func resourceComputeBackendBucket() *schema.Resource {
@ -16,40 +30,38 @@ func resourceComputeBackendBucket() *schema.Resource {
Delete: resourceComputeBackendBucketDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
State: resourceComputeBackendBucketImport,
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateGCPName,
},
"bucket_name": &schema.Schema{
"bucket_name": {
Type: schema.TypeString,
Required: true,
},
"description": &schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateRegexp(`^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`),
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"enable_cdn": &schema.Schema{
"enable_cdn": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"creation_timestamp": {
Type: schema.TypeString,
Computed: true,
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"self_link": &schema.Schema{
Type: schema.TypeString,
Computed: true,
@ -61,38 +73,43 @@ func resourceComputeBackendBucket() *schema.Resource {
func resourceComputeBackendBucketCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
bucket := compute.BackendBucket{
Name: d.Get("name").(string),
BucketName: d.Get("bucket_name").(string),
}
if v, ok := d.GetOk("description"); ok {
bucket.Description = v.(string)
}
if v, ok := d.GetOk("enable_cdn"); ok {
bucket.EnableCdn = v.(bool)
}
project, err := getProject(d, config)
if err != nil {
return err
}
log.Printf("[DEBUG] Creating new Backend Bucket: %#v", bucket)
op, err := config.clientCompute.BackendBuckets.Insert(
project, &bucket).Do()
if err != nil {
return fmt.Errorf("Error creating backend bucket: %s", err)
obj := map[string]interface{}{
"bucketName": expandComputeBackendBucketBucketName(d.Get("bucket_name")),
"description": expandComputeBackendBucketDescription(d.Get("description")),
"enableCdn": expandComputeBackendBucketEnableCdn(d.Get("enable_cdn")),
"name": expandComputeBackendBucketName(d.Get("name")),
}
log.Printf("[DEBUG] Waiting for new backend bucket, operation: %#v", op)
url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/global/backendBuckets")
if err != nil {
return err
}
log.Printf("[DEBUG] Creating new BackendBucket: %#v", obj)
res, err := Post(config, url, obj)
if err != nil {
return fmt.Errorf("Error creating BackendBucket: %s", err)
}
// Store the ID now
d.SetId(bucket.Name)
id, err := replaceVars(d, config, "{{name}}")
if err != nil {
return fmt.Errorf("Error constructing id: %s", err)
}
d.SetId(id)
// Wait for the operation to complete
waitErr := computeOperationWait(config.clientCompute, op, project, "Creating Backend Bucket")
op := &compute.Operation{}
err = Convert(res, op)
if err != nil {
return err
}
waitErr := computeOperationWait(config.clientCompute, op, project, "Creating BackendBucket")
if waitErr != nil {
// The resource didn't actually create
d.SetId("")
@ -110,18 +127,23 @@ func resourceComputeBackendBucketRead(d *schema.ResourceData, meta interface{})
return err
}
bucket, err := config.clientCompute.BackendBuckets.Get(
project, d.Id()).Do()
url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/global/backendBuckets/{{name}}")
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Backend Bucket %q", d.Get("name").(string)))
return err
}
d.Set("name", bucket.Name)
d.Set("bucket_name", bucket.BucketName)
d.Set("description", bucket.Description)
d.Set("enable_cdn", bucket.EnableCdn)
res, err := Get(config, url)
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("ComputeBackendBucket %q", d.Id()))
}
d.Set("bucket_name", flattenComputeBackendBucketBucketName(res["bucketName"]))
d.Set("creation_timestamp", flattenComputeBackendBucketCreationTimestamp(res["creationTimestamp"]))
d.Set("description", flattenComputeBackendBucketDescription(res["description"]))
d.Set("enable_cdn", flattenComputeBackendBucketEnableCdn(res["enableCdn"]))
d.Set("name", flattenComputeBackendBucketName(res["name"]))
d.Set("self_link", res["selfLink"])
d.Set("project", project)
d.Set("self_link", bucket.SelfLink)
return nil
}
@ -134,30 +156,31 @@ func resourceComputeBackendBucketUpdate(d *schema.ResourceData, meta interface{}
return err
}
bucket := compute.BackendBucket{
Name: d.Get("name").(string),
BucketName: d.Get("bucket_name").(string),
obj := map[string]interface{}{
"bucketName": expandComputeBackendBucketBucketName(d.Get("bucket_name")),
"description": expandComputeBackendBucketDescription(d.Get("description")),
"enableCdn": expandComputeBackendBucketEnableCdn(d.Get("enable_cdn")),
"name": expandComputeBackendBucketName(d.Get("name")),
}
// Optional things
if v, ok := d.GetOk("description"); ok {
bucket.Description = v.(string)
}
if v, ok := d.GetOk("enable_cdn"); ok {
bucket.EnableCdn = v.(bool)
}
log.Printf("[DEBUG] Updating existing Backend Bucket %q: %#v", d.Id(), bucket)
op, err := config.clientCompute.BackendBuckets.Update(
project, d.Id(), &bucket).Do()
url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/global/backendBuckets/{{name}}")
if err != nil {
return fmt.Errorf("Error updating backend bucket: %s", err)
return err
}
d.SetId(bucket.Name)
log.Printf("[DEBUG] Updating BackendBucket %q: %#v", d.Id(), obj)
res, err := Put(config, url, obj)
if err != nil {
return fmt.Errorf("Error updating BackendBucket %q: %s", d.Id(), err)
}
err = computeOperationWait(config.clientCompute, op, project, "Updating Backend Bucket")
op := &compute.Operation{}
err = Convert(res, op)
if err != nil {
return err
}
err = computeOperationWait(config.clientCompute, op, project, "Updating BackendBucket")
if err != nil {
return err
}
@ -173,18 +196,68 @@ func resourceComputeBackendBucketDelete(d *schema.ResourceData, meta interface{}
return err
}
log.Printf("[DEBUG] Deleting backend bucket %s", d.Id())
op, err := config.clientCompute.BackendBuckets.Delete(
project, d.Id()).Do()
if err != nil {
return fmt.Errorf("Error deleting backend bucket: %s", err)
}
err = computeOperationWait(config.clientCompute, op, project, "Deleting Backend Bucket")
url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/global/backendBuckets/{{name}}")
if err != nil {
return err
}
log.Printf("[DEBUG] Deleting BackendBucket %q", d.Id())
res, err := Delete(config, url)
if err != nil {
return fmt.Errorf("Error deleting BackendBucket %q: %s", d.Id(), err)
}
op := &compute.Operation{}
err = Convert(res, op)
if err != nil {
return err
}
err = computeOperationWait(config.clientCompute, op, project, "Deleting BackendBucket")
if err != nil {
return err
}
d.SetId("")
return nil
}
func resourceComputeBackendBucketImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
d.Set("name", d.Id())
return []*schema.ResourceData{d}, nil
}
func flattenComputeBackendBucketBucketName(v interface{}) interface{} {
return v
}
func flattenComputeBackendBucketCreationTimestamp(v interface{}) interface{} {
return v
}
func flattenComputeBackendBucketDescription(v interface{}) interface{} {
return v
}
func flattenComputeBackendBucketEnableCdn(v interface{}) interface{} {
return v
}
func flattenComputeBackendBucketName(v interface{}) interface{} {
return v
}
func expandComputeBackendBucketBucketName(v interface{}) interface{} {
return v
}
func expandComputeBackendBucketDescription(v interface{}) interface{} {
return v
}
func expandComputeBackendBucketEnableCdn(v interface{}) interface{} {
return v
}
func expandComputeBackendBucketName(v interface{}) interface{} {
return v
}

176
google/transport.go Normal file
View File

@ -0,0 +1,176 @@
package google
import (
"bytes"
"encoding/json"
"net/http"
"regexp"
"strings"
"reflect"
"google.golang.org/api/googleapi"
)
type serializableBody struct {
body map[string]interface{}
// ForceSendFields is a list of field names (e.g. "UtilizationTarget")
// to unconditionally include in API requests. By default, fields with
// empty values are omitted from API requests. However, any non-pointer,
// non-interface field appearing in ForceSendFields will be sent to the
// server regardless of whether the field is empty or not. This may be
// used to include empty fields in Patch requests.
ForceSendFields []string
// NullFields is a list of field names (e.g. "UtilizationTarget") to
// include in API requests with the JSON null value. By default, fields
// with empty values are omitted from API requests. However, any field
// with an empty value appearing in NullFields will be sent to the
// server as null. It is an error if a field in this list has a
// non-empty value. This may be used to include null fields in Patch
// requests.
NullFields []string
}
// MarshalJSON returns a JSON encoding of schema containing only selected fields.
// A field is selected if any of the following is true:
// * it has a non-empty value
// * its field name is present in forceSendFields and it is not a nil pointer or nil interface
// * its field name is present in nullFields.
func (b *serializableBody) MarshalJSON() ([]byte, error) {
// By default, all fields in a map are added to the json output
// This changes that to remove the entry with an empty value.
// This mimics the "omitempty" behavior.
// The "omitempty" option specifies that the field should be omitted
// from the encoding if the field has an empty value, defined as
// false, 0, a nil pointer, a nil interface value, and any empty array,
// slice, map, or string.
// TODO: Add support for ForceSendFields and NullFields.
for k, v := range b.body {
if isEmptyValue(reflect.ValueOf(v)) {
delete(b.body, k)
}
}
return json.Marshal(b.body)
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func Post(config *Config, url string, body map[string]interface{}) (map[string]interface{}, error) {
return sendRequest(config, "POST", url, body)
}
func Get(config *Config, url string) (map[string]interface{}, error) {
return sendRequest(config, "GET", url, nil)
}
func Put(config *Config, url string, body map[string]interface{}) (map[string]interface{}, error) {
return sendRequest(config, "PUT", url, body)
}
func Delete(config *Config, url string) (map[string]interface{}, error) {
return sendRequest(config, "DELETE", url, nil)
}
func sendRequest(config *Config, method, url string, body map[string]interface{}) (map[string]interface{}, error) {
reqHeaders := make(http.Header)
reqHeaders.Set("User-Agent", config.userAgent)
reqHeaders.Set("Content-Type", "application/json")
var buf bytes.Buffer
if body != nil {
err := json.NewEncoder(&buf).Encode(&serializableBody{
body: body})
if err != nil {
return nil, err
}
}
req, err := http.NewRequest(method, url+"?alt=json", &buf)
if err != nil {
return nil, err
}
req.Header = reqHeaders
res, err := config.client.Do(req)
if err != nil {
return nil, err
}
defer googleapi.CloseBody(res)
if err := googleapi.CheckResponse(res); err != nil {
return nil, err
}
result := make(map[string]interface{})
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func replaceVars(d TerraformResourceData, config *Config, linkTmpl string) (string, error) {
re := regexp.MustCompile("{{([[:word:]]+)}}")
var project, region, zone string
var err error
if strings.Contains(linkTmpl, "{{project}}") {
project, err = getProject(d, config)
if err != nil {
return "", err
}
}
if strings.Contains(linkTmpl, "{{region}}") {
region, err = getRegion(d, config)
if err != nil {
return "", err
}
}
if strings.Contains(linkTmpl, "{{zone}}") {
zone, err = getZone(d, config)
if err != nil {
return "", err
}
}
replaceFunc := func(s string) string {
m := re.FindStringSubmatch(s)[1]
if m == "project" {
return project
}
if m == "region" {
return region
}
if m == "zone" {
return zone
}
v, ok := d.GetOk(m)
if ok {
return v.(string)
}
return ""
}
return re.ReplaceAllStringFunc(linkTmpl, replaceFunc), nil
}

96
google/transport_test.go Normal file
View File

@ -0,0 +1,96 @@
package google
import (
"testing"
)
func TestReplaceVars(t *testing.T) {
cases := map[string]struct {
Template string
SchemaValues map[string]interface{}
Config *Config
Expected string
ExpectedError bool
}{
"unspecified project fails": {
Template: "projects/{{project}}/global/images",
ExpectedError: true,
},
"unspecified region fails": {
Template: "projects/{{project}}/regions/{{region}}/subnetworks",
Config: &Config{
Project: "default-project",
},
ExpectedError: true,
},
"unspecified zone fails": {
Template: "projects/{{project}}/zones/{{zone}}/instances",
Config: &Config{
Project: "default-project",
},
ExpectedError: true,
},
"regional with default values": {
Template: "projects/{{project}}/regions/{{region}}/subnetworks",
Config: &Config{
Project: "default-project",
Region: "default-region",
},
Expected: "projects/default-project/regions/default-region/subnetworks",
},
"zonal with default values": {
Template: "projects/{{project}}/zones/{{zone}}/instances",
Config: &Config{
Project: "default-project",
Zone: "default-zone",
},
Expected: "projects/default-project/zones/default-zone/instances",
},
"regional schema values": {
Template: "projects/{{project}}/regions/{{region}}/subnetworks/{{name}}",
SchemaValues: map[string]interface{}{
"project": "project1",
"region": "region1",
"name": "subnetwork1",
},
Expected: "projects/project1/regions/region1/subnetworks/subnetwork1",
},
"zonal schema values": {
Template: "projects/{{project}}/zones/{{zone}}/instances/{{name}}",
SchemaValues: map[string]interface{}{
"project": "project1",
"zone": "zone1",
"name": "instance1",
},
Expected: "projects/project1/zones/zone1/instances/instance1",
},
}
for tn, tc := range cases {
d := &ResourceDataMock{
FieldsInSchema: tc.SchemaValues,
}
config := tc.Config
if config == nil {
config = &Config{}
}
v, err := replaceVars(d, config, tc.Template)
if err != nil {
if !tc.ExpectedError {
t.Errorf("bad: %s; unexpected error %s", tn, err)
}
continue
}
if tc.ExpectedError {
t.Errorf("bad: %s; expected error", tn)
}
if v != tc.Expected {
t.Errorf("bad: %s; expected %q, got %q", tn, tc.Expected, v)
}
}
}

View File

@ -52,6 +52,8 @@ The following arguments are supported:
In addition to the arguments listed above, the following computed attributes are exported:
* `creation_timestamp` - Creation timestamp in RFC3339 text format.
* `self_link` - The URI of the created resource.
## Import