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 #8022

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/2640.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
`google_billing_subaccount`
```
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
"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 google/provider_test.go
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 @@ -904,6 +908,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
157 changes: 157 additions & 0 deletions google/resource_google_billing_subaccount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
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": {
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))
}

if err := d.Set("name", billingAccount.Name); err != nil {
return fmt.Errorf("Error setting name: %s", err)
}
if err := d.Set("display_name", billingAccount.DisplayName); err != nil {
return fmt.Errorf("Error setting display_na,e: %s", err)
}
if err := d.Set("open", billingAccount.Open); err != nil {
return fmt.Errorf("Error setting open: %s", err)
}
if err := d.Set("master_billing_account", billingAccount.MasterBillingAccount); err != nil {
return fmt.Errorf("Error setting master_billing_account: %s", err)
}
if err := d.Set("billing_account_id", strings.TrimPrefix(d.Get("name").(string), "billingAccounts/")); err != nil {
return fmt.Errorf("Error setting billing_account_id: %s", err)
}

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
}
131 changes: 131 additions & 0 deletions google/resource_google_billing_subaccount_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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{"deletion_policy"},
},
{
// Test Billing Subaccount update
Config: testAccBillingSubccount_update(masterBilling),
Check: testAccCheckGoogleBillingSubaccountExists("subaccount"),
},
{
ResourceName: "google_billing_subaccount.subaccount",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"deletion_policy"},
},
},
})
}

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"
deletion_policy = "RENAME_ON_DESTROY"
}
`, 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
}
50 changes: 50 additions & 0 deletions website/docs/r/google_billing_subaccount.html.markdown
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.

* `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.

## Import

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

```
$ terraform import google_billing_subaccount.default billingAccounts/{billing_account_id}
```
4 changes: 4 additions & 0 deletions website/google.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,10 @@
<a href="#">Resources</a>
<ul class="nav nav-auto-expand">

<li>
<a href="/docs/providers/google/r/google_billing_subaccount.html">google_billing_subaccount</a>
</li>

<li>
<a href="/docs/providers/google/r/google_folder.html">google_folder</a>
</li>
Expand Down