Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ func TestServiceEndpointAws_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData.Set("project_id", (*awsTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointAws(resourceData, &awsTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointAws(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointAws(resourceData)

require.Equal(t, awsTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, awsTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
require.Nil(t, err)
}

// verifies that if an error is produced on create, the error is not swallowed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ func TestServiceEndpointDockerRegistry_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData.Set("project_id", (*dockerRegistryTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointDockerRegistry(resourceData, &dockerRegistryTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointDockerRegistry(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointDockerRegistry(resourceData)

require.Equal(t, dockerRegistryTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, dockerRegistryTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
require.Nil(t, err)
}

// verifies that if an error is produced on create, the error is not swallowed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ func TestServiceEndpointExternalTFS_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData,
&externalTfsTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointExternalTFS(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointExternalTFS(resourceData)

require.Nil(t, err)
require.Equal(t, externalTfsTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, externalTfsTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ func TestServiceEndpointGcp_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData.Set("project_id", (*gcpForTerraformTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointGcp(resourceData, &gcpForTerraformTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointGcp(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointGcp(resourceData)

require.Equal(t, gcpForTerraformTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, gcpForTerraformTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
require.Nil(t, err)
}

// verifies that if an error is produced on create, the error is not swallowed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ func TestServiceEndpointGitHubEnterprise_ExpandFlatten_Roundtrip(t *testing.T) {
configureGhesAuthPersonal(resourceData)
flattenServiceEndpointGitHubEnterprise(resourceData, &ghesTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointGitHubEnterprise(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointGitHubEnterprise(resourceData)

require.Nil(t, err)
require.Equal(t, ghesTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, ghesTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ func TestServiceEndpointGitHub_ExpandFlatten_Roundtrip(t *testing.T) {
configureAuthPersonal(resourceData)
flattenServiceEndpointGitHub(resourceData, &ghTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointGitHub(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointGitHub(resourceData)

require.Nil(t, err)
require.Equal(t, ghTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, ghTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ func TestServiceEndpointIncomingWebhook_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData.Set("project_id", (*incomingWebhookTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointIncomingWebhook(resourceData, &incomingWebhookTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointIncomingWebhook(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointIncomingWebhook(resourceData)

require.Equal(t, incomingWebhookTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, incomingWebhookTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
require.Nil(t, err)
}

// verifies that if an error is produced on create, the error is not swallowed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ func testServiceEndpointJenkins_ExpandFlatten_Roundtrip(t *testing.T, ep *servic
resourceData.Set("project_id", (*se.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointJenkins(resourceData, se)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointJenkins(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointJenkins(resourceData)

require.Nil(t, err)
require.Equal(t, *se, *serviceEndpointAfterRoundTrip)
require.Equal(t, id, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
}
Expand All @@ -84,7 +83,7 @@ func TestServiceEndpointJenkins_Create_DoesNotSwallowErrorPassword(t *testing.T)
buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl)
clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()}

seJenkins, _ := expandServiceEndpointJenkins(resourceData)
seJenkins := expandServiceEndpointJenkins(resourceData)
buildClient.
EXPECT().
CreateServiceEndpoint(clients.Ctx, serviceendpoint.CreateServiceEndpointArgs{Endpoint: seJenkins}).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ func testServiceEndpointNexus_ExpandFlatten_Roundtrip(t *testing.T, ep *servicee
resourceData.Set("project_id", (*ep.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointNexus(resourceData, ep)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointNexus(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointNexus(resourceData)

require.Nil(t, err)
require.Equal(t, *ep, *serviceEndpointAfterRoundTrip)
require.Equal(t, id, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ func TestServiceEndpointNpm_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData.Set("project_id", (*npmTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String())
flattenServiceEndpointNpm(resourceData, &npmTestServiceEndpoint)

serviceEndpointAfterRoundTrip, err := expandServiceEndpointNpm(resourceData)
serviceEndpointAfterRoundTrip := expandServiceEndpointNpm(resourceData)

require.Equal(t, npmTestServiceEndpoint, *serviceEndpointAfterRoundTrip)
require.Equal(t, npmTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id)
require.Nil(t, err)
}

// verifies that if an error is produced on create, the error is not swallowed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
package serviceendpoint

import (
"context"
"fmt"
"maps"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/serviceendpoint"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/tfhelper"
)

// ResourceServiceEndpointPowerPlatform schema and implementation for PowerPlatform service endpoint resource
func ResourceServiceEndpointPowerPlatform() *schema.Resource {
r := &schema.Resource{
CreateContext: resourceServiceEndpointPowerPlatformCreate,
ReadContext: resourceServiceEndpointPowerPlatformRead,
UpdateContext: resourceServiceEndpointPowerPlatformUpdate,
DeleteContext: resourceServiceEndpointPowerPlatformDelete,
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(2 * time.Minute),
Read: schema.DefaultTimeout(1 * time.Minute),
Update: schema.DefaultTimeout(2 * time.Minute),
Delete: schema.DefaultTimeout(2 * time.Minute),
},
Importer: tfhelper.ImportProjectQualifiedResourceUUID(),
Schema: baseSchema(),
}

maps.Copy(r.Schema, map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
Required: true,
Description: "The Server URL for the Power Platform connection (e.g. https://org.crm.dynamics.com or generic)",
ValidateFunc: validation.IsURLWithScheme([]string{"http", "https"}),
},
"credentials": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"serviceprincipalid": {
Type: schema.TypeString,
Required: true,
Description: "The Application (Client) ID of the Service Principal.",
ValidateFunc: validation.IsUUID,
},
"serviceprincipalkey": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "The Client Secret of the Service Principal.",
ValidateFunc: validation.StringIsNotEmpty,
},
"tenant_id": {
Type: schema.TypeString,
Required: true,
Description: "The Tenant ID.",
ValidateFunc: validation.IsUUID,
},
},
},
},
"features": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"validate": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Whether or not to validate connection with Azure after create or update operations.",
},
},
},
},
})

return r
}

func resourceServiceEndpointPowerPlatformCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clients := m.(*client.AggregatedClient)
serviceEndpoint, err := expandServiceEndpointPowerPlatform(d)
if err != nil {
return diag.Errorf(errMsgTfConfigRead, err)
}

resp, err := createServiceEndpoint(d, clients, serviceEndpoint)
if err != nil {
return diag.Errorf("creating service endpoint in Azure DevOps: %+v", err)
}

d.SetId(resp.Id.String())

if v, ok := d.GetOk("features"); ok {
features := v.([]interface{})[0].(map[string]interface{})
if features["validate"].(bool) {
projectID := d.Get("project_id").(string)
err = validateServiceEndpoint(clients, resp, projectID, 60*time.Second)
if err != nil {
if delErr := deleteServiceEndpoint(clients, resp, d.Timeout(schema.TimeoutDelete)); delErr != nil {
return diag.Errorf("Error validating service endpoint and failed to delete it: %+v", err)
}
return diag.Errorf("Error validating service endpoint: %+v", err)
}
}
}

return resourceServiceEndpointPowerPlatformRead(clients.Ctx, d, m)
}

func resourceServiceEndpointPowerPlatformRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clients := m.(*client.AggregatedClient)
getArgs, err := serviceEndpointGetArgs(d)
if err != nil {
return diag.Errorf("reading service endpoint in Azure DevOps: %+v", err)
}

serviceEndpoint, err := clients.ServiceEndpointClient.GetServiceEndpointDetails(clients.Ctx, *getArgs)
if isServiceEndpointDeleted(d, err, serviceEndpoint, getArgs) {
return nil
}
if err != nil {
return diag.Errorf("looking up service endpoint given ID (%s) and project ID (%s): %v", getArgs.EndpointId, *getArgs.Project, err)
}

flattenServiceEndpointPowerPlatform(d, serviceEndpoint)
return nil
}

func resourceServiceEndpointPowerPlatformUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clients := m.(*client.AggregatedClient)
serviceEndpoint, err := expandServiceEndpointPowerPlatform(d)
if err != nil {
return diag.Errorf(errMsgTfConfigRead, err)
}

resp, err := updateServiceEndpoint(clients, serviceEndpoint)
if err != nil {
return diag.Errorf("updating service endpoint in Azure DevOps: %+v", err)
}

if v, ok := d.GetOk("features"); ok {
features := v.([]interface{})[0].(map[string]interface{})
if features["validate"].(bool) {
projectID := d.Get("project_id").(string)
err = validateServiceEndpoint(clients, resp, projectID, 60*time.Second)
if err != nil {
return diag.Errorf("Error validating service endpoint: %+v", err)
}
}
}

return resourceServiceEndpointPowerPlatformRead(clients.Ctx, d, m)
}

func resourceServiceEndpointPowerPlatformDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
clients := m.(*client.AggregatedClient)
serviceEndpoint, err := expandServiceEndpointPowerPlatform(d)
if err != nil {
return diag.Errorf(errMsgTfConfigRead, err)
}

err = deleteServiceEndpoint(clients, serviceEndpoint, d.Timeout(schema.TimeoutDelete))
if err != nil {
return diag.Errorf(" Deleting service endpoint in Azure DevOps: %+v", err)
}
return nil
}

func expandServiceEndpointPowerPlatform(d *schema.ResourceData) (*serviceendpoint.ServiceEndpoint, error) {
serviceEndpoint := doBaseExpansion(d)

serviceEndpoint.Type = converter.String("powerplatform-spn")

if v, ok := d.GetOk("url"); ok {
serviceEndpoint.Url = converter.String(v.(string))
} else {
return nil, fmt.Errorf("url is required for PowerPlatform service endpoint")
}

var credentials map[string]any
if v, ok := d.GetOk("credentials"); ok && len(v.([]any)) > 0 {
credentials = v.([]any)[0].(map[string]any)
} else {
return nil, fmt.Errorf("credentials block is required for PowerPlatform service endpoint")
}

parameters := map[string]string{
"tenantId": credentials["tenant_id"].(string),
"applicationId": credentials["serviceprincipalid"].(string),
"clientSecret": credentials["serviceprincipalkey"].(string),
}

serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{
Scheme: converter.String("None"),
Parameters: &parameters,
}

serviceEndpoint.Data = &map[string]string{}

return serviceEndpoint, nil
}

func flattenServiceEndpointPowerPlatform(d *schema.ResourceData, serviceEndpoint *serviceendpoint.ServiceEndpoint) {
doBaseFlattening(d, serviceEndpoint)

credentials := make(map[string]any)

if serviceEndpoint.Authorization != nil && serviceEndpoint.Authorization.Parameters != nil {
params := *serviceEndpoint.Authorization.Parameters

if v, ok := params["tenantId"]; ok {
credentials["tenant_id"] = v
}

if v, ok := params["applicationId"]; ok {
credentials["serviceprincipalid"] = v
}

if oldCredentials, ok := d.Get("credentials").([]any); ok && len(oldCredentials) > 0 {
oldMap := oldCredentials[0].(map[string]any)
credentials["serviceprincipalkey"] = oldMap["serviceprincipalkey"]
}
}

if serviceEndpoint.Url != nil {
d.Set("url", *serviceEndpoint.Url)
}

d.Set("credentials", []any{credentials})
}
Loading
Loading