diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2bb6900165d5..6f9065851ea3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -46,7 +46,7 @@ # AzureSDKOwners: @jhendrixMSFT # ServiceLabel: %Tables # PRLabel: %Tables -/sdk/data/aztables/ @jhendrixMSFT +/sdk/data/aztables/ @jhendrixMSFT @richardpark-msft # AzureSDKOwners: @gracewilcox # ServiceLabel: %KeyVault diff --git a/eng/config.json b/eng/config.json index 4d3233d5d3e5..87ef782d98e7 100644 --- a/eng/config.json +++ b/eng/config.json @@ -72,6 +72,15 @@ "Name": "keyvault/azsecrets", "CoverageGoal": 0.86 }, + { + "Name": "data/aztables", + "CoverageGoal": 0.77, + "EnvironmentVariables": { + "TABLES_STORAGE_ACCOUNT_NAME": "fakeaccount", + "TABLES_PRIMARY_STORAGE_ACCOUNT_KEY": "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", + "TABLES_SHARED_ACCESS_SIGNATURE": "?sig=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" + } + }, { "Name": "data", "CoverageGoal": 0.78, @@ -134,4 +143,4 @@ "CoverageGoal": 0.75 } ] -} \ No newline at end of file +} diff --git a/sdk/data/aztables/CHANGELOG.md b/sdk/data/aztables/CHANGELOG.md index fead49919478..ef28164b5b3b 100644 --- a/sdk/data/aztables/CHANGELOG.md +++ b/sdk/data/aztables/CHANGELOG.md @@ -1,8 +1,9 @@ # Release History -## 1.2.1 (Unreleased) +## 1.3.0 (Unreleased) ### Features Added +* Client/ServiceClient now supports `azcore.TokenCredential` authentication with Azure Cosmos DB for Table. ### Breaking Changes diff --git a/sdk/data/aztables/README.md b/sdk/data/aztables/README.md index d432c6fb0545..b98ed90249ca 100644 --- a/sdk/data/aztables/README.md +++ b/sdk/data/aztables/README.md @@ -59,12 +59,15 @@ func main() { For more information about table service URL's and how to configure custom domain names for Azure Storage check out the [official documentation][azure_portal_account_url] #### Types of credentials -The clients support different forms of authentication. Cosmos accounts can use a Shared Key Credential, Connection String, or an Shared Access Signature Token for authentication. Storage account can use the same credentials as a Cosmos account and can use the credentials in [`azidentity`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) like `azidentity.NewDefaultAzureCredential()`. -The aztables package supports any of the types that implement the `azcore.TokenCredential` interface, authorization via a Connection String, or authorization with a Shared Access Signature Token. +Both services (Cosmos and Storage) support the the following forms of authentication: +- Microsoft Entra ID token, using one of the collection of types from the [`azidentity`](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity) module, like [azidentity.DefaultAzureCredential](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-defaultazurecredential). Example [here](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/data/aztables#example-NewServiceClient). +- Shared Key Credential +- Connection String +- Shared Access Signature Token -##### Creating the client with an AAD credential -Use AAD authentication as the credential parameter to authenticate the client: +##### Creating the client with a Microsoft Entra ID credential +Use Microsoft Entra ID authentication as the credential parameter to authenticate the client: ```go import ( "fmt" diff --git a/sdk/data/aztables/access_policy_test.go b/sdk/data/aztables/access_policy_test.go index 0af75b7bbc9e..66423609f8d8 100644 --- a/sdk/data/aztables/access_policy_test.go +++ b/sdk/data/aztables/access_policy_test.go @@ -16,20 +16,18 @@ import ( ) func TestSetEmptyAccessPolicy(t *testing.T) { - client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.SetAccessPolicy", })) - defer delete() _, err := client.SetAccessPolicy(ctx, nil) require.NoError(t, err) } func TestSetAccessPolicy(t *testing.T) { - client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.GetAccessPolicy", })) - defer delete() start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) expiration := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) @@ -72,8 +70,7 @@ func TestSetAccessPolicy(t *testing.T) { } func TestSetMultipleAccessPolicies(t *testing.T) { - client, delete := initClientTest(t, "storage", true, tracing.Provider{}) - defer delete() + client := initClientTest(t, storageEndpoint, true, tracing.Provider{}) id := "empty" @@ -120,11 +117,10 @@ func TestSetMultipleAccessPolicies(t *testing.T) { } func TestSetTooManyAccessPolicies(t *testing.T) { - client, delete := initClientTest(t, "storage", true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, storageEndpoint, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.SetAccessPolicy", Status: tracing.SpanStatusError, })) - defer delete() start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) expiration := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) @@ -155,8 +151,7 @@ func TestSetTooManyAccessPolicies(t *testing.T) { } func TestSetNullAccessPolicy(t *testing.T) { - client, delete := initClientTest(t, "storage", true, tracing.Provider{}) - defer delete() + client := initClientTest(t, storageEndpoint, true, tracing.Provider{}) id := "null" @@ -178,8 +173,7 @@ func TestSetNullAccessPolicy(t *testing.T) { } func TestSetInvalidAccessPolicy(t *testing.T) { - client, delete := initClientTest(t, "storage", true, tracing.Provider{}) - defer delete() + client := initClientTest(t, storageEndpoint, true, tracing.Provider{}) signedIdentifiers := make([]*SignedIdentifier, 0) signedIdentifiers = append(signedIdentifiers, &SignedIdentifier{ diff --git a/sdk/data/aztables/assets.json b/sdk/data/aztables/assets.json index 33b89204972a..a4a3307be4c1 100644 --- a/sdk/data/aztables/assets.json +++ b/sdk/data/aztables/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "go", "TagPrefix": "go/data/aztables", - "Tag": "go/data/aztables_5a816669aa" + "Tag": "go/data/aztables_7fc6e189d6" } diff --git a/sdk/data/aztables/client_test.go b/sdk/data/aztables/client_test.go index 59854c338a56..d8f01cb2a44d 100644 --- a/sdk/data/aztables/client_test.go +++ b/sdk/data/aztables/client_test.go @@ -17,16 +17,20 @@ import ( "github.com/stretchr/testify/require" ) -var services = []string{"storage", "cosmos"} +var services = []endpointType{ + storageEndpoint, + cosmosEndpoint, + storageTokenCredentialEndpoint, + cosmosTokenCredentialEndpoint, +} func TestServiceErrors(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.CreateTable", Status: tracing.SpanStatusError, })) - defer delete() // Create a duplicate table to produce an error _, err := client.CreateTable(ctx, nil) @@ -42,10 +46,9 @@ func TestServiceErrors(t *testing.T) { func TestCreateTable(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, false, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, false, NewSpanValidator(t, SpanMatcher{ Name: "Client.Delete", })) - defer delete() _, err := client.CreateTable(ctx, nil) @@ -62,10 +65,9 @@ type mdforAddGet struct { func TestAddEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.AddEntity", })) - defer delete() simpleEntity := createSimpleEntity(1, "partition") @@ -85,8 +87,7 @@ func TestAddEntity(t *testing.T) { func TestAddComplexEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) entity := createComplexEntity(1, "partition") @@ -112,10 +113,9 @@ func TestAddComplexEntity(t *testing.T) { func TestDeleteEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.DeleteEntity", })) - defer delete() simpleEntity := createSimpleEntity(1, "partition") @@ -132,8 +132,7 @@ func TestDeleteEntity(t *testing.T) { func TestDeleteEntityWithETag(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) simpleEntity := createSimpleEntity(1, "partition") simpleEntity2 := createSimpleEntity(2, "partition") @@ -168,10 +167,9 @@ func TestDeleteEntityWithETag(t *testing.T) { func TestMergeEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.GetEntity", })) - defer delete() entityToCreate := createSimpleEntity(1, "partition") marshalled, err := json.Marshal(entityToCreate) @@ -230,11 +228,10 @@ func TestMergeEntity(t *testing.T) { func TestMergeEntityDoesNotExist(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.UpdateEntity", Status: tracing.SpanStatusError, })) - defer delete() entityToCreate := createSimpleEntity(1, "partition") marshalled, err := json.Marshal(entityToCreate) @@ -253,10 +250,9 @@ func TestMergeEntityDoesNotExist(t *testing.T) { func TestInsertEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.UpsertEntity", })) - defer delete() // 1. Create Basic Entity entityToCreate := createSimpleEntityWithRowKey(1, "parti'tion", "one'") @@ -322,8 +318,7 @@ func TestInsertEntity(t *testing.T) { func TestInsertEntityTwice(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) // 1. Create Basic Entity entityToCreate := createSimpleEntity(1, "partition") @@ -347,10 +342,9 @@ type mdForListEntities struct { func TestQuerySimpleEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Pager[ListEntitiesResponse].NextPage", })) - defer delete() // Add 5 entities entitiesToCreate := createSimpleEntities(5, "partition") @@ -407,8 +401,7 @@ func TestQuerySimpleEntity(t *testing.T) { func TestQueryComplexEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) // Add 5 entities entitiesToCreate := createComplexEntities(5, "partition") @@ -462,8 +455,7 @@ func TestQueryComplexEntity(t *testing.T) { func TestInvalidEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) badEntity := map[string]any{ "Value": 10, @@ -483,8 +475,7 @@ func TestInvalidEntity(t *testing.T) { func TestContinuationTokens(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) err := insertNEntities("contToken", 10, client) require.NoError(t, err) @@ -524,8 +515,7 @@ func TestContinuationTokens(t *testing.T) { func TestContinuationTokensFilters(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) err := insertNEntities("contToken", 10, client) require.NoError(t, err) diff --git a/sdk/data/aztables/entity_test.go b/sdk/data/aztables/entity_test.go index e1ebfb1cbc3b..387c4ba53fdf 100644 --- a/sdk/data/aztables/entity_test.go +++ b/sdk/data/aztables/entity_test.go @@ -15,8 +15,7 @@ import ( func TestAddBasicEntity(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) basicEntity := basicTestEntity{ Entity: Entity{ @@ -65,8 +64,7 @@ func TestAddBasicEntity(t *testing.T) { func TestEdmMarshalling(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) edmEntity := createEdmEntity(1, "partition") @@ -108,8 +106,7 @@ func TestEdmMarshalling(t *testing.T) { func TestEntityQuotes(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) pk, err := createRandomName(t, "partition") require.NoError(t, err) @@ -165,8 +162,7 @@ func TestEntityQuotes(t *testing.T) { func TestEntityUnicode(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initClientTest(t, service, true, tracing.Provider{}) - defer delete() + client := initClientTest(t, service, true, tracing.Provider{}) pk, err := createRandomName(t, "partition") require.NoError(t, err) diff --git a/sdk/data/aztables/go.mod b/sdk/data/aztables/go.mod index 833100355308..cf152390843a 100644 --- a/sdk/data/aztables/go.mod +++ b/sdk/data/aztables/go.mod @@ -3,8 +3,8 @@ module github.com/Azure/azure-sdk-for-go/sdk/data/aztables go 1.18 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 github.com/stretchr/testify v1.9.0 ) @@ -17,9 +17,9 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/sdk/data/aztables/go.sum b/sdk/data/aztables/go.sum index e95f0e352fb1..e89fdcb3dd33 100644 --- a/sdk/data/aztables/go.sum +++ b/sdk/data/aztables/go.sum @@ -1,17 +1,22 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -20,18 +25,19 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/sdk/data/aztables/internal/version.go b/sdk/data/aztables/internal/version.go index 7767d726b0cb..7c154e27d987 100644 --- a/sdk/data/aztables/internal/version.go +++ b/sdk/data/aztables/internal/version.go @@ -8,5 +8,5 @@ package internal const ( ModuleName = "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" - Version = "v1.2.1" + Version = "v1.3.0" ) diff --git a/sdk/data/aztables/proxy_test.go b/sdk/data/aztables/proxy_test.go index d32423187ea4..0d58787ed20e 100644 --- a/sdk/data/aztables/proxy_test.go +++ b/sdk/data/aztables/proxy_test.go @@ -16,10 +16,12 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" "github.com/Azure/azure-sdk-for-go/sdk/internal/recording" + "github.com/Azure/azure-sdk-for-go/sdk/internal/test/credential" "github.com/stretchr/testify/require" ) const recordingDirectory = "sdk/data/aztables/testdata" +const fakeAccount = recording.SanitizedValue func TestMain(m *testing.M) { code := run(m) @@ -76,7 +78,26 @@ func NewFakeCredential(accountName, accountKey string) *FakeCredential { } } -func createClientForRecording(t *testing.T, tableName string, serviceURL string, cred SharedKeyCredential, tp tracing.Provider) (*Client, error) { +func createClientForRecording(t *testing.T, tableName string, serviceURL string, tp tracing.Provider) (*Client, error) { + client, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + + tokenCredential, err := credential.New(nil) + require.NoError(t, err) + + options := &ClientOptions{ClientOptions: azcore.ClientOptions{ + TracingProvider: tp, + Transport: client, + }} + if !strings.HasSuffix(serviceURL, "/") && tableName != "" { + serviceURL += "/" + } + serviceURL += tableName + + return NewClient(serviceURL, tokenCredential, options) +} + +func createClientForRecordingForSharedKey(t *testing.T, tableName string, serviceURL string, cred SharedKeyCredential, tp tracing.Provider) (*Client, error) { client, err := recording.NewRecordingHTTPClient(t, nil) require.NoError(t, err) @@ -108,7 +129,21 @@ func createClientForRecordingWithNoCredential(t *testing.T, tableName string, se return NewClientWithNoCredential(serviceURL, options) } -func createServiceClientForRecording(t *testing.T, serviceURL string, cred SharedKeyCredential, tp tracing.Provider) (*ServiceClient, error) { +func createServiceClientForRecording(t *testing.T, serviceURL string, tp tracing.Provider) (*ServiceClient, error) { + client, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + + tokenCredential, err := credential.New(nil) + require.NoError(t, err) + + options := &ClientOptions{ClientOptions: azcore.ClientOptions{ + TracingProvider: tp, + Transport: client, + }} + return NewServiceClient(serviceURL, tokenCredential, options) +} + +func createServiceClientForRecordingForSharedKey(t *testing.T, serviceURL string, cred SharedKeyCredential, tp tracing.Provider) (*ServiceClient, error) { client, err := recording.NewRecordingHTTPClient(t, nil) require.NoError(t, err) @@ -130,17 +165,25 @@ func createServiceClientForRecordingWithNoCredential(t *testing.T, serviceURL st return NewServiceClientWithNoCredential(serviceURL, options) } -func initClientTest(t *testing.T, service string, createTable bool, tp tracing.Provider) (*Client, func()) { +func initClientTest(t *testing.T, service endpointType, createTable bool, tp tracing.Provider) *Client { var client *Client var err error - if service == string(storageEndpoint) { - client, err = createStorageClient(t, tp) - require.NoError(t, err) - } else if service == string(cosmosEndpoint) { - client, err = createCosmosClient(t, tp) - require.NoError(t, err) + + switch service { + case storageEndpoint: + client, err = createStorageClient(t, tp, &testClientOptions{UseSharedKey: true}) + case storageTokenCredentialEndpoint: + client, err = createStorageClient(t, tp, &testClientOptions{UseSharedKey: false}) + case cosmosEndpoint: + client, err = createCosmosClient(t, tp, &testClientOptions{UseSharedKey: true}) + case cosmosTokenCredentialEndpoint: + client, err = createCosmosClient(t, tp, &testClientOptions{UseSharedKey: false}) + default: + require.FailNowf(t, "Invalid client test option", "%s", string(service)) } + require.NoError(t, err) + err = recording.Start(t, recordingDirectory, nil) require.NoError(t, err) @@ -149,121 +192,169 @@ func initClientTest(t *testing.T, service string, createTable bool, tp tracing.P require.NoError(t, err) } - return client, func() { + t.Cleanup(func() { _, err = client.Delete(ctx, nil) require.NoError(t, err) err = recording.Stop(t, nil) require.NoError(t, err) - } + }) + + return client } -func initServiceTest(t *testing.T, service string, tp tracing.Provider) (*ServiceClient, func()) { +func initServiceTest(t *testing.T, service endpointType, tp tracing.Provider) *ServiceClient { var client *ServiceClient var err error - if service == string(storageEndpoint) { - client, err = createStorageServiceClient(t, tp) - require.NoError(t, err) - } else if service == string(cosmosEndpoint) { - client, err = createCosmosServiceClient(t, tp) - require.NoError(t, err) + switch service { + case storageEndpoint: + client, err = createStorageServiceClient(t, tp, &testClientOptions{UseSharedKey: true}) + case storageTokenCredentialEndpoint: + client, err = createStorageServiceClient(t, tp, &testClientOptions{UseSharedKey: false}) + case cosmosEndpoint: + client, err = createCosmosServiceClient(t, tp, &testClientOptions{UseSharedKey: true}) + case cosmosTokenCredentialEndpoint: + client, err = createCosmosServiceClient(t, tp, &testClientOptions{UseSharedKey: false}) + default: + require.FailNowf(t, "Invalid service test option", "%s", string(service)) } + require.NoError(t, err) err = recording.Start(t, recordingDirectory, nil) require.NoError(t, err) - return client, func() { + t.Cleanup(func() { err = recording.Stop(t, nil) require.NoError(t, err) - } + }) + + return client } func getSharedKeyCredential() (*SharedKeyCredential, error) { - if recording.GetRecordMode() == "playback" { + if recording.GetRecordMode() == recording.PlaybackMode { return NewSharedKeyCredential("accountName", "daaaaaaaaaabbbbbbbbbbcccccccccccccccccccdddddddddddddddddddeeeeeeeeeeefffffffffffggggg==") } - accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", "fakeaccount") + accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", fakeAccount) accountKey := recording.GetEnvVariable("TABLES_PRIMARY_COSMOS_ACCOUNT_KEY", "fakeAccountKey") return NewSharedKeyCredential(accountName, accountKey) } -func createStorageClient(t *testing.T, tp tracing.Provider) (*Client, error) { - var cred *SharedKeyCredential +func createStorageClient(t *testing.T, tp tracing.Provider, options *testClientOptions) (*Client, error) { + if options == nil { + options = &testClientOptions{} + } + var err error - accountName := recording.GetEnvVariable("TABLES_STORAGE_ACCOUNT_NAME", "fakeaccount") + accountName := recording.GetEnvVariable("TABLES_STORAGE_ACCOUNT_NAME", fakeAccount) accountKey := recording.GetEnvVariable("TABLES_PRIMARY_STORAGE_ACCOUNT_KEY", "fakeaccountkey") - if recording.GetRecordMode() == "playback" { - cred, err = getSharedKeyCredential() - require.NoError(t, err) - } else { - cred, err = NewSharedKeyCredential(accountName, accountKey) - require.NoError(t, err) - } - serviceURL := storageURI(accountName) tableName, err := createRandomName(t, tableNamePrefix) require.NoError(t, err) - return createClientForRecording(t, tableName, serviceURL, *cred, tp) + if options.UseSharedKey { + var cred *SharedKeyCredential + + if recording.GetRecordMode() == recording.PlaybackMode { + cred, err = getSharedKeyCredential() + require.NoError(t, err) + } else { + cred, err = NewSharedKeyCredential(accountName, accountKey) + require.NoError(t, err) + } + + return createClientForRecordingForSharedKey(t, tableName, serviceURL, *cred, tp) + } + + return createClientForRecording(t, tableName, serviceURL, tp) +} + +type testClientOptions struct { + UseSharedKey bool } -func createCosmosClient(t *testing.T, tp tracing.Provider) (*Client, error) { - var cred *SharedKeyCredential - accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", "fakeaccount") - if recording.GetRecordMode() == "playback" { - accountName = "fakeaccount" +func createCosmosClient(t *testing.T, tp tracing.Provider, options *testClientOptions) (*Client, error) { + if options == nil { + options = &testClientOptions{} } - cred, err := getSharedKeyCredential() - require.NoError(t, err) + accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", fakeAccount) + if recording.GetRecordMode() == recording.PlaybackMode { + accountName = fakeAccount + } serviceURL := cosmosURI(accountName) tableName, err := createRandomName(t, tableNamePrefix) require.NoError(t, err) - return createClientForRecording(t, tableName, serviceURL, *cred, tp) -} + if options.UseSharedKey { + cred, err := getSharedKeyCredential() + require.NoError(t, err) + return createClientForRecordingForSharedKey(t, tableName, serviceURL, *cred, tp) + } -func createStorageServiceClient(t *testing.T, tp tracing.Provider) (*ServiceClient, error) { - var cred *SharedKeyCredential - var err error - accountName := recording.GetEnvVariable("TABLES_STORAGE_ACCOUNT_NAME", "fakeaccount") - accountKey := recording.GetEnvVariable("TABLES_PRIMARY_STORAGE_ACCOUNT_KEY", "fakeaccountkey") + return createClientForRecording(t, tableName, serviceURL, tp) +} - if recording.GetRecordMode() == "playback" { - cred, err = getSharedKeyCredential() - require.NoError(t, err) - } else { - cred, err = NewSharedKeyCredential(accountName, accountKey) - require.NoError(t, err) +func createStorageServiceClient(t *testing.T, tp tracing.Provider, options *testClientOptions) (*ServiceClient, error) { + if options == nil { + options = &testClientOptions{} } + accountName := recording.GetEnvVariable("TABLES_STORAGE_ACCOUNT_NAME", fakeAccount) + accountKey := recording.GetEnvVariable("TABLES_PRIMARY_STORAGE_ACCOUNT_KEY", "fakeaccountkey") serviceURL := storageURI(accountName) - return createServiceClientForRecording(t, serviceURL, *cred, tp) + if options.UseSharedKey { + var cred *SharedKeyCredential + var err error + + if recording.GetRecordMode() == recording.PlaybackMode { + cred, err = getSharedKeyCredential() + require.NoError(t, err) + } else { + cred, err = NewSharedKeyCredential(accountName, accountKey) + require.NoError(t, err) + } + + return createServiceClientForRecordingForSharedKey(t, serviceURL, *cred, tp) + } + + return createServiceClientForRecording(t, serviceURL, tp) } -func createCosmosServiceClient(t *testing.T, tp tracing.Provider) (*ServiceClient, error) { - var cred *SharedKeyCredential - accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", "fakeaccount") - if recording.GetRecordMode() == "playback" { - accountName = "fakeaccount" +func createCosmosServiceClient(t *testing.T, tp tracing.Provider, options *testClientOptions) (*ServiceClient, error) { + if options == nil { + options = &testClientOptions{} } - cred, err := getSharedKeyCredential() - require.NoError(t, err) + accountName := recording.GetEnvVariable("TABLES_COSMOS_ACCOUNT_NAME", fakeAccount) + + if recording.GetRecordMode() == recording.PlaybackMode { + accountName = fakeAccount + } serviceURL := cosmosURI(accountName) - return createServiceClientForRecording(t, serviceURL, *cred, tp) + if options.UseSharedKey { + var cred *SharedKeyCredential + + cred, err := getSharedKeyCredential() + require.NoError(t, err) + + return createServiceClientForRecordingForSharedKey(t, serviceURL, *cred, tp) + } + + return createServiceClientForRecording(t, serviceURL, tp) } func createRandomName(t *testing.T, prefix string) (string, error) { h := fnv.New32a() + _, err := h.Write([]byte(t.Name())) return prefix + fmt.Sprint(h.Sum32()), err } diff --git a/sdk/data/aztables/recording_helpers_test.go b/sdk/data/aztables/recording_helpers_test.go index 7a000dffb154..ada2e70ca826 100644 --- a/sdk/data/aztables/recording_helpers_test.go +++ b/sdk/data/aztables/recording_helpers_test.go @@ -16,8 +16,10 @@ import ( type endpointType string const ( - storageEndpoint endpointType = "storage" - cosmosEndpoint endpointType = "cosmos" + storageEndpoint endpointType = "storage" + storageTokenCredentialEndpoint endpointType = "storage_tc" + cosmosEndpoint endpointType = "cosmos" + cosmosTokenCredentialEndpoint endpointType = "cosmos_tc" ) var ctx = context.Background() diff --git a/sdk/data/aztables/service_client.go b/sdk/data/aztables/service_client.go index 4b22a85f2a31..1a2796d2a455 100644 --- a/sdk/data/aztables/service_client.go +++ b/sdk/data/aztables/service_client.go @@ -16,6 +16,11 @@ import ( generated "github.com/Azure/azure-sdk-for-go/sdk/data/aztables/internal" ) +var ( + storageScope = []string{"https://storage.azure.com/.default"} + cosmosScope = []string{"https://cosmos.azure.com/.default"} +) + // ServiceClient represents a client to the table service. It can be used to query // the available tables, create/delete tables, and various other service level operations. type ServiceClient struct { @@ -27,8 +32,14 @@ type ServiceClient struct { // NewServiceClient creates a ServiceClient struct using the specified serviceURL, credential, and options. // Pass in nil for options to construct the client with the default ClientOptions. func NewServiceClient(serviceURL string, cred azcore.TokenCredential, options *ClientOptions) (*ServiceClient, error) { + scope := storageScope + + if isCosmosEndpoint(serviceURL) { + scope = cosmosScope + } + plOpts := runtime.PipelineOptions{ - PerRetry: []policy.Policy{runtime.NewBearerTokenPolicy(cred, []string{"https://storage.azure.com/.default"}, nil)}, + PerRetry: []policy.Policy{runtime.NewBearerTokenPolicy(cred, scope, nil)}, } client, err := newClient(serviceURL, plOpts, options) if err != nil { diff --git a/sdk/data/aztables/service_client_test.go b/sdk/data/aztables/service_client_test.go index 5f8905565c39..9e63a9a83a35 100644 --- a/sdk/data/aztables/service_client_test.go +++ b/sdk/data/aztables/service_client_test.go @@ -19,10 +19,9 @@ import ( func TestServiceErrorsServiceClient(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - service, delete := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ + service := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ Name: "ServiceClient.DeleteTable", })) - defer delete() tableName, err := createRandomName(t, tableNamePrefix) require.NoError(t, err) @@ -47,10 +46,9 @@ func TestServiceErrorsServiceClient(t *testing.T) { func TestCreateTableFromService(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - service, delete := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ + service := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ Name: "ServiceClient.CreateTable", })) - defer delete() tableName, err := createRandomName(t, tableNamePrefix) require.NoError(t, err) @@ -62,7 +60,7 @@ func TestCreateTableFromService(t *testing.T) { fmt.Printf("Error cleaning up test. %v\n", err.Error()) } } - defer deleteTable() + t.Cleanup(deleteTable) require.NoError(t, err) // require.Equal(t, *resp.TableResponse.TableName, tableName) @@ -73,15 +71,20 @@ func TestCreateTableFromService(t *testing.T) { func TestQueryTable(t *testing.T) { for _, svc := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), svc), func(t *testing.T) { - service, delete := initServiceTest(t, svc, tracing.Provider{}) - defer delete() + service := initServiceTest(t, svc, tracing.Provider{}) tableCount := 5 tableNames := make([]string, tableCount) prefix1 := "zzza" prefix2 := "zzzb" - defer require.NoError(t, clearAllTables(service)) + // clean up the tables on the last test for that resource (storage, cosmos) + if svc == cosmosTokenCredentialEndpoint || svc == storageTokenCredentialEndpoint { + t.Cleanup(func() { + require.NoError(t, clearAllTables(service)) + }) + } + // create 10 tables with our exected prefix and 1 with a different prefix for i := 0; i < tableCount; i++ { if i < (tableCount - 1) { @@ -91,8 +94,12 @@ func TestQueryTable(t *testing.T) { name := fmt.Sprintf("%v%v", prefix2, i) tableNames[i] = name } - _, err := service.CreateTable(ctx, tableNames[i], nil) - require.NoError(t, err) + + // only create the tables in the first test type for that resource (ie, storage, cosmos) + if svc == cosmosEndpoint || svc == storageEndpoint { + _, err := service.CreateTable(ctx, tableNames[i], nil) + require.NoError(t, err) + } } // Query for tables with no pagination. The filter should exclude one table from the results @@ -142,10 +149,10 @@ type mdForListTables struct { func TestListTables(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, delete := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ + client := initServiceTest(t, service, NewSpanValidator(t, SpanMatcher{ Name: "Pager[ListTablesResponse].NextPage", })) - defer delete() + tableName, err := createRandomName(t, tableNamePrefix) require.NoError(t, err) @@ -167,7 +174,7 @@ func TestListTables(t *testing.T) { count += len(resp.Tables) for _, table := range resp.Tables { - if service == "storage" { + if service == storageEndpoint { // cosmos doesn't send full metadata require.NotEmpty(t, table.Value) var md mdForListTables @@ -189,8 +196,7 @@ func TestListTables(t *testing.T) { } } } - defer deleteTable() - + t.Cleanup(deleteTable) }) } } @@ -211,14 +217,14 @@ func TestGetStatistics(t *testing.T) { accountName := recording.GetEnvVariable("TABLES_STORAGE_ACCOUNT_NAME", "fakeaccount") accountKey := recording.GetEnvVariable("TABLES_PRIMARY_STORAGE_ACCOUNT_KEY", "fakeAccountKey") - if recording.GetRecordMode() == "playback" { + if recording.GetRecordMode() == recording.PlaybackMode { cred, err = NewSharedKeyCredential("fakeaccount", "fakeAccountKey==") } else { cred, err = NewSharedKeyCredential(accountName, accountKey) } serviceURL := storageURI(accountName + "-secondary") - service, err := createServiceClientForRecording(t, serviceURL, *cred, NewSpanValidator(t, SpanMatcher{ + service, err := createServiceClientForRecordingForSharedKey(t, serviceURL, *cred, NewSpanValidator(t, SpanMatcher{ Name: "ServiceClient.GetStatistics", })) require.NoError(t, err) @@ -232,10 +238,9 @@ func TestGetStatistics(t *testing.T) { // Functionality is only available on storage accounts func TestGetProperties(t *testing.T) { - service, delete := initServiceTest(t, "storage", NewSpanValidator(t, SpanMatcher{ + service := initServiceTest(t, storageEndpoint, NewSpanValidator(t, SpanMatcher{ Name: "ServiceClient.GetProperties", })) - defer delete() resp, err := service.GetProperties(ctx, nil) require.NoError(t, err) @@ -244,10 +249,9 @@ func TestGetProperties(t *testing.T) { // Logging is only available on storage accounts func TestSetLogging(t *testing.T) { - service, delete := initServiceTest(t, "storage", NewSpanValidator(t, SpanMatcher{ + service := initServiceTest(t, storageEndpoint, NewSpanValidator(t, SpanMatcher{ Name: "ServiceClient.SetProperties", })) - defer delete() getResp, err := service.GetProperties(ctx, nil) require.NoError(t, err) @@ -280,8 +284,7 @@ func TestSetLogging(t *testing.T) { } func TestSetHoursMetrics(t *testing.T) { - service, delete := initServiceTest(t, "storage", tracing.Provider{}) - defer delete() + service := initServiceTest(t, storageEndpoint, tracing.Provider{}) getResp, err := service.GetProperties(ctx, nil) require.NoError(t, err) @@ -312,8 +315,7 @@ func TestSetHoursMetrics(t *testing.T) { } func TestSetMinuteMetrics(t *testing.T) { - service, delete := initServiceTest(t, "storage", tracing.Provider{}) - defer delete() + service := initServiceTest(t, storageEndpoint, tracing.Provider{}) getResp, err := service.GetProperties(ctx, nil) require.NoError(t, err) @@ -344,8 +346,7 @@ func TestSetMinuteMetrics(t *testing.T) { } func TestSetCors(t *testing.T) { - service, delete := initServiceTest(t, "storage", tracing.Provider{}) - defer delete() + service := initServiceTest(t, storageEndpoint, tracing.Provider{}) getResp, err := service.GetProperties(ctx, nil) require.NoError(t, err) @@ -377,8 +378,7 @@ func TestSetCors(t *testing.T) { } func TestSetTooManyCors(t *testing.T) { - service, delete := initServiceTest(t, "storage", tracing.Provider{}) - defer delete() + service := initServiceTest(t, storageEndpoint, tracing.Provider{}) corsRules1 := CorsRule{ AllowedHeaders: to.Ptr("x-ms-meta-data*"), @@ -400,8 +400,7 @@ func TestSetTooManyCors(t *testing.T) { } func TestRetentionTooLong(t *testing.T) { - service, delete := initServiceTest(t, "storage", tracing.Provider{}) - defer delete() + service := initServiceTest(t, storageEndpoint, tracing.Provider{}) metrics := Metrics{ Enabled: to.Ptr(true), diff --git a/sdk/data/aztables/test-resources.bicep b/sdk/data/aztables/test-resources.bicep new file mode 100644 index 000000000000..8670dc858bd3 --- /dev/null +++ b/sdk/data/aztables/test-resources.bicep @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +param baseName string + +@description('The principal to assign the role to. This is application object id.') +param testApplicationOid string + +var storageTableContributorRoleId = resourceId( + 'Microsoft.Authorization/roleDefinitions', + '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' +) +var location = resourceGroup().location +var primaryAccountName = '${baseName}prim' +var encryption = { + services: { + file: { + enabled: true + } + blob: { + enabled: true + } + } + keySource: 'Microsoft.Storage' +} +var networkAcls = { + bypass: 'AzureServices' + virtualNetworkRules: [] + ipRules: [] + defaultAction: 'Allow' +} + +// +// Accounts +// + +resource stgAccount 'Microsoft.Storage/storageAccounts@2019-04-01' = { + name: primaryAccountName + location: location + sku: { + name: 'Standard_RAGRS' + } + kind: 'StorageV2' + properties: { + networkAcls: networkAcls + supportsHttpsTrafficOnly: true + encryption: encryption + accessTier: 'Cool' + } +} + +resource cosmosdDBAccount 'Microsoft.DocumentDB/databaseAccounts@2020-04-01' = { + name: primaryAccountName + location: location + tags: { + defaultExperience: 'Azure Table' + 'hidden-cosmos-mmspecial': '' + CosmosAccountType: 'Non-Production' + } + kind: 'GlobalDocumentDB' + properties: { + enableAutomaticFailover: false + enableMultipleWriteLocations: false + isVirtualNetworkFilterEnabled: false + virtualNetworkRules: [] + disableKeyBasedMetadataWriteAccess: false + enableFreeTier: false + enableAnalyticalStorage: false + databaseAccountOfferType: 'Standard' + consistencyPolicy: { + defaultConsistencyLevel: 'BoundedStaleness' + maxIntervalInSeconds: 86400 + maxStalenessPrefix: 1000000 + } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + capabilities: [ + { + name: 'EnableTable' + } + ] + ipRules: [] + } +} + +// +// Roles and assignments +// + +resource tableDataContributorRoleId_id 'Microsoft.Authorization/roleAssignments@2018-09-01-preview' = { + name: guid('tableDataContributorRoleId${resourceGroup().id}') + properties: { + roleDefinitionId: storageTableContributorRoleId + principalId: testApplicationOid + } +} + +// CosmosDB has it's own data plane RBAC, so we need to set that up _slightly_ differently than our +// blob storage account, for instance. + +// we're missing _one_ permission that we need for our tests (reading throughput) +resource cosmosDBThroughputRoleDef 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2024-05-15' = { + name: guid('cosmosDBThroughputRoleDef${resourceGroup().id}') + parent: cosmosdDBAccount + properties: { + assignableScopes: [ + cosmosdDBAccount.id + ] + permissions: [ + { + dataActions: [ + 'Microsoft.DocumentDB/databaseAccounts/throughputSettings/read' + ] + } + ] + roleName: 'Custom role to read throughput' + type: 'CustomRole' + } +} + +resource cosmosDBThroughputRoleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15' = { + name: guid('cosmosDBThroughputRoleAssignment${resourceGroup().id}') + parent: cosmosdDBAccount + properties: { + principalId: testApplicationOid + roleDefinitionId: cosmosDBThroughputRoleDef.id + scope: cosmosdDBAccount.id + } +} + +resource cosmosDBThroughputRole 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15' = { + name: guid('customRoleAssignment${resourceGroup().id}') + parent: cosmosdDBAccount + properties: { + principalId: testApplicationOid + // Built-in role 'Azure Cosmos DB Built-in Data Contributor' + roleDefinitionId: '/${subscription().id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DocumentDB/databaseAccounts/${cosmosdDBAccount.name}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002' + + // resourceId( + // subscription().id, + // resourceGroup().name, + // 'providers/Microsoft.DocumentDB/databaseAccounts', + // cosmosdDBAccount.name, + // 'sqlRoleDefinitions', + // '00000000-0000-0000-0000-000000000002' // Built-in role 'Azure Cosmos DB Built-in Data Contributor' + // ) + scope: cosmosdDBAccount.id + } +} + +output TABLES_STORAGE_ACCOUNT_NAME string = primaryAccountName +#disable-next-line outputs-should-not-contain-secrets +output TABLES_PRIMARY_STORAGE_ACCOUNT_KEY string = stgAccount.listKeys().keys[0].value +output TABLES_COSMOS_ACCOUNT_NAME string = primaryAccountName +#disable-next-line outputs-should-not-contain-secrets +output TABLES_PRIMARY_COSMOS_ACCOUNT_KEY string = cosmosdDBAccount.listKeys().primaryMasterKey diff --git a/sdk/data/aztables/test-resources.json b/sdk/data/aztables/test-resources.json deleted file mode 100644 index 1658eecf704d..000000000000 --- a/sdk/data/aztables/test-resources.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "baseName": { - "type": "String" - }, - "testApplicationOid": { - "type": "string", - "metadata": { - "description": "The principal to assign the role to. This is application object id." - } - } - }, - "variables": { - "mgmtApiVersion": "2019-04-01", - "authorizationApiVersion": "2018-09-01-preview", - "blobDataContributorRoleId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3')]", - "location": "[resourceGroup().location]", - "primaryAccountName": "[concat(parameters('baseName'), 'prim')]", - "encryption": { - "services": { - "file": { - "enabled": true - }, - "blob": { - "enabled": true - } - }, - "keySource": "Microsoft.Storage" - }, - "networkAcls": { - "bypass": "AzureServices", - "virtualNetworkRules": [], - "ipRules": [], - "defaultAction": "Allow" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "[variables('authorizationApiVersion')]", - "name": "[guid(concat('tableDataContributorRoleId', resourceGroup().id))]", - "properties": { - "roleDefinitionId": "[variables('blobDataContributorRoleId')]", - "principalId": "[parameters('testApplicationOid')]" - } - }, - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "[variables('mgmtApiVersion')]", - "name": "[variables('primaryAccountName')]", - "location": "[variables('location')]", - "sku": { - "name": "Standard_RAGRS", - "tier": "Standard" - }, - "kind": "StorageV2", - "properties": { - "networkAcls": "[variables('networkAcls')]", - "supportsHttpsTrafficOnly": true, - "encryption": "[variables('encryption')]", - "accessTier": "Cool" - } - }, - { - "type": "Microsoft.DocumentDB/databaseAccounts", - "apiVersion": "2020-04-01", - "name": "[variables('primaryAccountName')]", - "location": "[variables('location')]", - "tags": { - "defaultExperience": "Azure Table", - "hidden-cosmos-mmspecial": "", - "CosmosAccountType": "Non-Production" - }, - "kind": "GlobalDocumentDB", - "properties": { - "publicNetworkAccess": "Enabled", - "enableAutomaticFailover": false, - "enableMultipleWriteLocations": false, - "isVirtualNetworkFilterEnabled": false, - "virtualNetworkRules": [], - "disableKeyBasedMetadataWriteAccess": false, - "enableFreeTier": false, - "enableAnalyticalStorage": false, - "databaseAccountOfferType": "Standard", - "consistencyPolicy": { - "defaultConsistencyLevel": "BoundedStaleness", - "maxIntervalInSeconds": 86400, - "maxStalenessPrefix": 1000000 - }, - "locations": [ - { - "locationName": "[variables('location')]", - "provisioningState": "Succeeded", - "failoverPriority": 0, - "isZoneRedundant": false - } - ], - "capabilities": [ - { - "name": "EnableTable" - } - ], - "ipRules": [] - } - } - ], - "outputs": { - "TABLES_STORAGE_ACCOUNT_NAME": { - "type": "string", - "value": "[variables('primaryAccountName')]" - }, - "TABLES_PRIMARY_STORAGE_ACCOUNT_KEY": { - "type": "string", - "value": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('primaryAccountName')), variables('mgmtApiVersion')).keys[0].value]" - }, - "TABLES_COSMOS_ACCOUNT_NAME": { - "type": "string", - "value": "[variables('primaryAccountName')]" - }, - "TABLES_PRIMARY_COSMOS_ACCOUNT_KEY": { - "type": "string", - "value": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('primaryAccountName')), '2020-04-01').primaryMasterKey]" - } - } -} \ No newline at end of file diff --git a/sdk/data/aztables/testdata/perf/go.mod b/sdk/data/aztables/testdata/perf/go.mod index 00df614f1cf2..ed335d49afc6 100644 --- a/sdk/data/aztables/testdata/perf/go.mod +++ b/sdk/data/aztables/testdata/perf/go.mod @@ -3,14 +3,14 @@ module github.com/Azure/azure-sdk-for-go/sdk/data/aztables/testdata/perf go 1.18 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.2.0 github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 ) require ( - golang.org/x/net v0.27.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/text v0.19.0 // indirect ) replace github.com/Azure/azure-sdk-for-go/sdk/data/aztables => ../.. diff --git a/sdk/data/aztables/testdata/perf/go.sum b/sdk/data/aztables/testdata/perf/go.sum index e14080564680..338b635c5061 100644 --- a/sdk/data/aztables/testdata/perf/go.sum +++ b/sdk/data/aztables/testdata/perf/go.sum @@ -1,6 +1,6 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= @@ -11,10 +11,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/sdk/data/aztables/transaction_batch_test.go b/sdk/data/aztables/transaction_batch_test.go index 6efc3683abd9..de99caf60338 100644 --- a/sdk/data/aztables/transaction_batch_test.go +++ b/sdk/data/aztables/transaction_batch_test.go @@ -17,10 +17,9 @@ import ( func TestBatchAdd(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ + client := initClientTest(t, service, true, NewSpanValidator(t, SpanMatcher{ Name: "Client.SubmitTransaction", })) - defer deleteAndStop() err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err) err = recording.AddGeneralRegexSanitizer("batch_00000000-0000-0000-0000-000000000000", "batch_[0-9A-Fa-f]{8}[-]([0-9A-Fa-f]{4}[-]?){3}[0-9a-fA-F]{12}", nil) @@ -55,8 +54,7 @@ func TestBatchAdd(t *testing.T) { func TestBatchInsert(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, tracing.Provider{}) - defer deleteAndStop() + client := initClientTest(t, service, true, tracing.Provider{}) err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err) err = recording.AddGeneralRegexSanitizer("batch_00000000-0000-0000-0000-000000000000", "batch_[0-9A-Fa-f]{8}[-]([0-9A-Fa-f]{4}[-]?){3}[0-9a-fA-F]{12}", nil) @@ -96,8 +94,7 @@ func TestBatchInsert(t *testing.T) { func TestBatchMixed(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, tracing.Provider{}) - defer deleteAndStop() + client := initClientTest(t, service, true, tracing.Provider{}) err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err) err = recording.AddGeneralRegexSanitizer("batch_00000000-0000-0000-0000-000000000000", "batch_[0-9A-Fa-f]{8}[-]([0-9A-Fa-f]{4}[-]?){3}[0-9a-fA-F]{12}", nil) @@ -196,8 +193,7 @@ func TestBatchMixed(t *testing.T) { func TestBatchError(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, tracing.Provider{}) - defer deleteAndStop() + client := initClientTest(t, service, true, tracing.Provider{}) err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err) @@ -235,8 +231,7 @@ func TestBatchError(t *testing.T) { func TestBatchErrorHandleResponse(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, tracing.Provider{}) - defer deleteAndStop() + client := initClientTest(t, service, true, tracing.Provider{}) err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err) @@ -274,8 +269,7 @@ func TestBatchErrorHandleResponse(t *testing.T) { func TestBatchComplex(t *testing.T) { for _, service := range services { t.Run(fmt.Sprintf("%v_%v", t.Name(), service), func(t *testing.T) { - client, deleteAndStop := initClientTest(t, service, true, tracing.Provider{}) - defer deleteAndStop() + client := initClientTest(t, service, true, tracing.Provider{}) err := recording.SetBodilessMatcher(t, nil) require.NoError(t, err)