From 08b4ef39013506bbeb9c119b5d78611aecec09f5 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Wed, 11 Mar 2026 17:15:32 +0800 Subject: [PATCH 1/2] audience parameter support --- deploy/parameter/helm-values.yaml | 2 + deploy/templates/deployment.yaml | 4 + .../loader/configuration_client_manager.go | 28 +++-- .../configuration_client_manager_test.go | 106 ++++++++++++++++++ 4 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 internal/loader/configuration_client_manager_test.go diff --git a/deploy/parameter/helm-values.yaml b/deploy/parameter/helm-values.yaml index 5b75f6b..ebdf819 100644 --- a/deploy/parameter/helm-values.yaml +++ b/deploy/parameter/helm-values.yaml @@ -67,6 +67,8 @@ autoscaling: targetCPUUtilizationPercentage: 80 targetMemoryUtilizationPercentage: 80 +audience: "" + env: azureClientId: "" azureTenantId: "" diff --git a/deploy/templates/deployment.yaml b/deploy/templates/deployment.yaml index ae79d3f..52cd825 100644 --- a/deploy/templates/deployment.yaml +++ b/deploy/templates/deployment.yaml @@ -88,6 +88,10 @@ spec: {{- end }} - name: REQUEST_TRACING_ENABLED value: "{{ .Values.requestTracing.enabled }}" + {{- if .Values.audience }} + - name: AZURE_APPCONFIG_AUDIENCE + value: {{ .Values.audience | quote }} + {{- end }} livenessProbe: httpGet: path: /healthz diff --git a/internal/loader/configuration_client_manager.go b/internal/loader/configuration_client_manager.go index 6ffd39e..82dc951 100644 --- a/internal/loader/configuration_client_manager.go +++ b/internal/loader/configuration_client_manager.go @@ -19,6 +19,7 @@ import ( acpv1 "azappconfig/provider/api/v1" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" azappconfig "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2" @@ -80,17 +81,30 @@ const ( ApiTokenExchangeAudience string = "api://AzureADTokenExchange" AnnotationClientID string = "azure.workload.identity/client-id" AnnotationTenantID string = "azure.workload.identity/tenant-id" + AzureAppConfigAudience string = "AZURE_APPCONFIG_AUDIENCE" ) -var ( - clientOptionWithModuleInfo *azappconfig.ClientOptions = &azappconfig.ClientOptions{ +func newClientOptions() *azappconfig.ClientOptions { + options := &azappconfig.ClientOptions{ ClientOptions: policy.ClientOptions{ Telemetry: policy.TelemetryOptions{ ApplicationID: fmt.Sprintf("%s/%s", properties.ModuleName, properties.ModuleVersion), }, }, } -) + + if audience, ok := os.LookupEnv(AzureAppConfigAudience); ok && audience != "" { + options.ClientOptions.Cloud = cloud.Configuration{ + Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ + azappconfig.ServiceName: { + Audience: audience, + }, + }, + } + } + + return options +} func NewConfigurationClientManager(ctx context.Context, provider acpv1.AzureAppConfigurationProvider) (ClientManager, error) { manager := &ConfigurationClientManager{ @@ -118,14 +132,14 @@ func NewConfigurationClientManager(ctx context.Context, provider acpv1.AzureAppC if manager.id, err = parseConnectionString(connectionString, IdSection); err != nil { return nil, err } - if staticClient, err = azappconfig.NewClientFromConnectionString(connectionString, clientOptionWithModuleInfo); err != nil { + if staticClient, err = azappconfig.NewClientFromConnectionString(connectionString, newClientOptions()); err != nil { return nil, err } } else { if manager.credential, err = CreateTokenCredential(ctx, provider.Spec.Auth, provider.Namespace); err != nil { return nil, err } - if staticClient, err = azappconfig.NewClient(*provider.Spec.Endpoint, manager.credential, clientOptionWithModuleInfo); err != nil { + if staticClient, err = azappconfig.NewClient(*provider.Spec.Endpoint, manager.credential, newClientOptions()); err != nil { return nil, err } manager.endpoint = *provider.Spec.Endpoint @@ -286,7 +300,7 @@ func QuerySrvTargetHost(ctx context.Context, host string) ([]string, error) { func (manager *ConfigurationClientManager) newConfigurationClient(endpoint string) (*azappconfig.Client, error) { if manager.credential != nil { - return azappconfig.NewClient(endpoint, manager.credential, clientOptionWithModuleInfo) + return azappconfig.NewClient(endpoint, manager.credential, newClientOptions()) } connectionStr := buildConnectionString(endpoint, manager.secret, manager.id) @@ -294,7 +308,7 @@ func (manager *ConfigurationClientManager) newConfigurationClient(endpoint strin return nil, fmt.Errorf("failed to build connection string for fallback client") } - return azappconfig.NewClientFromConnectionString(connectionStr, clientOptionWithModuleInfo) + return azappconfig.NewClientFromConnectionString(connectionStr, newClientOptions()) } func isValidEndpoint(host string, validDomain string) bool { diff --git a/internal/loader/configuration_client_manager_test.go b/internal/loader/configuration_client_manager_test.go new file mode 100644 index 0000000..7b1f8f5 --- /dev/null +++ b/internal/loader/configuration_client_manager_test.go @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package loader + +import ( + "azappconfig/provider/internal/properties" + "fmt" + "os" + "testing" + + azappconfig "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig/v2" +) + +func TestNewClientOptions(t *testing.T) { + expectedAppID := fmt.Sprintf("%s/%s", properties.ModuleName, properties.ModuleVersion) + + tests := []struct { + name string + envVars map[string]string + expectedAudience string + hasCloudConfig bool + }{ + { + name: "no audience set - default behavior", + envVars: nil, + expectedAudience: "", + hasCloudConfig: false, + }, + { + name: "audience set to Azure Government", + envVars: map[string]string{ + AzureAppConfigAudience: "https://appconfig.azure.us", + }, + expectedAudience: "https://appconfig.azure.us", + hasCloudConfig: true, + }, + { + name: "audience set to Azure China", + envVars: map[string]string{ + AzureAppConfigAudience: "https://appconfig.azure.cn", + }, + expectedAudience: "https://appconfig.azure.cn", + hasCloudConfig: true, + }, + { + name: "audience set to empty string", + envVars: map[string]string{ + AzureAppConfigAudience: "", + }, + expectedAudience: "", + hasCloudConfig: false, + }, + { + name: "audience set to custom value", + envVars: map[string]string{ + AzureAppConfigAudience: "https://custom.audience.example", + }, + expectedAudience: "https://custom.audience.example", + hasCloudConfig: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Clean up the env var before each test + os.Unsetenv(AzureAppConfigAudience) + + // Setup environment variables + if tt.envVars != nil { + for k, v := range tt.envVars { + if err := os.Setenv(k, v); err != nil { + t.Fatalf("Failed to set environment variable: %v", err) + } + defer func(key string) { + if err := os.Unsetenv(key); err != nil { + t.Errorf("Failed to unset environment variable: %v", err) + } + }(k) + } + } + + options := newClientOptions() + + // Verify telemetry ApplicationID is always set + if options.ClientOptions.Telemetry.ApplicationID != expectedAppID { + t.Errorf("Expected ApplicationID %q, got %q", expectedAppID, options.ClientOptions.Telemetry.ApplicationID) + } + + // Verify cloud configuration / audience + if tt.hasCloudConfig { + serviceConfig, exists := options.ClientOptions.Cloud.Services[azappconfig.ServiceName] + if !exists { + t.Fatal("Expected cloud service configuration to be set for azappconfig.ServiceName, but it was not found") + } + if serviceConfig.Audience != tt.expectedAudience { + t.Errorf("Expected audience %q, got %q", tt.expectedAudience, serviceConfig.Audience) + } + } else { + if len(options.ClientOptions.Cloud.Services) != 0 { + t.Errorf("Expected no cloud service configuration, but got %v", options.ClientOptions.Cloud.Services) + } + } + }) + } +} From 102f78e15e3d3a453d7ecdafe27c28a25b42bd07 Mon Sep 17 00:00:00 2001 From: "Lingling Ye (from Dev Box)" Date: Thu, 12 Mar 2026 18:05:30 +0800 Subject: [PATCH 2/2] fix lint --- internal/loader/configuration_client_manager_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/loader/configuration_client_manager_test.go b/internal/loader/configuration_client_manager_test.go index 7b1f8f5..9775e08 100644 --- a/internal/loader/configuration_client_manager_test.go +++ b/internal/loader/configuration_client_manager_test.go @@ -64,7 +64,9 @@ func TestNewClientOptions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Clean up the env var before each test - os.Unsetenv(AzureAppConfigAudience) + if err := os.Unsetenv(AzureAppConfigAudience); err != nil { + t.Fatalf("Failed to unset environment variable: %v", err) + } // Setup environment variables if tt.envVars != nil {