summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSharath Nair <snair3@agl.com.au>2022-05-23 12:50:08 +1000
committerSharath Nair <snair3@agl.com.au>2022-06-28 09:57:21 +1000
commit62c16e38a50c877bc5625ec18421983d29dbe096 (patch)
tree7523720ddc0b0b5e8f3d3e264a8fecfa3b326f5d
parentb103dc8da0d227178fa52f28a57db0fcdc6ebad4 (diff)
Extend azuredevops_serviceendpoint_azurerm to support managementGroup scopes
-rw-r--r--azuredevops/internal/acceptancetests/resource_serviceendpoint_azurerm_test.go48
-rw-r--r--azuredevops/internal/service/serviceendpoint/commons.go11
-rw-r--r--azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_azurerm.go111
-rw-r--r--website/docs/r/serviceendpoint_azurerm.html.markdown36
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.