From a0fd8d9cbf5d64cbd4ca9427d4ff14b2daf3c0dd Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Tue, 25 Nov 2025 13:25:27 +0100 Subject: [PATCH 1/6] To be able to create Power Platform Service Connection via Terraform #738 --- .../resource_serviceendpoint_aws_test.go | 3 +- ...rce_serviceendpoint_dockerregistry_test.go | 3 +- ...source_serviceendpoint_externaltfs_test.go | 3 +- ...urce_serviceendpoint_gcp_terraform_test.go | 3 +- ..._serviceendpoint_github_enterprise_test.go | 3 +- .../resource_serviceendpoint_github_test.go | 3 +- ...ce_serviceendpoint_incomingwebhook_test.go | 3 +- .../resource_serviceendpoint_jenkins_test.go | 5 +- .../resource_serviceendpoint_nexus_test.go | 3 +- .../resource_serviceendpoint_npm_test.go | 3 +- .../resource_serviceendpoint_powerplatform.go | 200 ++++++++++++ ...urce_serviceendpoint_powerplatform_test.go | 285 ++++++++++++++++++ ...source_serviceendpoint_runpipeline_test.go | 3 +- ...resource_serviceendpoint_sonarqube_test.go | 3 +- azuredevops/provider.go | 1 + azuredevops/provider_test.go | 1 + ...erviceendpoint_powerplatform.html.markdown | 80 +++++ 17 files changed, 580 insertions(+), 25 deletions(-) create mode 100644 azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go create mode 100644 azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go create mode 100644 website/docs/r/serviceendpoint_powerplatform.html.markdown diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_aws_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_aws_test.go index 6c8ad9d9c..5afcf6f43 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_aws_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_aws_test.go @@ -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 diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_dockerregistry_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_dockerregistry_test.go index 022b42707..dc24b2332 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_dockerregistry_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_dockerregistry_test.go @@ -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 diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go index 07eafc7b4..9036f139b 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go @@ -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) } diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_gcp_terraform_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_gcp_terraform_test.go index b66695470..b2f18b546 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_gcp_terraform_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_gcp_terraform_test.go @@ -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 diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_enterprise_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_enterprise_test.go index a071328e5..01170dcd5 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_enterprise_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_enterprise_test.go @@ -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) } diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_test.go index 2e9519d8e..96f45f685 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_github_test.go @@ -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) } diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_incomingwebhook_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_incomingwebhook_test.go index e90ebfafe..0cd9ed134 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_incomingwebhook_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_incomingwebhook_test.go @@ -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 diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_jenkins_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_jenkins_test.go index 18dfd9989..52bd97843 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_jenkins_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_jenkins_test.go @@ -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) } @@ -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}). diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_nexus_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_nexus_test.go index 139d8c363..7af804b85 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_nexus_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_nexus_test.go @@ -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) } diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_npm_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_npm_test.go index 78f12e96a..8b2990875 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_npm_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_npm_test.go @@ -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 diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go new file mode 100644 index 000000000..384269f99 --- /dev/null +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go @@ -0,0 +1,200 @@ +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, + Optional: 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, + }, + "tenantId": { + Type: schema.TypeString, + Required: true, + Description: "The Tenant ID.", + ValidateFunc: validation.IsUUID, + }, + }, + }, + }, + }) + + 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()) + 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) + } + + _, err = updateServiceEndpoint(clients, serviceEndpoint) + if err != nil { + return diag.Errorf("updating service endpoint in Azure DevOps: %+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["tenantId"].(string), + "applicationId": credentials["serviceprincipalid"].(string), + "clientSecret": credentials["serviceprincipalkey"].(string), + } + + serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{ + Scheme: converter.String("None"), + Parameters: ¶meters, + } + + 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["tenantId"] = 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}) +} diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go new file mode 100644 index 000000000..9aa648ef2 --- /dev/null +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go @@ -0,0 +1,285 @@ +//go:build (all || resource_serviceendpoint_powerplatform) && !exclude_serviceendpoints +// +build all resource_serviceendpoint_powerplatform +// +build !exclude_serviceendpoints + +package serviceendpoint + +import ( + "context" + "errors" + "testing" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/serviceendpoint" + "github.com/microsoft/terraform-provider-azuredevops/azdosdkmocks" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +var ( + powerplatformTestServiceEndpointID = uuid.New() + powerplatformRandomServiceEndpointProjectID = uuid.New() + powerplatformTestServiceEndpointProjectID = &powerplatformRandomServiceEndpointProjectID +) + +// getTestServiceEndpointPowerPlatform returns a valid PowerPlatform service endpoint struct +// matching the "powerplatform-spn" type and "None" scheme structure. +func getTestServiceEndpointPowerPlatform() serviceendpoint.ServiceEndpoint { + return serviceendpoint.ServiceEndpoint{ + Authorization: &serviceendpoint.EndpointAuthorization{ + Parameters: &map[string]string{ + "tenantId": "aba07645-051c-44b4-b806-c34d33f3dcd1", // fake tenant + "applicationId": "e31eaaac-47da-4156-b433-9b0538c94b7e", // fake app ID + "clientSecret": "supersecretkey", // fake secret + }, + Scheme: converter.String("None"), + }, + Data: &map[string]string{}, // Empty data as per requirement + Id: &powerplatformTestServiceEndpointID, + Name: converter.String("_POWERPLATFORM_UNIT_TEST_CONN_NAME"), + Type: converter.String("powerplatform-spn"), + Url: converter.String("https://org.crm.dynamics.com/"), + ServiceEndpointProjectReferences: &[]serviceendpoint.ServiceEndpointProjectReference{ + { + ProjectReference: &serviceendpoint.ProjectReference{ + Id: powerplatformTestServiceEndpointProjectID, + }, + Name: converter.String("_POWERPLATFORM_UNIT_TEST_CONN_NAME"), + Description: converter.String("_POWERPLATFORM_UNIT_TEST_CONN_DESCRIPTION"), + }, + }, + } +} + +var powerplatformTestServiceEndpoints = []serviceendpoint.ServiceEndpoint{ + getTestServiceEndpointPowerPlatform(), +} + +// verifies that the flatten/expand round trip yields the same service endpoint +func TestServiceEndpointPowerPlatform_ExpandFlatten_Roundtrip(t *testing.T) { + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + serviceEndpointAfterRoundTrip, err := expandServiceEndpointPowerPlatform(resourceData) + require.Nil(t, err) + + require.Equal(t, *resource.Authorization.Parameters, *serviceEndpointAfterRoundTrip.Authorization.Parameters) + require.Equal(t, resource.Url, serviceEndpointAfterRoundTrip.Url) + require.Equal(t, resource.Type, serviceEndpointAfterRoundTrip.Type) + require.Equal(t, powerplatformTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id) + } +} + +// verifies that if an error is produced on create, the error is not swallowed +func TestServiceEndpointPowerPlatform_Create_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointPowerPlatform() + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.CreateServiceEndpointArgs{Endpoint: &resource} + + buildClient. + EXPECT(). + CreateServiceEndpoint(clients.Ctx, expectedArgs). + Return(nil, errors.New("CreateServiceEndpoint() Failed")). + Times(1) + + err := r.Create(resourceData, clients) + require.Contains(t, err.Error(), "CreateServiceEndpoint() Failed") + } +} + +// verifies that if validation is enabled and fails, the error is returned and endpoint deleted +func TestServiceEndpointPowerPlatform_CreateWithValidate_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointPowerPlatform() + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + buildClient. + EXPECT(). + CreateServiceEndpoint(clients.Ctx, serviceendpoint.CreateServiceEndpointArgs{Endpoint: &resource}). + Return(&resource, nil). + Times(1) + + returnedServiceEndpoint := resource + returnedServiceEndpoint.IsReady = converter.Bool(true) + buildClient. + EXPECT(). + GetServiceEndpointDetails(clients.Ctx, serviceendpoint.GetServiceEndpointDetailsArgs{ + Project: converter.String(powerplatformRandomServiceEndpointProjectID.String()), + EndpointId: resource.Id, + }, + ). + Return(&returnedServiceEndpoint, nil). + Times(1) + + reqArgs := genExecuteServiceEndpointArgsPowerPlatform(&resource) + buildClient. + EXPECT(). + ExecuteServiceEndpointRequest(clients.Ctx, *reqArgs). + Return(nil, errors.New("ExecuteServiceEndpointRequest() Failed")). + Times(1) + + buildClient. + EXPECT(). + DeleteServiceEndpoint(clients.Ctx, serviceendpoint.DeleteServiceEndpointArgs{ + ProjectIds: &[]string{powerplatformTestServiceEndpointProjectID.String()}, EndpointId: resource.Id, + }). + Return(nil). + Times(1) + + err := r.Create(resourceData, clients) + require.Contains(t, err.Error(), "ExecuteServiceEndpointRequest() Failed") + } +} + +// verifies that if an error is produced on a read, it is not swallowed +func TestServiceEndpointPowerPlatform_Read_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointPowerPlatform() + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.GetServiceEndpointDetailsArgs{ + EndpointId: resource.Id, + Project: converter.String(powerplatformTestServiceEndpointProjectID.String()), + } + + buildClient. + EXPECT(). + GetServiceEndpointDetails(clients.Ctx, expectedArgs). + Return(nil, errors.New("GetServiceEndpoint() Failed")). + Times(1) + + err := r.Read(resourceData, clients) + require.Contains(t, err.Error(), "GetServiceEndpoint() Failed") + } +} + +// verifies that if an error is produced on an update, it is not swallowed +func TestServiceEndpointPowerPlatform_Update_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointPowerPlatform() + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.UpdateServiceEndpointArgs{ + Endpoint: &resource, + EndpointId: resource.Id, + } + + buildClient. + EXPECT(). + UpdateServiceEndpoint(clients.Ctx, expectedArgs). + Return(nil, errors.New("UpdateServiceEndpoint() Failed")). + Times(1) + + err := r.Update(resourceData, clients) + require.Contains(t, err.Error(), "UpdateServiceEndpoint() Failed") + } +} + +// verifies that if an error is produced on a delete, it is not swallowed +func TestServiceEndpointPowerPlatform_Delete_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointPowerPlatform() + for _, resource := range powerplatformTestServiceEndpoints { + resourceData := getResourceDataPowerPlatform(t, resource) + flattenServiceEndpointPowerPlatform(resourceData, &resource) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.DeleteServiceEndpointArgs{ + EndpointId: resource.Id, + ProjectIds: &[]string{ + powerplatformTestServiceEndpointProjectID.String(), + }, + } + + buildClient. + EXPECT(). + DeleteServiceEndpoint(clients.Ctx, expectedArgs). + Return(errors.New("DeleteServiceEndpoint() Failed")). + Times(1) + + err := r.Delete(resourceData, clients) + require.Contains(t, err.Error(), "DeleteServiceEndpoint() Failed") + } +} + +// Helper to create ResourceData with the correct fields for PowerPlatform +func getResourceDataPowerPlatform(t *testing.T, resource serviceendpoint.ServiceEndpoint) *schema.ResourceData { + resourceData := schema.TestResourceDataRaw(t, ResourceServiceEndpointPowerPlatform().Schema, nil) + + resourceData.Set("project_id", (*resource.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String()) + + if resource.Url != nil { + resourceData.Set("url", *resource.Url) + } + + params := *resource.Authorization.Parameters + credentials := []interface{}{ + map[string]interface{}{ + "serviceprincipalid": params["applicationId"], + "serviceprincipalkey": params["clientSecret"], + "tenantId": params["tenantId"], + }, + } + resourceData.Set("credentials", credentials) + + return resourceData +} + +// Helper to generate execution args for validation mocks +func genExecuteServiceEndpointArgsPowerPlatform(endpoint *serviceendpoint.ServiceEndpoint) *serviceendpoint.ExecuteServiceEndpointRequestArgs { + return &serviceendpoint.ExecuteServiceEndpointRequestArgs{ + ServiceEndpointRequest: &serviceendpoint.ServiceEndpointRequest{ + DataSourceDetails: &serviceendpoint.DataSourceDetails{ + DataSourceName: converter.String("TestConnection"), + }, + ResultTransformationDetails: &serviceendpoint.ResultTransformationDetails{}, + ServiceEndpointDetails: &serviceendpoint.ServiceEndpointDetails{ + Data: endpoint.Data, + Authorization: endpoint.Authorization, + Url: endpoint.Url, + Type: endpoint.Type, + }, + }, + Project: converter.String((*endpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String()), + EndpointId: converter.String(endpoint.Id.String()), + } +} diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_runpipeline_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_runpipeline_test.go index 61a60373e..462636025 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_runpipeline_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_runpipeline_test.go @@ -60,9 +60,8 @@ func TestServiceEndpointRunPipeline_ExpandFlatten_Roundtrip(t *testing.T) { rpConfigureExtraFields(resourceData) flattenServiceEndpointRunPipeline(resourceData, &rpTestServiceEndpoint) - serviceEndpointAfterRoundTrip, err := expandServiceEndpointRunPipeline(resourceData) + serviceEndpointAfterRoundTrip := expandServiceEndpointRunPipeline(resourceData) - require.Nil(t, err) require.Equal(t, rpTestServiceEndpoint, *serviceEndpointAfterRoundTrip) require.Equal(t, rpTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id) } diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_sonarqube_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_sonarqube_test.go index e9dea9ce4..c46a77960 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_sonarqube_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_sonarqube_test.go @@ -55,11 +55,10 @@ func TestServiceEndpointSonarQube_ExpandFlatten_Roundtrip(t *testing.T) { resourceData.Set("project_id", (*sonarQubeTestServiceEndpoint.ServiceEndpointProjectReferences)[0].ProjectReference.Id.String()) flattenServiceEndpointSonarQube(resourceData, &sonarQubeTestServiceEndpoint) - serviceEndpointAfterRoundTrip, err := expandServiceEndpointSonarQube(resourceData) + serviceEndpointAfterRoundTrip := expandServiceEndpointSonarQube(resourceData) require.Equal(t, sonarQubeTestServiceEndpoint, *serviceEndpointAfterRoundTrip) require.Equal(t, sonarQubeTestServiceEndpointProjectID, (*serviceEndpointAfterRoundTrip.ServiceEndpointProjectReferences)[0].ProjectReference.Id) - require.Nil(t, err) } // verifies that if an error is produced on create, the error is not swallowed diff --git a/azuredevops/provider.go b/azuredevops/provider.go index 5cc3afdc4..67877c389 100644 --- a/azuredevops/provider.go +++ b/azuredevops/provider.go @@ -127,6 +127,7 @@ func Provider() *schema.Provider { "azuredevops_serviceendpoint_octopusdeploy": serviceendpoint.ResourceServiceEndpointOctopusDeploy(), "azuredevops_serviceendpoint_openshift": serviceendpoint.ResourceServiceEndpointOpenshift(), "azuredevops_serviceendpoint_permissions": permissions.ResourceServiceEndpointPermissions(), + "azuredevops_serviceendpoint_powerplatform": serviceendpoint.ResourceServiceEndpointPowerPlatform(), "azuredevops_serviceendpoint_runpipeline": serviceendpoint.ResourceServiceEndpointRunPipeline(), "azuredevops_serviceendpoint_servicefabric": serviceendpoint.ResourceServiceEndpointServiceFabric(), "azuredevops_serviceendpoint_snyk": serviceendpoint.ResourceServiceEndpointSnyk(), diff --git a/azuredevops/provider_test.go b/azuredevops/provider_test.go index 05fac602b..13ca33e70 100644 --- a/azuredevops/provider_test.go +++ b/azuredevops/provider_test.go @@ -97,6 +97,7 @@ func TestProvider_HasChildResources(t *testing.T) { "azuredevops_serviceendpoint_octopusdeploy", "azuredevops_serviceendpoint_openshift", "azuredevops_serviceendpoint_permissions", + "azuredevops_serviceendpoint_powerplatform", "azuredevops_serviceendpoint_runpipeline", "azuredevops_serviceendpoint_servicefabric", "azuredevops_serviceendpoint_snyk", diff --git a/website/docs/r/serviceendpoint_powerplatform.html.markdown b/website/docs/r/serviceendpoint_powerplatform.html.markdown new file mode 100644 index 000000000..8d04c96aa --- /dev/null +++ b/website/docs/r/serviceendpoint_powerplatform.html.markdown @@ -0,0 +1,80 @@ +--- +layout: "azuredevops" +page_title: "AzureDevops: azuredevops_serviceendpoint_powerplatform" +description: |- + Manages a PowerPlatform Service Endpoint. +--- + +# azuredevops_serviceendpoint_powerplatform + +Manages a PowerPlatform Service Endpoint. + +## Example Usage + +```hcl +resource "azuredevops_project" "example" { + name = "Example Project" + visibility = "private" + version_control = "Git" + work_item_template = "Agile" + description = "Managed by Terraform" +} + +resource "azuredevops_serviceendpoint_powerplatform" "example" { + project_id = data.azuredevops_project.project.id + service_endpoint_name = "PowerPlaform-connection" + description = "Managed by Terraform" + url = "https://dev-environment.crm11.dynamics.com/" + credentials { + serviceprincipalid = "00000000-0000-0000-0000-000000000000" + serviceprincipalkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + tenantId = "00000000-0000-0000-0000-000000000000" + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `project_id` - (Required) The ID of the project. Changing this forces a new PowerPlatform Service Endpoint to be created. +* `service_endpoint_name` - (Required) The Service Endpoint Name. +* `url` - (Required) The Service Endpoint url. + +--- + +* `credentials` - (Optional) A `credentials` block as defined below. +* `description` - (Optional) Service connection description. + +--- + +A `credentials` block supports the following: + +* `serviceprincipalid` - (Required) The service principal application ID. +* `serviceprincipalkey` - (Required) The service principal application key. +* `tenantId` - (Required) The service principal tenant id. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the PowerPlatform Service Endpoint. + +* `authorization` - A `authorization` block as defined below. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 2 minutes) Used when creating the PowerPlatform Service Endpoint. +* `read` - (Defaults to 1 minute) Used when retrieving the PowerPlatform Service Endpoint. +* `update` - (Defaults to 2 minutes) Used when updating the PowerPlatform Service Endpoint. +* `delete` - (Defaults to 2 minutes) Used when deleting the PowerPlatform Service Endpoint. + +## Import + +PowerPlatform Service Endpoints can be imported using the `resource id`, e.g. + +```shell +terraform import azuredevops_serviceendpoint_powerplatform.example 00000000-0000-0000-0000-000000000000 +``` From 017c009e81304ee5d05456a0fec310fd53f1f262 Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Wed, 26 Nov 2025 09:34:16 +0100 Subject: [PATCH 2/6] feat: Creating the basis resources --- azuredevops/internal/client/client.go | 9 + .../service/audit/data_audit_stream.go | 56 ++++ .../service/audit/data_audit_streams.go | 54 +++ .../service/audit/resource_audit_stream.go | 124 +++++++ .../internal/service/audit/utils/stream.go | 287 ++++++++++++++++ .../azuredevops/v7/audit/client.go | 312 ++++++++++++++++++ .../azuredevops/v7/audit/models.go | 158 +++++++++ vendor/modules.txt | 1 + 8 files changed, 1001 insertions(+) create mode 100644 azuredevops/internal/service/audit/data_audit_stream.go create mode 100644 azuredevops/internal/service/audit/data_audit_streams.go create mode 100644 azuredevops/internal/service/audit/resource_audit_stream.go create mode 100644 azuredevops/internal/service/audit/utils/stream.go create mode 100644 vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go create mode 100644 vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go diff --git a/azuredevops/internal/client/client.go b/azuredevops/internal/client/client.go index 9750ff9cf..05d371d58 100644 --- a/azuredevops/internal/client/client.go +++ b/azuredevops/internal/client/client.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/microsoft/azure-devops-go-api/azuredevops/v7" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/build" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/dashboard" @@ -75,6 +76,7 @@ type AggregatedClient struct { ServiceHooksClient servicehooks.Client Ctx context.Context SecurityRolesClient securityroles.Client + AuditClient audit.Client } // GetAzdoClient builds and provides a connection to the Azure DevOps API @@ -222,6 +224,12 @@ func GetAzdoClient(authProvider azuredevops.AuthProvider, organizationURL string securityRolesClient := securityroles.NewClient(ctx, connection) + auditClient, err := audit.NewClient(ctx, connection) + if err != nil { + log.Printf("getAzdoClient(): audit.NewClient failed.") + return nil, err + } + aggregatedClient := &AggregatedClient{ OrganizationURL: organizationURL, CoreClient: coreClient, @@ -251,6 +259,7 @@ func GetAzdoClient(authProvider azuredevops.AuthProvider, organizationURL string WorkItemTrackingClient: workitemtrackingClient, ServiceHooksClient: serviceHooksClient, SecurityRolesClient: securityRolesClient, + AuditClient: auditClient, Ctx: ctx, } diff --git a/azuredevops/internal/service/audit/data_audit_stream.go b/azuredevops/internal/service/audit/data_audit_stream.go new file mode 100644 index 000000000..564347cfb --- /dev/null +++ b/azuredevops/internal/service/audit/data_audit_stream.go @@ -0,0 +1,56 @@ +package audit + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" +) + +func DataResourceAuditStream() *schema.Resource { + return &schema.Resource{ + ReadContext: dataResourceAuditStreamRead, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(1 * time.Minute), + }, + Schema: utils.DataAuditStreamSchema(map[string]*schema.Schema{}), + } +} + +func dataResourceAuditStreamRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + var diags diag.Diagnostics + + streamID, err := converter.ASCIIToIntPtr(d.Get("id").(string)) + if err != nil { + return diag.FromErr(err) + } + + listArgs := audit.QueryStreamByIdArgs{ + StreamId: streamID, + } + stream, err := clients.AuditClient.QueryStreamById(clients.Ctx, listArgs) + if err != nil { + return diag.FromErr(err) + } + + if stream == nil { + d.SetId("") + return diag.Errorf("Geen Audit Stream gevonden met id: %b", streamID) + } + + d.SetId(strconv.Itoa(*stream.Id)) + + if err := utils.FlattenAuditStream(d, stream); err != nil { + return diag.FromErr(err) + } + + return diags +} diff --git a/azuredevops/internal/service/audit/data_audit_streams.go b/azuredevops/internal/service/audit/data_audit_streams.go new file mode 100644 index 000000000..f2ffdcfa1 --- /dev/null +++ b/azuredevops/internal/service/audit/data_audit_streams.go @@ -0,0 +1,54 @@ +package audit + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" +) + +func DataResourceAuditStreams() *schema.Resource { + return &schema.Resource{ + ReadContext: dataResourceAuditStreamsRead, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(1 * time.Minute), + }, + Schema: utils.DataAuditStreamsSchema(map[string]*schema.Schema{}), + } +} + +func dataResourceAuditStreamsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + var diags diag.Diagnostics + + allStreams, err := clients.AuditClient.QueryAllStreams(clients.Ctx, audit.QueryAllStreamsArgs{}) + if err != nil { + return diag.FromErr(err) + } + + var streamList []any + + if allStreams != nil { + for _, stream := range *allStreams { + streamMap := make(map[string]any) + if err := utils.FlattenSingleAuditStream(streamMap, &stream); err != nil { + return diag.FromErr(err) + } + streamList = append(streamList, streamMap) + } + } + + if err := d.Set("streams", streamList); err != nil { + return diag.FromErr(err) + } + + d.SetId(acctest.RandomWithPrefix("streams-listing")) + + return diags +} diff --git a/azuredevops/internal/service/audit/resource_audit_stream.go b/azuredevops/internal/service/audit/resource_audit_stream.go new file mode 100644 index 000000000..4a21ccc61 --- /dev/null +++ b/azuredevops/internal/service/audit/resource_audit_stream.go @@ -0,0 +1,124 @@ +package audit + +import ( + "context" + "strconv" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" + streamutils "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/tfhelper" +) + +func ResourceAuditStream() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceAuditStreamCreate, + ReadContext: resourceAuditStreamRead, + UpdateContext: resourceAuditStreamUpdate, + DeleteContext: resourceAuditStreamDelete, + + 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.ImportProjectQualifiedResourceInteger(), + + Schema: streamutils.ResourceAuditStreamSchema(map[string]*schema.Schema{}), + } +} +func resourceAuditStreamCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + + stream := streamutils.ExpandAuditStream(d) + + createPayload := audit.CreateStreamArgs{ + Stream: &stream, + } + + createdStream, err := clients.AuditClient.CreateStream(clients.Ctx, createPayload) + + if err != nil { + return diag.FromErr(err) + } + + streamID := strconv.Itoa(*createdStream.Id) + d.SetId(streamID) + + return resourceAuditStreamRead(ctx, d, m) +} + +func resourceAuditStreamRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + var diags diag.Diagnostics + + streamID, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + stream, err := clients.AuditClient.QueryStreamById(clients.Ctx, audit.QueryStreamByIdArgs{ + StreamId: &streamID, + }) + + if err != nil { + return diag.FromErr(err) + } + + if err := streamutils.FlattenAuditStream(d, stream); err != nil { + return diag.FromErr(err) + } + + return diags +} + +func resourceAuditStreamUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + + streamID, _ := strconv.Atoi(d.Id()) + + stream := streamutils.ExpandAuditStream(d) + stream.Id = &streamID + + updatePayload := audit.UpdateStreamArgs{ + Stream: &stream, + } + + _, err := clients.AuditClient.UpdateStream(clients.Ctx, updatePayload) + if err != nil { + return diag.FromErr(err) + } + + return resourceAuditStreamRead(ctx, d, m) +} + +func resourceAuditStreamDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + clients := m.(*client.AggregatedClient) + + streamID, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = clients.AuditClient.DeleteStream(clients.Ctx, audit.DeleteStreamArgs{ + StreamId: &streamID, + }) + + if err != nil { + if utils.ResponseWasNotFound(err) { + + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + d.SetId("") + return nil +} diff --git a/azuredevops/internal/service/audit/utils/stream.go b/azuredevops/internal/service/audit/utils/stream.go new file mode 100644 index 000000000..531e78c5a --- /dev/null +++ b/azuredevops/internal/service/audit/utils/stream.go @@ -0,0 +1,287 @@ +package utils + +import ( + "strconv" // Import toegevoegd voor FlattenSingleAuditStream + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" +) + +func ResourceAuditStreamSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { + baseSchema := map[string]*schema.Schema{ + "display_name": { + Type: schema.TypeString, + Required: true, + Description: "De leesbare naam voor de auditstroom. (Vertaalt naar REST API veld 'displayName').", + }, + "consumer_type": { + Type: schema.TypeString, + Required: true, + Description: "Het type externe service (bijv. 'splunk', 'azureMonitorLogs'). (Vertaalt naar REST API veld 'consumerType').", + }, + "status": { + Type: schema.TypeString, + Optional: true, + Default: "enabled", + Description: "De gewenste status van de stroom ('enabled' of 'disabledByUser').", + }, + "consumer_inputs": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + Description: "Een lijst van key/value paren met de benodigde invoerparameters voor de consument.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + Description: "De sleutel van de invoerparameter (bijv. 'url', 'token').", + }, + "value": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + Description: "De bijbehorende waarde of secret.", + }, + }, + }, + }, + "created_time": { + Type: schema.TypeString, + Computed: true, + Description: "Het tijdstip waarop de stroom is aangemaakt.", + }, + "updated_time": { + Type: schema.TypeString, + Computed: true, + Description: "Het tijdstip waarop de stroom voor het laatst is bijgewerkt.", + }, + "status_reason": { + Type: schema.TypeString, + Computed: true, + Description: "De reden voor de huidige status, indien van toepassing.", + }, + } + for key, elem := range baseSchema { + outer[key] = elem + } + + return outer +} + +func DataAuditStreamSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { + baseSchema := map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "De unieke ID van de gevonden auditstroom.", + }, + "display_name": { + Type: schema.TypeString, + Required: true, + Description: "De leesbare naam om de auditstroom op te zoeken.", + }, + "consumer_type": { + Type: schema.TypeString, + Computed: true, + Description: "Het type externe service van de gevonden stream.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "De status van de gevonden stream.", + }, + "consumer_inputs": { + Type: schema.TypeSet, + Computed: true, + Description: "De key/value paren met de invoerparameters van de consument.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + }, + }, + }, + "created_time": { + Type: schema.TypeString, + Computed: true, + Description: "Het tijdstip waarop de stroom is aangemaakt.", + }, + "updated_time": { + Type: schema.TypeString, + Computed: true, + Description: "Het tijdstip waarop de stroom voor het laatst is bijgewerkt.", + }, + "status_reason": { + Type: schema.TypeString, + Computed: true, + Description: "De reden voor de huidige status.", + }, + } + for key, elem := range baseSchema { + outer[key] = elem + } + + return outer +} + +func DataAuditStreamsSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { + baseSchema := map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "De unieke ID van de data source (wordt ingesteld op een constante waarde).", + }, + + "streams": { + Type: schema.TypeList, + Computed: true, + Description: "Een lijst van alle geconfigureerde audit streams in de organisatie.", + Elem: &schema.Resource{ + Schema: DataAuditStreamSchema(outer), + }, + }, + } + for key, elem := range baseSchema { + outer[key] = elem + } + + return outer +} + +func ExpandAuditStreamStatus(statusStr string) *audit.AuditStreamStatus { + var status audit.AuditStreamStatus + + switch statusStr { + case "enabled": + status = audit.AuditStreamStatusValues.Enabled + case "disabledByUser": + status = audit.AuditStreamStatusValues.DisabledByUser + case "disabledBySystem": + status = audit.AuditStreamStatusValues.DisabledBySystem + case "deleted": + status = audit.AuditStreamStatusValues.Deleted + case "backfilling": + status = audit.AuditStreamStatusValues.Backfilling + default: + status = audit.AuditStreamStatusValues.Enabled + } + + return &status +} + +func ExpandAuditStream(d *schema.ResourceData) audit.AuditStream { + displayName := d.Get("display_name").(string) + consumerType := d.Get("consumer_type").(string) + + status := ExpandAuditStreamStatus(d.Get("status").(string)) + + return audit.AuditStream{ + DisplayName: &displayName, + ConsumerType: &consumerType, + Status: status, + ConsumerInputs: ExpandConsumerInputs(d), + } +} + +func ExpandConsumerInputs(d *schema.ResourceData) *map[string]string { + v, ok := d.GetOk("consumer_inputs") + if !ok || v == nil { + return nil + } + + tfInputs := v.(*schema.Set).List() + + apiInputs := make(map[string]string, len(tfInputs)) + + for _, input := range tfInputs { + inputMap := input.(map[string]interface{}) + + key := inputMap["key"].(string) + value := inputMap["value"].(string) + + apiInputs[key] = value + } + + return &apiInputs +} + +func FlattenConsumerInputs(inputs *map[string]string) *schema.Set { + if inputs == nil { + return nil + } + + inputSet := schema.NewSet(schema.HashResource(ResourceAuditStreamSchema(nil)["consumer_inputs"].Elem.(*schema.Resource)), []interface{}{}) + + for key, value := range *inputs { + inputMap := map[string]interface{}{ + "key": key, + "value": value, + } + inputSet.Add(inputMap) + } + return inputSet +} + +func FlattenAuditStream(d *schema.ResourceData, stream *audit.AuditStream) error { + if stream == nil { + return nil + } + + d.Set("display_name", *stream.DisplayName) + d.Set("consumer_type", *stream.ConsumerType) + d.Set("status", string(*stream.Status)) + + if stream.CreatedTime != nil { + d.Set("created_time", stream.CreatedTime.String()) + } + + if stream.UpdatedTime != nil { + d.Set("updated_time", stream.UpdatedTime.String()) + } + + if stream.StatusReason != nil { + d.Set("status_reason", *stream.StatusReason) + } + + d.Set("consumer_inputs", FlattenConsumerInputs(stream.ConsumerInputs)) + + return nil +} + +func FlattenSingleAuditStream(m map[string]interface{}, stream *audit.AuditStream) error { + if stream == nil { + return nil + } + + m["id"] = strconv.Itoa(*stream.Id) + m["display_name"] = *stream.DisplayName + m["consumer_type"] = *stream.ConsumerType + m["status"] = string(*stream.Status) + + if stream.CreatedTime != nil { + m["created_time"] = stream.CreatedTime.String() + } + if stream.UpdatedTime != nil { + m["updated_time"] = stream.UpdatedTime.String() + } + + if stream.StatusReason != nil { + m["status_reason"] = *stream.StatusReason + } + + if inputs := FlattenConsumerInputs(stream.ConsumerInputs); inputs != nil { + m["consumer_inputs"] = inputs.List() + } else { + m["consumer_inputs"] = []any{} + } + + return nil +} diff --git a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go new file mode 100644 index 000000000..b1badaf38 --- /dev/null +++ b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go @@ -0,0 +1,312 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// -------------------------------------------------------------------------------------------- +// Generated file, DO NOT EDIT +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// -------------------------------------------------------------------------------------------- + +package audit + +import ( + "bytes" + "context" + "encoding/json" + "github.com/google/uuid" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7" + "io" + "net/http" + "net/url" + "strconv" +) + +var ResourceAreaId, _ = uuid.Parse("94ff054d-5ee1-413d-9341-3f4a7827de2e") + +type Client interface { + // [Preview API] Create new Audit Stream + CreateStream(context.Context, CreateStreamArgs) (*AuditStream, error) + // [Preview API] Delete Audit Stream + DeleteStream(context.Context, DeleteStreamArgs) error + // [Preview API] Downloads audit log entries. + DownloadLog(context.Context, DownloadLogArgs) (io.ReadCloser, error) + // [Preview API] Get all auditable actions filterable by area. + GetActions(context.Context, GetActionsArgs) (*[]AuditActionInfo, error) + // [Preview API] Return all Audit Streams scoped to an organization + QueryAllStreams(context.Context, QueryAllStreamsArgs) (*[]AuditStream, error) + // [Preview API] Queries audit log entries + QueryLog(context.Context, QueryLogArgs) (*AuditLogQueryResult, error) + // [Preview API] Return Audit Stream with id of streamId if one exists otherwise throw + QueryStreamById(context.Context, QueryStreamByIdArgs) (*AuditStream, error) + // [Preview API] Update existing Audit Stream status + UpdateStatus(context.Context, UpdateStatusArgs) (*AuditStream, error) + // [Preview API] Update existing Audit Stream + UpdateStream(context.Context, UpdateStreamArgs) (*AuditStream, error) +} + +type ClientImpl struct { + Client azuredevops.Client +} + +func NewClient(ctx context.Context, connection *azuredevops.Connection) (Client, error) { + client, err := connection.GetClientByResourceAreaId(ctx, ResourceAreaId) + if err != nil { + return nil, err + } + return &ClientImpl{ + Client: *client, + }, nil +} + +// [Preview API] Create new Audit Stream +func (client *ClientImpl) CreateStream(ctx context.Context, args CreateStreamArgs) (*AuditStream, error) { + if args.Stream == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.Stream"} + } + queryParams := url.Values{} + if args.DaysToBackfill == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "daysToBackfill"} + } + queryParams.Add("daysToBackfill", strconv.Itoa(*args.DaysToBackfill)) + body, marshalErr := json.Marshal(*args.Stream) + if marshalErr != nil { + return nil, marshalErr + } + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + resp, err := client.Client.Send(ctx, http.MethodPost, locationId, "7.1-preview.1", nil, queryParams, bytes.NewReader(body), "application/json", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue AuditStream + err = client.Client.UnmarshalBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the CreateStream function +type CreateStreamArgs struct { + // (required) Stream entry + Stream *AuditStream + // (required) The number of days of previously recorded audit data that will be replayed into the stream. A value of zero will result in only new events being streamed. + DaysToBackfill *int +} + +// [Preview API] Delete Audit Stream +func (client *ClientImpl) DeleteStream(ctx context.Context, args DeleteStreamArgs) error { + routeValues := make(map[string]string) + if args.StreamId == nil { + return &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} + } + routeValues["streamId"] = strconv.Itoa(*args.StreamId) + + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + _, err := client.Client.Send(ctx, http.MethodDelete, locationId, "7.1-preview.1", routeValues, nil, nil, "", "application/json", nil) + if err != nil { + return err + } + + return nil +} + +// Arguments for the DeleteStream function +type DeleteStreamArgs struct { + // (required) Id of stream entry to delete + StreamId *int +} + +// [Preview API] Downloads audit log entries. +func (client *ClientImpl) DownloadLog(ctx context.Context, args DownloadLogArgs) (io.ReadCloser, error) { + queryParams := url.Values{} + if args.Format == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "format"} + } + queryParams.Add("format", *args.Format) + if args.StartTime != nil { + queryParams.Add("startTime", (*args.StartTime).AsQueryParameter()) + } + if args.EndTime != nil { + queryParams.Add("endTime", (*args.EndTime).AsQueryParameter()) + } + locationId, _ := uuid.Parse("b7b98a76-04e8-4f4d-ac72-9d46492caaac") + resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/octet-stream", nil) + if err != nil { + return nil, err + } + + return resp.Body, err +} + +// Arguments for the DownloadLog function +type DownloadLogArgs struct { + // (required) File format for download. Can be "json" or "csv". + Format *string + // (optional) Start time of download window. Optional + StartTime *azuredevops.Time + // (optional) End time of download window. Optional + EndTime *azuredevops.Time +} + +// [Preview API] Get all auditable actions filterable by area. +func (client *ClientImpl) GetActions(ctx context.Context, args GetActionsArgs) (*[]AuditActionInfo, error) { + queryParams := url.Values{} + if args.AreaName != nil { + queryParams.Add("areaName", *args.AreaName) + } + locationId, _ := uuid.Parse("6fa30b9a-9558-4e3b-a95f-a12572caa6e6") + resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue []AuditActionInfo + err = client.Client.UnmarshalCollectionBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the GetActions function +type GetActionsArgs struct { + // (optional) Optional. Get actions scoped to area + AreaName *string +} + +// [Preview API] Return all Audit Streams scoped to an organization +func (client *ClientImpl) QueryAllStreams(ctx context.Context, args QueryAllStreamsArgs) (*[]AuditStream, error) { + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, nil, nil, "", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue []AuditStream + err = client.Client.UnmarshalCollectionBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the QueryAllStreams function +type QueryAllStreamsArgs struct { +} + +// [Preview API] Queries audit log entries +func (client *ClientImpl) QueryLog(ctx context.Context, args QueryLogArgs) (*AuditLogQueryResult, error) { + queryParams := url.Values{} + if args.StartTime != nil { + queryParams.Add("startTime", (*args.StartTime).AsQueryParameter()) + } + if args.EndTime != nil { + queryParams.Add("endTime", (*args.EndTime).AsQueryParameter()) + } + if args.BatchSize != nil { + queryParams.Add("batchSize", strconv.Itoa(*args.BatchSize)) + } + if args.ContinuationToken != nil { + queryParams.Add("continuationToken", *args.ContinuationToken) + } + if args.SkipAggregation != nil { + queryParams.Add("skipAggregation", strconv.FormatBool(*args.SkipAggregation)) + } + locationId, _ := uuid.Parse("4e5fa14f-7097-4b73-9c85-00abc7353c61") + resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue AuditLogQueryResult + err = client.Client.UnmarshalBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the QueryLog function +type QueryLogArgs struct { + // (optional) Start time of download window. Optional + StartTime *azuredevops.Time + // (optional) End time of download window. Optional + EndTime *azuredevops.Time + // (optional) Max number of results to return. Optional + BatchSize *int + // (optional) Token used for returning next set of results from previous query. Optional + ContinuationToken *string + // (optional) Skips aggregating events and leaves them as individual entries instead. By default events are aggregated. Event types that are aggregated: AuditLog.AccessLog. + SkipAggregation *bool +} + +// [Preview API] Return Audit Stream with id of streamId if one exists otherwise throw +func (client *ClientImpl) QueryStreamById(ctx context.Context, args QueryStreamByIdArgs) (*AuditStream, error) { + routeValues := make(map[string]string) + if args.StreamId == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} + } + routeValues["streamId"] = strconv.Itoa(*args.StreamId) + + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", routeValues, nil, nil, "", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue AuditStream + err = client.Client.UnmarshalBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the QueryStreamById function +type QueryStreamByIdArgs struct { + // (required) Id of stream entry to retrieve + StreamId *int +} + +// [Preview API] Update existing Audit Stream status +func (client *ClientImpl) UpdateStatus(ctx context.Context, args UpdateStatusArgs) (*AuditStream, error) { + routeValues := make(map[string]string) + if args.StreamId == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} + } + routeValues["streamId"] = strconv.Itoa(*args.StreamId) + + queryParams := url.Values{} + if args.Status == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "status"} + } + queryParams.Add("status", string(*args.Status)) + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + resp, err := client.Client.Send(ctx, http.MethodPut, locationId, "7.1-preview.1", routeValues, queryParams, nil, "", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue AuditStream + err = client.Client.UnmarshalBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the UpdateStatus function +type UpdateStatusArgs struct { + // (required) Id of stream entry to be updated + StreamId *int + // (required) Status of the stream + Status *AuditStreamStatus +} + +// [Preview API] Update existing Audit Stream +func (client *ClientImpl) UpdateStream(ctx context.Context, args UpdateStreamArgs) (*AuditStream, error) { + if args.Stream == nil { + return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.Stream"} + } + body, marshalErr := json.Marshal(*args.Stream) + if marshalErr != nil { + return nil, marshalErr + } + locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") + resp, err := client.Client.Send(ctx, http.MethodPut, locationId, "7.1-preview.1", nil, nil, bytes.NewReader(body), "application/json", "application/json", nil) + if err != nil { + return nil, err + } + + var responseValue AuditStream + err = client.Client.UnmarshalBody(resp, &responseValue) + return &responseValue, err +} + +// Arguments for the UpdateStream function +type UpdateStreamArgs struct { + // (required) Stream entry + Stream *AuditStream +} diff --git a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go new file mode 100644 index 000000000..54d688408 --- /dev/null +++ b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go @@ -0,0 +1,158 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// -------------------------------------------------------------------------------------------- +// Generated file, DO NOT EDIT +// Changes may cause incorrect behavior and will be lost if the code is regenerated. +// -------------------------------------------------------------------------------------------- + +package audit + +import ( + "github.com/google/uuid" + "github.com/microsoft/azure-devops-go-api/azuredevops/v7" +) + +// Defines all the categories an AuditAction can be +type AuditActionCategory string + +type auditActionCategoryValuesType struct { + Unknown AuditActionCategory + Modify AuditActionCategory + Remove AuditActionCategory + Create AuditActionCategory + Access AuditActionCategory + Execute AuditActionCategory +} + +var AuditActionCategoryValues = auditActionCategoryValuesType{ + // The category is not known + Unknown: "unknown", + // An artifact has been Modified + Modify: "modify", + // An artifact has been Removed + Remove: "remove", + // An artifact has been Created + Create: "create", + // An artifact has been Accessed + Access: "access", + // An artifact has been Executed + Execute: "execute", +} + +type AuditActionInfo struct { + // The action id for the event, i.e Git.CreateRepo, Project.RenameProject + ActionId *string `json:"actionId,omitempty"` + // Area of Azure DevOps the action occurred + Area *string `json:"area,omitempty"` + // Type of action executed + Category *AuditActionCategory `json:"category,omitempty"` +} + +// The object returned when the audit log is queried. It contains the log and the information needed to query more audit entries. +type AuditLogQueryResult struct { + // The continuation token to pass to get the next set of results + ContinuationToken *string `json:"continuationToken,omitempty"` + // The list of audit log entries + DecoratedAuditLogEntries *[]DecoratedAuditLogEntry `json:"decoratedAuditLogEntries,omitempty"` + // True when there are more matching results to be fetched, false otherwise. + HasMore *bool `json:"hasMore,omitempty"` +} + +// This class represents an audit stream +type AuditStream struct { + // Inputs used to communicate with external service. Inputs could be url, a connection string, a token, etc. + ConsumerInputs *map[string]string `json:"consumerInputs,omitempty"` + // Type of the consumer, i.e. splunk, azureEventHub, etc. + ConsumerType *string `json:"consumerType,omitempty"` + // The time when the stream was created + CreatedTime *azuredevops.Time `json:"createdTime,omitempty"` + // Used to identify individual streams + DisplayName *string `json:"displayName,omitempty"` + // Unique stream identifier + Id *int `json:"id,omitempty"` + // Status of the stream, Enabled, Disabled + Status *AuditStreamStatus `json:"status,omitempty"` + // Reason for the current stream status, i.e. Disabled by the system, Invalid credentials, etc. + StatusReason *string `json:"statusReason,omitempty"` + // The time when the stream was last updated + UpdatedTime *azuredevops.Time `json:"updatedTime,omitempty"` +} + +// Represents the status of a stream +type AuditStreamStatus string + +type auditStreamStatusValuesType struct { + Unknown AuditStreamStatus + Enabled AuditStreamStatus + DisabledByUser AuditStreamStatus + DisabledBySystem AuditStreamStatus + Deleted AuditStreamStatus + Backfilling AuditStreamStatus +} + +var AuditStreamStatusValues = auditStreamStatusValuesType{ + // The state has not been set, The stream is new + Unknown: "unknown", + // The stream is enabled and can deliver events + Enabled: "enabled", + // The stream has been disabled by a user + DisabledByUser: "disabledByUser", + // The stream has been disabled by the system + DisabledBySystem: "disabledBySystem", + // The stream has been marked for deletion + Deleted: "deleted", + // The stream is delivering old events + Backfilling: "backfilling", +} + +type DecoratedAuditLogEntry struct { + // The action id for the event, i.e Git.CreateRepo, Project.RenameProject + ActionId *string `json:"actionId,omitempty"` + // ActivityId + ActivityId *uuid.UUID `json:"activityId,omitempty"` + // The Actor's Client Id (if actor is a service principal) + ActorClientId *uuid.UUID `json:"actorClientId,omitempty"` + // The Actor's CUID + ActorCUID *uuid.UUID `json:"actorCUID,omitempty"` + // DisplayName of the user who initiated the action + ActorDisplayName *string `json:"actorDisplayName,omitempty"` + // URL of Actor's Profile image + ActorImageUrl *string `json:"actorImageUrl,omitempty"` + // The Actor's UPN + ActorUPN *string `json:"actorUPN,omitempty"` + // The Actor's User Id (if actor is a user) + ActorUserId *uuid.UUID `json:"actorUserId,omitempty"` + // Area of Azure DevOps the action occurred + Area *string `json:"area,omitempty"` + // Type of authentication used by the actor + AuthenticationMechanism *string `json:"authenticationMechanism,omitempty"` + // Type of action executed + Category *AuditActionCategory `json:"category,omitempty"` + // DisplayName of the category + CategoryDisplayName *string `json:"categoryDisplayName,omitempty"` + // This allows related audit entries to be grouped together. Generally this occurs when a single action causes a cascade of audit entries. For example, project creation. + CorrelationId *uuid.UUID `json:"correlationId,omitempty"` + // External data such as CUIDs, item names, etc. + Data *map[string]interface{} `json:"data,omitempty"` + // Decorated details + Details *string `json:"details,omitempty"` + // EventId - Needs to be unique per service + Id *string `json:"id,omitempty"` + // IP Address where the event was originated + IpAddress *string `json:"ipAddress,omitempty"` + // When specified, the id of the project this event is associated to + ProjectId *uuid.UUID `json:"projectId,omitempty"` + // When specified, the name of the project this event is associated to + ProjectName *string `json:"projectName,omitempty"` + // DisplayName of the scope + ScopeDisplayName *string `json:"scopeDisplayName,omitempty"` + // The organization Id (Organization is the only scope currently supported) + ScopeId *uuid.UUID `json:"scopeId,omitempty"` + // The type of the scope (Organization is only scope currently supported) + ScopeType *string `json:"scopeType,omitempty"` + // The time when the event occurred in UTC + Timestamp *azuredevops.Time `json:"timestamp,omitempty"` + // The user agent from the request + UserAgent *string `json:"userAgent,omitempty"` +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1458f809a..9e23879a1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -273,6 +273,7 @@ github.com/mattn/go-isatty ## explicit; go 1.23.0 github.com/microsoft/azure-devops-go-api/azuredevops/v7 github.com/microsoft/azure-devops-go-api/azuredevops/v7/accounts +github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit github.com/microsoft/azure-devops-go-api/azuredevops/v7/build github.com/microsoft/azure-devops-go-api/azuredevops/v7/commerce github.com/microsoft/azure-devops-go-api/azuredevops/v7/core From 8f24afc2363ab58db76930ea06aff24f937b66fc Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Wed, 26 Nov 2025 11:21:55 +0100 Subject: [PATCH 3/6] fixing terraformfmt --- ...serviceendpoint_powerplatform.html.markdown | 18 +++++++++--------- website/docs/r/workitemquery.html.markdown | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/website/docs/r/serviceendpoint_powerplatform.html.markdown b/website/docs/r/serviceendpoint_powerplatform.html.markdown index 8d04c96aa..1fad1ee2f 100644 --- a/website/docs/r/serviceendpoint_powerplatform.html.markdown +++ b/website/docs/r/serviceendpoint_powerplatform.html.markdown @@ -21,15 +21,15 @@ resource "azuredevops_project" "example" { } resource "azuredevops_serviceendpoint_powerplatform" "example" { - project_id = data.azuredevops_project.project.id - service_endpoint_name = "PowerPlaform-connection" - description = "Managed by Terraform" - url = "https://dev-environment.crm11.dynamics.com/" - credentials { - serviceprincipalid = "00000000-0000-0000-0000-000000000000" - serviceprincipalkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - tenantId = "00000000-0000-0000-0000-000000000000" - } + project_id = data.azuredevops_project.project.id + service_endpoint_name = "PowerPlaform-connection" + description = "Managed by Terraform" + url = "https://dev-environment.crm11.dynamics.com/" + credentials { + serviceprincipalid = "00000000-0000-0000-0000-000000000000" + serviceprincipalkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + tenantId = "00000000-0000-0000-0000-000000000000" + } } ``` diff --git a/website/docs/r/workitemquery.html.markdown b/website/docs/r/workitemquery.html.markdown index 217dae351..8cd27a3a3 100644 --- a/website/docs/r/workitemquery.html.markdown +++ b/website/docs/r/workitemquery.html.markdown @@ -112,12 +112,12 @@ resource "azuredevops_workitemquery_permissions" "query_permissions" { azuredevops_workitemquery_folder.team_folder.name, azuredevops_workitemquery.my_team_bugs.name ) - principal = data.azuredevops_group.example-readers.id + principal = data.azuredevops_group.example-readers.id permissions = { - "Read" = "Allow" - "Contribute" = "Deny" + "Read" = "Allow" + "Contribute" = "Deny" "ManagePermissions" = "Deny" - "Delete" = "Deny" + "Delete" = "Deny" } } ``` From e4d4229b71ba790f0bf51ab888f9f5776cc69b44 Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Thu, 19 Mar 2026 17:49:31 +0100 Subject: [PATCH 4/6] Address review comments for Power Platform service endpoint PR --- .../resource_serviceendpoint_powerplatform.go | 51 ++++++++++++-- ...urce_serviceendpoint_powerplatform_test.go | 69 ++++++++----------- ...erviceendpoint_powerplatform.html.markdown | 8 +-- 3 files changed, 79 insertions(+), 49 deletions(-) diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go index 384269f99..ca9dc9499 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform.go @@ -41,7 +41,7 @@ func ResourceServiceEndpointPowerPlatform() *schema.Resource { }, "credentials": { Type: schema.TypeList, - Optional: true, + Required: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -58,7 +58,7 @@ func ResourceServiceEndpointPowerPlatform() *schema.Resource { Description: "The Client Secret of the Service Principal.", ValidateFunc: validation.StringIsNotEmpty, }, - "tenantId": { + "tenant_id": { Type: schema.TypeString, Required: true, Description: "The Tenant ID.", @@ -67,6 +67,21 @@ func ResourceServiceEndpointPowerPlatform() *schema.Resource { }, }, }, + "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 @@ -85,6 +100,21 @@ func resourceServiceEndpointPowerPlatformCreate(ctx context.Context, d *schema.R } 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) } @@ -114,11 +144,22 @@ func resourceServiceEndpointPowerPlatformUpdate(ctx context.Context, d *schema.R return diag.Errorf(errMsgTfConfigRead, err) } - _, err = updateServiceEndpoint(clients, serviceEndpoint) + 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) } @@ -155,7 +196,7 @@ func expandServiceEndpointPowerPlatform(d *schema.ResourceData) (*serviceendpoin } parameters := map[string]string{ - "tenantId": credentials["tenantId"].(string), + "tenantId": credentials["tenant_id"].(string), "applicationId": credentials["serviceprincipalid"].(string), "clientSecret": credentials["serviceprincipalkey"].(string), } @@ -179,7 +220,7 @@ func flattenServiceEndpointPowerPlatform(d *schema.ResourceData, serviceEndpoint params := *serviceEndpoint.Authorization.Parameters if v, ok := params["tenantId"]; ok { - credentials["tenantId"] = v + credentials["tenant_id"] = v } if v, ok := params["applicationId"]; ok { diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go index 9aa648ef2..a6a1b8b2f 100644 --- a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_powerplatform_test.go @@ -88,16 +88,15 @@ func TestServiceEndpointPowerPlatform_Create_DoesNotSwallowError(t *testing.T) { buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} - expectedArgs := serviceendpoint.CreateServiceEndpointArgs{Endpoint: &resource} - buildClient. EXPECT(). - CreateServiceEndpoint(clients.Ctx, expectedArgs). + CreateServiceEndpoint(clients.Ctx, gomock.Any()). Return(nil, errors.New("CreateServiceEndpoint() Failed")). Times(1) - err := r.Create(resourceData, clients) - require.Contains(t, err.Error(), "CreateServiceEndpoint() Failed") + diag := r.CreateContext(clients.Ctx, resourceData, clients) + require.True(t, diag.HasError()) + require.Contains(t, diag[0].Summary, "CreateServiceEndpoint() Failed") } } @@ -109,6 +108,11 @@ func TestServiceEndpointPowerPlatform_CreateWithValidate_DoesNotSwallowError(t * r := ResourceServiceEndpointPowerPlatform() for _, resource := range powerplatformTestServiceEndpoints { resourceData := getResourceDataPowerPlatform(t, resource) + resourceData.Set("features", []interface{}{ + map[string]interface{}{ + "validate": true, + }, + }) flattenServiceEndpointPowerPlatform(resourceData, &resource) buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) @@ -116,7 +120,7 @@ func TestServiceEndpointPowerPlatform_CreateWithValidate_DoesNotSwallowError(t * buildClient. EXPECT(). - CreateServiceEndpoint(clients.Ctx, serviceendpoint.CreateServiceEndpointArgs{Endpoint: &resource}). + CreateServiceEndpoint(clients.Ctx, gomock.Any()). Return(&resource, nil). Times(1) @@ -124,31 +128,25 @@ func TestServiceEndpointPowerPlatform_CreateWithValidate_DoesNotSwallowError(t * returnedServiceEndpoint.IsReady = converter.Bool(true) buildClient. EXPECT(). - GetServiceEndpointDetails(clients.Ctx, serviceendpoint.GetServiceEndpointDetailsArgs{ - Project: converter.String(powerplatformRandomServiceEndpointProjectID.String()), - EndpointId: resource.Id, - }, - ). + GetServiceEndpointDetails(clients.Ctx, gomock.Any()). Return(&returnedServiceEndpoint, nil). - Times(1) + Times(2) - reqArgs := genExecuteServiceEndpointArgsPowerPlatform(&resource) buildClient. EXPECT(). - ExecuteServiceEndpointRequest(clients.Ctx, *reqArgs). + ExecuteServiceEndpointRequest(clients.Ctx, gomock.Any()). Return(nil, errors.New("ExecuteServiceEndpointRequest() Failed")). Times(1) buildClient. EXPECT(). - DeleteServiceEndpoint(clients.Ctx, serviceendpoint.DeleteServiceEndpointArgs{ - ProjectIds: &[]string{powerplatformTestServiceEndpointProjectID.String()}, EndpointId: resource.Id, - }). + DeleteServiceEndpoint(clients.Ctx, gomock.Any()). Return(nil). Times(1) - err := r.Create(resourceData, clients) - require.Contains(t, err.Error(), "ExecuteServiceEndpointRequest() Failed") + diag := r.CreateContext(clients.Ctx, resourceData, clients) + require.True(t, diag.HasError()) + require.Contains(t, diag[0].Summary, "ExecuteServiceEndpointRequest() Failed") } } @@ -176,8 +174,9 @@ func TestServiceEndpointPowerPlatform_Read_DoesNotSwallowError(t *testing.T) { Return(nil, errors.New("GetServiceEndpoint() Failed")). Times(1) - err := r.Read(resourceData, clients) - require.Contains(t, err.Error(), "GetServiceEndpoint() Failed") + diag := r.ReadContext(clients.Ctx, resourceData, clients) + require.True(t, diag.HasError()) + require.Contains(t, diag[0].Summary, "GetServiceEndpoint() Failed") } } @@ -194,19 +193,15 @@ func TestServiceEndpointPowerPlatform_Update_DoesNotSwallowError(t *testing.T) { buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} - expectedArgs := serviceendpoint.UpdateServiceEndpointArgs{ - Endpoint: &resource, - EndpointId: resource.Id, - } - buildClient. EXPECT(). - UpdateServiceEndpoint(clients.Ctx, expectedArgs). + UpdateServiceEndpoint(clients.Ctx, gomock.Any()). Return(nil, errors.New("UpdateServiceEndpoint() Failed")). Times(1) - err := r.Update(resourceData, clients) - require.Contains(t, err.Error(), "UpdateServiceEndpoint() Failed") + diag := r.UpdateContext(clients.Ctx, resourceData, clients) + require.True(t, diag.HasError()) + require.Contains(t, diag[0].Summary, "UpdateServiceEndpoint() Failed") } } @@ -223,21 +218,15 @@ func TestServiceEndpointPowerPlatform_Delete_DoesNotSwallowError(t *testing.T) { buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} - expectedArgs := serviceendpoint.DeleteServiceEndpointArgs{ - EndpointId: resource.Id, - ProjectIds: &[]string{ - powerplatformTestServiceEndpointProjectID.String(), - }, - } - buildClient. EXPECT(). - DeleteServiceEndpoint(clients.Ctx, expectedArgs). + DeleteServiceEndpoint(clients.Ctx, gomock.Any()). Return(errors.New("DeleteServiceEndpoint() Failed")). Times(1) - err := r.Delete(resourceData, clients) - require.Contains(t, err.Error(), "DeleteServiceEndpoint() Failed") + diag := r.DeleteContext(clients.Ctx, resourceData, clients) + require.True(t, diag.HasError()) + require.Contains(t, diag[0].Summary, "DeleteServiceEndpoint() Failed") } } @@ -256,7 +245,7 @@ func getResourceDataPowerPlatform(t *testing.T, resource serviceendpoint.Service map[string]interface{}{ "serviceprincipalid": params["applicationId"], "serviceprincipalkey": params["clientSecret"], - "tenantId": params["tenantId"], + "tenant_id": params["tenantId"], }, } resourceData.Set("credentials", credentials) diff --git a/website/docs/r/serviceendpoint_powerplatform.html.markdown b/website/docs/r/serviceendpoint_powerplatform.html.markdown index 1fad1ee2f..a0eb819a8 100644 --- a/website/docs/r/serviceendpoint_powerplatform.html.markdown +++ b/website/docs/r/serviceendpoint_powerplatform.html.markdown @@ -21,14 +21,14 @@ resource "azuredevops_project" "example" { } resource "azuredevops_serviceendpoint_powerplatform" "example" { - project_id = data.azuredevops_project.project.id + project_id = azuredevops_project.example.id service_endpoint_name = "PowerPlaform-connection" description = "Managed by Terraform" url = "https://dev-environment.crm11.dynamics.com/" credentials { serviceprincipalid = "00000000-0000-0000-0000-000000000000" serviceprincipalkey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - tenantId = "00000000-0000-0000-0000-000000000000" + tenant_id = "00000000-0000-0000-0000-000000000000" } } ``` @@ -43,7 +43,7 @@ The following arguments are supported: --- -* `credentials` - (Optional) A `credentials` block as defined below. +* `credentials` - (Required) A `credentials` block as defined below. * `description` - (Optional) Service connection description. --- @@ -52,7 +52,7 @@ A `credentials` block supports the following: * `serviceprincipalid` - (Required) The service principal application ID. * `serviceprincipalkey` - (Required) The service principal application key. -* `tenantId` - (Required) The service principal tenant id. +* `tenant_id` - (Required) The service principal tenant id. ## Attributes Reference From f823822c49c03be42f75cf972a04b1646cb5a9dd Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Thu, 19 Mar 2026 18:12:14 +0100 Subject: [PATCH 5/6] Remove azuredevops/internal/service/audit and related client code --- azuredevops/internal/client/client.go | 9 - .../service/audit/data_audit_stream.go | 56 ---- .../service/audit/data_audit_streams.go | 54 ---- .../service/audit/resource_audit_stream.go | 124 -------- .../internal/service/audit/utils/stream.go | 287 ------------------ 5 files changed, 530 deletions(-) delete mode 100644 azuredevops/internal/service/audit/data_audit_stream.go delete mode 100644 azuredevops/internal/service/audit/data_audit_streams.go delete mode 100644 azuredevops/internal/service/audit/resource_audit_stream.go delete mode 100644 azuredevops/internal/service/audit/utils/stream.go diff --git a/azuredevops/internal/client/client.go b/azuredevops/internal/client/client.go index 0f257a73e..c3f3380eb 100644 --- a/azuredevops/internal/client/client.go +++ b/azuredevops/internal/client/client.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/microsoft/azure-devops-go-api/azuredevops/v7" - "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/build" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/core" "github.com/microsoft/azure-devops-go-api/azuredevops/v7/dashboard" @@ -78,7 +77,6 @@ type AggregatedClient struct { ServiceHooksClient servicehooks.Client Ctx context.Context SecurityRolesClient securityroles.Client - AuditClient audit.Client } // GetAzdoClient builds and provides a connection to the Azure DevOps API @@ -232,12 +230,6 @@ func GetAzdoClient(authProvider azuredevops.AuthProvider, organizationURL string securityRolesClient := securityroles.NewClient(ctx, connection) - auditClient, err := audit.NewClient(ctx, connection) - if err != nil { - log.Printf("getAzdoClient(): audit.NewClient failed.") - return nil, err - } - aggregatedClient := &AggregatedClient{ OrganizationURL: organizationURL, CoreClient: coreClient, @@ -268,7 +260,6 @@ func GetAzdoClient(authProvider azuredevops.AuthProvider, organizationURL string WorkItemTrackingProcessClient: workitemtrackingprocessClient, ServiceHooksClient: serviceHooksClient, SecurityRolesClient: securityRolesClient, - AuditClient: auditClient, Ctx: ctx, } diff --git a/azuredevops/internal/service/audit/data_audit_stream.go b/azuredevops/internal/service/audit/data_audit_stream.go deleted file mode 100644 index 564347cfb..000000000 --- a/azuredevops/internal/service/audit/data_audit_stream.go +++ /dev/null @@ -1,56 +0,0 @@ -package audit - -import ( - "context" - "strconv" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" -) - -func DataResourceAuditStream() *schema.Resource { - return &schema.Resource{ - ReadContext: dataResourceAuditStreamRead, - Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(1 * time.Minute), - }, - Schema: utils.DataAuditStreamSchema(map[string]*schema.Schema{}), - } -} - -func dataResourceAuditStreamRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - var diags diag.Diagnostics - - streamID, err := converter.ASCIIToIntPtr(d.Get("id").(string)) - if err != nil { - return diag.FromErr(err) - } - - listArgs := audit.QueryStreamByIdArgs{ - StreamId: streamID, - } - stream, err := clients.AuditClient.QueryStreamById(clients.Ctx, listArgs) - if err != nil { - return diag.FromErr(err) - } - - if stream == nil { - d.SetId("") - return diag.Errorf("Geen Audit Stream gevonden met id: %b", streamID) - } - - d.SetId(strconv.Itoa(*stream.Id)) - - if err := utils.FlattenAuditStream(d, stream); err != nil { - return diag.FromErr(err) - } - - return diags -} diff --git a/azuredevops/internal/service/audit/data_audit_streams.go b/azuredevops/internal/service/audit/data_audit_streams.go deleted file mode 100644 index f2ffdcfa1..000000000 --- a/azuredevops/internal/service/audit/data_audit_streams.go +++ /dev/null @@ -1,54 +0,0 @@ -package audit - -import ( - "context" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" -) - -func DataResourceAuditStreams() *schema.Resource { - return &schema.Resource{ - ReadContext: dataResourceAuditStreamsRead, - Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(1 * time.Minute), - }, - Schema: utils.DataAuditStreamsSchema(map[string]*schema.Schema{}), - } -} - -func dataResourceAuditStreamsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - var diags diag.Diagnostics - - allStreams, err := clients.AuditClient.QueryAllStreams(clients.Ctx, audit.QueryAllStreamsArgs{}) - if err != nil { - return diag.FromErr(err) - } - - var streamList []any - - if allStreams != nil { - for _, stream := range *allStreams { - streamMap := make(map[string]any) - if err := utils.FlattenSingleAuditStream(streamMap, &stream); err != nil { - return diag.FromErr(err) - } - streamList = append(streamList, streamMap) - } - } - - if err := d.Set("streams", streamList); err != nil { - return diag.FromErr(err) - } - - d.SetId(acctest.RandomWithPrefix("streams-listing")) - - return diags -} diff --git a/azuredevops/internal/service/audit/resource_audit_stream.go b/azuredevops/internal/service/audit/resource_audit_stream.go deleted file mode 100644 index 4a21ccc61..000000000 --- a/azuredevops/internal/service/audit/resource_audit_stream.go +++ /dev/null @@ -1,124 +0,0 @@ -package audit - -import ( - "context" - "strconv" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" - streamutils "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/service/audit/utils" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils" - "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/tfhelper" -) - -func ResourceAuditStream() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceAuditStreamCreate, - ReadContext: resourceAuditStreamRead, - UpdateContext: resourceAuditStreamUpdate, - DeleteContext: resourceAuditStreamDelete, - - 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.ImportProjectQualifiedResourceInteger(), - - Schema: streamutils.ResourceAuditStreamSchema(map[string]*schema.Schema{}), - } -} -func resourceAuditStreamCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - - stream := streamutils.ExpandAuditStream(d) - - createPayload := audit.CreateStreamArgs{ - Stream: &stream, - } - - createdStream, err := clients.AuditClient.CreateStream(clients.Ctx, createPayload) - - if err != nil { - return diag.FromErr(err) - } - - streamID := strconv.Itoa(*createdStream.Id) - d.SetId(streamID) - - return resourceAuditStreamRead(ctx, d, m) -} - -func resourceAuditStreamRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - var diags diag.Diagnostics - - streamID, err := strconv.Atoi(d.Id()) - if err != nil { - return diag.FromErr(err) - } - - stream, err := clients.AuditClient.QueryStreamById(clients.Ctx, audit.QueryStreamByIdArgs{ - StreamId: &streamID, - }) - - if err != nil { - return diag.FromErr(err) - } - - if err := streamutils.FlattenAuditStream(d, stream); err != nil { - return diag.FromErr(err) - } - - return diags -} - -func resourceAuditStreamUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - - streamID, _ := strconv.Atoi(d.Id()) - - stream := streamutils.ExpandAuditStream(d) - stream.Id = &streamID - - updatePayload := audit.UpdateStreamArgs{ - Stream: &stream, - } - - _, err := clients.AuditClient.UpdateStream(clients.Ctx, updatePayload) - if err != nil { - return diag.FromErr(err) - } - - return resourceAuditStreamRead(ctx, d, m) -} - -func resourceAuditStreamDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clients := m.(*client.AggregatedClient) - - streamID, err := strconv.Atoi(d.Id()) - if err != nil { - return diag.FromErr(err) - } - - err = clients.AuditClient.DeleteStream(clients.Ctx, audit.DeleteStreamArgs{ - StreamId: &streamID, - }) - - if err != nil { - if utils.ResponseWasNotFound(err) { - - d.SetId("") - return nil - } - return diag.FromErr(err) - } - - d.SetId("") - return nil -} diff --git a/azuredevops/internal/service/audit/utils/stream.go b/azuredevops/internal/service/audit/utils/stream.go deleted file mode 100644 index 531e78c5a..000000000 --- a/azuredevops/internal/service/audit/utils/stream.go +++ /dev/null @@ -1,287 +0,0 @@ -package utils - -import ( - "strconv" // Import toegevoegd voor FlattenSingleAuditStream - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit" -) - -func ResourceAuditStreamSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { - baseSchema := map[string]*schema.Schema{ - "display_name": { - Type: schema.TypeString, - Required: true, - Description: "De leesbare naam voor de auditstroom. (Vertaalt naar REST API veld 'displayName').", - }, - "consumer_type": { - Type: schema.TypeString, - Required: true, - Description: "Het type externe service (bijv. 'splunk', 'azureMonitorLogs'). (Vertaalt naar REST API veld 'consumerType').", - }, - "status": { - Type: schema.TypeString, - Optional: true, - Default: "enabled", - Description: "De gewenste status van de stroom ('enabled' of 'disabledByUser').", - }, - "consumer_inputs": { - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Description: "Een lijst van key/value paren met de benodigde invoerparameters voor de consument.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Required: true, - Description: "De sleutel van de invoerparameter (bijv. 'url', 'token').", - }, - "value": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - Description: "De bijbehorende waarde of secret.", - }, - }, - }, - }, - "created_time": { - Type: schema.TypeString, - Computed: true, - Description: "Het tijdstip waarop de stroom is aangemaakt.", - }, - "updated_time": { - Type: schema.TypeString, - Computed: true, - Description: "Het tijdstip waarop de stroom voor het laatst is bijgewerkt.", - }, - "status_reason": { - Type: schema.TypeString, - Computed: true, - Description: "De reden voor de huidige status, indien van toepassing.", - }, - } - for key, elem := range baseSchema { - outer[key] = elem - } - - return outer -} - -func DataAuditStreamSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { - baseSchema := map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "De unieke ID van de gevonden auditstroom.", - }, - "display_name": { - Type: schema.TypeString, - Required: true, - Description: "De leesbare naam om de auditstroom op te zoeken.", - }, - "consumer_type": { - Type: schema.TypeString, - Computed: true, - Description: "Het type externe service van de gevonden stream.", - }, - "status": { - Type: schema.TypeString, - Computed: true, - Description: "De status van de gevonden stream.", - }, - "consumer_inputs": { - Type: schema.TypeSet, - Computed: true, - Description: "De key/value paren met de invoerparameters van de consument.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": { - Type: schema.TypeString, - Computed: true, - }, - "value": { - Type: schema.TypeString, - Computed: true, - Sensitive: true, - }, - }, - }, - }, - "created_time": { - Type: schema.TypeString, - Computed: true, - Description: "Het tijdstip waarop de stroom is aangemaakt.", - }, - "updated_time": { - Type: schema.TypeString, - Computed: true, - Description: "Het tijdstip waarop de stroom voor het laatst is bijgewerkt.", - }, - "status_reason": { - Type: schema.TypeString, - Computed: true, - Description: "De reden voor de huidige status.", - }, - } - for key, elem := range baseSchema { - outer[key] = elem - } - - return outer -} - -func DataAuditStreamsSchema(outer map[string]*schema.Schema) map[string]*schema.Schema { - baseSchema := map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "De unieke ID van de data source (wordt ingesteld op een constante waarde).", - }, - - "streams": { - Type: schema.TypeList, - Computed: true, - Description: "Een lijst van alle geconfigureerde audit streams in de organisatie.", - Elem: &schema.Resource{ - Schema: DataAuditStreamSchema(outer), - }, - }, - } - for key, elem := range baseSchema { - outer[key] = elem - } - - return outer -} - -func ExpandAuditStreamStatus(statusStr string) *audit.AuditStreamStatus { - var status audit.AuditStreamStatus - - switch statusStr { - case "enabled": - status = audit.AuditStreamStatusValues.Enabled - case "disabledByUser": - status = audit.AuditStreamStatusValues.DisabledByUser - case "disabledBySystem": - status = audit.AuditStreamStatusValues.DisabledBySystem - case "deleted": - status = audit.AuditStreamStatusValues.Deleted - case "backfilling": - status = audit.AuditStreamStatusValues.Backfilling - default: - status = audit.AuditStreamStatusValues.Enabled - } - - return &status -} - -func ExpandAuditStream(d *schema.ResourceData) audit.AuditStream { - displayName := d.Get("display_name").(string) - consumerType := d.Get("consumer_type").(string) - - status := ExpandAuditStreamStatus(d.Get("status").(string)) - - return audit.AuditStream{ - DisplayName: &displayName, - ConsumerType: &consumerType, - Status: status, - ConsumerInputs: ExpandConsumerInputs(d), - } -} - -func ExpandConsumerInputs(d *schema.ResourceData) *map[string]string { - v, ok := d.GetOk("consumer_inputs") - if !ok || v == nil { - return nil - } - - tfInputs := v.(*schema.Set).List() - - apiInputs := make(map[string]string, len(tfInputs)) - - for _, input := range tfInputs { - inputMap := input.(map[string]interface{}) - - key := inputMap["key"].(string) - value := inputMap["value"].(string) - - apiInputs[key] = value - } - - return &apiInputs -} - -func FlattenConsumerInputs(inputs *map[string]string) *schema.Set { - if inputs == nil { - return nil - } - - inputSet := schema.NewSet(schema.HashResource(ResourceAuditStreamSchema(nil)["consumer_inputs"].Elem.(*schema.Resource)), []interface{}{}) - - for key, value := range *inputs { - inputMap := map[string]interface{}{ - "key": key, - "value": value, - } - inputSet.Add(inputMap) - } - return inputSet -} - -func FlattenAuditStream(d *schema.ResourceData, stream *audit.AuditStream) error { - if stream == nil { - return nil - } - - d.Set("display_name", *stream.DisplayName) - d.Set("consumer_type", *stream.ConsumerType) - d.Set("status", string(*stream.Status)) - - if stream.CreatedTime != nil { - d.Set("created_time", stream.CreatedTime.String()) - } - - if stream.UpdatedTime != nil { - d.Set("updated_time", stream.UpdatedTime.String()) - } - - if stream.StatusReason != nil { - d.Set("status_reason", *stream.StatusReason) - } - - d.Set("consumer_inputs", FlattenConsumerInputs(stream.ConsumerInputs)) - - return nil -} - -func FlattenSingleAuditStream(m map[string]interface{}, stream *audit.AuditStream) error { - if stream == nil { - return nil - } - - m["id"] = strconv.Itoa(*stream.Id) - m["display_name"] = *stream.DisplayName - m["consumer_type"] = *stream.ConsumerType - m["status"] = string(*stream.Status) - - if stream.CreatedTime != nil { - m["created_time"] = stream.CreatedTime.String() - } - if stream.UpdatedTime != nil { - m["updated_time"] = stream.UpdatedTime.String() - } - - if stream.StatusReason != nil { - m["status_reason"] = *stream.StatusReason - } - - if inputs := FlattenConsumerInputs(stream.ConsumerInputs); inputs != nil { - m["consumer_inputs"] = inputs.List() - } else { - m["consumer_inputs"] = []any{} - } - - return nil -} From 69a70076a08dadba1b38005e3f85a04e7ac6e604 Mon Sep 17 00:00:00 2001 From: dennismdejong Date: Thu, 19 Mar 2026 19:20:25 +0100 Subject: [PATCH 6/6] Update vendored dependencies --- .../azuredevops/v7/audit/client.go | 312 ------------------ .../azuredevops/v7/audit/models.go | 158 --------- vendor/modules.txt | 1 - 3 files changed, 471 deletions(-) delete mode 100644 vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go delete mode 100644 vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go diff --git a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go deleted file mode 100644 index b1badaf38..000000000 --- a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/client.go +++ /dev/null @@ -1,312 +0,0 @@ -// -------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -------------------------------------------------------------------------------------------- -// Generated file, DO NOT EDIT -// Changes may cause incorrect behavior and will be lost if the code is regenerated. -// -------------------------------------------------------------------------------------------- - -package audit - -import ( - "bytes" - "context" - "encoding/json" - "github.com/google/uuid" - "github.com/microsoft/azure-devops-go-api/azuredevops/v7" - "io" - "net/http" - "net/url" - "strconv" -) - -var ResourceAreaId, _ = uuid.Parse("94ff054d-5ee1-413d-9341-3f4a7827de2e") - -type Client interface { - // [Preview API] Create new Audit Stream - CreateStream(context.Context, CreateStreamArgs) (*AuditStream, error) - // [Preview API] Delete Audit Stream - DeleteStream(context.Context, DeleteStreamArgs) error - // [Preview API] Downloads audit log entries. - DownloadLog(context.Context, DownloadLogArgs) (io.ReadCloser, error) - // [Preview API] Get all auditable actions filterable by area. - GetActions(context.Context, GetActionsArgs) (*[]AuditActionInfo, error) - // [Preview API] Return all Audit Streams scoped to an organization - QueryAllStreams(context.Context, QueryAllStreamsArgs) (*[]AuditStream, error) - // [Preview API] Queries audit log entries - QueryLog(context.Context, QueryLogArgs) (*AuditLogQueryResult, error) - // [Preview API] Return Audit Stream with id of streamId if one exists otherwise throw - QueryStreamById(context.Context, QueryStreamByIdArgs) (*AuditStream, error) - // [Preview API] Update existing Audit Stream status - UpdateStatus(context.Context, UpdateStatusArgs) (*AuditStream, error) - // [Preview API] Update existing Audit Stream - UpdateStream(context.Context, UpdateStreamArgs) (*AuditStream, error) -} - -type ClientImpl struct { - Client azuredevops.Client -} - -func NewClient(ctx context.Context, connection *azuredevops.Connection) (Client, error) { - client, err := connection.GetClientByResourceAreaId(ctx, ResourceAreaId) - if err != nil { - return nil, err - } - return &ClientImpl{ - Client: *client, - }, nil -} - -// [Preview API] Create new Audit Stream -func (client *ClientImpl) CreateStream(ctx context.Context, args CreateStreamArgs) (*AuditStream, error) { - if args.Stream == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.Stream"} - } - queryParams := url.Values{} - if args.DaysToBackfill == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "daysToBackfill"} - } - queryParams.Add("daysToBackfill", strconv.Itoa(*args.DaysToBackfill)) - body, marshalErr := json.Marshal(*args.Stream) - if marshalErr != nil { - return nil, marshalErr - } - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - resp, err := client.Client.Send(ctx, http.MethodPost, locationId, "7.1-preview.1", nil, queryParams, bytes.NewReader(body), "application/json", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue AuditStream - err = client.Client.UnmarshalBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the CreateStream function -type CreateStreamArgs struct { - // (required) Stream entry - Stream *AuditStream - // (required) The number of days of previously recorded audit data that will be replayed into the stream. A value of zero will result in only new events being streamed. - DaysToBackfill *int -} - -// [Preview API] Delete Audit Stream -func (client *ClientImpl) DeleteStream(ctx context.Context, args DeleteStreamArgs) error { - routeValues := make(map[string]string) - if args.StreamId == nil { - return &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} - } - routeValues["streamId"] = strconv.Itoa(*args.StreamId) - - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - _, err := client.Client.Send(ctx, http.MethodDelete, locationId, "7.1-preview.1", routeValues, nil, nil, "", "application/json", nil) - if err != nil { - return err - } - - return nil -} - -// Arguments for the DeleteStream function -type DeleteStreamArgs struct { - // (required) Id of stream entry to delete - StreamId *int -} - -// [Preview API] Downloads audit log entries. -func (client *ClientImpl) DownloadLog(ctx context.Context, args DownloadLogArgs) (io.ReadCloser, error) { - queryParams := url.Values{} - if args.Format == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "format"} - } - queryParams.Add("format", *args.Format) - if args.StartTime != nil { - queryParams.Add("startTime", (*args.StartTime).AsQueryParameter()) - } - if args.EndTime != nil { - queryParams.Add("endTime", (*args.EndTime).AsQueryParameter()) - } - locationId, _ := uuid.Parse("b7b98a76-04e8-4f4d-ac72-9d46492caaac") - resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/octet-stream", nil) - if err != nil { - return nil, err - } - - return resp.Body, err -} - -// Arguments for the DownloadLog function -type DownloadLogArgs struct { - // (required) File format for download. Can be "json" or "csv". - Format *string - // (optional) Start time of download window. Optional - StartTime *azuredevops.Time - // (optional) End time of download window. Optional - EndTime *azuredevops.Time -} - -// [Preview API] Get all auditable actions filterable by area. -func (client *ClientImpl) GetActions(ctx context.Context, args GetActionsArgs) (*[]AuditActionInfo, error) { - queryParams := url.Values{} - if args.AreaName != nil { - queryParams.Add("areaName", *args.AreaName) - } - locationId, _ := uuid.Parse("6fa30b9a-9558-4e3b-a95f-a12572caa6e6") - resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue []AuditActionInfo - err = client.Client.UnmarshalCollectionBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the GetActions function -type GetActionsArgs struct { - // (optional) Optional. Get actions scoped to area - AreaName *string -} - -// [Preview API] Return all Audit Streams scoped to an organization -func (client *ClientImpl) QueryAllStreams(ctx context.Context, args QueryAllStreamsArgs) (*[]AuditStream, error) { - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, nil, nil, "", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue []AuditStream - err = client.Client.UnmarshalCollectionBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the QueryAllStreams function -type QueryAllStreamsArgs struct { -} - -// [Preview API] Queries audit log entries -func (client *ClientImpl) QueryLog(ctx context.Context, args QueryLogArgs) (*AuditLogQueryResult, error) { - queryParams := url.Values{} - if args.StartTime != nil { - queryParams.Add("startTime", (*args.StartTime).AsQueryParameter()) - } - if args.EndTime != nil { - queryParams.Add("endTime", (*args.EndTime).AsQueryParameter()) - } - if args.BatchSize != nil { - queryParams.Add("batchSize", strconv.Itoa(*args.BatchSize)) - } - if args.ContinuationToken != nil { - queryParams.Add("continuationToken", *args.ContinuationToken) - } - if args.SkipAggregation != nil { - queryParams.Add("skipAggregation", strconv.FormatBool(*args.SkipAggregation)) - } - locationId, _ := uuid.Parse("4e5fa14f-7097-4b73-9c85-00abc7353c61") - resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", nil, queryParams, nil, "", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue AuditLogQueryResult - err = client.Client.UnmarshalBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the QueryLog function -type QueryLogArgs struct { - // (optional) Start time of download window. Optional - StartTime *azuredevops.Time - // (optional) End time of download window. Optional - EndTime *azuredevops.Time - // (optional) Max number of results to return. Optional - BatchSize *int - // (optional) Token used for returning next set of results from previous query. Optional - ContinuationToken *string - // (optional) Skips aggregating events and leaves them as individual entries instead. By default events are aggregated. Event types that are aggregated: AuditLog.AccessLog. - SkipAggregation *bool -} - -// [Preview API] Return Audit Stream with id of streamId if one exists otherwise throw -func (client *ClientImpl) QueryStreamById(ctx context.Context, args QueryStreamByIdArgs) (*AuditStream, error) { - routeValues := make(map[string]string) - if args.StreamId == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} - } - routeValues["streamId"] = strconv.Itoa(*args.StreamId) - - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - resp, err := client.Client.Send(ctx, http.MethodGet, locationId, "7.1-preview.1", routeValues, nil, nil, "", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue AuditStream - err = client.Client.UnmarshalBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the QueryStreamById function -type QueryStreamByIdArgs struct { - // (required) Id of stream entry to retrieve - StreamId *int -} - -// [Preview API] Update existing Audit Stream status -func (client *ClientImpl) UpdateStatus(ctx context.Context, args UpdateStatusArgs) (*AuditStream, error) { - routeValues := make(map[string]string) - if args.StreamId == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.StreamId"} - } - routeValues["streamId"] = strconv.Itoa(*args.StreamId) - - queryParams := url.Values{} - if args.Status == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "status"} - } - queryParams.Add("status", string(*args.Status)) - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - resp, err := client.Client.Send(ctx, http.MethodPut, locationId, "7.1-preview.1", routeValues, queryParams, nil, "", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue AuditStream - err = client.Client.UnmarshalBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the UpdateStatus function -type UpdateStatusArgs struct { - // (required) Id of stream entry to be updated - StreamId *int - // (required) Status of the stream - Status *AuditStreamStatus -} - -// [Preview API] Update existing Audit Stream -func (client *ClientImpl) UpdateStream(ctx context.Context, args UpdateStreamArgs) (*AuditStream, error) { - if args.Stream == nil { - return nil, &azuredevops.ArgumentNilError{ArgumentName: "args.Stream"} - } - body, marshalErr := json.Marshal(*args.Stream) - if marshalErr != nil { - return nil, marshalErr - } - locationId, _ := uuid.Parse("77d60bf9-1882-41c5-a90d-3a6d3c13fd3b") - resp, err := client.Client.Send(ctx, http.MethodPut, locationId, "7.1-preview.1", nil, nil, bytes.NewReader(body), "application/json", "application/json", nil) - if err != nil { - return nil, err - } - - var responseValue AuditStream - err = client.Client.UnmarshalBody(resp, &responseValue) - return &responseValue, err -} - -// Arguments for the UpdateStream function -type UpdateStreamArgs struct { - // (required) Stream entry - Stream *AuditStream -} diff --git a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go b/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go deleted file mode 100644 index 54d688408..000000000 --- a/vendor/github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit/models.go +++ /dev/null @@ -1,158 +0,0 @@ -// -------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -// -------------------------------------------------------------------------------------------- -// Generated file, DO NOT EDIT -// Changes may cause incorrect behavior and will be lost if the code is regenerated. -// -------------------------------------------------------------------------------------------- - -package audit - -import ( - "github.com/google/uuid" - "github.com/microsoft/azure-devops-go-api/azuredevops/v7" -) - -// Defines all the categories an AuditAction can be -type AuditActionCategory string - -type auditActionCategoryValuesType struct { - Unknown AuditActionCategory - Modify AuditActionCategory - Remove AuditActionCategory - Create AuditActionCategory - Access AuditActionCategory - Execute AuditActionCategory -} - -var AuditActionCategoryValues = auditActionCategoryValuesType{ - // The category is not known - Unknown: "unknown", - // An artifact has been Modified - Modify: "modify", - // An artifact has been Removed - Remove: "remove", - // An artifact has been Created - Create: "create", - // An artifact has been Accessed - Access: "access", - // An artifact has been Executed - Execute: "execute", -} - -type AuditActionInfo struct { - // The action id for the event, i.e Git.CreateRepo, Project.RenameProject - ActionId *string `json:"actionId,omitempty"` - // Area of Azure DevOps the action occurred - Area *string `json:"area,omitempty"` - // Type of action executed - Category *AuditActionCategory `json:"category,omitempty"` -} - -// The object returned when the audit log is queried. It contains the log and the information needed to query more audit entries. -type AuditLogQueryResult struct { - // The continuation token to pass to get the next set of results - ContinuationToken *string `json:"continuationToken,omitempty"` - // The list of audit log entries - DecoratedAuditLogEntries *[]DecoratedAuditLogEntry `json:"decoratedAuditLogEntries,omitempty"` - // True when there are more matching results to be fetched, false otherwise. - HasMore *bool `json:"hasMore,omitempty"` -} - -// This class represents an audit stream -type AuditStream struct { - // Inputs used to communicate with external service. Inputs could be url, a connection string, a token, etc. - ConsumerInputs *map[string]string `json:"consumerInputs,omitempty"` - // Type of the consumer, i.e. splunk, azureEventHub, etc. - ConsumerType *string `json:"consumerType,omitempty"` - // The time when the stream was created - CreatedTime *azuredevops.Time `json:"createdTime,omitempty"` - // Used to identify individual streams - DisplayName *string `json:"displayName,omitempty"` - // Unique stream identifier - Id *int `json:"id,omitempty"` - // Status of the stream, Enabled, Disabled - Status *AuditStreamStatus `json:"status,omitempty"` - // Reason for the current stream status, i.e. Disabled by the system, Invalid credentials, etc. - StatusReason *string `json:"statusReason,omitempty"` - // The time when the stream was last updated - UpdatedTime *azuredevops.Time `json:"updatedTime,omitempty"` -} - -// Represents the status of a stream -type AuditStreamStatus string - -type auditStreamStatusValuesType struct { - Unknown AuditStreamStatus - Enabled AuditStreamStatus - DisabledByUser AuditStreamStatus - DisabledBySystem AuditStreamStatus - Deleted AuditStreamStatus - Backfilling AuditStreamStatus -} - -var AuditStreamStatusValues = auditStreamStatusValuesType{ - // The state has not been set, The stream is new - Unknown: "unknown", - // The stream is enabled and can deliver events - Enabled: "enabled", - // The stream has been disabled by a user - DisabledByUser: "disabledByUser", - // The stream has been disabled by the system - DisabledBySystem: "disabledBySystem", - // The stream has been marked for deletion - Deleted: "deleted", - // The stream is delivering old events - Backfilling: "backfilling", -} - -type DecoratedAuditLogEntry struct { - // The action id for the event, i.e Git.CreateRepo, Project.RenameProject - ActionId *string `json:"actionId,omitempty"` - // ActivityId - ActivityId *uuid.UUID `json:"activityId,omitempty"` - // The Actor's Client Id (if actor is a service principal) - ActorClientId *uuid.UUID `json:"actorClientId,omitempty"` - // The Actor's CUID - ActorCUID *uuid.UUID `json:"actorCUID,omitempty"` - // DisplayName of the user who initiated the action - ActorDisplayName *string `json:"actorDisplayName,omitempty"` - // URL of Actor's Profile image - ActorImageUrl *string `json:"actorImageUrl,omitempty"` - // The Actor's UPN - ActorUPN *string `json:"actorUPN,omitempty"` - // The Actor's User Id (if actor is a user) - ActorUserId *uuid.UUID `json:"actorUserId,omitempty"` - // Area of Azure DevOps the action occurred - Area *string `json:"area,omitempty"` - // Type of authentication used by the actor - AuthenticationMechanism *string `json:"authenticationMechanism,omitempty"` - // Type of action executed - Category *AuditActionCategory `json:"category,omitempty"` - // DisplayName of the category - CategoryDisplayName *string `json:"categoryDisplayName,omitempty"` - // This allows related audit entries to be grouped together. Generally this occurs when a single action causes a cascade of audit entries. For example, project creation. - CorrelationId *uuid.UUID `json:"correlationId,omitempty"` - // External data such as CUIDs, item names, etc. - Data *map[string]interface{} `json:"data,omitempty"` - // Decorated details - Details *string `json:"details,omitempty"` - // EventId - Needs to be unique per service - Id *string `json:"id,omitempty"` - // IP Address where the event was originated - IpAddress *string `json:"ipAddress,omitempty"` - // When specified, the id of the project this event is associated to - ProjectId *uuid.UUID `json:"projectId,omitempty"` - // When specified, the name of the project this event is associated to - ProjectName *string `json:"projectName,omitempty"` - // DisplayName of the scope - ScopeDisplayName *string `json:"scopeDisplayName,omitempty"` - // The organization Id (Organization is the only scope currently supported) - ScopeId *uuid.UUID `json:"scopeId,omitempty"` - // The type of the scope (Organization is only scope currently supported) - ScopeType *string `json:"scopeType,omitempty"` - // The time when the event occurred in UTC - Timestamp *azuredevops.Time `json:"timestamp,omitempty"` - // The user agent from the request - UserAgent *string `json:"userAgent,omitempty"` -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 50bb6711e..b713c98d3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -292,7 +292,6 @@ github.com/mattn/go-isatty ## explicit; go 1.23.0 github.com/microsoft/azure-devops-go-api/azuredevops/v7 github.com/microsoft/azure-devops-go-api/azuredevops/v7/accounts -github.com/microsoft/azure-devops-go-api/azuredevops/v7/audit github.com/microsoft/azure-devops-go-api/azuredevops/v7/build github.com/microsoft/azure-devops-go-api/azuredevops/v7/commerce github.com/microsoft/azure-devops-go-api/azuredevops/v7/core