Skip to content

Commit c2bbec3

Browse files
mattdotmawasile
andauthored
Add partner_id setting to provider (#781)
* feat: add partner_id configuration * Update .changes/unreleased/added-20250517-062944.yaml * Update internal/provider/provider.go * Update internal/provider/provider.go --------- Co-authored-by: mawasile <50197777+mawasile@users.noreply.github.com>
1 parent ed27265 commit c2bbec3

File tree

10 files changed

+147
-11
lines changed

10 files changed

+147
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: added
2+
body: support partner_id configuration and opt-out for default Terraform partner ID
3+
time: 2025-05-17T06:29:44.020285595Z
4+
custom:
5+
Issue: "781"

docs/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ We recommend using Environment Variables to pass the credentials to the provider
358358
| `POWER_PLATFORM_CLIENT_CERTIFICATE` | The Base64 format of your certificate that will be used for certificate-based authentication | |
359359
| `POWER_PLATFORM_CLIENT_CERTIFICATE_FILE_PATH` | The path to the certificate that will be used for certificate-based authentication | |
360360
| `POWER_PLATFORM_AZDO_SERVICE_CONNECTION_ID` | The GUID of the Azure DevOps service connection to be used for Azure DevOps Workload Identity Federation | |
361+
| `POWER_PLATFORM_PARTNER_ID` | Partner GUID used for Customer Usage Attribution. | |
362+
| `POWER_PLATFORM_DISABLE_TERRAFORM_PARTNER_ID` | If set to `true`, the default Terraform partner ID will not be sent. | |
363+
| `ARM_PARTNER_ID` | Alternative environment variable for the partner GUID. | |
364+
| `ARM_DISABLE_TERRAFORM_PARTNER_ID` | Alternative variable to disable the default Terraform partner ID. | |
361365

362366
-> Variables passed into the provider will override the environment variables.
363367

@@ -381,6 +385,8 @@ In addition to the authentication options, the following options are also suppor
381385
| Name | Description | Default Value |
382386
|------|-------------|---------------|
383387
| `telemetry_optout` | Opting out of telemetry will remove the User-Agent and session id headers from the requests made to the Power Platform service. There is no other telemetry data collected by the provider. This may affect the ability to identify and troubleshoot issues with the provider. | `false` |
388+
| `partner_id` | Optional GUID for Customer Usage Attribution. When set, the value is appended to the User-Agent header as `pid-<GUID>`. | |
389+
| `disable_terraform_partner_id` | When `true`, suppresses the default Terraform partner ID when no custom `partner_id` is provided. | `false` |
384390

385391

386392
If you are using Azure CLI for authentication, you can also turn off CLI's telemetry by executing the following [command](https://github.com/Azure/azure-cli?tab=readme-ov-file#telemetry-configuration):
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
terraform {
2+
required_providers {
3+
powerplatform = {
4+
source = "microsoft/power-platform"
5+
}
6+
}
7+
}
8+
9+
# Provider configuration including partner ID for Customer Usage Attribution
10+
provider "powerplatform" {
11+
use_cli = true
12+
partner_id = "00000000-0000-0000-0000-000000000000"
13+
}

internal/api/request.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ func (client *Client) buildCorrelationHeaders(ctx context.Context) (sessionId st
146146
func (client *Client) buildUserAgent(ctx context.Context) string {
147147
userAgent := fmt.Sprintf("terraform-provider-power-platform/%s (%s; %s) terraform/%s go/%s", common.ProviderVersion, runtime.GOOS, runtime.GOARCH, client.Config.TerraformVersion, runtime.Version())
148148

149+
if client.Config.PartnerId != "" {
150+
userAgent += fmt.Sprintf(" pid-%s", client.Config.PartnerId)
151+
} else if !client.Config.DisableTerraformPartnerId {
152+
userAgent += fmt.Sprintf(" pid-%s", constants.DEFAULT_TERRAFORM_PARTNER_ID)
153+
}
154+
149155
requestContext, ok := ctx.Value(helpers.REQUEST_CONTEXT_KEY).(helpers.RequestContextValue)
150156
if ok {
151157
userAgent += fmt.Sprintf(" %s %s", requestContext.ObjectName, requestContext.RequestType)

internal/api/request_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/microsoft/terraform-provider-power-platform/internal/config"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestUnitBuildUserAgent_WithPartnerId(t *testing.T) {
12+
cfg := config.ProviderConfig{PartnerId: "00000000-0000-0000-0000-000000000001"}
13+
client := NewApiClientBase(&cfg, NewAuthBase(&cfg))
14+
ua := client.buildUserAgent(context.Background())
15+
require.Contains(t, ua, "pid-00000000-0000-0000-0000-000000000001")
16+
}

internal/config/config.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package config
66
import (
77
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
88
"github.com/hashicorp/terraform-plugin-framework/types"
9+
"github.com/microsoft/terraform-provider-power-platform/internal/customtypes"
910
"github.com/microsoft/terraform-provider-power-platform/internal/helpers"
1011
)
1112

@@ -52,11 +53,13 @@ type ProviderConfig struct {
5253
EnableContinuousAccessEvaluation bool
5354

5455
// internal runtime configuration values
55-
TestMode bool
56-
Urls ProviderConfigUrls
57-
TelemetryOptout bool
58-
Cloud cloud.Configuration
59-
TerraformVersion string
56+
TestMode bool
57+
Urls ProviderConfigUrls
58+
TelemetryOptout bool
59+
PartnerId string
60+
DisableTerraformPartnerId bool
61+
Cloud cloud.Configuration
62+
TerraformVersion string
6063
}
6164

6265
type ProviderConfigUrls struct {
@@ -135,8 +138,10 @@ type ProviderConfigModel struct {
135138
UseOidc types.Bool `tfsdk:"use_oidc"`
136139
UseMsi types.Bool `tfsdk:"use_msi"`
137140

138-
Cloud types.String `tfsdk:"cloud"`
139-
TelemetryOptout types.Bool `tfsdk:"telemetry_optout"`
141+
Cloud types.String `tfsdk:"cloud"`
142+
TelemetryOptout types.Bool `tfsdk:"telemetry_optout"`
143+
PartnerId customtypes.UUID `tfsdk:"partner_id"`
144+
DisableTerraformPartnerId types.Bool `tfsdk:"disable_terraform_partner_id"`
140145

141146
TenantId types.String `tfsdk:"tenant_id"`
142147
AuxiliaryTenantIDs types.List `tfsdk:"auxiliary_tenant_ids"`

internal/constants/constants.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ const (
135135
HEADER_RETRY_AFTER = "Retry-After"
136136
HTTPS = "https"
137137
API_VERSION_PARAM = "api-version"
138+
139+
DEFAULT_TERRAFORM_PARTNER_ID = "222c6c49-1b0a-5959-a213-6608f9eb8820"
138140
)
139141

140142
const (
@@ -164,6 +166,11 @@ const (
164166
ENV_VAR_ARM_OIDC_TOKEN = "ARM_OIDC_TOKEN"
165167
ENV_VAR_ARM_OIDC_TOKEN_FILE_PATH = "ARM_OIDC_TOKEN_FILE_PATH"
166168
ENV_VAR_ARM_AUXILIARY_TENANT_IDS = "ARM_AUXILIARY_TENANT_IDS"
169+
170+
ENV_VAR_POWER_PLATFORM_PARTNER_ID = "POWER_PLATFORM_PARTNER_ID"
171+
ENV_VAR_ARM_PARTNER_ID = "ARM_PARTNER_ID"
172+
ENV_VAR_POWER_PLATFORM_DISABLE_TERRAFORM_PARTNER_ID = "POWER_PLATFORM_DISABLE_TERRAFORM_PARTNER_ID"
173+
ENV_VAR_ARM_DISABLE_TERRAFORM_PARTNER_ID = "ARM_DISABLE_TERRAFORM_PARTNER_ID"
167174
)
168175

169176
const (

internal/provider/provider.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package provider
66
import (
77
"context"
88
"fmt"
9+
"github.com/google/uuid"
910
"os"
1011

1112
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
@@ -63,10 +64,12 @@ type PowerPlatformProvider struct {
6364
func NewPowerPlatformProvider(ctx context.Context, testModeEnabled ...bool) func() provider.Provider {
6465
cloudUrls, cloudConfig := getCloudPublicUrls()
6566
providerConfig := config.ProviderConfig{
66-
Urls: *cloudUrls,
67-
Cloud: *cloudConfig,
68-
TerraformVersion: "unknown",
69-
TelemetryOptout: false,
67+
Urls: *cloudUrls,
68+
Cloud: *cloudConfig,
69+
TerraformVersion: "unknown",
70+
TelemetryOptout: false,
71+
PartnerId: constants.DEFAULT_TERRAFORM_PARTNER_ID,
72+
DisableTerraformPartnerId: false,
7073
}
7174

7275
if len(testModeEnabled) > 0 && testModeEnabled[0] {
@@ -166,6 +169,15 @@ func (p *PowerPlatformProvider) Schema(ctx context.Context, req provider.SchemaR
166169
MarkdownDescription: "Flag to indicate whether to opt out of telemetry. Default is `false`",
167170
Optional: true,
168171
},
172+
"partner_id": schema.StringAttribute{
173+
MarkdownDescription: "The GUID of the partner for Customer Usage Attribution (CUA).",
174+
Optional: true,
175+
CustomType: customtypes.UUIDType{},
176+
},
177+
"disable_terraform_partner_id": schema.BoolAttribute{
178+
MarkdownDescription: "Disable sending the default Terraform partner ID when no custom partner_id is provided. Default is `false`",
179+
Optional: true,
180+
},
169181
"use_msi": schema.BoolAttribute{
170182
MarkdownDescription: "Flag to indicate whether to use managed identity for authentication",
171183
Optional: true,
@@ -216,6 +228,22 @@ func (p *PowerPlatformProvider) Configure(ctx context.Context, req provider.Conf
216228
// Check for telemetry opt out
217229
telemetryOptOut := helpers.GetConfigBool(ctx, configValue.TelemetryOptout, constants.ENV_VAR_POWER_PLATFORM_TELEMETRY_OPTOUT, false)
218230

231+
partnerId := helpers.GetConfigMultiString(ctx, configValue.PartnerId.StringValue, []string{constants.ENV_VAR_POWER_PLATFORM_PARTNER_ID, constants.ENV_VAR_ARM_PARTNER_ID}, "")
232+
if partnerId != "" {
233+
if _, err := uuid.Parse(partnerId); err != nil {
234+
resp.Diagnostics.AddAttributeError(
235+
path.Root("partner_id"),
236+
"Invalid UUID",
237+
fmt.Sprintf("The partner_id must be a valid UUID: %v", err),
238+
)
239+
return
240+
}
241+
}
242+
disableTerraformPartnerId := helpers.GetConfigBool(ctx, configValue.DisableTerraformPartnerId, constants.ENV_VAR_POWER_PLATFORM_DISABLE_TERRAFORM_PARTNER_ID, false)
243+
if !disableTerraformPartnerId {
244+
disableTerraformPartnerId = helpers.GetConfigBool(ctx, types.BoolNull(), constants.ENV_VAR_ARM_DISABLE_TERRAFORM_PARTNER_ID, false)
245+
}
246+
219247
// Get CAE configuration
220248
enableCae := helpers.GetConfigBool(ctx, configValue.EnableContinuousAccessEvaluation, constants.ENV_VAR_POWER_PLATFORM_ENABLE_CAE, false)
221249

@@ -262,6 +290,8 @@ func (p *PowerPlatformProvider) Configure(ctx context.Context, req provider.Conf
262290
p.Config.Urls = *providerConfigUrls
263291
p.Config.Cloud = *cloudConfiguration
264292
p.Config.TelemetryOptout = telemetryOptOut
293+
p.Config.PartnerId = partnerId
294+
p.Config.DisableTerraformPartnerId = disableTerraformPartnerId
265295
p.Config.EnableContinuousAccessEvaluation = enableCae
266296
p.Config.TerraformVersion = req.TerraformVersion
267297

internal/provider/provider_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,45 @@ func TestUnitPowerPlatformProvider_Validate_Telementry_Optout_Is_True(t *testing
176176
},
177177
})
178178
}
179+
180+
func TestUnitPowerPlatformProvider_PartnerId_Valid(t *testing.T) {
181+
httpmock.Activate()
182+
defer httpmock.DeactivateAndReset()
183+
184+
mocks.ActivateEnvironmentHttpMocks()
185+
186+
httpmock.RegisterResponder("GET", `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?%24expand=properties%2FbillingPolicy%2Cproperties%2FcopilotPolicies&api-version=2023-06-01`,
187+
func(req *http.Request) (*http.Response, error) {
188+
return httpmock.NewStringResponse(http.StatusOK, httpmock.File("../services/environment/tests/datasource/Validate_Read/get_environments.json").String()), nil
189+
})
190+
191+
test.Test(t, test.TestCase{
192+
IsUnitTest: true,
193+
ProtoV6ProviderFactories: mocks.TestUnitTestProtoV6ProviderFactories,
194+
Steps: []test.TestStep{
195+
{
196+
Config: `provider "powerplatform" {
197+
use_cli = true
198+
partner_id = "00000000-0000-0000-0000-000000000001"
199+
}
200+
data "powerplatform_environments" "all" {}`,
201+
},
202+
},
203+
})
204+
}
205+
206+
func TestUnitPowerPlatformProvider_PartnerId_Invalid(t *testing.T) {
207+
test.Test(t, test.TestCase{
208+
IsUnitTest: true,
209+
ProtoV6ProviderFactories: mocks.TestUnitTestProtoV6ProviderFactories,
210+
Steps: []test.TestStep{
211+
{
212+
Config: `provider "powerplatform" {
213+
partner_id = "invalid-guid"
214+
}
215+
data "powerplatform_environments" "all" {}`,
216+
ExpectError: regexp.MustCompile("Invalid UUID"),
217+
},
218+
},
219+
})
220+
}

templates/index.md.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,10 @@ We recommend using Environment Variables to pass the credentials to the provider
358358
| `POWER_PLATFORM_CLIENT_CERTIFICATE` | The Base64 format of your certificate that will be used for certificate-based authentication | |
359359
| `POWER_PLATFORM_CLIENT_CERTIFICATE_FILE_PATH` | The path to the certificate that will be used for certificate-based authentication | |
360360
| `POWER_PLATFORM_AZDO_SERVICE_CONNECTION_ID` | The GUID of the Azure DevOps service connection to be used for Azure DevOps Workload Identity Federation | |
361+
| `POWER_PLATFORM_PARTNER_ID` | Partner GUID used for Customer Usage Attribution. | |
362+
| `POWER_PLATFORM_DISABLE_TERRAFORM_PARTNER_ID` | If set to `true`, the default Terraform partner ID will not be sent. | |
363+
| `ARM_PARTNER_ID` | Alternative environment variable for the partner GUID. | |
364+
| `ARM_DISABLE_TERRAFORM_PARTNER_ID` | Alternative variable to disable the default Terraform partner ID. | |
361365

362366
-> Variables passed into the provider will override the environment variables.
363367

@@ -381,6 +385,8 @@ In addition to the authentication options, the following options are also suppor
381385
| Name | Description | Default Value |
382386
|------|-------------|---------------|
383387
| `telemetry_optout` | Opting out of telemetry will remove the User-Agent and session id headers from the requests made to the Power Platform service. There is no other telemetry data collected by the provider. This may affect the ability to identify and troubleshoot issues with the provider. | `false` |
388+
| `partner_id` | Optional GUID for Customer Usage Attribution. When set, the value is appended to the User-Agent header as `pid-<GUID>`. | |
389+
| `disable_terraform_partner_id` | When `true`, suppresses the default Terraform partner ID when no custom `partner_id` is provided. | `false` |
384390

385391

386392
If you are using Azure CLI for authentication, you can also turn off CLI's telemetry by executing the following [command](https://github.com/Azure/azure-cli?tab=readme-ov-file#telemetry-configuration):

0 commit comments

Comments
 (0)