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

New Resource: azurerm_management_group_subscription_association #11069

Merged
merged 9 commits into from
Mar 31, 2021
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
1 change: 1 addition & 0 deletions azurerm/internal/acceptance/testcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func (td TestData) DataSourceTest(t *testing.T, steps []resource.TestStep) {
td.runAcceptanceTest(t, testCase)
}

// lintignore:AT001
func (td TestData) DataSourceTestInSequence(t *testing.T, steps []resource.TestStep) {
// DataSources don't need a check destroy - however since this is a wrapper function
// and not matching the ignore pattern `XXX_data_source_test.go`, this needs to be explicitly opted out
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func resourceManagementGroup() *schema.Resource {
"subscription_ids": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsUUID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestAccManagementGroup_withSubscriptions(t *testing.T) {
),
},
{
Config: r.basic(),
Config: r.removeSubscriptions(),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("subscription_ids.#").HasValue("0"),
Expand Down Expand Up @@ -283,3 +283,15 @@ resource "azurerm_management_group" "test" {
}
`, subscriptionID)
}

func (r ManagementGroupResource) removeSubscriptions() string {
return `
provider "azurerm" {
features {}
}

resource "azurerm_management_group" "test" {
subscription_ids = []
}
`
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ func NewManagementGroupId(managementGroupName string) ManagementGroupId {
}

func (r ManagementGroupId) ID() string {
managemntGroupIdFmt := "/providers/Microsoft.Management/managementGroups/%s"
return fmt.Sprintf(managemntGroupIdFmt, r.Name)
managementGroupIdFmt := "/providers/Microsoft.Management/managementGroups/%s"
return fmt.Sprintf(managementGroupIdFmt, r.Name)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package parse

import (
"fmt"

"github.com/hashicorp/go-uuid"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
)

type ManagementGroupSubscriptionAssociationId struct {
ManagementGroup string
SubscriptionId string
}

func NewManagementGroupSubscriptionAssociationID(managementGroupName string, subscriptionId string) ManagementGroupSubscriptionAssociationId {
return ManagementGroupSubscriptionAssociationId{
ManagementGroup: managementGroupName,
SubscriptionId: subscriptionId,
}
}

func (r ManagementGroupSubscriptionAssociationId) ID() string {
managementGroupSubscriptionAssociationFmt := "/managementGroup/%s/subscription/%s"
return fmt.Sprintf(managementGroupSubscriptionAssociationFmt, r.ManagementGroup, r.SubscriptionId)
}

func ManagementGroupSubscriptionAssociationID(input string) (*ManagementGroupSubscriptionAssociationId, error) {
id, err := azure.ParseAzureResourceIDWithoutSubscription(input)
if err != nil {
return nil, err
}

managementGroup, err := id.PopSegment("managementGroup")
if err != nil {
return nil, err
}

subscriptionId, err := id.PopSegment("subscription")
if err != nil {
return nil, err
}
if _, err := uuid.ParseUUID(subscriptionId); err != nil {
return nil, fmt.Errorf("expected subscription ID to be UUID, got %q", subscriptionId)
}

return &ManagementGroupSubscriptionAssociationId{
ManagementGroup: managementGroup,
SubscriptionId: subscriptionId,
}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package parse

import "testing"

func TestManagementGroupSubscriptionAssociationID(t *testing.T) {
testData := []struct {
Name string
Input string
Error bool
Expected *ManagementGroupSubscriptionAssociationId
}{
{
Name: "Empty",
Input: "",
Error: true,
},
{
Name: "Missing Subscription",
Input: "/managementGroup/MyManagementGroup",
Error: true,
},
{
Name: "Missing Subscription Id",
Input: "/managementGroup/MyManagementGroup/subscription/",
Error: true,
},
{
Name: "Missing Management Group",
Input: "/subscription/12345678-1234-1234-1234-123456789012",
Error: true,
},
{
Name: "Missing Management Group Name",
Input: "/managementGroup/subscription/12345678-1234-1234-1234-123456789012",
Error: true,
},
{
Name: "Wrong Case",
Input: "/MANAGEMENTGROUP/MyManagementGroup/SUBSCRIPTION/12345678-1234-1234-1234-123456789012",
Error: true,
},
{
Name: "Valid",
Input: "/managementGroup/MyManagementGroup/subscription/12345678-1234-1234-1234-123456789012",
Expected: &ManagementGroupSubscriptionAssociationId{
ManagementGroup: "MyManagementGroup",
SubscriptionId: "12345678-1234-1234-1234-123456789012",
},
},
}

for _, v := range testData {
t.Logf("[DEBUG] Testing %q", v.Name)

actual, err := ManagementGroupSubscriptionAssociationID(v.Input)
if err != nil {
if v.Error {
continue
}

t.Fatalf("Expected a value but got an error: %s", err)
}

if actual.ManagementGroup != v.Expected.ManagementGroup {
t.Fatalf("Expected %q but got %q for Name", v.Expected.ManagementGroup, actual.ManagementGroup)
}

if actual.SubscriptionId != v.Expected.SubscriptionId {
t.Fatalf("Expected %q but got %q for Name", v.Expected.SubscriptionId, actual.SubscriptionId)
}
}
}
3 changes: 2 additions & 1 deletion azurerm/internal/services/managementgroup/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource {
// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azurerm_management_group": resourceManagementGroup(),
"azurerm_management_group": resourceManagementGroup(),
"azurerm_management_group_subscription_association": resourceManagementGroupSubscriptionAssociation(),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package managementgroup

import (
"fmt"
"log"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2018-03-01-preview/managementgroups"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/managementgroup/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/managementgroup/validate"
subscriptionParse "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/subscription/parse"
subscriptionValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/subscription/validate"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceManagementGroupSubscriptionAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceManagementGroupSubscriptionAssociationCreate,
Read: resourceManagementGroupSubscriptionAssociationRead,
Delete: resourceManagementGroupSubscriptionAssociationDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Read: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.ManagementGroupSubscriptionAssociationID(id)
return err
}),

Schema: map[string]*schema.Schema{
"management_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validate.ManagementGroupID,
},

"subscription_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: subscriptionValidate.SubscriptionID,
},
},
}
}

func resourceManagementGroupSubscriptionAssociationCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).ManagementGroups.SubscriptionClient
groupsClient := meta.(*clients.Client).ManagementGroups.GroupsClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

managementGroupId, err := parse.ManagementGroupID(d.Get("management_group_id").(string))
if err != nil {
return err
}

subscriptionId, err := subscriptionParse.SubscriptionID(d.Get("subscription_id").(string))
if err != nil {
return err
}

id := parse.NewManagementGroupSubscriptionAssociationID(managementGroupId.Name, subscriptionId.SubscriptionID)

existing, err := groupsClient.Get(ctx, id.ManagementGroup, "children", utils.Bool(false), "", "")
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("failed checking Management Group %q: %+v", id.ManagementGroup, err)
}
}

props := existing.Properties
if props == nil {
return fmt.Errorf("could not read properties for Management Group %q to check if Subscription Association for %q already exists", id.ManagementGroup, id.SubscriptionId)
}

if props.Children != nil {
for _, v := range *props.Children {
if v.Type == managementgroups.Type1Subscriptions && v.Name != nil && *v.Name == id.SubscriptionId {
return tf.ImportAsExistsError("azurerm_management_group_subscription_association", id.ID())
}
}
}

if _, err := client.Create(ctx, id.ManagementGroup, id.SubscriptionId, ""); err != nil {
return fmt.Errorf("creating Management Group Subscription Association between %q and %q: %+v", managementGroupId.Name, subscriptionId, err)
}

d.SetId(id.ID())

return resourceManagementGroupSubscriptionAssociationRead(d, meta)
}

func resourceManagementGroupSubscriptionAssociationRead(d *schema.ResourceData, meta interface{}) error {
// There is no "read" function on the appropriate client so we need to check if the Subscription is in the Management Group subscription list
client := meta.(*clients.Client).ManagementGroups.GroupsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.ManagementGroupSubscriptionAssociationID(d.Id())
if err != nil {
return err
}

managementGroup, err := client.Get(ctx, id.ManagementGroup, "children", utils.Bool(false), "", "")
if err != nil {
return fmt.Errorf("reading Management Group %q for Subscription Associations: %+v", id.ManagementGroup, err)
}
found := false
if props := managementGroup.Properties; props != nil {
if props.Children == nil {
return fmt.Errorf("could not read properties for Management Group %q", id.ManagementGroup)
}

for _, v := range *props.Children {
if v.Type == managementgroups.Type1Subscriptions {
if v.Name != nil && *v.Name == id.SubscriptionId {
found = true
}
}
}

if !found {
log.Printf("[INFO] Subscription %q not found in Management group %q, removing from state", id.SubscriptionId, id.ManagementGroup)
d.SetId("")
return nil
}

managementGroupId := parse.NewManagementGroupId(id.ManagementGroup)
d.Set("management_group_id", managementGroupId.ID())
subscriptionId := subscriptionParse.NewSubscriptionId(id.SubscriptionId)
d.Set("subscription_id", subscriptionId.ID())
}

return nil
}

func resourceManagementGroupSubscriptionAssociationDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).ManagementGroups.SubscriptionClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.ManagementGroupSubscriptionAssociationID(d.Id())
if err != nil {
return err
}

resp, err := client.Delete(ctx, id.ManagementGroup, id.SubscriptionId, "")
if err != nil {
if !utils.ResponseWasNotFound(resp) {
return fmt.Errorf("deleting Management Group Subscription Association between Management Group %q and Subscription %q: %+v", id.ManagementGroup, id.SubscriptionId, err)
}
}

return nil
}
Loading