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

Merged
Merged
Show file tree
Hide file tree
Changes from 6 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
17 changes: 17 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"github.com/Azure/azure-sdk-for-go/services/preview/msi/mgmt/2015-08-31-preview/msi"
"github.com/Azure/azure-sdk-for-go/services/preview/operationalinsights/mgmt/2015-11-01-preview/operationalinsights"
"github.com/Azure/azure-sdk-for-go/services/preview/operationsmanagement/mgmt/2015-11-01-preview/operationsmanagement"
"github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2018-03-01-preview/management"
"github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql"
"github.com/Azure/azure-sdk-for-go/services/recoveryservices/mgmt/2016-06-01/recoveryservices"
"github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis"
Expand All @@ -53,6 +54,7 @@ import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
"github.com/Azure/azure-sdk-for-go/services/trafficmanager/mgmt/2017-05-01/trafficmanager"
"github.com/Azure/azure-sdk-for-go/services/web/mgmt/2018-02-01/web"

mainStorage "github.com/Azure/azure-sdk-for-go/storage"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
Expand Down Expand Up @@ -165,6 +167,10 @@ type ArmClient struct {
// Logic
logicWorkflowsClient logic.WorkflowsClient

// Management Groups
managementGroupsClient managementgroups.Client
managementGroupsSubscriptionClient managementgroups.SubscriptionsClient

// Monitor
actionGroupsClient insights.ActionGroupsClient
monitorAlertRulesClient insights.AlertRulesClient
Expand Down Expand Up @@ -428,6 +434,7 @@ func getArmClient(c *authentication.Config) (*ArmClient, error) {
client.registerOperationalInsightsClients(endpoint, c.SubscriptionID, auth, sender)
client.registerRecoveryServiceClients(endpoint, c.SubscriptionID, auth)
client.registerPolicyClients(endpoint, c.SubscriptionID, auth)
client.registerManagementGroupClients(endpoint, auth)
client.registerRedisClients(endpoint, c.SubscriptionID, auth, sender)
client.registerRelayClients(endpoint, c.SubscriptionID, auth, sender)
client.registerResourcesClients(endpoint, c.SubscriptionID, auth)
Expand Down Expand Up @@ -1039,6 +1046,16 @@ func (c *ArmClient) registerPolicyClients(endpoint, subscriptionId string, auth
c.policyDefinitionsClient = policyDefinitionsClient
}

func (c *ArmClient) registerManagementGroupClients(endpoint string, auth autorest.Authorizer) {
managementGroupsClient := managementgroups.NewClientWithBaseURI(endpoint)
c.configureClient(&managementGroupsClient.Client, auth)
c.managementGroupsClient = managementGroupsClient

managementGroupsSubscriptionClient := managementgroups.NewSubscriptionsClientWithBaseURI(endpoint)
c.configureClient(&managementGroupsSubscriptionClient.Client, auth)
c.managementGroupsSubscriptionClient = managementGroupsSubscriptionClient
}

var (
storageKeyCacheMu sync.RWMutex
storageKeyCache = make(map[string]string)
Expand Down
2 changes: 2 additions & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_logic_app_workflow": resourceArmLogicAppWorkflow(),
"azurerm_managed_disk": resourceArmManagedDisk(),
"azurerm_management_lock": resourceArmManagementLock(),
"azurerm_management_group": resourceManagementGroup(),
"azurerm_metric_alertrule": resourceArmMetricAlertRule(),
"azurerm_monitor_action_group": resourceArmMonitorActionGroup(),
"azurerm_mysql_configuration": resourceArmMySQLConfiguration(),
Expand Down Expand Up @@ -382,6 +383,7 @@ func determineAzureResourceProvidersToRegister(providerList []resources.Provider
"microsoft.insights": {},
"Microsoft.Logic": {},
"Microsoft.ManagedIdentity": {},
"Microsoft.Management": {},
"Microsoft.Network": {},
"Microsoft.NotificationHubs": {},
"Microsoft.OperationalInsights": {},
Expand Down
192 changes: 192 additions & 0 deletions azurerm/resource_arm_management_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package azurerm

import (
"fmt"
"log"
"strings"

"github.com/Azure/azure-sdk-for-go/services/preview/resources/mgmt/2018-03-01-preview/management"
"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceManagementGroup() *schema.Resource {
return &schema.Resource{
Create: resourceManagementGroupCreateUpdate,
Update: resourceManagementGroupCreateUpdate,
Read: resourceManagementGroupRead,
Delete: resourceManagementGroupDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"subscription_ids": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func resourceManagementGroupCreateUpdate(d *schema.ResourceData, meta interface{}) error {

client := meta.(*ArmClient).managementGroupsClient
subscriptionsClient := meta.(*ArmClient).managementGroupsSubscriptionClient
ctx := meta.(*ArmClient).StopContext

armTenantID := meta.(*ArmClient).tenantId
name := d.Get("name").(string)
subscriptionIds := d.Get("subscription_ids").([]interface{})
log.Printf("[INFO] Creating management group %q", name)

parentID := fmt.Sprintf("/providers/Microsoft.Management/managementGroups/%s", armTenantID)
properties := managementgroups.CreateManagementGroupRequest{
CreateManagementGroupProperties: &managementgroups.CreateManagementGroupProperties{
TenantID: &armTenantID,
DisplayName: &name,
Details: &managementgroups.CreateManagementGroupDetails{
Parent: &managementgroups.CreateParentGroupInfo{
ID: utils.String(parentID),
},
},
},
Type: utils.String("/providers/Microsoft.Management/managementGroups"),
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't need to set ID or Type here - can we remove these?

Name: &name,
Copy link
Contributor

Choose a reason for hiding this comment

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

we shouldn't need to set the name field here since it's specified in the CreateOrUpdate method below (which forms the URI as part of the request) - so I think we can remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tried it out.. gave me a Original Error: autorest/azure: Service returned an error. Status=400 Code="BadRequest" Message="Name of the group doesn't match the group id." error..

}

log.Printf("[DEBUG] Invoking managementGroupClient")
createManagementGroupFuture, err := client.CreateOrUpdate(ctx, name, properties, "no-cache")
if err != nil {
log.Printf("[DEBUG] Error creating Management Group %q: %+v", name, err)
return fmt.Errorf("Error creating Management Group %q: %+v", name, err)
}

err = createManagementGroupFuture.WaitForCompletion(ctx, client.Client)
if err != nil {
return fmt.Errorf("Error waiting for creation of Management Group %q: %+v", name, err)
}

recurse := false

resp, err := client.Get(ctx, name, "", &recurse, "", "no-cache")
if err != nil {
log.Printf("[DEBUG] Error retrieving Management Group %q: %+v", name, err)
return fmt.Errorf("Error retrieving Management Group %q: %+v", name, err)
}

d.SetId(*resp.ID)

for _, subscription := range subscriptionIds {
data := subscription.(string)
log.Printf("[DEBUG] Adding subscriptionId %q to management group %q", data, name)
Copy link
Contributor

Choose a reason for hiding this comment

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

given this is an entirely different API call - I'm wondering if this'd make sense as a separate resource?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

based on the SDK package /mgmt/2018-03-01-preview/management, there are two different clients for managementgroup (client) and subscriptions (subscriptionclient) - in this case though, subscriptionclient really only gets used in the managementgroup context, and purely to associate a subscription to a management group (and not for creating subscriptions).. i think there's merit creating a dedicated subscription object, but for the creation/deletion of subscriptions (which is a different SDK package). My two cents..

_, err = subscriptionsClient.Create(ctx, name, data, "no-cache")
if err != nil {
log.Printf("[DEBUG] Error assigning subscription %q to management group %q", data, name)
return err
}
}

return resourceManagementGroupRead(d, meta)
}

func resourceManagementGroupRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).managementGroupsClient
ctx := meta.(*ArmClient).StopContext

recurse := true
resp, err := client.Get(ctx, d.Get("name").(string), "children", &recurse, "", "no-cache")
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] Error reading Management Group %q - removing from state", d.Id())
d.SetId("")
return nil
}

return fmt.Errorf("Error reading Management Group %+v", err)
}

subscriptionIds := []string{}

if props := resp.Properties; props != nil {
if children := props.Children; children != nil {
for _, child := range *children {
subscriptionID, err := parseSubscriptionID(*child.ID)
if err != nil {
log.Printf("%q", err)
return fmt.Errorf("Unable to parse child subscription ID %+v", err)
}
log.Printf("[INFO] Reading subscription %q from management group %q", subscriptionID, d.Get("name").(string))
subscriptionIds = append(subscriptionIds, subscriptionID)
}
}
}

d.Set("subscription_ids", subscriptionIds)
Copy link
Contributor

Choose a reason for hiding this comment

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

can we also set the name and display_name field here?


return nil
}

func resourceManagementGroupDelete(d *schema.ResourceData, meta interface{}) error {
//before deleting a management group, return any subscriptions to the root management group

client := meta.(*ArmClient).managementGroupsClient
subscriptionsClient := meta.(*ArmClient).managementGroupsSubscriptionClient
ctx := meta.(*ArmClient).StopContext
armTenantID := meta.(*ArmClient).tenantId
name := d.Get("name").(string)

subscriptionIds := d.Get("subscription_ids").([]interface{})
if subscriptionIds != nil {
for _, subscription := range subscriptionIds {
data := subscription.(string)
log.Printf("[DEBUG] Adding subscriptionId %q to management group %q", data, armTenantID)
_, err := subscriptionsClient.Create(ctx, armTenantID, data, "no-cache")
if err != nil {
log.Printf("[DEBUG] Error assigning subscription %q to management group %q", data, armTenantID)
return err
}
}
}

resp, err := client.Delete(ctx, name, "no-cache")
if err != nil {
log.Printf("[DEBUG] Error deleting management group %q", name)
return fmt.Errorf("Error deleting management group %q", name)
}

err = resp.WaitForCompletion(ctx, client.Client)
if err != nil {
return fmt.Errorf("Error deleting management group %q", name)
}

_, err = resp.Result(client)

if err != nil {
return fmt.Errorf("Error deleting management group %q", name)
}

return nil
}
func parseSubscriptionID(id string) (string, error) {
components := strings.Split(id, "/")

if len(components) == 0 {
return "", fmt.Errorf("Subscription Id is empty or not formatted correctly: %s", id)
}

if len(components) != 3 {
return "", fmt.Errorf("Subscription Id should have 2 segments, got %d: '%s'", len(components)-1, id)
}

return components[2], nil
}
Loading