Skip to content

Commit 714b998

Browse files
logachevNicholas M. Iodice
andauthored
Implement Automatic AzureRM Service Connection (#277)
* Add automatic option * Save * Fix tests * Add acc tests * Change case * Update docs * Add tests * Remove secret hash * Format * Fix acceptance test * Fix tests * Lint * Return hash * Lint * Add missing tests * Feedback Co-authored-by: Nicholas M. Iodice <niiodice@microsoft.com>
1 parent 6703184 commit 714b998

File tree

6 files changed

+327
-119
lines changed

6 files changed

+327
-119
lines changed

azuredevops/crud/serviceendpoint/crud_service_endpoint.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func genBaseSchema() map[string]*schema.Schema {
5454
Type: schema.TypeString,
5555
Required: true,
5656
ValidateFunc: validate.NoEmptyStrings,
57+
ForceNew: true,
5758
},
5859
"description": {
5960
Type: schema.TypeString,
Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package azuredevops
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
58
"github.com/microsoft/azure-devops-go-api/azuredevops/serviceendpoint"
69
crud "github.com/microsoft/terraform-provider-azuredevops/azuredevops/crud/serviceendpoint"
@@ -10,48 +13,116 @@ import (
1013

1114
func resourceServiceEndpointAzureRM() *schema.Resource {
1215
r := crud.GenBaseServiceEndpointResource(flattenServiceEndpointAzureRM, expandServiceEndpointAzureRM, parseImportedProjectIDAndServiceEndpointID)
13-
crud.MakeUnprotectedSchema(r, "azurerm_spn_clientid", "ARM_CLIENT_ID", "The service principal id which should be used.")
14-
crud.MakeProtectedSchema(r, "azurerm_spn_clientsecret", "ARM_CLIENT_SECRET", "The service principal secret which should be used.")
1516
crud.MakeUnprotectedSchema(r, "azurerm_spn_tenantid", "ARM_TENANT_ID", "The service principal tenant id which should be used.")
1617
crud.MakeUnprotectedSchema(r, "azurerm_subscription_id", "ARM_SUBSCRIPTION_ID", "The Azure subscription Id which should be used.")
1718
crud.MakeUnprotectedSchema(r, "azurerm_subscription_name", "ARM_SUBSCRIPTION_NAME", "The Azure subscription name which should be used.")
18-
crud.MakeUnprotectedSchema(r, "azurerm_scope", "ARM_SCOPE", "The Azure scope which should be used by the spn.")
19+
20+
r.Schema["resource_group"] = &schema.Schema{
21+
Type: schema.TypeString,
22+
Optional: true,
23+
ForceNew: true,
24+
Description: "Scope Resource Group",
25+
ConflictsWith: []string{"credentials"},
26+
}
27+
28+
secretHashKey, secretHashSchema := tfhelper.GenerateSecreteMemoSchema("serviceprincipalkey")
29+
r.Schema["credentials"] = &schema.Schema{
30+
Type: schema.TypeList,
31+
Optional: true,
32+
MaxItems: 1,
33+
ForceNew: true,
34+
ConflictsWith: []string{"resource_group"},
35+
Elem: &schema.Resource{
36+
Schema: map[string]*schema.Schema{
37+
"serviceprincipalid": {
38+
Type: schema.TypeString,
39+
Required: true,
40+
Description: "The service principal id which should be used.",
41+
},
42+
"serviceprincipalkey": {
43+
Type: schema.TypeString,
44+
Required: true,
45+
Description: "The service principal secret which should be used.",
46+
Sensitive: true,
47+
DiffSuppressFunc: tfhelper.DiffFuncSuppressSecretChanged,
48+
},
49+
secretHashKey: secretHashSchema,
50+
},
51+
},
52+
}
53+
1954
return r
2055
}
2156

2257
// Convert internal Terraform data structure to an AzDO data structure
2358
func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.ServiceEndpoint, *string) {
2459
serviceEndpoint, projectID := crud.DoBaseExpansion(d)
60+
61+
scope := fmt.Sprintf("/subscriptions/%s", d.Get("azurerm_subscription_id"))
62+
scopeLevel := "Subscription"
63+
if _, ok := d.GetOk("resource_group"); ok {
64+
scope += fmt.Sprintf("/resourcegroups/%s", d.Get("resource_group"))
65+
scopeLevel = "ResourceGroup"
66+
}
67+
2568
serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{
2669
Parameters: &map[string]string{
2770
"authenticationType": "spnKey",
28-
"scope": d.Get("azurerm_scope").(string),
29-
"serviceprincipalid": d.Get("azurerm_spn_clientid").(string),
30-
"serviceprincipalkey": d.Get("azurerm_spn_clientsecret").(string),
71+
"serviceprincipalid": "",
72+
"serviceprincipalkey": "",
3173
"tenantid": d.Get("azurerm_spn_tenantid").(string),
3274
},
3375
Scheme: converter.String("ServicePrincipal"),
3476
}
3577
serviceEndpoint.Data = &map[string]string{
36-
"creationMode": "Manual",
78+
"creationMode": "Automatic",
3779
"environment": "AzureCloud",
3880
"scopeLevel": "Subscription",
39-
"SubscriptionId": d.Get("azurerm_subscription_id").(string),
40-
"SubscriptionName": d.Get("azurerm_subscription_name").(string),
81+
"subscriptionId": d.Get("azurerm_subscription_id").(string),
82+
"subscriptionName": d.Get("azurerm_subscription_name").(string),
83+
}
84+
85+
if scopeLevel == "ResourceGroup" {
86+
(*serviceEndpoint.Authorization.Parameters)["scope"] = scope
87+
}
88+
89+
if _, ok := d.GetOk("credentials"); ok {
90+
credentials := d.Get("credentials").([]interface{})[0].(map[string]interface{})
91+
(*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"] = credentials["serviceprincipalid"].(string)
92+
(*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"] = credentials["serviceprincipalkey"].(string)
93+
(*serviceEndpoint.Data)["creationMode"] = "Manual"
4194
}
95+
4296
serviceEndpoint.Type = converter.String("azurerm")
4397
serviceEndpoint.Url = converter.String("https://management.azure.com/")
4498
return serviceEndpoint, projectID
4599
}
46100

101+
func flattenCredentials(serviceEndpoint *serviceendpoint.ServiceEndpoint, hashKey string, hashValue string) interface{} {
102+
return []map[string]interface{}{{
103+
"serviceprincipalid": (*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"],
104+
"serviceprincipalkey": (*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"],
105+
hashKey: hashValue,
106+
}}
107+
}
108+
47109
// Convert AzDO data structure to internal Terraform data structure
48110
func flattenServiceEndpointAzureRM(d *schema.ResourceData, serviceEndpoint *serviceendpoint.ServiceEndpoint, projectID *string) {
49111
crud.DoBaseFlattening(d, serviceEndpoint, projectID)
50-
d.Set("azurerm_scope", (*serviceEndpoint.Authorization.Parameters)["scope"])
51-
d.Set("azurerm_spn_clientid", (*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"])
52-
tfhelper.HelpFlattenSecret(d, "azurerm_spn_clientsecret")
112+
scope := (*serviceEndpoint.Authorization.Parameters)["scope"]
113+
114+
if (*serviceEndpoint.Data)["creationMode"] == "Manual" {
115+
newHash, hashKey := tfhelper.HelpFlattenSecretNested(d, "credentials", d.Get("credentials.0").(map[string]interface{}), "serviceprincipalkey")
116+
credentials := flattenCredentials(serviceEndpoint, hashKey, newHash)
117+
d.Set("credentials", credentials)
118+
}
119+
120+
s := strings.SplitN(scope, "/", -1)
121+
if len(s) == 5 {
122+
d.Set("resource_group", s[4])
123+
}
124+
53125
d.Set("azurerm_spn_tenantid", (*serviceEndpoint.Authorization.Parameters)["tenantid"])
54-
d.Set("azurerm_spn_clientsecret", (*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"])
55-
d.Set("azurerm_subscription_id", (*serviceEndpoint.Data)["SubscriptionId"])
56-
d.Set("azurerm_subscription_name", (*serviceEndpoint.Data)["SubscriptionName"])
126+
d.Set("azurerm_subscription_id", (*serviceEndpoint.Data)["subscriptionId"])
127+
d.Set("azurerm_subscription_name", (*serviceEndpoint.Data)["subscriptionName"])
57128
}

0 commit comments

Comments
 (0)