Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add billing subaccount #2640

Merged
merged 15 commits into from
Dec 15, 2020
147 changes: 147 additions & 0 deletions third_party/terraform/resources/resource_google_billing_subaccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package google

import (
"fmt"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"google.golang.org/api/cloudbilling/v1"
)

func resourceBillingSubaccount() *schema.Resource {
return &schema.Resource{
Create: resourceBillingSubaccountCreate,
Read: resourceBillingSubaccountRead,
Delete: resourceBillingSubaccountDelete,
Update: resourceBillingSubaccountUpdate,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"display_name": {
rileykarson marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeString,
Required: true,
},
"master_billing_account": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: compareSelfLinkOrResourceName,
},
"deletion_policy": {
Type: schema.TypeString,
Optional: true,
Default: "",
ValidateFunc: validation.StringInSlice([]string{"RENAME_ON_DESTROY", ""}, false),
},
"billing_account_id": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Computed: true,
},
"open": {
Type: schema.TypeBool,
Computed: true,
},
},
}
}

func resourceBillingSubaccountCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}

displayName := d.Get("display_name").(string)
masterBillingAccount := d.Get("master_billing_account").(string)

billingAccount := &cloudbilling.BillingAccount{
DisplayName: displayName,
MasterBillingAccount: canonicalBillingAccountName(masterBillingAccount),
}

res, err := config.NewBillingClient(userAgent).BillingAccounts.Create(billingAccount).Do()
if err != nil {
return fmt.Errorf("Error creating billing subaccount '%s' in master account '%s': %s", displayName, masterBillingAccount, err)
}

d.SetId(res.Name)

return resourceBillingSubaccountRead(d, meta)
}

func resourceBillingSubaccountRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}

id := d.Id()

billingAccount, err := config.NewBillingClient(userAgent).BillingAccounts.Get(d.Id()).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Billing Subaccount Not Found : %s", id))
}

d.Set("name", billingAccount.Name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind checking the error returned from these d.Set calls, and returning a message including the field name if there's a problem? Like in https://github.com/hashicorp/terraform-provider-google-beta/blob/master/google-beta/resource_container_cluster.go#L1584.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added error checking for d.Set calls

d.Set("display_name", billingAccount.DisplayName)
d.Set("open", billingAccount.Open)
d.Set("master_billing_account", billingAccount.MasterBillingAccount)
d.Set("billing_account_id", strings.TrimPrefix(d.Get("name").(string), "billingAccounts/"))

return nil
}

func resourceBillingSubaccountUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}

if ok := d.HasChange("display_name"); ok {
billingAccount := &cloudbilling.BillingAccount{
DisplayName: d.Get("display_name").(string),
}
_, err := config.NewBillingClient(userAgent).BillingAccounts.Patch(d.Id(), billingAccount).UpdateMask("display_name").Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Error updating billing account : %s", d.Id()))
}
}
return resourceBillingSubaccountRead(d, meta)
}

func resourceBillingSubaccountDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}

deletionPolicy := d.Get("deletion_policy").(string)

if deletionPolicy == "RENAME_ON_DESTROY" {
t := time.Now()
billingAccount := &cloudbilling.BillingAccount{
DisplayName: "Terraform Destroyed " + t.Format("20060102150405"),
}
_, err := config.NewBillingClient(userAgent).BillingAccounts.Patch(d.Id(), billingAccount).UpdateMask("display_name").Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Error updating billing account : %s", d.Id()))
}
}

d.SetId("")

return nil
}
141 changes: 141 additions & 0 deletions third_party/terraform/tests/resource_google_billing_subaccount_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package google

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccBillingSubaccount_renameOnDestroy(t *testing.T) {
t.Parallel()

masterBilling := getTestMasterBillingAccountFromEnv(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckGoogleBillingSubaccountRenameOnDestroy,
Steps: []resource.TestStep{
{
// Test Billing Subaccount creation
Config: testAccBillingSubccount_renameOnDestroy(masterBilling),
Check: testAccCheckGoogleBillingSubaccountExists("subaccount_with_rename_on_destroy"),
},
},
})
}

func TestAccBillingSubaccount_basic(t *testing.T) {
t.Parallel()

masterBilling := getTestMasterBillingAccountFromEnv(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
// Test Billing Subaccount creation
Config: testAccBillingSubccount_basic(masterBilling),
Check: testAccCheckGoogleBillingSubaccountExists("subaccount"),
},
{
ResourceName: "google_billing_subaccount.subaccount",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"rename_on_destroy"},
},
{
// Test Billing Subaccount update
Config: testAccBillingSubccount_update(masterBilling),
Check: testAccCheckGoogleBillingSubaccountExists("subaccount"),
},
{
ResourceName: "google_billing_subaccount.subaccount",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"rename_on_destroy"},
},
},
})
}

func testAccBillingSubccount_basic(masterBillingAccountId string) string {
return fmt.Sprintf(`
resource "google_billing_subaccount" "subaccount" {
display_name = "Test Billing Subaccount"
master_billing_account = "%s"
}
`, masterBillingAccountId)
}

func testAccBillingSubccount_update(masterBillingAccountId string) string {
return fmt.Sprintf(`
resource "google_billing_subaccount" "subaccount" {
display_name = "Rename Test Billing Subaccount"
master_billing_account = "%s"
}
`, masterBillingAccountId)
}

