Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend/azure/update to latest sdks #36258

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ require (
golang.org/x/tools v0.25.0
golang.org/x/tools/cmd/cover v0.1.0-deprecated
google.golang.org/grpc v1.59.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0
google.golang.org/protobuf v1.34.2
honnef.co/go/tools v0.5.1
)
Expand All @@ -91,14 +91,10 @@ require (
cloud.google.com/go/iam v1.1.1 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
github.com/AlecAivazis/survey/v2 v2.3.7 // indirect
github.com/Azure/azure-sdk-for-go v59.2.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.4 // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
Expand Down Expand Up @@ -154,7 +150,6 @@ require (
github.com/cli/shurcooL-graphql v0.0.4 // indirect
github.com/cloudflare/circl v1.4.0 // indirect
github.com/creack/pty v1.1.17 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/fatih/color v1.17.0 // indirect
Expand Down Expand Up @@ -183,14 +178,20 @@ require (
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.58 // indirect
github.com/hashicorp/consul/api v1.13.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-azure-helpers v0.43.0 // indirect
github.com/hashicorp/go-azure-helpers v0.71.0 // indirect
github.com/hashicorp/go-azure-sdk/resource-manager v0.20241212.1154051 // indirect
github.com/hashicorp/go-azure-sdk/sdk v0.20241212.1154051 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/serf v0.9.6 // indirect
github.com/hashicorp/terraform-plugin-go v0.14.3 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/henvic/httpretty v0.1.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
Expand All @@ -208,8 +209,6 @@ require (
github.com/lib/pq v1.10.3 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/manicminer/hamilton v0.44.0 // indirect
github.com/manicminer/hamilton-autorest v0.2.0 // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand Down Expand Up @@ -240,9 +239,12 @@ require (
github.com/tencentyun/cos-go-sdk-v5 v0.7.42 // indirect
github.com/thanhpk/randstr v1.0.6 // indirect
github.com/thlib/go-timezone-local v0.0.3 // indirect
github.com/tombuildsstuff/giovanni v0.15.1 // indirect
github.com/tombuildsstuff/giovanni v0.27.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser v0.1.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.mongodb.org/mongo-driver v1.16.1 // indirect
go.opencensus.io v0.24.0 // indirect
Expand Down Expand Up @@ -274,6 +276,7 @@ require (
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.4.0 // indirect
)

// Some of the packages in this codebase are split into separate Go modules
Expand Down
88 changes: 37 additions & 51 deletions go.sum

Large diffs are not rendered by default.

291 changes: 291 additions & 0 deletions internal/backend/remote-state/azure/api_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package azure

import (
"context"
"fmt"
"log"
"net/http"
"os"
"strings"

"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-01-01/storageaccounts"
"github.com/hashicorp/go-azure-sdk/sdk/auth"
"github.com/hashicorp/go-azure-sdk/sdk/client"
"github.com/hashicorp/go-azure-sdk/sdk/environments"
"github.com/hashicorp/terraform/internal/httpclient"
"github.com/hashicorp/terraform/version"
"github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/blobs"
"github.com/tombuildsstuff/giovanni/storage/2023-11-03/blob/containers"
)

type Client struct {
// These Clients are only initialized if an Access Key isn't provided
storageAccountsClient *storageaccounts.StorageAccountsClient

// Caching
containersClient *containers.Client
blobsClient *blobs.Client

environment environments.Environment
storageAccountName string

accountDetail *AccountDetails

accessKey string
sasToken string
// azureAdStorageAuth is only here if we're using AzureAD Authentication but is an Authorizer for Storage
azureAdStorageAuth auth.Authorizer
}

func buildClient(ctx context.Context, config BackendConfig) (*Client, error) {
client := Client{
environment: config.AuthConfig.Environment,
storageAccountName: config.StorageAccountName,
}

// if we have an Access Key - we don't need the other clients
if config.AccessKey != "" {
client.accessKey = config.AccessKey
return &client, nil
}

// likewise with a SAS token
if config.SasToken != "" {
sasToken := config.SasToken
if strings.TrimSpace(sasToken) == "" {
return nil, fmt.Errorf("sasToken cannot be empty")
}
client.sasToken = strings.TrimPrefix(sasToken, "?")

return &client, nil
}

if config.UseAzureADAuthentication {
var err error
client.azureAdStorageAuth, err = auth.NewAuthorizerFromCredentials(ctx, *config.AuthConfig, config.AuthConfig.Environment.Storage)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Storage API: %+v", err)
}
}

resourceManagerAuth, err := auth.NewAuthorizerFromCredentials(ctx, *config.AuthConfig, config.AuthConfig.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("unable to build authorizer for Resource Manager API: %+v", err)
}

client.storageAccountsClient, err = storageaccounts.NewStorageAccountsClientWithBaseURI(config.AuthConfig.Environment.ResourceManager)
if err != nil {
return nil, fmt.Errorf("building Storage Accounts client: %+v", err)
}
client.configureClient(client.storageAccountsClient.Client, resourceManagerAuth)

// Populating the storage account detail
said := commonids.NewStorageAccountID(config.SubscriptionID, config.ResourceGroupName, client.storageAccountName)
resp, err := client.storageAccountsClient.GetProperties(ctx, said, storageaccounts.DefaultGetPropertiesOperationOptions())
if err != nil {
return nil, fmt.Errorf("getting %s: %+v", said, err)
}
if resp.Model == nil {
return nil, fmt.Errorf("unexpected null model of %s", said)
}
client.accountDetail, err = populateAccountDetails(said, *resp.Model)
if err != nil {
return nil, fmt.Errorf("populating details for %s: %+v", said, err)
}

return &client, nil
}

func (c *Client) getBlobClient(ctx context.Context) (bc *blobs.Client, err error) {
if c.blobsClient != nil {
return c.blobsClient, nil
}

defer func() {
if err == nil {
c.blobsClient = bc
}
}()

if c.sasToken != "" {
log.Printf("[DEBUG] Building the Blob Client from a SAS Token")
baseURL, err := naiveStorageAccountBlobBaseURL(c.environment, c.storageAccountName)
if err != nil {
return nil, fmt.Errorf("build storage account blob base URL: %v", err)
}
blobsClient, err := blobs.NewWithBaseUri(baseURL)
if err != nil {
return nil, fmt.Errorf("new blob client: %v", err)
}
c.configureClient(blobsClient.Client, nil)
blobsClient.Client.AppendRequestMiddleware(func(r *http.Request) (*http.Request, error) {
if r.URL.RawQuery == "" {
r.URL.RawQuery = c.sasToken
} else if !strings.Contains(r.URL.RawQuery, c.sasToken) {
r.URL.RawQuery = fmt.Sprintf("%s&%s", r.URL.RawQuery, c.sasToken)
}
return r, nil
})
return blobsClient, nil
}

if c.accessKey != "" {
log.Printf("[DEBUG] Building the Blob Client from an Access Key")
baseURL, err := naiveStorageAccountBlobBaseURL(c.environment, c.storageAccountName)
if err != nil {
return nil, fmt.Errorf("build storage account blob base URL: %v", err)
}
blobsClient, err := blobs.NewWithBaseUri(baseURL)
if err != nil {
return nil, fmt.Errorf("new blob client: %v", err)
}
c.configureClient(blobsClient.Client, nil)

authorizer, err := auth.NewSharedKeyAuthorizer(c.storageAccountName, c.accessKey, auth.SharedKey)
if err != nil {
return nil, fmt.Errorf("new shared key authorizer: %v", err)
}
c.configureClient(blobsClient.Client, authorizer)

return blobsClient, nil
}

// Neither shared access key nor sas token specified, then we have the storage account details populated.
// This detail can be used to get the "most" correct blob endpoint comparing to the naive construction.
baseUri, err := c.accountDetail.DataPlaneEndpoint(EndpointTypeBlob)
if err != nil {
return nil, err
}
blobsClient, err := blobs.NewWithBaseUri(*baseUri)
if err != nil {
return nil, fmt.Errorf("new blob client: %v", err)
}

if c.azureAdStorageAuth != nil {
log.Printf("[DEBUG] Building the Blob Client from AAD auth")
c.configureClient(blobsClient.Client, c.azureAdStorageAuth)
return blobsClient, nil
}

log.Printf("[DEBUG] Building the Blob Client from an Access Token (using user credentials)")
key, err := c.accountDetail.AccountKey(ctx, c.storageAccountsClient)
if err != nil {
return nil, fmt.Errorf("Error retrieving key for Storage Account %q: %s", c.storageAccountName, err)
}
accessKey := *key

authorizer, err := auth.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, auth.SharedKey)
if err != nil {
return nil, fmt.Errorf("new shared key authorizer: %v", err)
}
c.configureClient(blobsClient.Client, authorizer)

return blobsClient, nil
}

func (c *Client) getContainersClient(ctx context.Context) (cc *containers.Client, err error) {
if c.containersClient != nil {
return c.containersClient, nil
}

defer func() {
if err == nil {
c.containersClient = cc
}
}()

if c.sasToken != "" {
log.Printf("[DEBUG] Building the Container Client from a SAS Token")
baseURL, err := naiveStorageAccountBlobBaseURL(c.environment, c.storageAccountName)
if err != nil {
return nil, fmt.Errorf("build storage account blob base URL: %v", err)
}
containersClient, err := containers.NewWithBaseUri(baseURL)
if err != nil {
return nil, fmt.Errorf("new container client: %v", err)
}
c.configureClient(containersClient.Client, nil)
containersClient.Client.AppendRequestMiddleware(func(r *http.Request) (*http.Request, error) {
if r.URL.RawQuery == "" {
r.URL.RawQuery = c.sasToken
} else if !strings.Contains(r.URL.RawQuery, c.sasToken) {
r.URL.RawQuery = fmt.Sprintf("%s&%s", r.URL.RawQuery, c.sasToken)
}
return r, nil
})
return containersClient, nil
}

if c.accessKey != "" {
log.Printf("[DEBUG] Building the Container Client from an Access Key")
baseURL, err := naiveStorageAccountBlobBaseURL(c.environment, c.storageAccountName)
if err != nil {
return nil, fmt.Errorf("build storage account blob base URL: %v", err)
}
containersClient, err := containers.NewWithBaseUri(baseURL)
if err != nil {
return nil, fmt.Errorf("new container client: %v", err)
}
c.configureClient(containersClient.Client, nil)

authorizer, err := auth.NewSharedKeyAuthorizer(c.storageAccountName, c.accessKey, auth.SharedKey)
if err != nil {
return nil, fmt.Errorf("new shared key authorizer: %v", err)
}
c.configureClient(containersClient.Client, authorizer)

return containersClient, nil
}

// Neither shared access key nor sas token specified, then we have the storage account details populated.
// This detail can be used to get the "most" correct blob endpoint comparing to the naive construction.
baseUri, err := c.accountDetail.DataPlaneEndpoint(EndpointTypeBlob)
if err != nil {
return nil, err
}
containersClient, err := containers.NewWithBaseUri(*baseUri)
if err != nil {
return nil, fmt.Errorf("new container client: %v", err)
}

if c.azureAdStorageAuth != nil {
log.Printf("[DEBUG] Building the Container Client from AAD auth")
c.configureClient(containersClient.Client, c.azureAdStorageAuth)
return containersClient, nil
}

log.Printf("[DEBUG] Building the Container Client from an Access Token (using user credentials)")
key, err := c.accountDetail.AccountKey(ctx, c.storageAccountsClient)
if err != nil {
return nil, fmt.Errorf("Error retrieving key for Storage Account %q: %s", c.storageAccountName, err)
}
accessKey := *key

authorizer, err := auth.NewSharedKeyAuthorizer(c.storageAccountName, accessKey, auth.SharedKey)
if err != nil {
return nil, fmt.Errorf("new shared key authorizer: %v", err)
}
c.configureClient(containersClient.Client, authorizer)

return containersClient, nil
}

func (c *Client) configureClient(client client.BaseClient, authorizer auth.Authorizer) {
client.SetAuthorizer(authorizer)
client.SetUserAgent(buildUserAgent(client.GetUserAgent()))
}

func buildUserAgent(userAgent string) string {
userAgent = strings.TrimSpace(fmt.Sprintf("%s %s", userAgent, httpclient.TerraformUserAgent(version.Version)))

// append the CloudShell version to the user agent if it exists
if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" {
userAgent = fmt.Sprintf("%s %s", userAgent, azureAgent)
}

return userAgent
}
Loading
Loading