diff options
| author | Sharath Nair <snair3@agl.com.au> | 2022-05-23 12:50:08 +1000 |
|---|---|---|
| committer | Sharath Nair <snair3@agl.com.au> | 2022-06-28 09:57:21 +1000 |
| commit | 62c16e38a50c877bc5625ec18421983d29dbe096 (patch) | |
| tree | 7523720ddc0b0b5e8f3d3e264a8fecfa3b326f5d | |
| parent | b103dc8da0d227178fa52f28a57db0fcdc6ebad4 (diff) | |
Extend azuredevops_serviceendpoint_azurerm to support managementGroup scopes
4 files changed, 189 insertions, 17 deletions
diff --git a/azuredevops/internal/acceptancetests/resource_serviceendpoint_azurerm_test.go b/azuredevops/internal/acceptancetests/resource_serviceendpoint_azurerm_test.go index c0357b22..91d81091 100644 --- a/azuredevops/internal/acceptancetests/resource_serviceendpoint_azurerm_test.go +++ b/azuredevops/internal/acceptancetests/resource_serviceendpoint_azurerm_test.go @@ -62,6 +62,54 @@ func TestAccServiceEndpointAzureRm_CreateAndUpdate(t *testing.T) { }) } +func TestAccServiceEndpointAzureRm_MgmtGrpCreateAndUpdate(t *testing.T) { + t.Skip("Skipping test TestAccServiceEndpointAzureRm_MgmtGrpCreateAndUpdate: test resource limit") + projectName := testutils.GenerateResourceName() + serviceEndpointNameFirst := testutils.GenerateResourceName() + serviceEndpointNameSecond := testutils.GenerateResourceName() + serviceprincipalidFirst := uuid.New().String() + serviceprincipalidSecond := uuid.New().String() + serviceprincipalkeyFirst := uuid.New().String() + serviceprincipalkeySecond := uuid.New().String() + + resourceType := "azuredevops_serviceendpoint_azurerm" + tfSvcEpNode := resourceType + ".serviceendpointrm" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testutils.PreCheck(t, nil) }, + Providers: testutils.GetProviders(), + CheckDestroy: testutils.CheckServiceEndpointDestroyed(resourceType), + Steps: []resource.TestStep{ + { + Config: testutils.HclServiceEndpointAzureRMResource(projectName, serviceEndpointNameFirst, serviceprincipalidFirst, serviceprincipalkeyFirst), + Check: resource.ComposeTestCheckFunc( + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameFirst), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_spn_tenantid"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameFirst), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_managment_group_id"), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_management_group_name"), + resource.TestCheckResourceAttr(tfSvcEpNode, "credentials.0.serviceprincipalid", serviceprincipalidFirst), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "credentials.0.serviceprincipalkey_hash"), + resource.TestCheckResourceAttr(tfSvcEpNode, "credentials.0.serviceprincipalkey", serviceprincipalkeyFirst), + ), + }, { + Config: testutils.HclServiceEndpointAzureRMResource(projectName, serviceEndpointNameSecond, serviceprincipalidSecond, serviceprincipalkeySecond), + Check: resource.ComposeTestCheckFunc( + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameSecond), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_spn_tenantid"), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_management_group_id"), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "azurerm_management_group_name"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameSecond), + resource.TestCheckResourceAttr(tfSvcEpNode, "credentials.0.serviceprincipalid", serviceprincipalidSecond), + resource.TestCheckResourceAttrSet(tfSvcEpNode, "credentials.0.serviceprincipalkey_hash"), + resource.TestCheckResourceAttr(tfSvcEpNode, "credentials.0.serviceprincipalkey", serviceprincipalkeySecond), + ), + }, + }, + }) +} + func TestAccServiceEndpointAzureRm_AutomaticCreateAndUpdate(t *testing.T) { t.Skip("Skipping test TestAccServiceEndpointAzureRm_AutomaticCreateAndUpdate: test resource limit") diff --git a/azuredevops/internal/service/serviceendpoint/commons.go b/azuredevops/internal/service/serviceendpoint/commons.go index 57b26f3c..938f7341 100644 --- a/azuredevops/internal/service/serviceendpoint/commons.go +++ b/azuredevops/internal/service/serviceendpoint/commons.go @@ -150,6 +150,17 @@ func makeUnprotectedSchema(r *schema.Resource, keyName, envVarName, description } } +// makeUnprotectedOptionalSchema create unprotected schema with optional arguments +func makeUnprotectedOptionalSchema(r *schema.Resource, keyName, envVarName, description string, conflictsWith []string) { + r.Schema[keyName] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc(envVarName, nil), + Description: description, + ConflictsWith: conflictsWith, + } +} + func genServiceEndpointCreateFunc(flatFunc flatFunc, expandFunc expandFunc) func(d *schema.ResourceData, m interface{}) error { return func(d *schema.ResourceData, m interface{}) error { clients := m.(*client.AggregatedClient) diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_azurerm.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_azurerm.go index 3fe60e6d..1f87e688 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_azurerm.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_azurerm.go @@ -15,17 +15,23 @@ import ( func ResourceServiceEndpointAzureRM() *schema.Resource { r := genBaseServiceEndpointResource(flattenServiceEndpointAzureRM, expandServiceEndpointAzureRM) makeUnprotectedSchema(r, "azurerm_spn_tenantid", "ARM_TENANT_ID", "The service principal tenant id which should be used.") - makeUnprotectedSchema(r, "azurerm_subscription_id", "ARM_SUBSCRIPTION_ID", "The Azure subscription Id which should be used.") - makeUnprotectedSchema(r, "azurerm_subscription_name", "ARM_SUBSCRIPTION_NAME", "The Azure subscription name which should be used.") r.Schema["resource_group"] = &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, Description: "Scope Resource Group", - ConflictsWith: []string{"credentials"}, + ConflictsWith: []string{"credentials", "azurerm_management_group_id"}, } + // Subscription scopeLevel + makeUnprotectedOptionalSchema(r, "azurerm_subscription_id", "ARM_SUBSCRIPTION_ID", "The Azure subscription Id which should be used.", []string{"azurerm_management_group_id"}) + makeUnprotectedOptionalSchema(r, "azurerm_subscription_name", "ARM_SUBSCRIPTION_NAME", "The Azure subscription name which should be used.", []string{"azurerm_management_group_id"}) + + // ManagementGroup scopeLevel + makeUnprotectedOptionalSchema(r, "azurerm_management_group_id", "ARM_MGMT_GROUP_ID", "The Azure managementGroup Id which should be used.", []string{"azurerm_subscription_id", "resource_group"}) + makeUnprotectedOptionalSchema(r, "azurerm_management_group_name", "ARM_MGMT_GROUP_NAME", "The Azure managementGroup name which should be used.", []string{"azurerm_subscription_id", "resource_group"}) + secretHashKey, secretHashSchema := tfhelper.GenerateSecreteMemoSchema("serviceprincipalkey") r.Schema["credentials"] = &schema.Schema{ Type: schema.TypeList, @@ -58,11 +64,32 @@ func ResourceServiceEndpointAzureRM() *schema.Resource { func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.ServiceEndpoint, *uuid.UUID, error) { serviceEndpoint, projectID := doBaseExpansion(d) - scope := fmt.Sprintf("/subscriptions/%s", d.Get("azurerm_subscription_id")) - scopeLevel := "Subscription" - if _, ok := d.GetOk("resource_group"); ok { - scope += fmt.Sprintf("/resourcegroups/%s", d.Get("resource_group")) - scopeLevel = "ResourceGroup" + // Validate one of either subscriptionId or managementGroupId is set + subId := d.Get("azurerm_subscription_id").(string) + subName := d.Get("azurerm_subscription_name").(string) + + mgmtGrpId := d.Get("azurerm_management_group_id").(string) + mgmtGrpName := d.Get("azurerm_management_group_name").(string) + + scopeLevelMap := map[string][]string{ + "subscription": []string{subId, subName}, + "managementGroup": []string{mgmtGrpId, mgmtGrpName}, + } + + if err := validateScopeLevel(scopeLevelMap); err != nil { + return nil, nil, err + } + + var scope string + var scopeLevel string + + if _, ok := d.GetOk("azurerm_subscription_id"); ok { + scope = fmt.Sprintf("/subscriptions/%s", d.Get("azurerm_subscription_id")) + scopeLevel = "Subscription" + if _, ok := d.GetOk("resource_group"); ok { + scope += fmt.Sprintf("/resourcegroups/%s", d.Get("resource_group")) + scopeLevel = "ResourceGroup" + } } serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{ @@ -75,17 +102,29 @@ func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.Serv Scheme: converter.String("ServicePrincipal"), } serviceEndpoint.Data = &map[string]string{ - "creationMode": "Automatic", - "environment": "AzureCloud", - "scopeLevel": "Subscription", - "subscriptionId": d.Get("azurerm_subscription_id").(string), - "subscriptionName": d.Get("azurerm_subscription_name").(string), + "creationMode": "Automatic", + "environment": "AzureCloud", + } + + if scopeLevel == "Subscription" || scopeLevel == "ResourceGroup" { + (*serviceEndpoint.Data)["subscriptionId"] = d.Get("azurerm_subscription_id").(string) + (*serviceEndpoint.Data)["subscriptionName"] = d.Get("azurerm_subscription_name").(string) + } + + if scopeLevel == "Subscription" { + (*serviceEndpoint.Data)["scopeLevel"] = "Subscription" } if scopeLevel == "ResourceGroup" { (*serviceEndpoint.Authorization.Parameters)["scope"] = scope } + if _, ok := d.GetOk("azurerm_management_group_id"); ok { + (*serviceEndpoint.Data)["scopeLevel"] = "ManagementGroup" + (*serviceEndpoint.Data)["managementGroupId"] = d.Get("azurerm_management_group_id").(string) + (*serviceEndpoint.Data)["managementGroupName"] = d.Get("azurerm_management_group_name").(string) + } + if _, ok := d.GetOk("credentials"); ok { credentials := d.Get("credentials").([]interface{})[0].(map[string]interface{}) (*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"] = credentials["serviceprincipalid"].(string) @@ -124,6 +163,48 @@ func flattenServiceEndpointAzureRM(d *schema.ResourceData, serviceEndpoint *serv } d.Set("azurerm_spn_tenantid", (*serviceEndpoint.Authorization.Parameters)["tenantid"]) - d.Set("azurerm_subscription_id", (*serviceEndpoint.Data)["subscriptionId"]) - d.Set("azurerm_subscription_name", (*serviceEndpoint.Data)["subscriptionName"]) + + if _, ok := (*serviceEndpoint.Data)["managementGroupId"]; ok { + d.Set("azurerm_management_group_id", (*serviceEndpoint.Data)["managementGroupId"]) + d.Set("azurerm_management_group_name", (*serviceEndpoint.Data)["managementGroupName"]) + } + + if _, ok := (*serviceEndpoint.Data)["subscriptionId"]; ok { + d.Set("azurerm_subscription_id", (*serviceEndpoint.Data)["subscriptionId"]) + d.Set("azurerm_subscription_name", (*serviceEndpoint.Data)["subscriptionName"]) + } +} + +// Validation function to ensure either Subscription or ManagementGroup scopeLevels are set correctly +func validateScopeLevel(scopeMap map[string][]string) error { + // Check for empty + if strings.TrimSpace(strings.Join(scopeMap["subscription"], "")) == "" && strings.TrimSpace(strings.Join(scopeMap["managementGroup"], "")) == "" { + return fmt.Errorf("One of either subscription scoped (azurerm_subscription_id, azurerm_subscription_name) or managementGroup scoped (azurerm_management_ggroup_id, azurerm_management_group_name) details must be provided") + } + + // check for valid subscription details + var subElementCount int + for _, ele := range scopeMap["subscription"] { + if ele == "" { + subElementCount = subElementCount + 1 + } + } + + if subElementCount == 1 { + return fmt.Errorf("azurerm_subscription_id and azurerm_subscription_name must be provided") + } + + // check for valid managementGroup details + var mgmtElementCount int + for _, ele := range scopeMap["managementGroup"] { + if ele == "" { + mgmtElementCount = mgmtElementCount + 1 + } + } + + if mgmtElementCount == 1 { + return fmt.Errorf("azurerm_management_group_id and azurerm_management_group_name must be provided") + } + + return nil } diff --git a/website/docs/r/serviceendpoint_azurerm.html.markdown b/website/docs/r/serviceendpoint_azurerm.html.markdown index 216f1e88..f6f742aa 100644 --- a/website/docs/r/serviceendpoint_azurerm.html.markdown +++ b/website/docs/r/serviceendpoint_azurerm.html.markdown @@ -18,6 +18,7 @@ For detailed steps to create a service principal with Azure cli see the [documen ## Example Usage ### Manual AzureRM Service Endpoint +#### Subscription Scoped ```hcl resource "azuredevops_project" "example" { @@ -42,6 +43,32 @@ resource "azuredevops_serviceendpoint_azurerm" "example" { } ``` +### Manual AzureRM Service Endpoint +#### ManagementGroup Scoped + +```hcl +resource "azuredevops_project" "example" { + name = "Example Project" + visibility = "private" + version_control = "Git" + work_item_template = "Agile" + description = "Managed by Terraform" +} + +resource "azuredevops_serviceendpoint_azurerm" "example" { + project_id = azuredevops_project.example.id + service_endpoint_name = "Example AzureRM" + description = "Managed by Terraform" + credentials { + serviceprincipalid = "00000000-0000-0000-0000-000000000000" + serviceprincipalkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } + azurerm_spn_tenantid = "00000000-0000-0000-0000-000000000000" + azurerm_management_group_id = "managementGroup" + azurerm_management_group_name = "managementGroup" +} +``` + ### Automatic AzureRM Service Endpoint ```hcl @@ -68,8 +95,13 @@ The following arguments are supported: - `project_id` - (Required) The ID of the project. - `service_endpoint_name` - (Required) The Service Endpoint name. - `azurerm_spn_tenantid` - (Required) The tenant id if the service principal. -- `azurerm_subscription_id` - (Required) The subscription Id of the Azure targets. -- `azurerm_subscription_name` - (Required) The subscription Name of the targets. +- `azurerm_management_group_id` - (Optional) The management group Id of the Azure targets. +- `azurerm_management_group_name` - (Optional) The management group Name of the targets. +- `azurerm_subscription_id` - (Optional) The subscription Id of the Azure targets. +- `azurerm_subscription_name` - (Optional) The subscription Name of the targets. + +~> **NOTE:** One of either `Subscription` scoped i.e. `azure_subscription_id`, `azurerm_subscription_name` or `ManagementGroup` scoped i.e. `azure_management_group_id`, `azurerm_management_group_name` values must be specified. + - `description` - (Optional) Service connection description. - `credentials` - (Optional) A `credentials` block. - `resource_group` - (Optional) The resource group used for scope of automatic service endpoint. |