func testAccBillingSubccount_renameOnDestroy(masterBillingAccountId string) string {
return fmt.Sprintf(`
resource "google_billing_subaccount" "subaccount_with_rename_on_destroy" {
display_name = "Test Billing Subaccount (Rename on Destroy)"
master_billing_account = "%s"
rename_on_destroy = true
adarobin marked this conversation as resolved.
Show resolved Hide resolved
}
`, masterBillingAccountId)
}

func testAccBillingSubccount_updateRenameOnDestroy(masterBillingAccountId string) string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unused, and can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed testAccBillingSubccount_updateRenameOnDestroy

return fmt.Sprintf(`
resource "google_billing_subaccount" "subaccount_with_rename_on_destroy" {
display_name = "Rename Test Billing Subaccount (Rename on Destroy)"
master_billing_account = "%s"
rename_on_destroy = true
}
`, masterBillingAccountId)
}

func testAccCheckGoogleBillingSubaccountExists(bindingResourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
subaccount, ok := s.RootModule().Resources["google_billing_subaccount."+bindingResourceName]
if !ok {
return fmt.Errorf("Not found: %s", bindingResourceName)
}

config := testAccProvider.Meta().(*Config)
_, err := config.NewBillingClient(config.userAgent).BillingAccounts.Get(subaccount.Primary.ID).Do()
if err != nil {
return err
}

return nil
}
}

func testAccCheckGoogleBillingSubaccountRenameOnDestroy(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
if rs.Type != "google_billing_subaccount" {
continue
}
if strings.HasPrefix(name, "data.") {
continue
}

config := testAccProvider.Meta().(*Config)

res, err := config.NewBillingClient(config.userAgent).BillingAccounts.Get(rs.Primary.ID).Do()
if err != nil {
return err
}

if !strings.HasPrefix(res.DisplayName, "Terraform Destroyed") {
return fmt.Errorf("Billing account %s was not renamed on destroy", rs.Primary.ID)
}
}

return nil
}
1 change: 1 addition & 0 deletions third_party/terraform/utils/provider.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ end # products.each do
"google_billing_account_iam_binding": ResourceIamBinding(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_billing_account_iam_member": ResourceIamMember(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_billing_account_iam_policy": ResourceIamPolicy(IamBillingAccountSchema, NewBillingAccountIamUpdater, BillingAccountIdParseFunc),
"google_billing_subaccount": resourceBillingSubaccount(),
"google_cloudfunctions_function": resourceCloudFunctionsFunction(),
"google_composer_environment": resourceComposerEnvironment(),
"google_compute_attached_disk": resourceComputeAttachedDisk(),
Expand Down
9 changes: 9 additions & 0 deletions third_party/terraform/utils/provider_test.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ type VcrSource struct {

var sources map[string]VcrSource

var masterBillingAccountEnvVars = []string{
"GOOGLE_MASTER_BILLING_ACCOUNT",
}

func init() {
configs = make(map[string]*Config)
sources = make(map[string]VcrSource)
Expand Down Expand Up @@ -920,6 +924,11 @@ func getTestBillingAccountFromEnv(t *testing.T) string {
return multiEnvSearch(billingAccountEnvVars)
}

func getTestMasterBillingAccountFromEnv(t *testing.T) string {
skipIfEnvNotSet(t, masterBillingAccountEnvVars...)
return multiEnvSearch(masterBillingAccountEnvVars)
}

func getTestServiceAccountFromEnv(t *testing.T) string {
skipIfEnvNotSet(t, serviceAccountEnvVars...)
return multiEnvSearch(serviceAccountEnvVars)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
subcategory: "Cloud Platform"
layout: "google"
page_title: "Google: google_billing_subaccount"
sidebar_current: "docs-google-billing-subaccount"
description: |-
Allows management of a Google Cloud Billing Subaccount.
---

# google\_billing\_subaccount

Allows creation and management of a Google Cloud Billing Subaccount.

!> **WARNING:** Deleting this Terraform resource will not delete or close the billing subaccount.

```hcl
resource "google_billing_subaccount" "subaccount" {
display_name = "My Billing Account"
master_billing_account = "012345-567890-ABCDEF"
}
```

## Argument Reference

* `display_name` (Required) - The display name of the billing account.
adarobin marked this conversation as resolved.
Show resolved Hide resolved

* `master_billing_account` (Required) - The name of the master billing account that the subaccount
will be created under in the form `{billing_account_id}` or `billingAccounts/{billing_account_id}`.

* `deletion_policy` (Optional) - If set to "RENAME_ON_DESTROY" the billing account display_name
will be changed to "Terraform Destroyed" along with a timestamp. If set to "" this will not occur.
Default is "".

## Attributes Reference

The following additional attributes are exported:

* `open` - `true` if the billing account is open, `false` if the billing account is closed.

* `name` - The resource name of the billing account in the form `billingAccounts/{billing_account_id}`.

* `billing_account_id` - The billing account id.
adarobin marked this conversation as resolved.
Show resolved Hide resolved

## Import

Billing Subaccounts can be imported using any of these accepted formats:

```
$ terraform import google_billing_subaccount.default billingAccounts/{billing_account_id}
```