Skip to content

Commit

Permalink
Implementation of AzureRM End point (service connection) (microsoft#225)
Browse files Browse the repository at this point in the history
* Add AzureRM service endpoint implementation 

Co-authored-by: Nicholas M. Iodice <[email protected]>
  • Loading branch information
mikaelkrief and Nicholas M. Iodice authored Feb 3, 2020
1 parent 2fca2d2 commit edc5411
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 12 deletions.
26 changes: 26 additions & 0 deletions azuredevops/crud/serviceendpoint/crud_service_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/microsoft/azure-devops-go-api/azuredevops/serviceendpoint"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/config"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/tfhelper"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/validate"
)

Expand Down Expand Up @@ -105,6 +106,31 @@ func GetScheme(d *schema.ResourceData) (string, error) {
return scheme, nil
}

// MakeProtectedSchema create protected schema
func MakeProtectedSchema(r *schema.Resource, keyName, envVarName, description string) {
r.Schema[keyName] = &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc(envVarName, nil),
Description: description,
Sensitive: true,
DiffSuppressFunc: tfhelper.DiffFuncSuppressSecretChanged,
}

secretHashKey, secretHashSchema := tfhelper.GenerateSecreteMemoSchema(keyName)
r.Schema[secretHashKey] = secretHashSchema
}

// MakeUnprotectedSchema create unprotected schema
func MakeUnprotectedSchema(r *schema.Resource, keyName, envVarName, description string) {
r.Schema[keyName] = &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc(envVarName, nil),
Description: description,
}
}

