From c4481e9f9345858de24efc745a36e33691fe17b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 2 Jan 2026 23:56:28 +0000 Subject: [PATCH 1/6] Initial plan From e521db53a45489a78e114c3942cd661f3ff71628 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:05:48 +0000 Subject: [PATCH 2/6] Add subscriptionId support to remote state configuration for cross-tenant authentication Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../pkg/azsdk/storage/storage_blob_client.go | 31 ++++++++++++++++--- cli/azd/test/functional/remote_state_test.go | 18 ++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/cli/azd/pkg/azsdk/storage/storage_blob_client.go b/cli/azd/pkg/azsdk/storage/storage_blob_client.go index 7b70c9276db..84951c009bc 100644 --- a/cli/azd/pkg/azsdk/storage/storage_blob_client.go +++ b/cli/azd/pkg/azsdk/storage/storage_blob_client.go @@ -17,11 +17,19 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/cloud" ) +// SubscriptionTenantResolver allows resolving the correct tenant ID +// that allows the current account access to a given subscription. +type SubscriptionTenantResolver interface { + // Resolve the tenant ID required by the current account to access the given subscription. + LookupTenant(ctx context.Context, subscriptionId string) (tenantId string, err error) +} + // AccountConfig contains the configuration for connecting to a storage account type AccountConfig struct { - AccountName string - ContainerName string - Endpoint string + AccountName string + ContainerName string + Endpoint string + SubscriptionId string } var ( @@ -172,6 +180,7 @@ func NewBlobSdkClient( accountConfig *AccountConfig, coreClientOptions *azcore.ClientOptions, cloud *cloud.Cloud, + tenantResolver SubscriptionTenantResolver, ) (*azblob.Client, error) { blobOptions := &azblob.ClientOptions{ ClientOptions: *coreClientOptions, @@ -181,8 +190,20 @@ func NewBlobSdkClient( accountConfig.Endpoint = cloud.StorageEndpointSuffix } - // Use home tenant ID - credential, err := credentialProvider.GetTokenCredential(context.Background(), "") + // Determine which tenant to use for authentication + tenantId := "" + if accountConfig.SubscriptionId != "" { + // If a subscription ID is configured, resolve the tenant ID for that subscription + resolvedTenantId, err := tenantResolver.LookupTenant(context.Background(), accountConfig.SubscriptionId) + if err != nil { + return nil, fmt.Errorf( + "failed to resolve tenant for subscription '%s': %w", accountConfig.SubscriptionId, err) + } + tenantId = resolvedTenantId + } + // Otherwise, use home tenant ID (empty string) + + credential, err := credentialProvider.GetTokenCredential(context.Background(), tenantId) if err != nil { return nil, err } diff --git a/cli/azd/test/functional/remote_state_test.go b/cli/azd/test/functional/remote_state_test.go index 0740ff33ac5..b89bb1e4917 100644 --- a/cli/azd/test/functional/remote_state_test.go +++ b/cli/azd/test/functional/remote_state_test.go @@ -91,14 +91,30 @@ func createBlobClient( ) require.NoError(t, err) + // Create a mock SubscriptionTenantResolver that returns empty tenant (home tenant) + tenantResolver := &mockTenantResolver{} + sdkClient, err := storage.NewBlobSdkClient( - auth.NewMultiTenantCredentialProvider(authManager), storageConfig, coreClientOptions, cloud.AzurePublic()) + auth.NewMultiTenantCredentialProvider(authManager), + storageConfig, + coreClientOptions, + cloud.AzurePublic(), + tenantResolver, + ) require.NoError(t, err) require.NotNil(t, sdkClient) return storage.NewBlobClient(storageConfig, sdkClient) } +// mockTenantResolver is a simple mock implementation for testing +type mockTenantResolver struct{} + +func (m *mockTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) { + // For tests, just return empty string (home tenant) + return "", nil +} + type remoteStateTestFunc func(storageConfig *storage.AccountConfig) func runTestWithRemoteState(t *testing.T, testFunc remoteStateTestFunc) { From ecf66bbbe04f155a418fbccf709f6530ba28f0b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:08:03 +0000 Subject: [PATCH 3/6] Add unit tests for cross-tenant blob storage authentication Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- .../azsdk/storage/storage_blob_client_test.go | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 cli/azd/pkg/azsdk/storage/storage_blob_client_test.go diff --git a/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go new file mode 100644 index 00000000000..7483a8a20a5 --- /dev/null +++ b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package storage + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/azure/azure-dev/cli/azd/pkg/cloud" + "github.com/pkg/errors" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +// mockMultiTenantCredentialProvider is a mock implementation for testing +type mockMultiTenantCredentialProvider struct { + mock.Mock +} + +func (m *mockMultiTenantCredentialProvider) GetTokenCredential( + ctx context.Context, tenantId string) (azcore.TokenCredential, error) { + args := m.Called(ctx, tenantId) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(azcore.TokenCredential), args.Error(1) +} + +// mockSubscriptionTenantResolver is a mock implementation for testing +type mockSubscriptionTenantResolver struct { + mock.Mock +} + +func (m *mockSubscriptionTenantResolver) LookupTenant( + ctx context.Context, subscriptionId string) (string, error) { + args := m.Called(ctx, subscriptionId) + return args.String(0), args.Error(1) +} + +// mockTokenCredential is a minimal mock implementation for testing +type mockTokenCredential struct { + mock.Mock +} + +func (m *mockTokenCredential) GetToken( + ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { + args := m.Called(ctx, opts) + return args.Get(0).(azcore.AccessToken), args.Error(1) +} + +func Test_NewBlobSdkClient_UsesHomeTenantWhenNoSubscriptionId(t *testing.T) { + mockCredProvider := &mockMultiTenantCredentialProvider{} + mockTenantResolver := &mockSubscriptionTenantResolver{} + mockCred := &mockTokenCredential{} + + accountConfig := &AccountConfig{ + AccountName: "testaccount", + ContainerName: "testcontainer", + // No SubscriptionId set - should use home tenant + } + + coreClientOptions := &azcore.ClientOptions{} + + // Expect credential provider to be called with empty tenant ID (home tenant) + mockCredProvider.On("GetTokenCredential", mock.Anything, "").Return(mockCred, nil) + + // TenantResolver should NOT be called when no subscription ID is provided + mockTenantResolver.AssertNotCalled(t, "LookupTenant", mock.Anything, mock.Anything) + + client, err := NewBlobSdkClient( + mockCredProvider, + accountConfig, + coreClientOptions, + cloud.AzurePublic(), + mockTenantResolver, + ) + + require.NoError(t, err) + require.NotNil(t, client) + mockCredProvider.AssertExpectations(t) + mockTenantResolver.AssertExpectations(t) +} + +func Test_NewBlobSdkClient_ResolvesTenantWhenSubscriptionIdProvided(t *testing.T) { + mockCredProvider := &mockMultiTenantCredentialProvider{} + mockTenantResolver := &mockSubscriptionTenantResolver{} + mockCred := &mockTokenCredential{} + + testSubscriptionId := "test-subscription-id" + testTenantId := "test-tenant-id" + + accountConfig := &AccountConfig{ + AccountName: "testaccount", + ContainerName: "testcontainer", + SubscriptionId: testSubscriptionId, + } + + coreClientOptions := &azcore.ClientOptions{} + + // Expect tenant resolver to be called with the subscription ID + mockTenantResolver.On("LookupTenant", mock.Anything, testSubscriptionId).Return(testTenantId, nil) + + // Expect credential provider to be called with resolved tenant ID + mockCredProvider.On("GetTokenCredential", mock.Anything, testTenantId).Return(mockCred, nil) + + client, err := NewBlobSdkClient( + mockCredProvider, + accountConfig, + coreClientOptions, + cloud.AzurePublic(), + mockTenantResolver, + ) + + require.NoError(t, err) + require.NotNil(t, client) + mockCredProvider.AssertExpectations(t) + mockTenantResolver.AssertExpectations(t) +} + +func Test_NewBlobSdkClient_ReturnsErrorWhenTenantResolutionFails(t *testing.T) { + mockCredProvider := &mockMultiTenantCredentialProvider{} + mockTenantResolver := &mockSubscriptionTenantResolver{} + + testSubscriptionId := "test-subscription-id" + + accountConfig := &AccountConfig{ + AccountName: "testaccount", + ContainerName: "testcontainer", + SubscriptionId: testSubscriptionId, + } + + coreClientOptions := &azcore.ClientOptions{} + + // Simulate tenant resolution failure + mockTenantResolver.On("LookupTenant", mock.Anything, testSubscriptionId). + Return("", errors.New("subscription not found")) + + client, err := NewBlobSdkClient( + mockCredProvider, + accountConfig, + coreClientOptions, + cloud.AzurePublic(), + mockTenantResolver, + ) + + require.Error(t, err) + require.Nil(t, client) + require.Contains(t, err.Error(), "failed to resolve tenant for subscription") + mockTenantResolver.AssertExpectations(t) +} From b0091cfa2bcced33df76632ac2a3d7f4983ea2f2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:10:04 +0000 Subject: [PATCH 4/6] Fix environment manager tests to register SubscriptionTenantResolver mock Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- cli/azd/pkg/environment/manager_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/azd/pkg/environment/manager_test.go b/cli/azd/pkg/environment/manager_test.go index 1d965545812..e499b7370c6 100644 --- a/cli/azd/pkg/environment/manager_test.go +++ b/cli/azd/pkg/environment/manager_test.go @@ -380,6 +380,12 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) { mockContext.Container.MustRegisterSingleton(func() *azcore.ClientOptions { return mockContext.CoreClientOptions }) + + // Register a mock SubscriptionTenantResolver for tests + mockContext.Container.MustRegisterSingleton(func() storage.SubscriptionTenantResolver { + return &mockSubscriptionTenantResolver{} + }) + mockContext.Container.MustRegisterSingleton(storage.NewBlobSdkClient) mockContext.Container.MustRegisterSingleton(config.NewManager) mockContext.Container.MustRegisterSingleton(storage.NewBlobClient) @@ -405,6 +411,14 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) { }) } +// mockSubscriptionTenantResolver is a simple mock for testing +type mockSubscriptionTenantResolver struct{} + +func (m *mockSubscriptionTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) { + // For tests, just return empty string (home tenant) + return "", nil +} + type MockDataStore struct { mock.Mock } From bcf34566eada6fcdf948a6f18b3dadd87bfaef0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:14:40 +0000 Subject: [PATCH 5/6] Fix test assertion order in blob client unit test Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- cli/azd/go.mod | 6 +----- cli/azd/go.sum | 17 ----------------- .../azsdk/storage/storage_blob_client_test.go | 7 +++---- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/cli/azd/go.mod b/cli/azd/go.mod index ea0a2caaa97..04eb7a073ba 100644 --- a/cli/azd/go.mod +++ b/cli/azd/go.mod @@ -10,7 +10,6 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement v1.1.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices v1.8.0 @@ -28,7 +27,6 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2 - github.com/Azure/azure-storage-file-go v0.8.0 github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/Masterminds/semver/v3 v3.4.0 @@ -61,6 +59,7 @@ require ( github.com/moby/patternmatcher v0.6.0 github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d github.com/otiai10/copy v1.14.1 + github.com/pkg/errors v0.9.1 github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sergi/go-diff v1.4.0 @@ -87,7 +86,6 @@ require ( ) require ( - github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect @@ -122,7 +120,6 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mailru/easyjson v0.9.1 // indirect - github.com/mattn/go-ieproxy v0.0.12 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect @@ -136,7 +133,6 @@ require ( github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/cli/azd/go.sum b/cli/azd/go.sum index 33ff063c330..20d420b309a 100644 --- a/cli/azd/go.sum +++ b/cli/azd/go.sum @@ -25,9 +25,6 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AssemblyAI/assemblyai-go-sdk v1.3.0 h1:AtOVgGxUycvK4P4ypP+1ZupecvFgnfH+Jsum0o5ILoU= github.com/AssemblyAI/assemblyai-go-sdk v1.3.0/go.mod h1:H0naZbvpIW49cDA5ZZ/gggeXqi7ojSGB1mqshRk6kNE= -github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= -github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= @@ -42,8 +39,6 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappcon github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration v1.1.1/go.mod h1:21Lewei+tg5zp5xmyOxfDY//2tBvWQXee0UoM8xZjr8= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0 h1:ilMZ576u8sm975EqV+AKEtD4u9TLwqEo2XY9csPXBRo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers/v3 v3.1.0/go.mod h1:LGhzy+pg9AKr1Z7ZRyTC1qr1xNyVqLsqydvLdY+2iQk= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1 h1:hINezNnGCNxNRWLA8uzb4srfqhPnVnZ/XO7v9B0YMfA= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform/v2 v2.0.1/go.mod h1:iFAEO9giU3p1Ly0K3p6xMMXUqMMk6f//tXQ8OL5gK8Q= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0 h1:JI8PcWOImyvIUEZ0Bbmfe05FOlWkMi2KhjG+cAKaUms= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice/v2 v2.3.0/go.mod h1:nJLFPGJkyKfDDyJiPuHIXsCi/gpJkm07EvRgiX7SGlI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0 h1:Hp+EScFOu9HeCbeW8WU2yQPJd4gGwhMgKxWe+G6jNzw= @@ -90,8 +85,6 @@ github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 h1:FwladfywkNirM+FZY github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2/go.mod h1:vv5Ad0RrIoT1lJFdWBZwt4mB1+j+V8DUroixmKDTCdk= github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2 h1:l3SabZmNuXCMCbQUIeR4W6/N4j8SeH/lwX+a6leZhHo= github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.2/go.mod h1:k+mEZ4f1pVqZTRqtSDW2AhZ/3wT5qLpsUA75C/k7dtE= -github.com/Azure/azure-storage-file-go v0.8.0 h1:OX8DGsleWLUE6Mw4R/OeWEZMvsTIpwN94J59zqKQnTI= -github.com/Azure/azure-storage-file-go v0.8.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= @@ -274,7 +267,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -291,10 +283,6 @@ github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= -github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -483,8 +471,6 @@ golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= @@ -500,8 +486,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -518,7 +502,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= diff --git a/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go index 7483a8a20a5..145dc7dcf16 100644 --- a/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go +++ b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go @@ -67,9 +67,6 @@ func Test_NewBlobSdkClient_UsesHomeTenantWhenNoSubscriptionId(t *testing.T) { // Expect credential provider to be called with empty tenant ID (home tenant) mockCredProvider.On("GetTokenCredential", mock.Anything, "").Return(mockCred, nil) - // TenantResolver should NOT be called when no subscription ID is provided - mockTenantResolver.AssertNotCalled(t, "LookupTenant", mock.Anything, mock.Anything) - client, err := NewBlobSdkClient( mockCredProvider, accountConfig, @@ -81,7 +78,9 @@ func Test_NewBlobSdkClient_UsesHomeTenantWhenNoSubscriptionId(t *testing.T) { require.NoError(t, err) require.NotNil(t, client) mockCredProvider.AssertExpectations(t) - mockTenantResolver.AssertExpectations(t) + + // TenantResolver should NOT be called when no subscription ID is provided + mockTenantResolver.AssertNotCalled(t, "LookupTenant", mock.Anything, mock.Anything) } func Test_NewBlobSdkClient_ResolvesTenantWhenSubscriptionIdProvided(t *testing.T) { From f961b76082a93bba7d3c86d0a0e1b8e0db5a7441 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 3 Jan 2026 01:08:26 +0000 Subject: [PATCH 6/6] Use account.SubscriptionTenantResolver instead of duplicate interface and standard errors package Co-authored-by: vhvb1989 <24213737+vhvb1989@users.noreply.github.com> --- cli/azd/go.mod | 2 +- cli/azd/pkg/azsdk/storage/storage_blob_client.go | 10 ++-------- cli/azd/pkg/azsdk/storage/storage_blob_client_test.go | 7 +++++-- cli/azd/pkg/environment/manager_test.go | 5 ++++- cli/azd/test/functional/remote_state_test.go | 3 +++ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cli/azd/go.mod b/cli/azd/go.mod index 04eb7a073ba..d3aca725545 100644 --- a/cli/azd/go.mod +++ b/cli/azd/go.mod @@ -59,7 +59,6 @@ require ( github.com/moby/patternmatcher v0.6.0 github.com/nathan-fiscaletti/consolesize-go v0.0.0-20220204101620-317176b6684d github.com/otiai10/copy v1.14.1 - github.com/pkg/errors v0.9.1 github.com/psanford/memfs v0.0.0-20241019191636-4ef911798f9b github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sergi/go-diff v1.4.0 @@ -133,6 +132,7 @@ require ( github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pkoukk/tiktoken-go v0.1.8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/cli/azd/pkg/azsdk/storage/storage_blob_client.go b/cli/azd/pkg/azsdk/storage/storage_blob_client.go index 84951c009bc..57ecc153608 100644 --- a/cli/azd/pkg/azsdk/storage/storage_blob_client.go +++ b/cli/azd/pkg/azsdk/storage/storage_blob_client.go @@ -13,17 +13,11 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/azure/azure-dev/cli/azd/pkg/account" "github.com/azure/azure-dev/cli/azd/pkg/auth" "github.com/azure/azure-dev/cli/azd/pkg/cloud" ) -// SubscriptionTenantResolver allows resolving the correct tenant ID -// that allows the current account access to a given subscription. -type SubscriptionTenantResolver interface { - // Resolve the tenant ID required by the current account to access the given subscription. - LookupTenant(ctx context.Context, subscriptionId string) (tenantId string, err error) -} - // AccountConfig contains the configuration for connecting to a storage account type AccountConfig struct { AccountName string @@ -180,7 +174,7 @@ func NewBlobSdkClient( accountConfig *AccountConfig, coreClientOptions *azcore.ClientOptions, cloud *cloud.Cloud, - tenantResolver SubscriptionTenantResolver, + tenantResolver account.SubscriptionTenantResolver, ) (*azblob.Client, error) { blobOptions := &azblob.ClientOptions{ ClientOptions: *coreClientOptions, diff --git a/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go index 145dc7dcf16..9a2ff25b7a6 100644 --- a/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go +++ b/cli/azd/pkg/azsdk/storage/storage_blob_client_test.go @@ -5,12 +5,13 @@ package storage import ( "context" + "errors" "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/azure/azure-dev/cli/azd/pkg/account" "github.com/azure/azure-dev/cli/azd/pkg/cloud" - "github.com/pkg/errors" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -34,6 +35,8 @@ type mockSubscriptionTenantResolver struct { mock.Mock } +var _ account.SubscriptionTenantResolver = (*mockSubscriptionTenantResolver)(nil) + func (m *mockSubscriptionTenantResolver) LookupTenant( ctx context.Context, subscriptionId string) (string, error) { args := m.Called(ctx, subscriptionId) @@ -78,7 +81,7 @@ func Test_NewBlobSdkClient_UsesHomeTenantWhenNoSubscriptionId(t *testing.T) { require.NoError(t, err) require.NotNil(t, client) mockCredProvider.AssertExpectations(t) - + // TenantResolver should NOT be called when no subscription ID is provided mockTenantResolver.AssertNotCalled(t, "LookupTenant", mock.Anything, mock.Anything) } diff --git a/cli/azd/pkg/environment/manager_test.go b/cli/azd/pkg/environment/manager_test.go index e499b7370c6..b8af5bd12ed 100644 --- a/cli/azd/pkg/environment/manager_test.go +++ b/cli/azd/pkg/environment/manager_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/azure/azure-dev/cli/azd/pkg/account" "github.com/azure/azure-dev/cli/azd/pkg/auth" "github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage" "github.com/azure/azure-dev/cli/azd/pkg/cloud" @@ -382,7 +383,7 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) { }) // Register a mock SubscriptionTenantResolver for tests - mockContext.Container.MustRegisterSingleton(func() storage.SubscriptionTenantResolver { + mockContext.Container.MustRegisterSingleton(func() account.SubscriptionTenantResolver { return &mockSubscriptionTenantResolver{} }) @@ -414,6 +415,8 @@ func registerContainerComponents(t *testing.T, mockContext *mocks.MockContext) { // mockSubscriptionTenantResolver is a simple mock for testing type mockSubscriptionTenantResolver struct{} +var _ account.SubscriptionTenantResolver = (*mockSubscriptionTenantResolver)(nil) + func (m *mockSubscriptionTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) { // For tests, just return empty string (home tenant) return "", nil diff --git a/cli/azd/test/functional/remote_state_test.go b/cli/azd/test/functional/remote_state_test.go index b89bb1e4917..3aba4ee5cac 100644 --- a/cli/azd/test/functional/remote_state_test.go +++ b/cli/azd/test/functional/remote_state_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/azure/azure-dev/cli/azd/pkg/account" "github.com/azure/azure-dev/cli/azd/pkg/auth" "github.com/azure/azure-dev/cli/azd/pkg/azsdk/storage" "github.com/azure/azure-dev/cli/azd/pkg/cloud" @@ -110,6 +111,8 @@ func createBlobClient( // mockTenantResolver is a simple mock implementation for testing type mockTenantResolver struct{} +var _ account.SubscriptionTenantResolver = (*mockTenantResolver)(nil) + func (m *mockTenantResolver) LookupTenant(ctx context.Context, subscriptionId string) (string, error) { // For tests, just return empty string (home tenant) return "", nil