// Make the Azure DevOps API call to create the endpoint
func createServiceEndpoint(clients *config.AggregatedClient, endpoint *serviceendpoint.ServiceEndpoint, project *string) (*serviceendpoint.ServiceEndpoint, error) {
if *endpoint.Type == "github" && *endpoint.Authorization.Scheme == "InstallationToken" {
Expand Down
1 change: 1 addition & 0 deletions azuredevops/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func Provider() *schema.Provider {
"azuredevops_variable_group": resourceVariableGroup(),
"azuredevops_serviceendpoint_github": resourceServiceEndpointGitHub(),
"azuredevops_serviceendpoint_dockerhub": resourceServiceEndpointDockerHub(),
"azuredevops_serviceendpoint_azurerm": resourceServiceEndpointAzureRM(),
"azuredevops_azure_git_repository": resourceAzureGitRepository(),
"azuredevops_user_entitlement": resourceUserEntitlement(),
"azuredevops_group_membership": resourceGroupMembership(),
Expand Down
1 change: 1 addition & 0 deletions azuredevops/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestAzureDevOpsProvider_HasChildResources(t *testing.T) {
"azuredevops_project",
"azuredevops_serviceendpoint_github",
"azuredevops_serviceendpoint_dockerhub",
"azuredevops_serviceendpoint_azurerm",
"azuredevops_variable_group",
"azuredevops_azure_git_repository",
"azuredevops_user_entitlement",
Expand Down
10 changes: 5 additions & 5 deletions azuredevops/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func resourceProjectRead(d *schema.ResourceData, m interface{}) error {

id := d.Id()
name := d.Get("project_name").(string)
project, err := projectRead(clients, id, name)
project, err := ProjectRead(clients, id, name)
if err != nil {
return fmt.Errorf("Error looking up project with ID %s and Name %s", id, name)
}
Expand All @@ -153,10 +153,10 @@ func resourceProjectRead(d *schema.ResourceData, m interface{}) error {
return nil
}

// Lookup a project using the ID, or name if the ID is not set. Note, usage of the name in place
// ProjectRead Lookup a project using the ID, or name if the ID is not set. Note, usage of the name in place
// of the ID is an explicitly stated supported behavior:
// https://docs.microsoft.com/en-us/rest/api/azure/devops/core/projects/get?view=azure-devops-rest-5.0
func projectRead(clients *config.AggregatedClient, projectID string, projectName string) (*core.TeamProject, error) {
func ProjectRead(clients *config.AggregatedClient, projectID string, projectName string) (*core.TeamProject, error) {
identifier := projectID
if identifier == "" {
identifier = projectName
Expand Down Expand Up @@ -338,7 +338,7 @@ func ParseImportedProjectIDAndID(clients *config.AggregatedClient, id string) (s
}

// Get the project ID
currentProject, err := projectRead(clients, project, project)
currentProject, err := ProjectRead(clients, project, project)
if err != nil {
return "", 0, err
}
Expand All @@ -354,7 +354,7 @@ func ParseImportedProjectIDAndUUID(clients *config.AggregatedClient, id string)
}

// Get the project ID
currentProject, err := projectRead(clients, project, project)
currentProject, err := ProjectRead(clients, project, project)
if err != nil {
return "", "", err
}
Expand Down
8 changes: 4 additions & 4 deletions azuredevops/resource_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func TestAzureDevOpsProject_ProjectRead_UsesIdIfSet(t *testing.T) {
}).
Times(1)

projectRead(clients, id, name)
ProjectRead(clients, id, name)
}

// verifies that the project name is used for reads if the ID is not set
Expand All @@ -267,7 +267,7 @@ func TestAzureDevOpsProject_ProjectRead_UsesNameIfIdNotSet(t *testing.T) {
}).
Times(1)

projectRead(clients, id, name)
ProjectRead(clients, id, name)
}

// creates an operation given a status
Expand Down Expand Up @@ -340,7 +340,7 @@ func testAccCheckProjectResourceExists(expectedName string) resource.TestCheckFu

clients := testAccProvider.Meta().(*config.AggregatedClient)
id := resource.Primary.ID
project, err := projectRead(clients, id, "")
project, err := ProjectRead(clients, id, "")

if err != nil {
return fmt.Errorf("Project with ID=%s cannot be found!. Error=%v", id, err)
Expand Down Expand Up @@ -369,7 +369,7 @@ func testAccProjectCheckDestroy(s *terraform.State) error {
id := resource.Primary.ID

// indicates the project still exists - this should fail the test
if _, err := projectRead(clients, id, ""); err == nil {
if _, err := ProjectRead(clients, id, ""); err == nil {
return fmt.Errorf("project with ID %s should not exist", id)
}
}
Expand Down
57 changes: 57 additions & 0 deletions azuredevops/resource_serviceendpoint_azurerm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package azuredevops

import (
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/microsoft/azure-devops-go-api/azuredevops/serviceendpoint"
crud "github.com/microsoft/terraform-provider-azuredevops/azuredevops/crud/serviceendpoint"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/tfhelper"
)

func resourceServiceEndpointAzureRM() *schema.Resource {
r := crud.GenBaseServiceEndpointResource(flattenServiceEndpointAzureRM, expandServiceEndpointAzureRM, parseImportedProjectIDAndServiceEndpointID)
crud.MakeUnprotectedSchema(r, "azurerm_spn_clientid", "ARM_CLIENT_ID", "The service principal id which should be used.")
crud.MakeProtectedSchema(r, "azurerm_spn_clientsecret", "ARM_CLIENT_SECRET", "The service principal secret which should be used.")
crud.MakeUnprotectedSchema(r, "azurerm_spn_tenantid", "ARM_TENANT_ID", "The service principal tenant id which should be used.")
crud.MakeUnprotectedSchema(r, "azurerm_subscription_id", "ARM_SUBSCRIPTION_ID", "The Azure subscription Id which should be used.")
crud.MakeUnprotectedSchema(r, "azurerm_subscription_name", "ARM_SUBSCRIPTION_NAME", "The Azure subscription name which should be used.")
crud.MakeUnprotectedSchema(r, "azurerm_scope", "ARM_SCOPE", "The Azure scope which should be used by the spn.")
return r
}

// Convert internal Terraform data structure to an AzDO data structure
func expandServiceEndpointAzureRM(d *schema.ResourceData) (*serviceendpoint.ServiceEndpoint, *string) {
serviceEndpoint, projectID := crud.DoBaseExpansion(d)
serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{
Parameters: &map[string]string{
"authenticationType": "spnKey",
"scope": d.Get("azurerm_scope").(string),
"serviceprincipalid": d.Get("azurerm_spn_clientid").(string),
"serviceprincipalkey": d.Get("azurerm_spn_clientsecret").(string),
"tenantid": d.Get("azurerm_spn_tenantid").(string),
},
Scheme: converter.String("ServicePrincipal"),
}
serviceEndpoint.Data = &map[string]string{
"creationMode": "Manual",
"environment": "AzureCloud",
"scopeLevel": "Subscription",
"SubscriptionId": d.Get("azurerm_subscription_id").(string),
"SubscriptionName": d.Get("azurerm_subscription_name").(string),
}
serviceEndpoint.Type = converter.String("azurerm")
serviceEndpoint.Url = converter.String("https://management.azure.com/")
return serviceEndpoint, projectID
}

// Convert AzDO data structure to internal Terraform data structure
func flattenServiceEndpointAzureRM(d *schema.ResourceData, serviceEndpoint *serviceendpoint.ServiceEndpoint, projectID *string) {
crud.DoBaseFlattening(d, serviceEndpoint, projectID)
d.Set("azurerm_scope", (*serviceEndpoint.Authorization.Parameters)["scope"])
d.Set("azurerm_spn_clientid", (*serviceEndpoint.Authorization.Parameters)["serviceprincipalid"])
tfhelper.HelpFlattenSecret(d, "azurerm_spn_clientsecret")
d.Set("azurerm_spn_tenantid", (*serviceEndpoint.Authorization.Parameters)["tenantid"])
d.Set("azurerm_spn_clientsecret", (*serviceEndpoint.Authorization.Parameters)["serviceprincipalkey"])
d.Set("azurerm_subscription_id", (*serviceEndpoint.Data)["SubscriptionId"])
d.Set("azurerm_subscription_name", (*serviceEndpoint.Data)["SubscriptionName"])
}
Loading

0 comments on commit edc5411

Please sign in to comment.