From c5e1271239ee89301b715ffd21a0b6a13360f4c5 Mon Sep 17 00:00:00 2001 From: Ian Booth Date: Tue, 15 Aug 2023 16:17:43 +1000 Subject: [PATCH] Remove the obsolete Azure SDK and use the new apis for the Azure provider --- cloud/fallback-public-cloud.yaml | 88 +-- cloud/fallback_public_cloud.go | 88 +-- cmd/juju/cloud/addcredential.go | 1 + cmd/juju/common/cloudcredential.go | 1 + cmd/modelcmd/base.go | 1 + cmd/modelcmd/credentials.go | 1 + environs/interface.go | 3 + go.mod | 84 +-- go.sum | 171 +++-- provider/azure/config_test.go | 4 +- provider/azure/credentials.go | 100 ++- provider/azure/credentials_test.go | 274 ++------ provider/azure/disk.go | 33 +- provider/azure/environ.go | 234 +------ provider/azure/environ_test.go | 93 ++- provider/azure/environprovider.go | 11 +- provider/azure/environprovider_test.go | 16 +- provider/azure/export_test.go | 11 - provider/azure/init.go | 2 - provider/azure/instance_test.go | 15 +- .../azure/internal/azureauth/legacyauth.go | 115 ---- .../internal/azureauth/serviceprincipal.go | 440 +++++-------- .../azureauth/serviceprincipal_test.go | 600 ++++++++---------- provider/azure/internal/azureauth/utils.go | 135 ++-- .../azure/internal/azureauth/utils_test.go | 158 +---- provider/azure/internal/azurecli/az.go | 83 +-- provider/azure/internal/azurecli/az_test.go | 381 +---------- .../azure/internal/azurestorage/interface.go | 123 ---- provider/azure/internal/azuretesting/json.go | 127 ---- .../azure/internal/azuretesting/recorder.go | 31 - .../azure/internal/azuretesting/senders.go | 236 ++++++- .../azure/internal/azuretesting/storage.go | 111 ---- .../azure/internal/imageutils/images_test.go | 21 +- provider/azure/internal/tracing/tracing.go | 39 -- .../azure/internal/useragent/useragent.go | 24 - provider/azure/storage.go | 336 +--------- provider/azure/storage_test.go | 52 +- provider/azure/upgrades_test.go | 5 +- provider/azure/utils.go | 13 +- 39 files changed, 1158 insertions(+), 3103 deletions(-) delete mode 100644 provider/azure/internal/azureauth/legacyauth.go delete mode 100644 provider/azure/internal/azurestorage/interface.go delete mode 100644 provider/azure/internal/azuretesting/json.go delete mode 100644 provider/azure/internal/azuretesting/storage.go delete mode 100644 provider/azure/internal/useragent/useragent.go diff --git a/cloud/fallback-public-cloud.yaml b/cloud/fallback-public-cloud.yaml index 1e2f64b7e7d..0df86bc8b01 100644 --- a/cloud/fallback-public-cloud.yaml +++ b/cloud/fallback-public-cloud.yaml @@ -133,6 +133,8 @@ clouds: southamerica-east1: endpoint: https://www.googleapis.com azure: + # Note: the storage endpoint definitions below are no longer used by + # recent Juju versions, and are retained for compatibility only. type: azure description: Microsoft Azure auth-types: [ interactive, service-principal-secret ] @@ -140,175 +142,175 @@ clouds: centralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus3: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southeastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japaneast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japanwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com brazilsouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiasoutheast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com centralindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uksouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com ukwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreasouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francesouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricanorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricawest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanynorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanywestcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaenorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwayeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwaywest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandnorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com swedencentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com qatarcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com azure-china: type: azure description: Microsoft Azure China diff --git a/cloud/fallback_public_cloud.go b/cloud/fallback_public_cloud.go index 6b64033307b..e2074b26040 100644 --- a/cloud/fallback_public_cloud.go +++ b/cloud/fallback_public_cloud.go @@ -140,6 +140,8 @@ clouds: southamerica-east1: endpoint: https://www.googleapis.com azure: + # Note: the storage endpoint definitions below are no longer used by + # recent Juju versions, and are retained for compatibility only. type: azure description: Microsoft Azure auth-types: [ interactive, service-principal-secret ] @@ -147,175 +149,175 @@ clouds: centralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westcentralus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westus3: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com northeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westeurope: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com eastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southeastasia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japaneast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com japanwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com brazilsouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiacentral2: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com australiasoutheast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com centralindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com westindia: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com canadaeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uksouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com ukwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreacentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com koreasouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com francesouth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricanorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com southafricawest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanynorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com germanywestcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaecentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com uaenorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwayeast: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com norwaywest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandnorth: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com switzerlandwest: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com swedencentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com qatarcentral: endpoint: https://management.azure.com storage-endpoint: https://core.windows.net - identity-endpoint: https://graph.windows.net + identity-endpoint: https://login.microsoftonline.com azure-china: type: azure description: Microsoft Azure China diff --git a/cmd/juju/cloud/addcredential.go b/cmd/juju/cloud/addcredential.go index 76b496f1364..c9b72d9a867 100644 --- a/cmd/juju/cloud/addcredential.go +++ b/cmd/juju/cloud/addcredential.go @@ -448,6 +448,7 @@ func finalizeProvider(ctxt *cmd.Context, cloud *jujucloud.Cloud, regionName, def newCredential, err := credentialsProvider.FinalizeCredential( ctxt, environs.FinalizeCredentialParams{ Credential: jujucloud.NewCredential(authType, attrs), + CloudName: cloud.Name, CloudEndpoint: cloudEndpoint, CloudStorageEndpoint: cloudStorageEndpoint, CloudIdentityEndpoint: cloudIdentityEndpoint, diff --git a/cmd/juju/common/cloudcredential.go b/cmd/juju/common/cloudcredential.go index 3738f23bb68..0f2cd36a12e 100644 --- a/cmd/juju/common/cloudcredential.go +++ b/cmd/juju/common/cloudcredential.go @@ -120,6 +120,7 @@ func GetOrDetectCredential( credential, err = provider.FinalizeCredential( ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: args.Cloud.Name, CloudEndpoint: region.Endpoint, CloudStorageEndpoint: region.StorageEndpoint, CloudIdentityEndpoint: region.IdentityEndpoint, diff --git a/cmd/modelcmd/base.go b/cmd/modelcmd/base.go index 6f0b644070b..a042c634595 100644 --- a/cmd/modelcmd/base.go +++ b/cmd/modelcmd/base.go @@ -695,6 +695,7 @@ func (g bootstrapConfigGetter) getBootstrapConfigParams(controllerName string) ( credential, err = provider.FinalizeCredential( g.ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: bootstrapConfig.Cloud, CloudEndpoint: bootstrapConfig.CloudEndpoint, CloudStorageEndpoint: bootstrapConfig.CloudStorageEndpoint, CloudIdentityEndpoint: bootstrapConfig.CloudIdentityEndpoint, diff --git a/cmd/modelcmd/credentials.go b/cmd/modelcmd/credentials.go index e81e12b3748..85411f92058 100644 --- a/cmd/modelcmd/credentials.go +++ b/cmd/modelcmd/credentials.go @@ -94,6 +94,7 @@ func VerifyCredentials(ctx *cmd.Context, aCloud *cloud.Cloud, credential *cloud. credential, err = provider.FinalizeCredential( ctx, environs.FinalizeCredentialParams{ Credential: *credential, + CloudName: aCloud.Name, CloudEndpoint: cloudEndpoint, CloudStorageEndpoint: cloudStorageEndpoint, CloudIdentityEndpoint: cloudIdentityEndpoint, diff --git a/environs/interface.go b/environs/interface.go index cfac8ae8ae5..cba2bfc8576 100644 --- a/environs/interface.go +++ b/environs/interface.go @@ -190,6 +190,9 @@ type FinalizeCredentialParams struct { // Credential is the credential that the provider should finalize. Credential cloud.Credential + // CloudName is the name of the cloud that the credentials are for. + CloudName string + // CloudEndpoint is the endpoint for the cloud that the credentials are // for. This may be used by the provider to communicate with the cloud // to finalize the credentials. diff --git a/go.mod b/go.mod index 71adf3ab0e9..468e86aad55 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,16 @@ module github.com/juju/juju go 1.20 require ( - github.com/Azure/azure-sdk-for-go v65.0.0+incompatible - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 - github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 - github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.0.0 - github.com/Azure/go-autorest/autorest v0.11.18 - github.com/Azure/go-autorest/autorest/adal v0.9.13 - github.com/Azure/go-autorest/autorest/date v0.3.0 - github.com/Azure/go-autorest/autorest/mocks v0.4.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 github.com/EvilSuperstars/go-cidrman v0.0.0-20170211231153-4e5a4a63d9b7 github.com/altoros/gosigma v0.0.0-20150408145232-31228935eec6 github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 @@ -31,15 +27,16 @@ require ( github.com/canonical/pebble v0.0.0-20230307221844-5842ea68c9c7 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e github.com/coreos/go-systemd/v22 v22.3.2 - github.com/docker/distribution v2.7.1+incompatible + github.com/docker/distribution v2.8.2+incompatible github.com/dustin/go-humanize v1.0.0 github.com/go-goose/goose/v5 v5.0.0-20220707165353-781664254fe4 - github.com/go-logr/logr v1.2.2 + github.com/go-logr/logr v1.2.4 github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 github.com/gofrs/uuid v4.2.0+incompatible github.com/google/go-querystring v1.1.0 + github.com/google/uuid v1.3.0 github.com/googleapis/gnostic v0.5.5 - github.com/gorilla/handlers v0.0.0-20170224193955-13d73096a474 + github.com/gorilla/handlers v1.3.0 github.com/gorilla/schema v0.0.0-20160426231512-08023a0215e7 github.com/gorilla/websocket v1.5.0 github.com/gosuri/uitable v0.0.1 @@ -93,22 +90,25 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kr/pretty v0.3.1 github.com/lxc/lxd v0.0.0-20220816180258-7e0418163fa9 - github.com/mattn/go-isatty v0.0.14 + github.com/mattn/go-isatty v0.0.16 + github.com/microsoft/kiota-abstractions-go v1.2.0 + github.com/microsoft/kiota-http-go v1.0.1 + github.com/microsoftgraph/msgraph-sdk-go v1.14.0 github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb github.com/mitchellh/mapstructure v1.5.0 github.com/oracle/oci-go-sdk/v47 v47.1.0 github.com/packethost/packngo v0.28.1 github.com/pkg/sftp v1.13.5 - github.com/prometheus/client_golang v1.7.1 + github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_model v0.2.0 github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vmware/govmomi v0.21.1-0.20191008161538-40aebf13ba45 go.uber.org/mock v0.2.0 - golang.org/x/crypto v0.7.0 - golang.org/x/net v0.8.0 + golang.org/x/crypto v0.9.0 + golang.org/x/net v0.10.0 golang.org/x/oauth2 v0.6.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.6.0 + golang.org/x/sys v0.8.0 golang.org/x/tools v0.7.0 google.golang.org/api v0.78.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c @@ -132,15 +132,10 @@ require ( require ( cloud.google.com/go/compute v1.6.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // 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/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.0.0 // indirect @@ -149,21 +144,21 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/canonical/x-go v0.0.0-20230113154138-0ccdb0b57a43 // indirect - github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-macaroon-bakery/macaroonpb v1.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect @@ -198,9 +193,14 @@ require ( github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect github.com/masterzen/winrm v0.0.0-20211231115050-232efb40349e // indirect - github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.4 // indirect + github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect + github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -209,10 +209,11 @@ require ( github.com/onsi/gomega v1.10.4 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pborman/uuid v1.2.1 // indirect - github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/term v1.1.0 // indirect - github.com/prometheus/common v0.10.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect @@ -221,15 +222,20 @@ require ( github.com/rs/xid v1.4.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/xdg-go/stringprep v1.0.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.etcd.io/bbolt v1.3.5 // indirect go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/mod v0.9.0 // indirect - golang.org/x/term v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect diff --git a/go.sum b/go.sum index e25541687fb..95e0773be8b 100644 --- a/go.sum +++ b/go.sum @@ -52,58 +52,48 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= -github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1 h1:AXFNQ6kLaPODEpGSMWjmbkt6iP7fa1DIEzjx6JRFC9U= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.5.1/go.mod h1:yOYJv0tO0TTNcje8ahhBHQcdAiYqRIp5fsog5FPefr4= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 h1:9cn6ICCGiWFNA/slKnrkf+ENyvaCRKHtuoGtnLIAgao= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1 h1:kRt6idL93W/nYRkUPbZ81yxJeLFevvrLYkyJEVzLpYM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3 v3.0.0-beta.1/go.mod h1:nPsyC5G3IY+ljp+OHp8w/xa9UuLWe7ehFADNkqCSTaw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 h1:/Di3vB4sNeQ+7A8efjUVENvyB945Wruvstucqp7ZArg= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0 h1:xxe4naFUPYEW1W6C8yWrfFNmyZLnEbO+CsbsSF83wDo= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2 v2.0.0/go.mod h1:aLFjumYDvv63tH1qnqkcmdjdZ6Sn+/viPv7H3jft0oY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0 h1:lMW1lD/17LUA5z1XTURo7LcVG2ICBPlyMHjIUrcFZNQ= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0 h1:Jc2KcpCDMu7wJfkrzn7fs/53QMDXH78GuqnH4HOd7zs= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.0.0/go.mod h1:PFVgFsclKzPqYRT/BiwpfUN22cab0C7FlgXR3iWpwMo= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.0.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0 h1:xXmHA6JxGDHOY2anNQhpgIibZOiEaOvPLZOiAs07/4k= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.0.0/go.mod h1:qkZjuhvy20x2Ckq4BzopZ8UjZLhib6nRJbRQiC6EFXY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.0.0 h1:TMEyRFKh1zaSPmoQh3kxK+xRAYVq8guCI/7SMO0F3KY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.0.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0 h1:8d4U82r7ItT1Es91x3eUcAQweih36KWvUha8AZ9X0Rs= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault v1.2.0/go.mod h1:/1bkGperHinQbAHMWivoec/Ucu6//iXo6jn5mhmqCVU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 h1:QM6sE5k2ZT/vI5BEe0r7mqjsUSnhVBFbOsVkEuaEfiA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 h1:Pmy0+3ox1IC3sp6musv87BFPIdQbqyPFjn7I8I0o2Js= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0/go.mod h1:ThfyMjs6auYrWPnYJjI3H4H++oVPrz01pizpu8lfl3A= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0 h1:LcJtQjCXJUm1s7JpUHZvu+bpgURhCatxVNbGADXniX0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0/go.mod h1:+OgGVo0Httq7N5oayfvaLQ/Jq+2gJdqfp++Hyyl7Tws= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -121,6 +111,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -170,8 +161,9 @@ github.com/canonical/x-go v0.0.0-20230113154138-0ccdb0b57a43 h1:bey1JgA3D2EBabr2 github.com/canonical/x-go v0.0.0-20230113154138-0ccdb0b57a43/go.mod h1:A0/Jvt7qKuCDss37TYRNXSscVyS+tLWM5kBYipQOpWQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= @@ -180,6 +172,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= +github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -213,9 +207,9 @@ github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVz github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20141228071148-145fabdb1ab7 h1:n/ETHd/ASEuDgfz8mVStuNUN2iUqKg4mNhxowUFhNjw= github.com/dustin/go-humanize v0.0.0-20141228071148-145fabdb1ab7/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -241,7 +235,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE= github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3/go.mod h1:bJWSKrZyQvfTnb2OudyUjurSG4/edverV7n82+K3JiM= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= github.com/frankban/quicktest v1.1.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= @@ -263,14 +256,19 @@ github.com/go-goose/goose/v5 v5.0.0-20220707165353-781664254fe4 h1:IzjmVasvOk8hq github.com/go-goose/goose/v5 v5.0.0-20220707165353-781664254fe4/go.mod h1:BxICmnmP7QlxZhKP2BHkpWQS0tbb3LrsrLtd9TQyyms= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1 h1:uvQJoKTHrFFu8zxoaopNKedRzwdy3+8H72we4T/5cGs= github.com/go-macaroon-bakery/macaroon-bakery/v3 v3.0.1/go.mod h1:H59IYeChwvD1po3dhGUPvq5na+4NVD7SJlbhGKvslr0= github.com/go-macaroon-bakery/macaroonpb v1.0.0 h1:It9exBaRMZ9iix1iJ6gwzfwsDE6ExNuwtAJ9e09v6XE= @@ -295,9 +293,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -353,8 +350,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -398,8 +396,8 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20170224193955-13d73096a474 h1:KNovrfevBTefw9X8FKnoaKhKOc+UWmGsQRsiZRTkGl4= -github.com/gorilla/handlers v0.0.0-20170224193955-13d73096a474/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI= +github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/schema v0.0.0-20160426231512-08023a0215e7 h1:mOUfGq/7wiwNfHY5Wyz+aRzEfqUCGdUhUcSPsfHVaPs= @@ -485,9 +483,11 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= @@ -660,6 +660,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -710,21 +711,38 @@ github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microsoft/kiota-abstractions-go v1.2.0 h1:lUriJgqdCY/QajwWQOgTCQE9Atywfe2NHhgoTCSXTRE= +github.com/microsoft/kiota-abstractions-go v1.2.0/go.mod h1:RkxyZ5x87Njik7iVeQY9M2wtrrL1MJZcXiI/BxD/82g= +github.com/microsoft/kiota-authentication-azure-go v1.0.0 h1:29FNZZ/4nnCOwFcGWlB/sxPvWz487HA2bXH8jR5k2Rk= +github.com/microsoft/kiota-authentication-azure-go v1.0.0/go.mod h1:rnx3PRlkGdXDcA/0lZQTbBwyYGmc+3POt7HpE/e4jGw= +github.com/microsoft/kiota-http-go v1.0.1 h1:818u3aiLpxj35hZgfUSqphQ18IUTK3gVdTE4cQ5vjLw= +github.com/microsoft/kiota-http-go v1.0.1/go.mod h1:H0cg+ly+5ZSR8z4swj5ea9O/GB5ll2YuYeQ0/pJs7AY= +github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= +github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= +github.com/microsoft/kiota-serialization-json-go v1.0.4 h1:5TaISWwd2Me8clrK7SqNATo0tv9seOq59y4I5953egQ= +github.com/microsoft/kiota-serialization-json-go v1.0.4/go.mod h1:rM4+FsAY+9AEpBsBzkFFis+b/LZLlNKKewuLwK9Q6Mg= +github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= +github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= +github.com/microsoftgraph/msgraph-sdk-go v1.14.0 h1:YdhMvzu8bXcfIQGRur6NkXnv4cPOsMBJ44XjfWLOt9Y= +github.com/microsoftgraph/msgraph-sdk-go v1.14.0/go.mod h1:ccLv84FJFtwdSzYWM/HlTes5FLzkzzBsYh9kg93/WS8= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0 h1:7NWTfyXvOjoizW7PmxNp3+8wCKPgpODs/D1cUZ3fkAY= +github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0/go.mod h1:tQb4q3YMIj2dWhhXhQSJ4ELpol931ANKzHSYK5kX1qE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -750,10 +768,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= @@ -787,8 +805,8 @@ github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -806,8 +824,9 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -817,8 +836,9 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -848,6 +868,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -870,15 +891,16 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -902,6 +924,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yohcop/openid-go v1.0.0/go.mod h1:/408xiwkeItSPJZSTPF7+VtZxPkPrRRpRNK2vjGh6yI= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -921,6 +945,12 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -948,8 +978,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1047,8 +1077,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1126,6 +1156,7 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1146,15 +1177,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1165,15 +1197,16 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1183,8 +1216,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/provider/azure/config_test.go b/provider/azure/config_test.go index 6c422f9f1e7..34de38ce726 100644 --- a/provider/azure/config_test.go +++ b/provider/azure/config_test.go @@ -4,13 +4,13 @@ package azure_test import ( - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/environs" "github.com/juju/juju/environs/config" "github.com/juju/juju/provider/azure" + "github.com/juju/juju/provider/azure/internal/azuretesting" "github.com/juju/juju/testing" ) @@ -32,7 +32,7 @@ var _ = gc.Suite(&configSuite{}) func (s *configSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) s.provider = newProvider(c, azure.ProviderConfig{ - Sender: mocks.NewSender(), + Sender: &azuretesting.MockSender{}, }) } diff --git a/provider/azure/credentials.go b/provider/azure/credentials.go index f48c3019eb5..2209f85f014 100644 --- a/provider/azure/credentials.go +++ b/provider/azure/credentials.go @@ -7,11 +7,11 @@ import ( "context" "fmt" "io" - "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/juju/errors" "github.com/juju/juju/cloud" @@ -46,8 +46,6 @@ type AzureCLI interface { ListAccounts() ([]azurecli.Account, error) FindAccountsWithCloudName(name string) ([]azurecli.Account, error) ShowAccount(subscription string) (*azurecli.Account, error) - GetAccessToken(tenant, resource string) (*azurecli.AccessToken, error) - FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) ListClouds() ([]azurecli.Cloud, error) } @@ -135,7 +133,7 @@ func (c environProviderCredentials) DetectCredentials(cloudName string) (*cloud. if !ok { continue } - cred, err := c.accountCredential(acc, cloudInfo) + cred, err := c.accountCredential(acc) if err != nil { logger.Debugf("cannot get credential for %s: %s", acc.Name, err) continue @@ -164,16 +162,8 @@ func (c environProviderCredentials) FinalizeCredential( case deviceCodeAuthType: subscriptionId := args.Credential.Attributes()[credAttrSubscriptionId] if subscriptionId != "" { - // If a subscription ID was specified then fall - // back to the interactive device login. attempt - // to get subscription details from Azure CLI. - graphResourceId := azureauth.TokenResource(args.CloudIdentityEndpoint) - resourceManagerResourceId, err := azureauth.ResourceManagerResourceId(args.CloudStorageEndpoint) - if err != nil { - return nil, errors.Trace(err) - } opts := azcore.ClientOptions{ - Cloud: azureCloud(args.CloudEndpoint, args.CloudIdentityEndpoint), + Cloud: azureCloud(args.CloudName, args.CloudEndpoint, args.CloudIdentityEndpoint), Transport: c.transporter, } clientOpts := arm.ClientOptions{ClientOptions: opts} @@ -183,15 +173,22 @@ func (c environProviderCredentials) FinalizeCredential( return nil, errors.Trace(err) } return c.deviceCodeCredential(ctx, args, azureauth.ServicePrincipalParams{ - GraphEndpoint: args.CloudIdentityEndpoint, - GraphResourceId: graphResourceId, - ResourceManagerEndpoint: args.CloudEndpoint, - ResourceManagerResourceId: resourceManagerResourceId, - SubscriptionId: subscriptionId, - TenantId: tenantID, + SubscriptionId: subscriptionId, + TenantId: tenantID, }) } - params, err := c.getServicePrincipalParams(args.CloudEndpoint) + var azCloudName string + switch args.CloudName { + case "azure": + azCloudName = "AzureCloud" + case "azure-china": + azCloudName = "AzureChinaCloud" + case "azure-gov": + azCloudName = "AzureUSGovernment" + default: + return nil, errors.Errorf("unknown Azure cloud name %q", args.CloudName) + } + params, err := c.getServicePrincipalParams(azCloudName) if err != nil { return nil, errors.Trace(err) } @@ -228,26 +225,22 @@ func (c environProviderCredentials) azureCLICredential( args environs.FinalizeCredentialParams, params azureauth.ServicePrincipalParams, ) (*cloud.Credential, error) { - graphToken, err := c.azureCLI.GetAccessToken(params.TenantId, params.GraphResourceId) + cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ + TenantID: params.TenantId, + }) if err != nil { - // The version of Azure CLI may not support - // get-access-token so fallback to using device - // authentication. - logger.Debugf("error getting access token: %s", err) - return c.deviceCodeCredential(ctx, args, params) + return nil, errors.Trace(err) } - params.GraphTokenProvider = graphToken.Token() + params.Credential = cred - resourceManagerAuthorizer, err := c.azureCLI.GetAccessToken(params.TenantId, params.ResourceManagerResourceId) if err != nil { return nil, errors.Annotatef(err, "cannot get access token for %s", params.SubscriptionId) } - params.ResourceManagerTokenProvider = resourceManagerAuthorizer.Token() sdkCtx := context.Background() applicationId, spObjectId, password, err := c.servicePrincipalCreator.Create(sdkCtx, params) if err != nil { - return nil, errors.Annotatef(err, "cannot get service principal") + return nil, errors.Annotatef(err, "cannot create service principal") } out := cloud.NewCredential(clientCredentialsAuthType, map[string]string{ credAttrSubscriptionId: params.SubscriptionId, @@ -261,26 +254,19 @@ func (c environProviderCredentials) azureCLICredential( func (c environProviderCredentials) accountCredential( acc azurecli.Account, - cloudInfo azurecli.Cloud, ) (cloud.Credential, error) { - graphToken, err := c.azureCLI.GetAccessToken(acc.AuthTenantId(), cloudInfo.Endpoints.ActiveDirectoryGraphResourceID) - if err != nil { - return cloud.Credential{}, errors.Annotatef(err, "cannot get access token for %s", acc.ID) - } - armToken, err := c.azureCLI.GetAccessToken(acc.AuthTenantId(), cloudInfo.Endpoints.ResourceManager) + cred, err := azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ + TenantID: acc.AuthTenantId(), + AdditionallyAllowedTenants: []string{"*"}, + }) if err != nil { - return cloud.Credential{}, errors.Annotatef(err, "cannot get access token for %s", acc.ID) + return cloud.Credential{}, errors.Annotate(err, "cannot get az cli credential") } sdkCtx := context.Background() applicationId, spObjectId, password, err := c.servicePrincipalCreator.Create(sdkCtx, azureauth.ServicePrincipalParams{ - GraphEndpoint: cloudInfo.Endpoints.ActiveDirectoryGraphResourceID, - GraphResourceId: cloudInfo.Endpoints.ActiveDirectoryGraphResourceID, - GraphTokenProvider: graphToken.Token(), - ResourceManagerEndpoint: cloudInfo.Endpoints.ResourceManager, - ResourceManagerResourceId: cloudInfo.Endpoints.ResourceManager, - ResourceManagerTokenProvider: armToken.Token(), - SubscriptionId: acc.ID, - TenantId: graphToken.Tenant, + Credential: cred, + SubscriptionId: acc.ID, + TenantId: acc.TenantId, }) if err != nil { return cloud.Credential{}, errors.Annotate(err, "cannot get service principal") @@ -294,23 +280,13 @@ func (c environProviderCredentials) accountCredential( }), nil } -func (c environProviderCredentials) getServicePrincipalParams(cloudEndpoint string) (azureauth.ServicePrincipalParams, error) { - if !strings.HasSuffix(cloudEndpoint, "/") { - cloudEndpoint += "/" - } - clouds, err := c.azureCLI.FindCloudsWithResourceManagerEndpoint(cloudEndpoint) - if err != nil { - return azureauth.ServicePrincipalParams{}, errors.Annotatef(err, "cannot list clouds") - } - if len(clouds) != 1 { - return azureauth.ServicePrincipalParams{}, errors.Errorf("cannot find cloud for %s", cloudEndpoint) - } - accounts, err := c.azureCLI.FindAccountsWithCloudName(clouds[0].Name) +func (c environProviderCredentials) getServicePrincipalParams(cloudName string) (azureauth.ServicePrincipalParams, error) { + accounts, err := c.azureCLI.FindAccountsWithCloudName(cloudName) if err != nil { return azureauth.ServicePrincipalParams{}, errors.Annotatef(err, "cannot get accounts") } if len(accounts) < 1 { - return azureauth.ServicePrincipalParams{}, errors.Errorf("no %s accounts found", clouds[0].Name) + return azureauth.ServicePrincipalParams{}, errors.Errorf("no %s accounts found", cloudName) } acc := accounts[0] for _, a := range accounts[1:] { @@ -319,12 +295,8 @@ func (c environProviderCredentials) getServicePrincipalParams(cloudEndpoint stri } } return azureauth.ServicePrincipalParams{ - GraphEndpoint: clouds[0].Endpoints.ActiveDirectoryGraphResourceID, - GraphResourceId: clouds[0].Endpoints.ActiveDirectoryGraphResourceID, - ResourceManagerEndpoint: clouds[0].Endpoints.ResourceManager, - ResourceManagerResourceId: clouds[0].Endpoints.ResourceManager, - SubscriptionId: acc.ID, - TenantId: acc.AuthTenantId(), + SubscriptionId: acc.ID, + TenantId: acc.AuthTenantId(), }, nil } diff --git a/provider/azure/credentials_test.go b/provider/azure/credentials_test.go index 48f1f2acb87..8d708d88bae 100644 --- a/provider/azure/credentials_test.go +++ b/provider/azure/credentials_test.go @@ -5,7 +5,6 @@ package azure_test import ( "context" - "fmt" "io" "github.com/juju/cmd/v3/cmdtesting" @@ -78,15 +77,6 @@ func (s *credentialsSuite) TestDetectCredentialsListError(c *gc.C) { c.Assert(err, jc.Satisfies, errors.IsNotFound) } -const ( - expectedToken = "tenant-id|https://graph.invalid/|access-token" - expectedToken2 = "tenant-id2|https://graph.invalid/|access-token" - expectedHomeToken = "home-tenant-id|https://graph.invalid/|access-token" - expectedArmToken = "tenant-id|https://arm.invalid/|access-token" - expectedArmToken2 = "tenant-id2|https://arm.invalid/|access-token" - expectedHomeArmToken = "home-tenant-id|https://arm.invalid/|access-token" -) - func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { s.azureCLI.Accounts = []azurecli.Account{{ CloudName: "AzureCloud", @@ -98,10 +88,6 @@ func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { HomeTenantId: "home-tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -114,30 +100,19 @@ func (s *credentialsSuite) TestDetectCredentialsOneAccount(c *gc.C) { c.Assert(cred.AuthCredentials["test-account"].Label, gc.Equals, "AzureCloud subscription test-account") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 4) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"home-tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"home-tenant-id", "https://arm.invalid/"}) calls = s.servicePrincipalCreator.Calls() c.Assert(calls, gc.HasLen, 1) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedHomeToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedHomeArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account-id", - TenantId: "tenant-id", + SubscriptionId: "test-account-id", + TenantId: "tenant-id", }) } @@ -151,10 +126,6 @@ func (s *credentialsSuite) TestDetectCredentialsCloudError(c *gc.C) { TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid", - ResourceManager: "https://arm.invalid", - }, IsActive: true, Name: "AzureCloud", }} @@ -189,10 +160,6 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { TenantId: "tenant-id2", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -206,49 +173,27 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccounts(c *gc.C) { c.Assert(cred.AuthCredentials["test-account2"].Label, gc.Equals, "AzureCloud subscription test-account2") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 6) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://arm.invalid/"}) - c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://graph.invalid/"}) - c.Assert(calls[5].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[5].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://arm.invalid/"}) calls = s.servicePrincipalCreator.Calls() c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account1-id", - TenantId: "tenant-id", + SubscriptionId: "test-account1-id", + TenantId: "tenant-id", }) c.Assert(calls[1].FuncName, gc.Equals, "Create") params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken2) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken2) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account2-id", - TenantId: "tenant-id2", + SubscriptionId: "test-account2-id", + TenantId: "tenant-id2", }) } @@ -269,14 +214,10 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { TenantId: "tenant-id2", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, nil, nil, nil, errors.New("test error")) + s.servicePrincipalCreator.SetErrors(nil, errors.New("test error")) cred, err := s.provider.DetectCredentials("") c.Assert(err, jc.ErrorIsNil) c.Assert(cred, gc.Not(gc.IsNil)) @@ -286,32 +227,27 @@ func (s *credentialsSuite) TestDetectCredentialsTwoAccountsOneError(c *gc.C) { c.Assert(cred.AuthCredentials["test-account1"].Label, gc.Equals, "AzureCloud subscription test-account1") calls := s.azureCLI.Calls() - c.Assert(calls, gc.HasLen, 5) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "ListAccounts") c.Assert(calls[1].FuncName, gc.Equals, "ListClouds") - c.Assert(calls[2].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://graph.invalid/"}) - c.Assert(calls[3].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[3].Args, jc.DeepEquals, []interface{}{"tenant-id", "https://arm.invalid/"}) - c.Assert(calls[4].FuncName, gc.Equals, "GetAccessToken") - c.Assert(calls[4].Args, jc.DeepEquals, []interface{}{"tenant-id2", "https://graph.invalid/"}) calls = s.servicePrincipalCreator.Calls() - c.Assert(calls, gc.HasLen, 1) + c.Assert(calls, gc.HasLen, 2) c.Assert(calls[0].FuncName, gc.Equals, "Create") params, ok := calls[0].Args[1].(azureauth.ServicePrincipalParams) c.Assert(ok, jc.IsTrue) - c.Assert(params.GraphTokenProvider.OAuthToken(), gc.Equals, expectedToken) - c.Assert(params.ResourceManagerTokenProvider.OAuthToken(), gc.Equals, expectedArmToken) - params.GraphTokenProvider = nil - params.ResourceManagerTokenProvider = nil + params.Credential = nil c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid/", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid/", - ResourceManagerResourceId: "https://arm.invalid/", - SubscriptionId: "test-account1-id", - TenantId: "tenant-id", + SubscriptionId: "test-account1-id", + TenantId: "tenant-id", + }) + c.Assert(calls[1].FuncName, gc.Equals, "Create") + params, ok = calls[1].Args[1].(azureauth.ServicePrincipalParams) + c.Assert(ok, jc.IsTrue) + params.Credential = nil + c.Assert(params, jc.DeepEquals, azureauth.ServicePrincipalParams{ + SubscriptionId: "test-account2-id", + TenantId: "tenant-id2", }) } @@ -338,12 +274,8 @@ func (s *credentialsSuite) TestFinalizeCredentialInteractive(c *gc.C) { s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") args := s.servicePrincipalCreator.Calls()[0].Args c.Assert(args[2], jc.DeepEquals, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid/", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - SubscriptionId: fakeSubscriptionId, - TenantId: fakeTenantId, + SubscriptionId: fakeSubscriptionId, + TenantId: fakeTenantId, }) } @@ -377,19 +309,14 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLI(c *gc.C) { TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) c.Assert(err, jc.ErrorIsNil) c.Assert(cred, gc.Not(gc.IsNil)) @@ -418,20 +345,15 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIShowAccountError(c *gc. TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, errors.New("test error")) + s.azureCLI.SetErrors(errors.New("test error")) in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) c.Assert(err, gc.ErrorMatches, `cannot get accounts: test error`) c.Assert(cred, gc.IsNil) @@ -454,58 +376,17 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIGraphTokenError(c *gc.C TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} - s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) - in := cloud.NewCredential("interactive", nil) - ctx := cmdtesting.Context(c) - cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", - }) - c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) - c.Assert(cred, gc.IsNil) -} - -func (s *credentialsSuite) TestFinalizeCredentialAzureCLIResourceManagerTokenError(c *gc.C) { - s.azureCLI.Accounts = []azurecli.Account{{ - CloudName: "AzureCloud", - ID: "test-account1-id", - IsDefault: true, - Name: "test-account1", - State: "Enabled", - TenantId: "tenant-id", - }, { - CloudName: "AzureCloud", - ID: "test-account2-id", - IsDefault: false, - Name: "test-account2", - State: "Enabled", - TenantId: "tenant-id", - }} - s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, - IsActive: true, - Name: "AzureCloud", - }} - s.azureCLI.SetErrors(nil, nil, nil, errors.New("test error")) + s.servicePrincipalCreator.SetErrors(errors.New("test error")) in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) - c.Assert(err, gc.ErrorMatches, `cannot get access token for test-account1-id: test error`) + c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) c.Assert(cred, gc.IsNil) } @@ -526,10 +407,6 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c TenantId: "tenant-id", }} s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, IsActive: true, Name: "AzureCloud", }} @@ -537,57 +414,13 @@ func (s *credentialsSuite) TestFinalizeCredentialAzureCLIServicePrincipalError(c in := cloud.NewCredential("interactive", nil) ctx := cmdtesting.Context(c) cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", + Credential: in, + CloudName: "azure", }) - c.Assert(err, gc.ErrorMatches, `cannot get service principal: test error`) + c.Assert(err, gc.ErrorMatches, `cannot create service principal: test error`) c.Assert(cred, gc.IsNil) } -func (s *credentialsSuite) TestFinalizeCredentialAzureCLIDeviceFallback(c *gc.C) { - s.azureCLI.Accounts = []azurecli.Account{{ - CloudName: "AzureCloud", - ID: "test-account1-id", - IsDefault: true, - Name: "test-account1", - State: "Enabled", - TenantId: "tenant-id", - }, { - CloudName: "AzureCloud", - ID: "test-account2-id", - IsDefault: false, - Name: "test-account2", - State: "Enabled", - TenantId: "tenant-id", - }} - s.azureCLI.Clouds = []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectoryGraphResourceID: "https://graph.invalid/", - ResourceManager: "https://arm.invalid/", - }, - IsActive: true, - Name: "AzureCloud", - }} - s.azureCLI.SetErrors(nil, nil, errors.New("test error")) - in := cloud.NewCredential("interactive", nil) - ctx := cmdtesting.Context(c) - cred, err := s.provider.FinalizeCredential(ctx, environs.FinalizeCredentialParams{ - Credential: in, - CloudEndpoint: "https://arm.invalid", - CloudIdentityEndpoint: "https://graph.invalid", - }) - c.Assert(err, jc.ErrorIsNil) - c.Assert(cred, gc.Not(gc.IsNil)) - c.Assert(cred.AuthType(), gc.Equals, cloud.AuthType("service-principal-secret")) - attrs := cred.Attributes() - c.Assert(attrs["subscription-id"], gc.Equals, "test-account1-id") - c.Assert(attrs["application-id"], gc.Equals, "appid") - c.Assert(attrs["application-password"], gc.Equals, "service-principal-password") - c.Assert(attrs["application-object-id"], gc.Equals, "application-object-id") - s.servicePrincipalCreator.CheckCallNames(c, "InteractiveCreate") -} - type servicePrincipalCreator struct { testing.Stub } @@ -650,22 +483,6 @@ func (e *azureCLI) findAccount(tenant string) (*azurecli.Account, error) { return nil, errors.New("account not found") } -func (e *azureCLI) GetAccessToken(tenant, resource string) (*azurecli.AccessToken, error) { - e.MethodCall(e, "GetAccessToken", tenant, resource) - if err := e.NextErr(); err != nil { - return nil, err - } - acc, err := e.findAccount(tenant) - if err != nil { - return nil, err - } - return &azurecli.AccessToken{ - AccessToken: fmt.Sprintf("%s|%s|access-token", tenant, resource), - Tenant: acc.TenantId, - TokenType: "Bearer", - }, nil -} - func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { e.MethodCall(e, "ShowCloud", name) if err := e.NextErr(); err != nil { @@ -679,19 +496,6 @@ func (e *azureCLI) ShowCloud(name string) (*azurecli.Cloud, error) { return nil, errors.New("cloud not found") } -func (e *azureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]azurecli.Cloud, error) { - e.MethodCall(e, "FindCloudsWithResourceManagerEndpoint", url) - if err := e.NextErr(); err != nil { - return nil, err - } - for _, cloud := range e.Clouds { - if cloud.Endpoints.ResourceManager == url { - return []azurecli.Cloud{cloud}, nil - } - } - return nil, errors.New("cloud not found") -} - func (e *azureCLI) ListClouds() ([]azurecli.Cloud, error) { e.MethodCall(e, "ListClouds") if err := e.NextErr(); err != nil { diff --git a/provider/azure/disk.go b/provider/azure/disk.go index 8a6ac26de7e..77c2e87c9cd 100644 --- a/provider/azure/disk.go +++ b/provider/azure/disk.go @@ -7,12 +7,11 @@ import ( stdcontext "context" "fmt" "strconv" - "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" "github.com/gofrs/uuid" "github.com/juju/errors" @@ -326,35 +325,31 @@ func (env *azureEnviron) createVaultKey( resp, err := keyClient.CreateKey( ctx, - vaultName, - azkeys.KeyTypeRSA, - &azkeys.CreateKeyOptions{ + keyName, + azkeys.CreateKeyParameters{ + Kty: to.Ptr(azkeys.KeyTypeRSA), // TODO(wallyworld) - make these configurable via storage pool attributes - Size: to.Ptr(int32(4096)), - Operations: []*azkeys.Operation{ - to.Ptr(azkeys.OperationWrapKey), - to.Ptr(azkeys.OperationUnwrapKey), + KeySize: to.Ptr(int32(4096)), + KeyOps: []*azkeys.KeyOperation{ + to.Ptr(azkeys.KeyOperationWrapKey), + to.Ptr(azkeys.KeyOperationUnwrapKey), }, - Properties: &azkeys.Properties{ - Name: to.Ptr(keyName), + KeyAttributes: &azkeys.KeyAttributes{ Enabled: to.Ptr(true), }, - }) + }, + nil) if err == nil { - return resp.Key.ID, nil + return to.Ptr(string(toValue(resp.Key.KID))), nil } if !errorutils.IsConflictError(err) { return nil, errors.Trace(err) } // If the key was previously soft deleted, recover it. - poller, err := keyClient.BeginRecoverDeletedKey(ctx, keyName, nil) - var result azkeys.RecoverDeletedKeyResponse - if err == nil { - result, err = poller.PollUntilDone(ctx, 5*time.Second) - } + result, err := keyClient.RecoverDeletedKey(ctx, keyName, nil) if err != nil { return nil, errors.Annotatef(err, "restoring soft deleted vault key %q in %q", keyName, vaultName) } - return result.Key.ID, nil + return to.Ptr(string(toValue(result.Key.KID))), nil } diff --git a/provider/azure/environ.go b/provider/azure/environ.go index 0c37482022b..710c2acfecc 100644 --- a/provider/azure/environ.go +++ b/provider/azure/environ.go @@ -20,7 +20,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - legacystorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage" // Pin this legacy storage API to 2017-10-01 since it's only used for unmanaged storage "github.com/juju/collections/set" "github.com/juju/errors" "github.com/juju/names/v4" @@ -44,10 +43,8 @@ import ( "github.com/juju/juju/environs/tags" "github.com/juju/juju/provider/azure/internal/armtemplates" "github.com/juju/juju/provider/azure/internal/azureauth" - internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" "github.com/juju/juju/provider/azure/internal/errorutils" "github.com/juju/juju/provider/azure/internal/tracing" - "github.com/juju/juju/provider/azure/internal/useragent" "github.com/juju/juju/provider/common" "github.com/juju/juju/tools" jujuversion "github.com/juju/juju/version" @@ -120,14 +117,6 @@ type azureEnviron struct { config *azureModelConfig instanceTypes map[string]instances.InstanceType commonResourcesCreated bool - - // These are needed for models originalled deployed - // prior to Juju 2.3. Microsoft has deprecated this - // type of storage and it will be removed 1-08-2024. - storage legacystorage.BaseClient - storageAccountName string - storageAccount **legacystorage.Account - storageAccountKey *legacystorage.AccountKey } var _ environs.Environ = (*azureEnviron)(nil) @@ -166,19 +155,6 @@ func (env *azureEnviron) SetCloudSpec(ctx stdcontext.Context, cloud environsclou } } env.modelName = cfg.Name() - - // We need a deterministic storage account name, so that we can - // defer creation of the storage account to the VM deployment, - // and retain the ability to create multiple deployments in - // parallel. - // - // We use the last 20 non-hyphen hex characters of the model's - // UUID as the storage account name, prefixed with "juju". The - // probability of clashing with another storage account should - // be negligible. - uuidAlphaNumeric := strings.Replace(env.config.Config.UUID(), "-", "", -1) - env.storageAccountName = "juju" + uuidAlphaNumeric[len(uuidAlphaNumeric)-20:] - return nil } @@ -190,7 +166,7 @@ func (env *azureEnviron) initEnviron(ctx stdcontext.Context) error { } env.clientOptions = azcore.ClientOptions{ - Cloud: azureCloud(env.cloud.Endpoint, env.cloud.IdentityEndpoint), + Cloud: azureCloud(env.cloud.Name, env.cloud.Endpoint, env.cloud.IdentityEndpoint), PerCallPolicies: []policy.Policy{ &tracing.LoggingPolicy{ Logger: logger.Child("azureapi"), @@ -221,15 +197,6 @@ func (env *azureEnviron) initEnviron(ctx stdcontext.Context) error { if err != nil { return errors.Annotate(err, "set up credential") } - - // Set up legacy storage client, only used for models upgraded - // from Juju 2.2 or earlier. - env.storage = legacystorage.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId) - useragent.UpdateClient(&env.storage.Client) - env.storage.Client.Authorizer = azureauth.NewLegacyAuth(env.cloud, tenantID).Auth() - if env.provider.config.Sender != nil { - env.storage.Client.Sender = env.provider.config.Sender - } return nil } @@ -339,11 +306,6 @@ func (env *azureEnviron) initResourceGroup(ctx context.ProviderCallContext, cont } } - // New models are not given a storage account. Initialise the - // storage account pointer to a pointer to a nil pointer, so - // "getStorageAccount" avoids making an API call. - env.storageAccount = new(*legacystorage.Account) - return nil } @@ -708,17 +670,6 @@ func (env *azureEnviron) createVirtualMachine( } } - maybeStorageAccount, err := env.getStorageAccount(ctx) - if errors.IsNotFound(err) { - // Only models created prior to Juju 2.3 will have a storage - // account. Juju 2.3 onwards exclusively uses managed disks - // for all new models, and handles both managed and unmanaged - // disks for upgraded models. - maybeStorageAccount = nil - } else if err != nil { - return errors.Trace(err) - } - osProfile, seriesOS, err := newOSProfile( vmName, instanceConfig, env.provider.config.RandomWindowsAdminPassword, @@ -729,8 +680,6 @@ func (env *azureEnviron) createVirtualMachine( } storageProfile, err := newStorageProfile( vmName, - maybeStorageAccount, - env.config.storageAccountType, instanceSpec, ) if err != nil { @@ -772,24 +721,13 @@ func (env *azureEnviron) createVirtualMachine( } } if createAvailabilitySet && availabilitySetName != "" { - var ( - availabilitySetProperties interface{} - availabilityStorageOptions *armtemplates.Sku - ) - if maybeStorageAccount == nil { - // This model uses managed disks; we must create - // the availability set as "aligned" to support - // them. - availabilitySetProperties = &armcompute.AvailabilitySetProperties{ - // Azure complains when the fault domain count - // is not specified, even though it is meant - // to be optional and default to the maximum. - // The maximum depends on the location, and - // there is no API to query it. - PlatformFaultDomainCount: to.Ptr(maxFaultDomains(env.location)), - } - // Availability needs to be 'Aligned' to support managed disks. - availabilityStorageOptions = &armtemplates.Sku{Name: "Aligned"} + availabilitySetProperties := &armcompute.AvailabilitySetProperties{ + // Azure complains when the fault domain count + // is not specified, even though it is meant + // to be optional and default to the maximum. + // The maximum depends on the location, and + // there is no API to query it. + PlatformFaultDomainCount: to.Ptr(maxFaultDomains(env.location)), } res = append(res, armtemplates.Resource{ APIVersion: computeAPIVersion, @@ -798,7 +736,7 @@ func (env *azureEnviron) createVirtualMachine( Location: env.location, Tags: envTags, Properties: availabilitySetProperties, - Sku: availabilityStorageOptions, + Sku: &armtemplates.Sku{Name: "Aligned"}, }) vmDependsOn = append(vmDependsOn, availabilitySetId) } @@ -976,38 +914,10 @@ func (env *azureEnviron) waitCommonResourcesCreated(ctx context.ProviderCallCont if env.commonResourcesCreated { return nil } - deployment, err := env.waitCommonResourcesCreatedLocked(ctx) - if err != nil { + if _, err := env.waitCommonResourcesCreatedLocked(ctx); err != nil { return errors.Trace(err) } env.commonResourcesCreated = true - if deployment != nil { - // Check if the common deployment created - // a storage account. If it didn't, we can - // avoid a query for the storage account. - var hasStorageAccount bool - if deployment.Properties != nil { - for _, p := range deployment.Properties.Providers { - if toValue(p.Namespace) != "Microsoft.Storage" { - continue - } - if p.ResourceTypes == nil { - continue - } - for _, rt := range p.ResourceTypes { - if toValue(rt.ResourceType) != "storageAccounts" { - continue - } - hasStorageAccount = true - break - } - break - } - } - if !hasStorageAccount { - env.storageAccount = new(*legacystorage.Account) - } - } return nil } @@ -1120,8 +1030,6 @@ func availabilitySetName( // based on the series and chosen instance spec. func newStorageProfile( vmName string, - maybeStorageAccount *legacystorage.Account, - storageAccountType string, instanceSpec *instances.InstanceSpec, ) (*armcompute.StorageProfile, error) { logger.Debugf("creating storage profile for %q", vmName) @@ -1142,18 +1050,9 @@ func newStorageProfile( CreateOption: to.Ptr(armcompute.DiskCreateOptionTypesFromImage), Caching: to.Ptr(armcompute.CachingTypesReadWrite), DiskSizeGB: to.Ptr(int32(osDiskSizeGB)), - } - - if maybeStorageAccount == nil { - // This model uses managed disks. - osDisk.ManagedDisk = &armcompute.ManagedDiskParameters{ - StorageAccountType: to.Ptr(armcompute.StorageAccountTypes(storageAccountType)), - } - } else { - // This model uses unmanaged disks. - osDiskVhdRoot := blobContainerURL(maybeStorageAccount, osDiskVHDContainer) - vhdURI := osDiskVhdRoot + osDiskName + vhdExtension - osDisk.Vhd = &armcompute.VirtualHardDisk{to.Ptr(vhdURI)} + ManagedDisk: &armcompute.ManagedDiskParameters{ + StorageAccountType: to.Ptr(armcompute.StorageAccountTypesStandardLRS), + }, } return &armcompute.StorageProfile{ @@ -1275,11 +1174,6 @@ func (env *azureEnviron) StopInstances(ctx context.ProviderCallContext, ids ...i return nil } - maybeStorageClient, _, err := env.maybeGetStorageClient(ctx) - if err != nil { - return errors.Trace(err) - } - // List network interfaces and public IP addresses. instanceNics, err := env.instanceNetworkInterfaces( ctx, @@ -1310,7 +1204,6 @@ func (env *azureEnviron) StopInstances(ctx context.ProviderCallContext, ids ...i err := env.deleteVirtualMachine( ctx, id, - maybeStorageClient, instanceNics[id], instancePips[id], ) @@ -1366,7 +1259,6 @@ func isDeployConflictError(err error) bool { func (env *azureEnviron) deleteVirtualMachine( ctx context.ProviderCallContext, instId instance.Id, - maybeStorageClient internalazurestorage.Client, networkInterfaces []*armnetwork.Interface, publicIPAddresses []*armnetwork.PublicIPAddress, ) error { @@ -1389,15 +1281,6 @@ func (env *azureEnviron) deleteVirtualMachine( return errors.Annotate(err, "deleting virtual machine") } } - if maybeStorageClient != nil { - logger.Debugf("- deleting OS VHD (%s)", vmName) - blobClient := maybeStorageClient.GetBlobService() - vhdContainer := blobClient.GetContainerReference(osDiskVHDContainer) - vhdBlob := vhdContainer.Blob(vmName) - _, err := vhdBlob.DeleteIfExists(nil) - return errorutils.HandleCredentialError(errors.Annotate(err, "deleting OS VHD"), ctx) - } - // Delete the managed OS disk. logger.Debugf("- deleting OS disk (%s)", vmName) disks, err := env.disksClient() @@ -2356,97 +2239,6 @@ func (env *azureEnviron) getInstanceTypesLocked(ctx context.ProviderCallContext) return instanceTypes, nil } -// maybeGetStorageClient returns the environment's storage client if it -// has one, and nil if it does not. -func (env *azureEnviron) maybeGetStorageClient(ctx stdcontext.Context) (internalazurestorage.Client, *legacystorage.Account, error) { - storageClient, storageAccount, err := env.getStorageClient(ctx) - if errors.IsNotFound(err) { - // Only models created prior to Juju 2.3 will have a storage - // account. Juju 2.3 onwards exclusively uses managed disks - // for all new models, and handles both managed and unmanaged - // disks for upgraded models. - storageClient = nil - storageAccount = nil - } else if err != nil { - return nil, nil, errors.Trace(err) - } - return storageClient, storageAccount, nil -} - -// getStorageClient queries the storage account key, and uses it to construct -// a new storage client. -func (env *azureEnviron) getStorageClient(ctx stdcontext.Context) (internalazurestorage.Client, *legacystorage.Account, error) { - env.mu.Lock() - defer env.mu.Unlock() - storageAccount, err := env.getStorageAccountLocked(ctx) - if err != nil { - return nil, nil, errors.Annotate(err, "getting storage account") - } - storageAccountKey, err := env.getStorageAccountKeyLocked( - ctx, toValue(storageAccount.Name), false, - ) - if err != nil { - return nil, nil, errors.Annotate(err, "getting storage account key") - } - client, err := getStorageClient( - env.provider.config.NewStorageClient, - env.storageEndpoint, - storageAccount, - storageAccountKey, - ) - if err != nil { - return nil, nil, errors.Annotate(err, "getting storage client") - } - return client, storageAccount, nil -} - -// getStorageAccount returns the storage account for this environment's -// resource group. -func (env *azureEnviron) getStorageAccount(ctx stdcontext.Context) (*legacystorage.Account, error) { - env.mu.Lock() - defer env.mu.Unlock() - return env.getStorageAccountLocked(ctx) -} - -func (env *azureEnviron) getStorageAccountLocked(ctx stdcontext.Context) (*legacystorage.Account, error) { - if env.storageAccount != nil { - if *env.storageAccount == nil { - return nil, errors.NotFoundf("storage account") - } - return *env.storageAccount, nil - } - client := legacystorage.AccountsClient{env.storage} - account, err := client.GetProperties(ctx, env.resourceGroup, env.storageAccountName) - if err != nil { - if isNotFoundResult(account.Response, err) { - // Remember that the account was not found - // by storing a pointer to a nil pointer. - env.storageAccount = new(*legacystorage.Account) - return nil, errors.NewNotFound(err, fmt.Sprintf("storage account not found")) - } - return nil, errors.Annotate(err, "checking legacy storage account") - } - env.storageAccount = new(*legacystorage.Account) - *env.storageAccount = &account - return &account, nil -} - -// getStorageAccountKeysLocked returns a storage account key for this -// environment's storage account. If refresh is true, any cached key -// will be refreshed. This method assumes that env.mu is held. -func (env *azureEnviron) getStorageAccountKeyLocked(ctx stdcontext.Context, accountName string, refresh bool) (*legacystorage.AccountKey, error) { - if !refresh && env.storageAccountKey != nil { - return env.storageAccountKey, nil - } - client := legacystorage.AccountsClient{env.storage} - key, err := getStorageAccountKey(ctx, client, env.resourceGroup, accountName) - if err != nil { - return nil, errors.Trace(err) - } - env.storageAccountKey = key - return key, nil -} - // Region is specified in the HasRegion interface. func (env *azureEnviron) Region() (simplestreams.CloudSpec, error) { return simplestreams.CloudSpec{ diff --git a/provider/azure/environ_test.go b/provider/azure/environ_test.go index ef0e57d6eee..31f5c065db6 100644 --- a/provider/azure/environ_test.go +++ b/provider/azure/environ_test.go @@ -23,8 +23,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-08-01/storage" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/clock/testclock" "github.com/juju/names/v4" gitjujutesting "github.com/juju/testing" @@ -95,11 +93,10 @@ var ( type environSuite struct { testing.BaseSuite - provider environs.EnvironProvider - requests []*http.Request - storageClient azuretesting.MockStorageClient - sender azuretesting.Senders - retryClock mockClock + provider environs.EnvironProvider + requests []*http.Request + sender azuretesting.Senders + retryClock mockClock controllerUUID string envTags map[string]string @@ -127,7 +124,6 @@ func (s *environSuite) SetUpTest(c *gc.C) { s.provider = newProvider(c, azure.ProviderConfig{ Sender: azuretesting.NewSerialSender(&s.sender), RequestInspector: &azuretesting.RequestRecorderPolicy{Requests: &s.requests}, - NewStorageClient: s.storageClient.NewClient, RetryClock: &testclock.AutoAdvancingClock{ &s.retryClock, s.retryClock.Advance, }, @@ -294,12 +290,6 @@ func openEnviron( Config: cfg, }) c.Assert(err, jc.ErrorIsNil) - - // Legacy storage hasn't been a thing for over 5 years. - // We'll disable it in tests due to issues with mocking - // the new and legacy SDKs together and only test for - // managed storage. - azure.DisableLegacyStorage(env) return env } @@ -348,19 +338,18 @@ func fakeCloudSpec() environscloudspec.CloudSpec { } func discoverAuthSender() *azuretesting.MockSender { - sender := mocks.NewSender() - resp := mocks.NewResponseWithStatus("", http.StatusUnauthorized) - mocks.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ + sender := &azuretesting.MockSender{ + PathPattern: ".*/subscriptions/(" + fakeSubscriptionId + "|" + fakeManagedSubscriptionId + ")", + } + resp := azuretesting.NewResponseWithStatus("", http.StatusUnauthorized) + azuretesting.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ fmt.Sprintf( `authorization_uri="https://testing.invalid/%s"`, fakeTenantId, ), }) sender.AppendResponse(resp) - return &azuretesting.MockSender{ - Sender: sender, - PathPattern: ".*/subscriptions/(" + fakeSubscriptionId + "|" + fakeManagedSubscriptionId + ")", - } + return sender } func (s *environSuite) initResourceGroupSenders(resourceGroupName string) azuretesting.Senders { @@ -373,6 +362,7 @@ type startInstanceSenderParams struct { subnets []*armnetwork.Subnet diskEncryptionSetName string vaultName string + vaultKeyName string existingNetwork string withQuotaRetry bool withConflictRetry bool @@ -399,19 +389,6 @@ func (s *environSuite) startInstanceSenders(args startInstanceSenderParams) azur senders = append(senders, makeSender("/deployments/common", s.commonDeployment)) } - // If the deployment has any providers, then we assume - // storage accounts are in use, for unmanaged storage. - if s.commonDeployment.Properties.Providers != nil { - storageAccount := &storage.Account{ - AccountProperties: &storage.AccountProperties{ - PrimaryEndpoints: &storage.Endpoints{ - Blob: to.Ptr("https://blob.storage/"), - }, - }, - } - senders = append(senders, makeSender("/storageAccounts/juju400d80004b1d0d06f00d", storageAccount)) - } - if args.vaultName != "" { senders = append(senders, makeSender("/diskEncryptionSets/"+args.diskEncryptionSetName, &armcompute.DiskEncryptionSet{ Identity: &armcompute.EncryptionSetIdentity{ @@ -420,9 +397,9 @@ func (s *environSuite) startInstanceSenders(args startInstanceSenderParams) azur }, })) vaultName := args.vaultName + "-deadbeef" - deletedVaultSender := azuretesting.MockSender{Sender: mocks.NewSender()} + deletedVaultSender := azuretesting.MockSender{} deletedVaultSender.PathPattern = ".*/locations/westus/deletedVaults/" + vaultName - deletedVaultSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + deletedVaultSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vault not found", http.StatusNotFound, ), 1) senders = append(senders, &deletedVaultSender) @@ -433,12 +410,9 @@ func (s *environSuite) startInstanceSenders(args startInstanceSenderParams) azur VaultURI: to.Ptr("https://vault-uri"), }, })) - // Need to make 2 sends as the SDK sends a second time after token auth. - for i := 0; i < 2; i++ { - senders = append(senders, makeSender("keys/my-vault-deadbeef/create", &keyBundle{ - Key: &jsonWebKey{Kid: to.Ptr("https://key-url")}, - })) - } + senders = append(senders, makeSender(fmt.Sprintf("keys/%s/create", args.vaultKeyName), &keyBundle{ + Key: &jsonWebKey{Kid: to.Ptr("https://key-url")}, + })) } } @@ -501,9 +475,9 @@ func (s *environSuite) resourceSKUsSender() *azuretesting.MockSender { } func makeResourceGroupNotFoundSender(pattern string) *azuretesting.MockSender { - sender := azuretesting.MockSender{Sender: mocks.NewSender()} + sender := azuretesting.MockSender{} sender.PathPattern = pattern - sender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "resource group not found", http.StatusNotFound, ), 1) return &sender @@ -516,9 +490,9 @@ func makeSender(pattern string, v interface{}) *azuretesting.MockSender { } func makeSenderWithStatus(pattern string, statusCode int) *azuretesting.MockSender { - sender := azuretesting.MockSender{Sender: mocks.NewSender()} + sender := azuretesting.MockSender{} sender.PathPattern = pattern - sender.AppendResponse(mocks.NewResponseWithStatus("", statusCode)) + sender.AppendResponse(azuretesting.NewResponseWithStatus("", statusCode)) return &sender } @@ -541,7 +515,7 @@ func newAzureResponseError(code int, status string) error { } func (s *environSuite) makeErrorSender(pattern string, err error, repeat int) *azuretesting.MockSender { - sender := &azuretesting.MockSender{Sender: mocks.NewSender()} + sender := &azuretesting.MockSender{} sender.PathPattern = pattern sender.SetAndRepeatError(err, repeat) return sender @@ -660,6 +634,7 @@ func (s *environSuite) assertStartInstance( } diskEncryptionSetName := "" vaultName := "" + vaultKeyName := "" if len(rootDiskSourceParams) > 0 { encrypted, _ := rootDiskSourceParams["encrypted"].(string) if encrypted == "true" { @@ -668,12 +643,14 @@ func (s *environSuite) assertStartInstance( } diskEncryptionSetName, _ = rootDiskSourceParams["disk-encryption-set-name"].(string) vaultName, _ = rootDiskSourceParams["vault-name-prefix"].(string) + vaultKeyName, _ = rootDiskSourceParams["vault-key-name"].(string) } } s.sender = s.startInstanceSenders(startInstanceSenderParams{ bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, withQuotaRetry: withQuotaRetry, withConflictRetry: withConflictRetry, }) @@ -684,6 +661,7 @@ func (s *environSuite) assertStartInstance( bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, withQuotaRetry: withQuotaRetry, existingAvailabilitySet: true, })...) @@ -696,6 +674,7 @@ func (s *environSuite) assertStartInstance( bootstrap: false, diskEncryptionSetName: diskEncryptionSetName, vaultName: vaultName, + vaultKeyName: vaultKeyName, existingCommon: true, })...) } @@ -776,8 +755,8 @@ func (s *environSuite) TestStartInstanceNoAuthorizedKeys(c *gc.C) { } func (s *environSuite) createSenderWithUnauthorisedStatusCode(c *gc.C) { - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = azuretesting.Senders{unauthSender, unauthSender, unauthSender} } @@ -1821,8 +1800,8 @@ func (s *environSuite) TestBootstrapWithAutocert(c *gc.C) { func (s *environSuite) TestAllRunningInstancesResourceGroupNotFound(c *gc.C) { env := s.openEnviron(c) - sender := mocks.NewSender() - sender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender := &azuretesting.MockSender{} + sender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "resource group not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{sender, sender} @@ -1856,12 +1835,12 @@ func (s *environSuite) TestAllRunningInstancesIgnoresCommonDeployment(c *gc.C) { func (s *environSuite) TestStopInstancesNotFound(c *gc.C) { env := s.openEnviron(c) - sender0 := mocks.NewSender() - sender0.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender0 := &azuretesting.MockSender{} + sender0.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vm not found", http.StatusNotFound, ), 2) - sender1 := mocks.NewSender() - sender1.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender1 := &azuretesting.MockSender{} + sender1.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "vm not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{sender0, sender1} @@ -1970,8 +1949,8 @@ func (s *environSuite) TestStopInstancesMultiple(c *gc.C) { func (s *environSuite) TestStopInstancesDeploymentNotFound(c *gc.C) { env := s.openEnviron(c) - cancelSender := mocks.NewSender() - cancelSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + cancelSender := &azuretesting.MockSender{} + cancelSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "deployment not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{cancelSender} diff --git a/provider/azure/environprovider.go b/provider/azure/environprovider.go index 74d9067afaa..f71a59b7200 100644 --- a/provider/azure/environprovider.go +++ b/provider/azure/environprovider.go @@ -7,6 +7,7 @@ import ( stdcontext "context" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + azurecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/juju/clock" "github.com/juju/errors" @@ -17,7 +18,6 @@ import ( environscloudspec "github.com/juju/juju/environs/cloudspec" "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/context" - "github.com/juju/juju/provider/azure/internal/azurestorage" "github.com/juju/juju/provider/azure/internal/errorutils" ) @@ -50,10 +50,6 @@ type ProviderConfig struct { // CreateTokenCredential is set by tests to create a token. CreateTokenCredential func(appId, appPassword, tenantID string, opts azcore.ClientOptions) (azcore.TokenCredential, error) - // NewStorageClient will be used to construct new storage - // clients. - NewStorageClient azurestorage.NewClientFunc - // RetryClock is used for retrying some operations, like // waiting for deployments to complete. // @@ -83,9 +79,6 @@ type ProviderConfig struct { // Validate validates the Azure provider configuration. func (cfg ProviderConfig) Validate() error { - if cfg.NewStorageClient == nil { - return errors.NotValidf("nil NewStorageClient") - } if cfg.RetryClock == nil { return errors.NotValidf("nil RetryClock") } @@ -195,7 +188,7 @@ var verifyCredentials = func(e *azureEnviron, ctx context.ProviderCallContext) e // This is used at bootstrap - the ctx invalid credential callback will log // a suitable message. _, err := e.credential.GetToken(ctx, policy.TokenRequestOptions{ - Scopes: []string{"https://management.core.windows.net/.default"}, + Scopes: []string{e.clientOptions.Cloud.Services[azurecloud.ResourceManager].Audience + "/.default"}, }) return errorutils.HandleCredentialError(err, ctx) } diff --git a/provider/azure/environprovider_test.go b/provider/azure/environprovider_test.go index 3a94f1ea438..a26dfb09b79 100644 --- a/provider/azure/environprovider_test.go +++ b/provider/azure/environprovider_test.go @@ -45,13 +45,11 @@ func (s *environProviderSuite) SetUpTest(c *gc.C) { }, }) s.spec = environscloudspec.CloudSpec{ - Type: "azure", - Name: "azure", - Region: "westus", - Endpoint: "https://api.azurestack.local", - IdentityEndpoint: "https://login.azurestack.local", - StorageEndpoint: "https://storage.azurestack.local", - Credential: fakeServicePrincipalCredential(), + Type: "azure", + Name: "azure", + Region: "westus", + StorageEndpoint: "https://storage.azurestack.local", + Credential: fakeServicePrincipalCredential(), } s.sender = nil } @@ -117,10 +115,6 @@ func (s *environProviderSuite) testOpenError(c *gc.C, spec environscloudspec.Clo } func newProvider(c *gc.C, config azure.ProviderConfig) environs.EnvironProvider { - if config.NewStorageClient == nil { - var storage azuretesting.MockStorageClient - config.NewStorageClient = storage.NewClient - } if config.RetryClock == nil { config.RetryClock = testclock.NewClock(time.Time{}) } diff --git a/provider/azure/export_test.go b/provider/azure/export_test.go index 219398f535b..bbb744f8f83 100644 --- a/provider/azure/export_test.go +++ b/provider/azure/export_test.go @@ -3,16 +3,5 @@ package azure -import ( - legacystorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage" - - "github.com/juju/juju/environs" -) - -func DisableLegacyStorage(env environs.Environ) { - azureEnv := env.(*azureEnviron) - azureEnv.storageAccount = new(*legacystorage.Account) -} - const ComputeAPIVersion = computeAPIVersion const NetworkAPIVersion = networkAPIVersion diff --git a/provider/azure/init.go b/provider/azure/init.go index 5b389ef6dd5..99398244913 100644 --- a/provider/azure/init.go +++ b/provider/azure/init.go @@ -13,7 +13,6 @@ import ( "github.com/juju/juju/environs" "github.com/juju/juju/provider/azure/internal/azureauth" "github.com/juju/juju/provider/azure/internal/azurecli" - "github.com/juju/juju/provider/azure/internal/azurestorage" ) const ( @@ -33,7 +32,6 @@ func NewProvider(config ProviderConfig) (environs.CloudEnvironProvider, error) { func init() { environProvider, err := NewProvider(ProviderConfig{ - NewStorageClient: azurestorage.NewClient, RetryClock: &clock.WallClock, RandomWindowsAdminPassword: randomAdminPassword, GenerateSSHKey: ssh.GenerateKey, diff --git a/provider/azure/instance_test.go b/provider/azure/instance_test.go index a4ef26d727c..2dca9da9519 100644 --- a/provider/azure/instance_test.go +++ b/provider/azure/instance_test.go @@ -13,7 +13,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -464,9 +463,9 @@ func (s *instanceSuite) TestInstanceClosePorts(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - sender := mocks.NewSender() - notFoundSender := mocks.NewSender() - notFoundSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus( + sender := &azuretesting.MockSender{} + notFoundSender := &azuretesting.MockSender{} + notFoundSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus( "rule not found", http.StatusNotFound, ), 2) s.sender = azuretesting.Senders{nsgSender, sender, notFoundSender, notFoundSender, notFoundSender} @@ -497,8 +496,8 @@ func (s *instanceSuite) TestInstanceOpenPorts(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - okSender := mocks.NewSender() - okSender.AppendResponse(mocks.NewResponseWithContent("{}")) + okSender := &azuretesting.MockSender{} + okSender.AppendResponse(azuretesting.NewResponseWithContent("{}")) s.sender = azuretesting.Senders{nsgSender, okSender, okSender, okSender, okSender} err := fwInst.OpenPorts(s.callCtx, "0", firewall.IngressRules{ @@ -589,8 +588,8 @@ func (s *instanceSuite) TestInstanceOpenPortsAlreadyOpen(c *gc.C) { fwInst, ok := inst.(instances.InstanceFirewaller) c.Assert(ok, gc.Equals, true) - okSender := mocks.NewSender() - okSender.AppendResponse(mocks.NewResponseWithContent("{}")) + okSender := &azuretesting.MockSender{} + okSender.AppendResponse(azuretesting.NewResponseWithContent("{}")) s.sender = azuretesting.Senders{nsgSender, okSender, okSender} err := fwInst.OpenPorts(s.callCtx, "0", firewall.IngressRules{ diff --git a/provider/azure/internal/azureauth/legacyauth.go b/provider/azure/internal/azureauth/legacyauth.go deleted file mode 100644 index 0dfc4266e2f..00000000000 --- a/provider/azure/internal/azureauth/legacyauth.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2016 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azureauth - -import ( - "sync" - - "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/juju/errors" - - environscloudspec "github.com/juju/juju/environs/cloudspec" - "github.com/juju/juju/provider/azure/internal/useragent" -) - -// NewLegacyAuth creates a legacy auth for the specified cloud. -// Only used with the legacy unmanaged storage API client which -// is deprecated by Azure and removed in Juju 3.0 -func NewLegacyAuth(cloud environscloudspec.CloudSpec, tenantID string) *cloudSpecAuth { - return &cloudSpecAuth{ - cloud: cloud, - tenantID: tenantID, - } -} - -// cloudSpecAuth provides an implementation of autorest.Authorizer. -type cloudSpecAuth struct { - cloud environscloudspec.CloudSpec - sender autorest.Sender - mu sync.Mutex - tokens map[string]*adal.ServicePrincipalToken - tenantID string -} - -func (c *cloudSpecAuth) Auth() *autorest.BearerAuthorizerCallback { - return autorest.NewBearerAuthorizerCallback(c.sender, func(tenantID, resourceID string) (*autorest.BearerAuthorizer, error) { - token, resourceID, err := c.getToken(resourceID) - if err != nil { - return nil, errors.Annotatef(err, "constructing service principal token for resource %q", resourceID) - } - return autorest.NewBearerAuthorizer(token), nil - }) -} - -func (c *cloudSpecAuth) getToken(resourceID string) (*adal.ServicePrincipalToken, string, error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.tokens == nil { - c.tokens = make(map[string]*adal.ServicePrincipalToken) - } - - if resourceID == "" { - var err error - resourceID, err = ResourceManagerResourceId(c.cloud.StorageEndpoint) - if err != nil { - return nil, "", errors.Trace(err) - } - } - - if token := c.tokens[resourceID]; token != nil { - return token, resourceID, nil - } - logger.Debugf("get auth token for %v", resourceID) - token, err := AuthToken(c.cloud, c.sender, resourceID, c.tenantID) - if err != nil { - return nil, resourceID, errors.Annotatef(err, "constructing service principal token for resource %q", resourceID) - } - - c.tokens[resourceID] = token - return token, resourceID, nil -} - -// AuthToken returns a service principal token, suitable for authorizing -// Resource Manager API requests, based on the supplied CloudSpec. -func AuthToken(cloud environscloudspec.CloudSpec, sender autorest.Sender, resourceID, tenantID string) (*adal.ServicePrincipalToken, error) { - if authType := cloud.Credential.AuthType(); authType != "service-principal-secret" { - // We currently only support a single auth-type for - // non-interactive authentication. Interactive auth - // is used only to generate a service-principal. - return nil, errors.NotSupportedf("auth-type %q", authType) - } - - logger.Debugf("getting new auth token for resource %q", resourceID) - credAttrs := cloud.Credential.Attributes() - appId := credAttrs["application-id"] - appPassword := credAttrs["application-password"] - client := subscriptions.Client{subscriptions.NewWithBaseURI(cloud.Endpoint)} - useragent.UpdateClient(&client.Client) - client.Sender = sender - oauthConfig, err := adal.NewOAuthConfig("https://login.windows.net", tenantID) - if err != nil { - return nil, errors.Trace(err) - } - - token, err := adal.NewServicePrincipalToken( - *oauthConfig, - appId, - appPassword, - resourceID, - func(t adal.Token) error { - logger.Debugf("auth token refreshed for resource %q", resourceID) - return nil - }, - ) - if err != nil { - return nil, errors.Annotate(err, "constructing service principal token") - } - tokenClient := autorest.NewClientWithUserAgent("") - useragent.UpdateClient(&tokenClient) - tokenClient.Sender = sender - token.SetSender(&tokenClient) - return token, nil -} diff --git a/provider/azure/internal/azureauth/serviceprincipal.go b/provider/azure/internal/azureauth/serviceprincipal.go index 923aef3dcc2..51218e65be2 100644 --- a/provider/azure/internal/azureauth/serviceprincipal.go +++ b/provider/azure/internal/azureauth/serviceprincipal.go @@ -11,25 +11,23 @@ import ( "strings" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/date" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3" + "github.com/google/uuid" "github.com/juju/clock" "github.com/juju/errors" - "github.com/juju/loggo" "github.com/juju/retry" "github.com/juju/utils/v3" - - "github.com/juju/juju/provider/azure/internal/tracing" - "github.com/juju/juju/provider/azure/internal/useragent" + abstractions "github.com/microsoft/kiota-abstractions-go" + "github.com/microsoftgraph/msgraph-sdk-go" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" ) -var logger = loggo.GetLogger("juju.provider.azure.internal.azureauth") - const ( // jujuApplicationId is the ID of the Azure application that we use // for interactive authentication. When the user logs in, a service @@ -54,32 +52,11 @@ func MaybeJujuApplicationObjectID(appID string) (string, error) { return "", errors.Errorf("unexpected application ID %q", appID) } +// ServicePrincipalParams are used when creating Juju service principal. type ServicePrincipalParams struct { - // GraphEndpoint of the Azure graph API. - GraphEndpoint string - - // GraphResourceId is the resource ID of the graph API that is - // used when acquiring access tokens. - GraphResourceId string - - // GraphTokenProvider is the authorization needed to contact the + // Credential is the authorization needed to contact the // Azure graph API. - GraphTokenProvider adal.OAuthTokenProvider - - // AuthEndpoint is the endpoint for auth requests. - AuthEndpoint string - - // ResourceManagerEndpoint is the endpoint of the azure resource - // manager API. - ResourceManagerEndpoint string - - // ResourceManagerResourceId is the resource ID of the resource manager API that is - // used when acquiring access tokens. - ResourceManagerResourceId string - - // ResourceManagerTokenProvider is the authorization needed to - // contact the Azure resource manager API. - ResourceManagerTokenProvider adal.OAuthTokenProvider + Credential azcore.TokenCredential // SubscriptionId is the subscription ID of the account creating // the service principal. @@ -90,55 +67,14 @@ type ServicePrincipalParams struct { TenantId string } -func (p ServicePrincipalParams) directoryClient(sender autorest.Sender, requestInspector autorest.PrepareDecorator) graphrbac.BaseClient { - directoryClient := graphrbac.NewWithBaseURI(p.GraphEndpoint, p.TenantId) - directoryClient.Authorizer = autorest.NewBearerAuthorizer(p.GraphTokenProvider) - directoryClient.Sender = sender - setClientInspectors(&directoryClient.Client, requestInspector, CheckForGraphError, "azure.directory") - return directoryClient -} - -func (p ServicePrincipalParams) authorizationClient(sender autorest.Sender, requestInspector autorest.PrepareDecorator) authorization.BaseClient { - authorizationClient := authorization.NewWithBaseURI(p.ResourceManagerEndpoint, p.SubscriptionId) - useragent.UpdateClient(&authorizationClient.Client) - authorizationClient.Authorizer = autorest.NewBearerAuthorizer(p.ResourceManagerTokenProvider) - authorizationClient.Sender = sender - setClientInspectors(&authorizationClient.Client, requestInspector, nil, "azure.authorization") - return authorizationClient -} - -func setClientInspectors( - client *autorest.Client, - requestInspector autorest.PrepareDecorator, - responseInspector autorest.RespondDecorator, - loggingModule string, -) { - logger := loggo.GetLogger(loggingModule) - client.ResponseInspector = tracing.RespondDecorator(logger) - if responseInspector != nil { - tracer := client.ResponseInspector - client.ResponseInspector = func(r autorest.Responder) autorest.Responder { - r = tracer(r) - r = responseInspector(r) - return r - } - } - client.RequestInspector = tracing.PrepareDecorator(logger) - if requestInspector != nil { - tracer := client.RequestInspector - client.RequestInspector = func(p autorest.Preparer) autorest.Preparer { - p = tracer(p) - p = requestInspector(p) - return p - } - } -} - +// ServicePrincipalCreator creates a service principal for the +// Juju enterprise application. type ServicePrincipalCreator struct { - Sender autorest.Sender - RequestInspector autorest.PrepareDecorator - Clock clock.Clock - NewUUID func() (utils.UUID, error) + RequestAdaptor abstractions.RequestAdapter + Sender policy.Transporter + + Clock clock.Clock + NewUUID func() (uuid.UUID, error) } // InteractiveCreate creates a new ServicePrincipal by performing device @@ -148,77 +84,42 @@ type ServicePrincipalCreator struct { // and SubscriptionId need to be specified in params, the other values // will be derived. func (c *ServicePrincipalCreator) InteractiveCreate(sdkCtx context.Context, stderr io.Writer, params ServicePrincipalParams) (appid, spid, password string, _ error) { - adEndpoint := params.AuthEndpoint - if adEndpoint == "" { - adEndpoint = "https://login.windows.net" - } - oauthConfig, err := adal.NewOAuthConfig(adEndpoint, params.TenantId) - if err != nil { - return "", "", "", errors.Annotate(err, "getting OAuth configuration") - } - - client := autorest.NewClientWithUserAgent("") - useragent.UpdateClient(&client) - client.Sender = c.Sender - setClientInspectors(&client, c.RequestInspector, nil, "azure.autorest") - // Perform the interactive authentication. The user will be prompted to // open a URL and input a device code, after which they will have to // enter their username and password if they are not already // authenticated with Azure. fmt.Fprintln(stderr, "Initiating interactive authentication.") fmt.Fprintln(stderr) - clientId := jujuApplicationId - deviceCode, err := adal.InitiateDeviceAuthWithContext(sdkCtx, &client, *oauthConfig, clientId, params.ResourceManagerResourceId) - if err != nil { - return "", "", "", errors.Annotate(err, "initiating interactive authentication") - } - fmt.Fprintln(stderr, toValue(deviceCode.Message)+"\n") - token, err := adal.WaitForUserCompletionWithContext(sdkCtx, &client, deviceCode) - if err != nil { - return "", "", "", errors.Annotate(err, "waiting for interactive authentication to completed") - } - - // Create service principal tokens that we can use to authorize API - // requests to Active Directory and Resource Manager. These tokens - // are only valid for a short amount of time, so we must create a - // service principal password that can be used to obtain new tokens. - armSpt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, params.ResourceManagerResourceId, *token) - if err != nil { - return "", "", "", errors.Annotate(err, "creating temporary ARM service principal token") - } - armSpt.SetSender(&client) - if err := armSpt.Refresh(); err != nil { - return "", "", "", errors.Trace(err) - } - - // The application requires permissions for both ARM and AD, so we - // can use the token for both APIs. - graphToken := armSpt.Token() - graphToken.Resource = params.GraphResourceId - graphSpt, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, params.GraphResourceId, graphToken) - if err != nil { - return "", "", "", errors.Annotate(err, "creating temporary Graph service principal token") - } - graphSpt.SetSender(&client) - if err := graphSpt.Refresh(); err != nil { - return "", "", "", errors.Trace(err) - } - params.GraphTokenProvider = graphSpt - params.ResourceManagerTokenProvider = armSpt - userObject, err := graphrbac.SignedInUserClient{params.directoryClient(c.Sender, c.RequestInspector)}.Get(sdkCtx) - if err != nil { - return "", "", "", errors.Trace(err) + if params.Credential == nil { + cred, err := azidentity.NewDeviceCodeCredential(&azidentity.DeviceCodeCredentialOptions{ + ClientOptions: azcore.ClientOptions{}, + AdditionallyAllowedTenants: []string{"*"}, + ClientID: jujuApplicationId, + DisableInstanceDiscovery: false, + TenantID: params.TenantId, + }) + if err != nil { + return "", "", "", errors.Trace(err) + } + params.Credential = cred } - fmt.Fprintf(stderr, "Authenticated as %q.\n", toValue(userObject.DisplayName)) return c.Create(sdkCtx, params) } // Create creates a new service principal using the values specified in params. -func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServicePrincipalParams) (appid, spid, password string, _ error) { - servicePrincipalObjectId, password, err := c.createOrUpdateServicePrincipal(sdkCtx, params) +func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServicePrincipalParams) (appid, spid, password string, err error) { + var client *msgraphsdkgo.GraphServiceClient + if c.RequestAdaptor != nil { + client = msgraphsdkgo.NewGraphServiceClient(c.RequestAdaptor) + } else { + client, err = msgraphsdkgo.NewGraphServiceClientWithCredentials(params.Credential, nil) + } + if err != nil { + return "", "", "", errors.Trace(err) + } + servicePrincipalObjectId, password, err := c.createOrUpdateServicePrincipal(sdkCtx, client) if err != nil { return "", "", "", errors.Trace(err) } @@ -228,7 +129,7 @@ func (c *ServicePrincipalCreator) Create(sdkCtx context.Context, params ServiceP return jujuApplicationId, servicePrincipalObjectId, password, nil } -func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context.Context, params ServicePrincipalParams) (servicePrincipalObjectId, password string, _ error) { +func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context.Context, client *msgraphsdkgo.GraphServiceClient) (servicePrincipalObjectId, password string, _ error) { passwordCredential, err := c.preparePasswordCredential() if err != nil { return "", "", errors.Annotate(err, "preparing password credential") @@ -241,29 +142,47 @@ func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context. // service principal; thus, we retry until it exists. The // error checking is based on the logic in azure-cli's // create_service_principal_for_rbac. - client := graphrbac.ServicePrincipalsClient{params.directoryClient(c.Sender, c.RequestInspector)} - var servicePrincipal graphrbac.ServicePrincipal + + addPassword := func(servicePrincipal models.ServicePrincipalable) (string, string, error) { + requestBody := serviceprincipals.NewItemAddPasswordPostRequestBody() + requestBody.SetPasswordCredential(passwordCredential) + + spID := toValue(servicePrincipal.GetId()) + addPassword, err := client.ServicePrincipals().ByServicePrincipalId(spID).AddPassword().Post(context.Background(), requestBody, nil) + if err != nil { + return "", "", errors.Annotate(ReportableError(err), "creating service principal password") + } + return toValue(servicePrincipal.GetId()), toValue(addPassword.GetSecretText()), nil + } + + // The service principal might already exist, so we need to query + // its object ID, and fetch the existing password credentials + // to update. + servicePrincipal, err := client.ServicePrincipalsWithAppId(to.Ptr(jujuApplicationId)).Get(sdkCtx, nil) + if err == nil { + id, password, err := addPassword(servicePrincipal) + if err != nil { + return "", "", errors.Annotate(err, "creating service principal password") + } + return id, password, nil + } + if !isNotFound(err) { + return "", "", errors.Annotate(ReportableError(err), "looking for existing service principal") + } + createServicePrincipal := func() error { - var err error - creds := []graphrbac.PasswordCredential{passwordCredential} - servicePrincipal, err = client.Create( - sdkCtx, - graphrbac.ServicePrincipalCreateParameters{ - AppID: to.Ptr(jujuApplicationId), - AccountEnabled: to.Ptr(true), - PasswordCredentials: &creds, - }, - ) - return err + requestBody := models.NewServicePrincipal() + requestBody.SetAppId(to.Ptr(jujuApplicationId)) + requestBody.SetAccountEnabled(to.Ptr(true)) + servicePrincipal, err = client.ServicePrincipals().Post(sdkCtx, requestBody, nil) + return errors.Annotate(ReportableError(err), "creating service principal") } retryArgs := retry.CallArgs{ Func: createServicePrincipal, IsFatalError: func(err error) bool { - if ge := AsGraphError(err); ge != nil { - if strings.Contains(ge.Message(), " does not reference ") || strings.Contains(ge.Message(), " does not exist ") { - // The application doesn't exist yet, retry later. - return false - } + err = ReportableError(err) + if strings.Contains(err.Error(), " does not reference ") || strings.Contains(err.Error(), " does not exist ") { + return false } return true }, @@ -272,120 +191,72 @@ func (c *ServicePrincipalCreator) createOrUpdateServicePrincipal(sdkCtx context. MaxDuration: time.Minute, } if err := retry.Call(retryArgs); err != nil { - if !isMultipleObjectsWithSameKeyValueErr(err) { - return "", "", errors.Annotate(err, "creating service principal") + if !isAlreadyExists(err) { + return "", "", errors.Trace(err) } // The service principal already exists, so we'll fall out // and update the service principal's password credentials. - } else { - // The service principal was created successfully, with the - // requested password credential. - return toValue(servicePrincipal.ObjectID), toValue(passwordCredential.Value), nil + servicePrincipal, err = client.ServicePrincipalsWithAppId(to.Ptr(jujuApplicationId)).Get(sdkCtx, nil) + if err != nil { + return "", "", errors.Annotate(ReportableError(err), "looking for service principal") + } } - // The service principal already exists, so we need to query - // its object ID, and fetch the existing password credentials - // to update. - servicePrincipal, err = getServicePrincipal(sdkCtx, client, jujuApplicationId) + id, password, err := addPassword(servicePrincipal) if err != nil { - return "", "", errors.Trace(err) + return "", "", errors.Annotate(err, "creating service principal password") } - if err := addServicePrincipalPasswordCredential( - sdkCtx, - client, - toValue(servicePrincipal.ObjectID), - passwordCredential, - ); err != nil { - return "", "", errors.Annotate(err, "updating password credentials") - } - return toValue(servicePrincipal.ObjectID), toValue(passwordCredential.Value), nil -} - -func isMultipleObjectsWithSameKeyValueErr(err error) bool { - if ge := AsGraphError(err); ge != nil { - return ge.Code() == "Request_MultipleObjectsWithSameKeyValue" - } - return false + return id, password, nil } -func (c *ServicePrincipalCreator) preparePasswordCredential() (graphrbac.PasswordCredential, error) { - password, err := c.newUUID() - if err != nil { - return graphrbac.PasswordCredential{}, errors.Annotate(err, "generating password") - } +func (c *ServicePrincipalCreator) preparePasswordCredential() (*models.PasswordCredential, error) { passwordKeyUUID, err := c.newUUID() if err != nil { - return graphrbac.PasswordCredential{}, errors.Annotate(err, "generating password key ID") + return nil, errors.Annotate(err, "generating password key ID") } startDate := c.clock().Now().UTC() endDate := startDate.Add(passwordExpiryDuration) - return graphrbac.PasswordCredential{ - CustomKeyIdentifier: to.Ptr([]byte("juju-" + startDate.Format("20060102"))), - KeyID: to.Ptr(passwordKeyUUID.String()), - Value: to.Ptr(password.String()), - StartDate: &date.Time{startDate}, - EndDate: &date.Time{endDate}, - }, nil -} -func addServicePrincipalPasswordCredential( - sdkCtx context.Context, - client graphrbac.ServicePrincipalsClient, - servicePrincipalObjectId string, - passwordCredential graphrbac.PasswordCredential, -) error { - existing, err := client.ListPasswordCredentials(sdkCtx, servicePrincipalObjectId) - if err != nil { - return errors.Trace(err) - } - var existingValues []graphrbac.PasswordCredential - if existing.Value != nil { - existingValues = *existing.Value - } - passwordCredentials := append(existingValues, passwordCredential) - _, err = client.UpdatePasswordCredentials( - sdkCtx, - servicePrincipalObjectId, - graphrbac.PasswordCredentialsUpdateParameters{&passwordCredentials}, - ) - return errors.Trace(err) -} - -func getServicePrincipal(sdkCtx context.Context, client graphrbac.ServicePrincipalsClient, appID string) (graphrbac.ServicePrincipal, error) { - filter := fmt.Sprintf( - "appId eq '%s'", - appID, - ) - it, err := client.ListComplete(sdkCtx, filter) - if err != nil { - return graphrbac.ServicePrincipal{}, errors.Annotate(err, "listing service principals") - } - for it.NotDone() { - sp := it.Value() - if toValue(sp.AppID) == appID { - return sp, nil - } - if err := it.NextWithContext(sdkCtx); err != nil { - return graphrbac.ServicePrincipal{}, errors.Annotate(err, "listing service principals") - } - } - return graphrbac.ServicePrincipal{}, errors.NotFoundf("service principal") + cred := models.NewPasswordCredential() + cred.SetCustomKeyIdentifier([]byte("juju-" + startDate.Format("20060102"))) + cred.SetKeyId(to.Ptr(passwordKeyUUID)) + cred.SetStartDateTime(&startDate) + cred.SetEndDateTime(&endDate) + return cred, nil } func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, params ServicePrincipalParams, servicePrincipalObjectId string) error { - client := params.authorizationClient(c.Sender, c.RequestInspector) // Find the role definition with the name "Owner". roleScope := path.Join("subscriptions", params.SubscriptionId) - roleDefinitionsClient := authorization.RoleDefinitionsClient{client} - result, err := roleDefinitionsClient.List(sdkCtx, roleScope, "roleName eq 'Owner'") + + clientFactory, err := armauthorization.NewClientFactory(params.SubscriptionId, params.Credential, &arm.ClientOptions{ + ClientOptions: policy.ClientOptions{ + Transport: c.Sender, + }, + }) if err != nil { - return errors.Annotate(err, "listing role definitions") + return errors.Annotate(err, "failed to create auth client") + } + roleDefinitionClient := clientFactory.NewRoleDefinitionsClient() + pager := roleDefinitionClient.NewListPager(roleScope, &armauthorization.RoleDefinitionsClientListOptions{ + Filter: to.Ptr("roleName eq 'Owner'"), + }) + var roleDefinitionId string +done: + for pager.More() { + next, err := pager.NextPage(sdkCtx) + if err != nil { + return errors.Annotate(err, "fetching role definitions") + } + for _, r := range next.Value { + roleDefinitionId = toValue(r.ID) + break done + } } - ownerRoles := result.Values() - if len(ownerRoles) == 0 { + + if roleDefinitionId == "" { return errors.NotFoundf("Owner role definition") } - roleDefinitionId := ownerRoles[0].ID // The UUID value for the role assignment name is unimportant. Azure // will prevent multiple role assignments for the same role definition @@ -394,25 +265,25 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p if err != nil { return errors.Annotate(err, "generating role assignment ID") } - roleAssignmentsClient := authorization.RoleAssignmentsClient{client} roleAssignmentName := roleAssignmentUUID.String() + roleAssignmentClient := clientFactory.NewRoleAssignmentsClient() retryArgs := retry.CallArgs{ Func: func() error { - _, err := roleAssignmentsClient.Create( + _, err := roleAssignmentClient.Create( sdkCtx, roleScope, roleAssignmentName, - authorization.RoleAssignmentCreateParameters{ - Properties: &authorization.RoleAssignmentProperties{ - RoleDefinitionID: roleDefinitionId, + armauthorization.RoleAssignmentCreateParameters{ + Properties: &armauthorization.RoleAssignmentProperties{ + RoleDefinitionID: to.Ptr(roleDefinitionId), PrincipalID: to.Ptr(servicePrincipalObjectId), + PrincipalType: to.Ptr(armauthorization.PrincipalTypeServicePrincipal), }, - }, + }, nil, ) return err }, IsFatalError: func(err error) bool { - serviceErr, ok := serviceError(err) - if ok && strings.Contains(serviceErr.Message, " does not exist in the directory ") { + if strings.Contains(err.Error(), " does not exist in the directory ") { // The service principal doesn't exist yet, retry later. return false } @@ -423,9 +294,10 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p MaxDuration: time.Minute, } if err := retry.Call(retryArgs); err != nil { - if err, ok := serviceError(err); ok { + var respErr *azcore.ResponseError + if errors.As(err, &respErr) { const serviceErrorCodeRoleAssignmentExists = "RoleAssignmentExists" - if err.Code == serviceErrorCodeRoleAssignmentExists { + if respErr.ErrorCode == serviceErrorCodeRoleAssignmentExists { return nil } } @@ -434,6 +306,16 @@ func (c *ServicePrincipalCreator) createRoleAssignment(sdkCtx context.Context, p return nil } +func isAlreadyExists(err error) bool { + dataErr, ok := AsDataError(err) + return ok && dataErr.Code() == "Request_MultipleObjectsWithSameKeyValue" +} + +func isNotFound(err error) bool { + dataErr, ok := AsDataError(err) + return ok && dataErr.Code() == "Request_ResourceNotFound" +} + func (c *ServicePrincipalCreator) clock() clock.Clock { if c.Clock == nil { return clock.WallClock @@ -441,41 +323,17 @@ func (c *ServicePrincipalCreator) clock() clock.Clock { return c.Clock } -func (c *ServicePrincipalCreator) newUUID() (utils.UUID, error) { +func (c *ServicePrincipalCreator) newUUID() (uuid.UUID, error) { if c.NewUUID == nil { - return utils.NewUUID() + u, err := utils.NewUUID() + if err != nil { + return uuid.UUID{}, errors.Trace(err) + } + return uuid.Parse(u.String()) } return c.NewUUID() } -// serviceError returns the *azure.ServiceError underlying the -// supplied error, if any, and a bool indicating whether one -// was found. -func serviceError(err error) (*azure.ServiceError, bool) { - if err == nil { - return nil, false - } - err = errors.Cause(err) - if d, ok := err.(autorest.DetailedError); ok { - err = errors.Cause(d.Original) - } - if se, ok := err.(*azure.ServiceError); ok { - return se, true - } - if r, ok := err.(*azure.RequestError); ok { - return r.ServiceError, true - } - // The error Azure gives us back can also be a struct - // not a pointer. - if se, ok := err.(azure.ServiceError); ok { - return &se, true - } - if r, ok := err.(azure.RequestError); ok { - return r.ServiceError, true - } - return nil, false -} - func toValue[T any](v *T) T { if v == nil { return *new(T) diff --git a/provider/azure/internal/azureauth/serviceprincipal_test.go b/provider/azure/internal/azureauth/serviceprincipal_test.go index ef133e3a045..51b5e7f8464 100644 --- a/provider/azure/internal/azureauth/serviceprincipal_test.go +++ b/provider/azure/internal/azureauth/serviceprincipal_test.go @@ -6,146 +6,109 @@ package azureauth_test import ( "bytes" "context" - "encoding/json" "fmt" - "io/ioutil" "net/http" + "regexp" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/authorization/mgmt/2015-07-01/authorization" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/mocks" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v3" + "github.com/google/uuid" "github.com/juju/clock/testclock" + "github.com/juju/errors" "github.com/juju/testing" jc "github.com/juju/testing/checkers" - "github.com/juju/utils/v3" + abstractions "github.com/microsoft/kiota-abstractions-go" + "github.com/microsoft/kiota-abstractions-go/authentication" + "github.com/microsoft/kiota-abstractions-go/serialization" + "github.com/microsoft/kiota-abstractions-go/store" + nethttplibrary "github.com/microsoft/kiota-http-go" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/provider/azure/internal/azureauth" "github.com/juju/juju/provider/azure/internal/azuretesting" ) -func clockStartTime() time.Time { - t, _ := time.Parse("2006-Jan-02 3:04am", "2016-Sep-19 9:47am") - return t +type requestResult struct { + PathPattern string + Params map[string]string + Result serialization.Parsable + Err error } -type InteractiveSuite struct { - testing.IsolationSuite - clock *testclock.Clock - newUUID func() (utils.UUID, error) -} - -var _ = gc.Suite(&InteractiveSuite{}) - -const fakeTenantId = "11111111-1111-1111-1111-111111111111" +type MockRequestAdaptor struct { + *nethttplibrary.NetHttpRequestAdapter -func deviceCodeSender() autorest.Sender { - return azuretesting.NewSenderWithValue(adal.DeviceCode{ - DeviceCode: to.Ptr("device-code"), - Interval: to.Ptr(int64(1)), // 1 second between polls - Message: to.Ptr("open your browser, etc."), - }) -} - -func tokenSender() autorest.Sender { - return azuretesting.NewSenderWithValue(adal.Token{ - RefreshToken: "refresh-token", - ExpiresOn: json.Number(fmt.Sprint(time.Now().Add(time.Hour).Unix())), - }) + results []requestResult } -func passwordCredentialsListSender() autorest.Sender { - v := []graphrbac.PasswordCredential{{ - KeyID: to.Ptr("password-credential-key-id"), - }} - return azuretesting.NewSenderWithValue(graphrbac.PasswordCredentialListResult{ - Value: &v, - }) -} - -func updatePasswordCredentialsSender() autorest.Sender { - sender := mocks.NewSender() - sender.AppendResponse(mocks.NewResponseWithStatus("", http.StatusNoContent)) - return sender -} - -func currentUserSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.User{ - DisplayName: to.Ptr("Foo Bar"), - }) -} - -func createServicePrincipalSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.ServicePrincipal{ - AppID: to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66"), - ObjectID: to.Ptr("sp-object-id"), - }) -} - -func createServicePrincipalAlreadyExistsSender() autorest.Sender { - sender := mocks.NewSender() - bodyData := `{"odata.error":{"code":"Request_MultipleObjectsWithSameKeyValue"}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) - return sender +func (m *MockRequestAdaptor) Send(ctx context.Context, requestInfo *abstractions.RequestInformation, constructor serialization.ParsableFactory, errorMappings abstractions.ErrorMappings) (serialization.Parsable, error) { + if len(m.results) == 0 { + return nil, errors.Errorf("no results for %q", requestInfo.PathParameters) + } + res := m.results[0] + m.results = m.results[1:] + if res.PathPattern != "" { + matched, err := regexp.MatchString(res.PathPattern, requestInfo.UrlTemplate) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf( + "request path %q did not match pattern %q", + requestInfo.UrlTemplate, res.PathPattern, + ) + } + } + for k, v := range res.Params { + if val := requestInfo.PathParameters[k]; val != v { + return nil, fmt.Errorf( + "request path parameter %q=%q did not match parameter %q", + k, v, val, + ) + } + } + return res.Result, res.Err } -func createServicePrincipalNotExistSender() autorest.Sender { - sender := mocks.NewSender() - bodyData := `{"odata.error":{"code":"Request_ResourceNotFound","message":{"lang":"en","value":"... does not exist in the directory ..."}}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) - return sender +type InteractiveSuite struct { + testing.IsolationSuite + clock testclock.AdvanceableClock + newUUID func() (uuid.UUID, error) } -func createServicePrincipalNotReferenceSender() autorest.Sender { - sender := mocks.NewSender() - // Error message cribbed from https://github.com/kubernetes/kubernetes-anywhere/issues/251 - bodyData := `{"odata.error":{"code":"Request_BadRequest","message":{"lang":"en","value":"The appId of the service principal does not reference a valid application object."}}}` - body := mocks.NewBody(bodyData) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusBadRequest, "")) - return sender -} +var _ = gc.Suite(&InteractiveSuite{}) -func servicePrincipalListSender() autorest.Sender { - return azuretesting.NewSenderWithValue(graphrbac.ServicePrincipalListResult{ - Value: &[]graphrbac.ServicePrincipal{{ - AppID: to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66"), - ObjectID: to.Ptr("sp-object-id"), - }}, - }) -} +const fakeTenantId = "11111111-1111-1111-1111-111111111111" -func roleDefinitionListSender() autorest.Sender { - roleDefinitions := []authorization.RoleDefinition{{ +func roleDefinitionListSender() *azuretesting.MockSender { + roleDefinitions := []*armauthorization.RoleDefinition{{ ID: to.Ptr("owner-role-id"), Name: to.Ptr("Owner"), }} - return azuretesting.NewSenderWithValue(authorization.RoleDefinitionListResult{ - Value: &roleDefinitions, + return azuretesting.NewSenderWithValue(armauthorization.RoleDefinitionListResult{ + Value: roleDefinitions, }) } -func roleAssignmentSender() autorest.Sender { - return azuretesting.NewSenderWithValue(authorization.RoleAssignment{}) +func roleAssignmentSender() *azuretesting.MockSender { + return azuretesting.NewSenderWithValue(armauthorization.RoleAssignment{}) } -func roleAssignmentAlreadyExistsSender() autorest.Sender { - sender := mocks.NewSender() - body := mocks.NewBody(`{"error":{"code":"RoleAssignmentExists", "message":"Odata v4 compliant message"}}`) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) +func roleAssignmentAlreadyExistsSender() *azuretesting.MockSender { + sender := &azuretesting.MockSender{} + body := azuretesting.NewBody(`{"error":{"code":"RoleAssignmentExists", "message":"Odata v4 compliant message"}}`) + sender.AppendResponse(azuretesting.NewResponseWithBodyAndStatus(body, http.StatusConflict, "")) return sender } -func roleAssignmentPrincipalNotExistSender() autorest.Sender { - sender := mocks.NewSender() +func roleAssignmentPrincipalNotExistSender() *azuretesting.MockSender { + sender := &azuretesting.MockSender{} // Based on https://github.com/Azure/azure-powershell/issues/655#issuecomment-186332230 - body := mocks.NewBody(`{"error":{"code":"PrincipalNotFound","message":"Principal foo does not exist in the directory bar"}}`) - sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) + body := azuretesting.NewBody(`{"error":{"code":"PrincipalNotFound","message":"Principal foo does not exist in the directory bar"}}`) + sender.AppendResponse(azuretesting.NewResponseWithBodyAndStatus(body, http.StatusNotFound, "")) return sender } @@ -156,37 +119,47 @@ func (s *InteractiveSuite) SetUpTest(c *gc.C) { "44444444-4444-4444-4444-444444444444", // password key ID "55555555-5555-5555-5555-555555555555", // role assignment ID } - s.newUUID = func() (utils.UUID, error) { - uuid, err := utils.UUIDFromString(uuids[0]) + s.newUUID = func() (res uuid.UUID, err error) { + res, err = uuid.Parse(uuids[0]) if err != nil { - return utils.UUID{}, err + return res, err } uuids = uuids[1:] - return uuid, nil + return res, nil } - s.clock = testclock.NewClock(clockStartTime()) + s.clock = testclock.NewDilatedWallClock(10 * time.Millisecond) } func (s *InteractiveSuite) TestInteractive(c *gc.C) { - var requests []*http.Request + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), // CheckForUserCompletion returns a token. - - // Token.Refresh returns a token. We do this - // twice: once for ARM, and once for AAD. - tokenSender(), - tokenSender(), - - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } var stderr bytes.Buffer @@ -194,13 +167,9 @@ func (s *InteractiveSuite) TestInteractive(c *gc.C) { sdkCtx := context.Background() appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: subscriptionId, - TenantId: fakeTenantId, + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") @@ -209,239 +178,214 @@ func (s *InteractiveSuite) TestInteractive(c *gc.C) { c.Assert(stderr.String(), gc.Equals, ` Initiating interactive authentication. -open your browser, etc. - -Authenticated as "Foo Bar". `[1:]) +} - c.Assert(requests, gc.HasLen, 8) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") - c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - - // The service principal creation includes the password. Check that the - // password returned from the function is the same as the one set in the - // request. - var params graphrbac.ServicePrincipalCreateParameters - err = json.NewDecoder(requests[5].Body).Decode(¶ms) +func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) c.Assert(err, jc.ErrorIsNil) - c.Assert((*params.PasswordCredentials), gc.HasLen, 1) - assertPasswordCredential(c, (*params.PasswordCredentials)[0]) -} -func assertPasswordCredential(c *gc.C, cred graphrbac.PasswordCredential) { - var startDate, endDate time.Time - if cred.StartDate != nil { - startDate = cred.StartDate.Time - } - if cred.EndDate != nil { - endDate = cred.EndDate.Time - } - c.Assert(startDate, gc.Equals, clockStartTime()) - c.Assert(endDate.Sub(startDate), gc.Equals, 365*24*time.Hour) - - cred.StartDate = nil - cred.EndDate = nil - c.Assert(cred, jc.DeepEquals, graphrbac.PasswordCredential{ - CustomKeyIdentifier: to.Ptr([]byte("juju-20160919")), - KeyID: to.Ptr("44444444-4444-4444-4444-444444444444"), - Value: to.Ptr("33333333-3333-3333-3333-333333333333"), - }) -} + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} -func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) { - var requests []*http.Request + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentAlreadyExistsSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, _, _, err := spc.InteractiveCreate(sdkCtx, ioutil.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") + c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") + c.Assert(spObjectId, gc.Equals, "sp-object-id") + c.Assert(stderr.String(), gc.Equals, ` +Initiating interactive authentication. + +`[1:]) +} + +func dataError(code string) error { + result := odataerrors.NewODataError() + mainErr := odataerrors.NewMainError() + mainErr.SetCode(to.Ptr(code)) + bs := store.NewInMemoryBackingStore() + result.SetBackingStore(bs) + result.SetErrorEscaped(mainErr) + return result } -func (s *InteractiveSuite) TestInteractiveServicePrincipalAlreadyExists(c *gc.C) { - var requests []*http.Request +func (s *InteractiveSuite) TestInteractiveServicePrincipalNotFound(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Err: dataError("Request_ResourceNotFound"), + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals"), + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalAlreadyExistsSender(), - servicePrincipalListSender(), - passwordCredentialsListSender(), - updatePasswordCredentialsSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: s.clock, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, ioutil.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, + Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 11) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // list - c.Check(requests[7].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // list - c.Check(requests[8].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // update - c.Check(requests[9].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[10].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - - // Make sure that we don't wipe existing password credentials, and that - // the new password credential matches the one returned from the - // function. - var params graphrbac.PasswordCredentialsUpdateParameters - err = json.NewDecoder(requests[8].Body).Decode(¶ms) - c.Assert(err, jc.ErrorIsNil) - c.Assert((*params.Value), gc.HasLen, 2) - c.Assert((*params.Value)[0], jc.DeepEquals, graphrbac.PasswordCredential{ - KeyID: to.Ptr("password-credential-key-id"), - }) - assertPasswordCredential(c, (*params.Value)[1]) } -func (s *InteractiveSuite) TestInteractiveServicePrincipalApplicationNotExist(c *gc.C) { - s.testInteractiveRetriesCreateServicePrincipal(c, createServicePrincipalNotExistSender()) -} +func (s *InteractiveSuite) TestInteractiveServicePrincipalNotFoundRace(c *gc.C) { + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) -func (s *InteractiveSuite) TestInteractiveServicePrincipalApplicationNotReference(c *gc.C) { - s.testInteractiveRetriesCreateServicePrincipal(c, createServicePrincipalNotReferenceSender()) -} + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Err: dataError("Request_ResourceNotFound"), + }, { + Err: dataError("Request_MultipleObjectsWithSameKeyValue"), + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} -func (s *InteractiveSuite) testInteractiveRetriesCreateServicePrincipal(c *gc.C, errorSender autorest.Sender) { - var requests []*http.Request + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - errorSender, - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentAlreadyExistsSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: &testclock.AutoAdvancingClock{ - Clock: s.clock, - Advance: s.clock.Advance, - }, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, ioutil.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 9) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") } func (s *InteractiveSuite) TestInteractiveRetriesRoleAssignment(c *gc.C) { - var requests []*http.Request + ra, err := nethttplibrary.NewNetHttpRequestAdapter(&authentication.AnonymousAuthenticationProvider{}) + c.Assert(err, jc.ErrorIsNil) + + sp := models.NewServicePrincipal() + sp.SetAppId(to.Ptr("60a04dc9-1857-425f-8076-5ba81ca53d66")) + sp.SetId(to.Ptr("sp-object-id")) + cred := models.NewPasswordCredential() + cred.SetSecretText(to.Ptr("33333333-3333-3333-3333-333333333333")) + + mockAdaptor := &MockRequestAdaptor{NetHttpRequestAdapter: ra} + mockAdaptor.results = []requestResult{{ + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals(appId='{appId}')") + ".*", + Params: map[string]string{"appId": "60a04dc9-1857-425f-8076-5ba81ca53d66"}, + Result: sp, + }, { + PathPattern: regexp.QuoteMeta("{+baseurl}/servicePrincipals/{servicePrincipal%2Did}/addPassword"), + Params: map[string]string{"servicePrincipal%2Did": "sp-object-id"}, + Result: cred, + }} + + authSenders := &azuretesting.Senders{ + roleDefinitionListSender(), + roleAssignmentPrincipalNotExistSender(), + roleAssignmentSender(), + } spc := azureauth.ServicePrincipalCreator{ - Sender: &azuretesting.Senders{ - deviceCodeSender(), - tokenSender(), - tokenSender(), - tokenSender(), - currentUserSender(), - createServicePrincipalSender(), - roleDefinitionListSender(), - roleAssignmentPrincipalNotExistSender(), - roleAssignmentSender(), - }, - RequestInspector: azuretesting.RequestRecorder(&requests), - Clock: &testclock.AutoAdvancingClock{ - Clock: s.clock, - Advance: s.clock.Advance, - }, - NewUUID: s.newUUID, + Sender: authSenders, + RequestAdaptor: mockAdaptor, + Clock: s.clock, + NewUUID: s.newUUID, } + + var stderr bytes.Buffer + subscriptionId := "22222222-2222-2222-2222-222222222222" sdkCtx := context.Background() - _, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, ioutil.Discard, azureauth.ServicePrincipalParams{ - GraphEndpoint: "https://graph.invalid", - GraphResourceId: "https://graph.invalid", - ResourceManagerEndpoint: "https://arm.invalid", - ResourceManagerResourceId: "https://management.core.invalid/", - AuthEndpoint: "https://testing.invalid", - SubscriptionId: "22222222-2222-2222-2222-222222222222", - TenantId: fakeTenantId, + appId, spObjectId, password, err := spc.InteractiveCreate(sdkCtx, &stderr, azureauth.ServicePrincipalParams{ + SubscriptionId: subscriptionId, + TenantId: fakeTenantId, Credential: &azuretesting.FakeCredential{}, }) c.Assert(err, jc.ErrorIsNil) + c.Assert(appId, gc.Equals, "60a04dc9-1857-425f-8076-5ba81ca53d66") c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333") c.Assert(spObjectId, gc.Equals, "sp-object-id") - - c.Assert(requests, gc.HasLen, 9) - c.Check(requests[0].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode") - c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token") - c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me") - c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create - c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions") - c.Check(requests[7].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") - c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555") } diff --git a/provider/azure/internal/azureauth/utils.go b/provider/azure/internal/azureauth/utils.go index dc165391693..b154688df4f 100644 --- a/provider/azure/internal/azureauth/utils.go +++ b/provider/azure/internal/azureauth/utils.go @@ -4,100 +4,31 @@ package azureauth import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "strings" - - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/azure" "github.com/juju/errors" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" ) -// ResourceManagerResourceId returns the resource ID for the -// Azure Resource Manager application to use in auth requests, -// based on the given core endpoint URI (e.g. https://core.windows.net). -// -// The core endpoint URI is the same as given in "storage-endpoint" -// in Azure cloud definitions, which serves as the suffix for blob -// storage URLs. -func ResourceManagerResourceId(coreEndpointURI string) (string, error) { - u, err := url.Parse(coreEndpointURI) - if err != nil { - return "", err - } - u.Host = "management." + u.Host - return TokenResource(u.String()), nil -} - -// TokenResource returns a resource value suitable for auth tokens, based on -// an endpoint URI. -func TokenResource(uri string) string { - resource := uri - if !strings.HasSuffix(resource, "/") { - resource += "/" - } - return resource +// DataError is a go error that wraps the odataerrors.ODataError response type. +type DataError struct { + *odataerrors.ODataError } -// CheckForGraphError attempts to unmarshal the body into a GraphError. -// If this succeeds then the GraphError is returned as an error, -// otherwise the response is passed on to the next Responder. -func CheckForGraphError(r autorest.Responder) autorest.Responder { - return autorest.ResponderFunc(func(resp *http.Response) error { - err, _ := maybeGraphError(resp) - if err != nil { - return errors.Trace(err) - } - return r.Respond(resp) - }) +// Code returns the code from the wrapped DataError. +func (e *DataError) Code() string { + return *e.ODataError.GetErrorEscaped().GetCode() } -func maybeGraphError(resp *http.Response) (error, bool) { - if resp.Body != nil { - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errors.Trace(err), false - } - resp.Body = ioutil.NopCloser(bytes.NewReader(b)) - - // Remove any UTF-8 BOM, if present. - b = bytes.TrimPrefix(b, []byte("\ufeff")) - var ge graphrbac.GraphError - if err := json.Unmarshal(b, &ge); err == nil { - if ge.OdataError != nil && ge.Code != nil { - return &GraphError{ge}, true - } - } +// Message returns the message from the wrapped DataError. +func (e *DataError) Message() string { + msg := e.ODataError.GetErrorEscaped().GetMessage() + if msg != nil { + return *msg } - return nil, false -} - -// GraphError is a go error that wraps the graphrbac.GraphError response -// type, which doesn't implement the error interface. -type GraphError struct { - graphrbac.GraphError -} - -// Code returns the code from the GraphError. -func (e *GraphError) Code() string { - return *e.GraphError.Code -} - -// Message returns the message from the GraphError. -func (e *GraphError) Message() string { - if e.GraphError.OdataError == nil || e.GraphError.ErrorMessage == nil || e.GraphError.Message == nil { - return "" - } - return *e.GraphError.Message + return e.ODataError.Message } // Error implements the error interface. -func (e *GraphError) Error() string { +func (e *DataError) Error() string { s := e.Code() if m := e.Message(); m != "" { s += ": " + m @@ -105,21 +36,31 @@ func (e *GraphError) Error() string { return s } -// AsGraphError returns a GraphError if one is contained within the given -// error, otherwise it returns nil. -func AsGraphError(err error) *GraphError { - err = errors.Cause(err) - if de, ok := err.(autorest.DetailedError); ok { - err = de.Original +// AsDataError returns a wrapped error that exposes the +// underlying error code and message (if possible). +func AsDataError(err error) (*DataError, bool) { + if err == nil { + return nil, false } - if ge, _ := err.(*GraphError); ge != nil { - return ge + var dataErr *DataError + if errors.As(err, &dataErr) { + return dataErr, true } - if de, ok := err.(*azure.RequestError); ok { - ge, ok := maybeGraphError(de.Response) - if ok { - return ge.(*GraphError) - } + + var apiErr *odataerrors.ODataError + if !errors.As(err, &apiErr) { + return nil, false + } + return &DataError{apiErr}, true +} + +// ReportableError returns a wrapped error that exposes the +// underlying error code and message (if possible), or just +// the passed in error. +func ReportableError(err error) error { + var apiErr *odataerrors.ODataError + if !errors.As(err, &apiErr) { + return err } - return nil + return &DataError{apiErr} } diff --git a/provider/azure/internal/azureauth/utils_test.go b/provider/azure/internal/azureauth/utils_test.go index cb30799dc69..8b1be322ab3 100644 --- a/provider/azure/internal/azureauth/utils_test.go +++ b/provider/azure/internal/azureauth/utils_test.go @@ -4,155 +4,39 @@ package azureauth_test import ( - "io/ioutil" - "net/http" - "strings" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - "github.com/juju/errors" "github.com/juju/testing" jc "github.com/juju/testing/checkers" + "github.com/microsoft/kiota-abstractions-go/store" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/provider/azure/internal/azureauth" ) -type TokenResourceSuite struct { - testing.IsolationSuite -} - -var _ = gc.Suite(&TokenResourceSuite{}) - -func (s *TokenResourceSuite) TestTokenResource(c *gc.C) { - out := azureauth.TokenResource("https://graph.windows.net") - c.Assert(out, gc.Equals, "https://graph.windows.net/") - out = azureauth.TokenResource("https://graph.windows.net/") - c.Assert(out, gc.Equals, "https://graph.windows.net/") -} - -var checkForGraphErrorTests = []struct { - about string - responseBody string - expectError string -}{{ - about: "error body", - responseBody: `{"odata.error":{"code":"ErrorCode","message":{"value": "error message"}}}`, - expectError: "ErrorCode: error message", -}, { - about: "not error", - responseBody: `{}`, - expectError: "", -}, { - about: "error body with unicode BOM", - responseBody: "\ufeff" + `{"odata.error":{"code":"ErrorCode","message":{"value": "error message"}}}`, - expectError: "ErrorCode: error message", -}, { - about: "not error with unicode BOM", - responseBody: "\ufeff{}", - expectError: "", -}} - type ErrorSuite struct { testing.IsolationSuite } var _ = gc.Suite(&ErrorSuite{}) -func (ErrorSuite) TestCheckForGraphError(c *gc.C) { - for i, test := range checkForGraphErrorTests { - c.Logf("test %d. %s", i, test.about) - - var nextResponderCalled bool - var r autorest.Responder - r = autorest.ResponderFunc(func(resp *http.Response) error { - nextResponderCalled = true - - // If the next in the chain is called then the response body should be unchanged. - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - c.Assert(err, jc.ErrorIsNil) - c.Check(string(b), gc.Equals, test.responseBody) - return nil - }) - - r = azureauth.CheckForGraphError(r) - - err := r.Respond(&http.Response{Body: ioutil.NopCloser(strings.NewReader(test.responseBody))}) - if test.expectError == "" { - c.Check(nextResponderCalled, gc.Equals, true) - continue - } - c.Assert(err, gc.Not(jc.ErrorIsNil)) - ge := azureauth.AsGraphError(err) - c.Check(ge, gc.Not(gc.IsNil)) - c.Check(err.Error(), gc.Equals, test.expectError) - } -} - -var asGraphErrorTests = []struct { - about string - err error - expectGraphError string -}{{ - about: "graph error", - err: &azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }, - expectGraphError: "ErrorCode: error message", -}, { - about: "nil error", - err: nil, -}, { - about: "unrelated error", - err: errors.New("test error"), -}, { - about: "in autorest.DetailedError", - err: autorest.DetailedError{ - Original: &azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }, - }, - expectGraphError: "ErrorCode: error message", -}, { - about: "traced graph error", - err: errors.Trace(&azureauth.GraphError{ - GraphError: graphrbac.GraphError{ - OdataError: &graphrbac.OdataError{ - Code: to.Ptr("ErrorCode"), - ErrorMessage: &graphrbac.ErrorMessage{ - Message: to.Ptr("error message"), - }, - }, - }, - }), - expectGraphError: "ErrorCode: error message", -}} - -func (ErrorSuite) TestAsGraphError(c *gc.C) { - for i, test := range asGraphErrorTests { - c.Logf("test %d. %s", i, test.about) - ge := azureauth.AsGraphError(test.err) - if test.expectGraphError == "" { - c.Check(ge, gc.IsNil) - continue - } - c.Assert(ge, gc.Not(gc.IsNil)) - c.Check(ge.Error(), gc.Equals, test.expectGraphError) - } +func (ErrorSuite) TestAsDataError(c *gc.C) { + dataErr := odataerrors.NewODataError() + dataErr.SetBackingStore(store.NewInMemoryBackingStore()) + me := odataerrors.NewMainError() + me.SetCode(to.Ptr("code")) + me.SetMessage(to.Ptr("message")) + dataErr.SetErrorEscaped(me) + + de, ok := azureauth.AsDataError(dataErr) + c.Assert(ok, jc.IsTrue) + c.Assert(de.Error(), gc.Equals, "code: message") + + _, ok = azureauth.AsDataError(nil) + c.Assert(ok, jc.IsFalse) + + azDataErr := &azureauth.DataError{} + de, ok = azureauth.AsDataError(azDataErr) + c.Assert(ok, jc.IsTrue) + c.Assert(de, jc.DeepEquals, azDataErr) } diff --git a/provider/azure/internal/azurecli/az.go b/provider/azure/internal/azurecli/az.go index 144c47f544f..39bfcb5e40d 100644 --- a/provider/azure/internal/azurecli/az.go +++ b/provider/azure/internal/azurecli/az.go @@ -10,7 +10,6 @@ import ( "os/exec" "strings" - "github.com/Azure/go-autorest/autorest/adal" "github.com/juju/errors" "github.com/juju/loggo" "github.com/kr/pretty" @@ -80,44 +79,6 @@ func (a AzureCLI) run(v interface{}, args ...string) error { return nil } -// AccessToken contains the result of the GetAccessToken function. -type AccessToken struct { - AccessToken string `json:"accessToken"` - ExpiresOn string `json:"expiresOn"` - Subscription string `json:"subscription"` - Tenant string `json:"tenant"` - TokenType string `json:"tokenType"` -} - -// Token creates an adal.Token from the AccessToken. This token can be -// used with go-autorest to access azure endpoints. -func (t AccessToken) Token() *adal.Token { - return &adal.Token{ - AccessToken: t.AccessToken, - Type: t.TokenType, - } -} - -// GetAccessToken gets an access token from the Azure CLI to access the -// given resource using the given subscription. Either subscription or -// resource may be empty in which case the default from the az -// application are used. -func (a AzureCLI) GetAccessToken(tenant, resource string) (*AccessToken, error) { - logger.Debugf("getting access token for tenant %q", tenant) - cmd := []string{"account", "get-access-token"} - if tenant != "" { - cmd = append(cmd, "--tenant", tenant) - } - if resource != "" { - cmd = append(cmd, "--resource", resource) - } - var tok AccessToken - if err := a.run(&tok, cmd...); err != nil { - return nil, errors.Trace(err) - } - return &tok, nil -} - // Account contains details of an azure account (subscription). type Account struct { CloudName string `json:"cloudName"` @@ -189,31 +150,9 @@ func (a AzureCLI) FindAccountsWithCloudName(name string) ([]Account, error) { // Cloud contains details of a cloud configured in the Azure CLI. type Cloud struct { - Endpoints CloudEndpoints `json:"endpoints"` - IsActive bool `json:"isActive"` - Name string `json:"name"` - Profile string `json:"profile"` - Suffixes CloudSuffixes `json:"suffixes"` -} - -// CloudEndpoints contains the endpoints used by a cloud. -type CloudEndpoints struct { - ActiveDirectory string `json:"activeDirectory"` - ActiveDirectoryGraphResourceID string `json:"activeDirectoryGraphResourceId"` - ActiveDirectoryResourceID string `json:"activeDirectoryResourceId"` - BatchResourceID string `json:"batchResourceId"` - Management string `json:"management"` - ResourceManager string `json:"resourceManager"` - SQLManagement string `json:"sqlManagement"` -} - -// CloudSuffixes contains the suffixes used with a cloud. -type CloudSuffixes struct { - AzureDatalakeAnalyticsCatalogAndJobEndpoint string `json:"azureDatalakeAnalyticsCatalogAndJobEndpoint"` - AzureDatalakeStoreFileSystemEndpoint string `json:"azureDatalakeStoreFileSystemEndpoint"` - KeyvaultDNS string `json:"keyvaultDns"` - SQLServerHostname string `json:"sqlServerHostname"` - StorageEndpoint string `json:"storageEndpoint"` + IsActive bool `json:"isActive"` + Name string `json:"name"` + Profile string `json:"profile"` } // ShowCloud returns the details of the cloud with the given name. If the @@ -230,22 +169,6 @@ func (a AzureCLI) ShowCloud(name string) (*Cloud, error) { return &cloud, nil } -// FindCloudsWithResourceManagerEndpoint returns a list of clouds which -// use the given url for it's resource manager endpoint. -func (a AzureCLI) FindCloudsWithResourceManagerEndpoint(url string) ([]Cloud, error) { - var clouds []Cloud - cmd := []string{ - "cloud", - "list", - "--query", - fmt.Sprintf("[?endpoints.resourceManager=='%s']", url), - } - if err := a.run(&clouds, cmd...); err != nil { - return nil, err - } - return clouds, nil -} - // ListClouds returns the details for all clouds available in the Azure // CLI. func (a AzureCLI) ListClouds() ([]Cloud, error) { diff --git a/provider/azure/internal/azurecli/az_test.go b/provider/azure/internal/azurecli/az_test.go index 8f058113317..e6ca9458a22 100644 --- a/provider/azure/internal/azurecli/az_test.go +++ b/provider/azure/internal/azurecli/az_test.go @@ -7,7 +7,6 @@ import ( "os/exec" "strings" - "github.com/Azure/go-autorest/autorest/adal" "github.com/juju/errors" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -19,109 +18,6 @@ type azSuite struct{} var _ = gc.Suite(&azSuite{}) -func (s *azSuite) TestGetAccessToken(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - stdout: []byte(` -{ - "accessToken": "ACCESSTOKEN", - "expiresOn": "2017-06-07 09:27:58.063743", - "subscription": "5544b9a5-0000-0000-0000-fedceb5d3696", - "tenant": "a52afd7f-0000-0000-0000-e47a54b982da", - "tokenType": "Bearer" -} -`[1:]), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, jc.ErrorIsNil) - c.Assert(tok, jc.DeepEquals, &azurecli.AccessToken{ - AccessToken: "ACCESSTOKEN", - ExpiresOn: "2017-06-07 09:27:58.063743", - Subscription: "5544b9a5-0000-0000-0000-fedceb5d3696", - Tenant: "a52afd7f-0000-0000-0000-e47a54b982da", - TokenType: "Bearer", - }) -} - -func (s *azSuite) TestGetAccessTokenWithTenantAndResource(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token --tenant 5544b9a5-0000-0000-0000-fedceb5d3696 --resource resid -o json": { - stdout: []byte(` -{ - "accessToken": "ACCESSTOKEN", - "expiresOn": "2017-06-07 09:27:58.063743", - "subscription": "5544b9a5-0000-0000-0000-fedceb5d3696", - "tenant": "a52afd7f-0000-0000-0000-e47a54b982da", - "tokenType": "Bearer" -} -`[1:]), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("5544b9a5-0000-0000-0000-fedceb5d3696", "resid") - c.Assert(err, jc.ErrorIsNil) - c.Assert(tok, jc.DeepEquals, &azurecli.AccessToken{ - AccessToken: "ACCESSTOKEN", - ExpiresOn: "2017-06-07 09:27:58.063743", - Subscription: "5544b9a5-0000-0000-0000-fedceb5d3696", - Tenant: "a52afd7f-0000-0000-0000-e47a54b982da", - TokenType: "Bearer", - }) -} - -func (s *azSuite) TestGetAccessTokenError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - error: &exec.ExitError{Stderr: []byte("test error")}, - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, gc.ErrorMatches, `execution failure: test error`) - c.Assert(tok, gc.IsNil) -} - -func (s *azSuite) TestGetAccessTokenJSONError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az account get-access-token -o json": { - stdout: []byte(`}`), - }, - }, - }.Exec, - } - tok, err := azcli.GetAccessToken("", "") - c.Assert(err, gc.ErrorMatches, `cannot unmarshal output: invalid character '}' looking for beginning of value`) - c.Assert(tok, gc.IsNil) -} - -func (s *azSuite) TestAzureTokenFromAccessToken(c *gc.C) { - tok := azurecli.AccessToken{ - AccessToken: "0123456789", - ExpiresOn: "2017-06-05 10:20:43.752534", - Subscription: "00000000-0000-0000-0000-00000001", - Tenant: "00000000-0000-0000-0000-00000002", - TokenType: "Bearer", - } - tok1 := tok.Token() - c.Assert(tok1, jc.DeepEquals, &adal.Token{ - AccessToken: "0123456789", - Type: "Bearer", - }) -} - func (s *azSuite) TestShowAccount(c *gc.C) { azcli := azurecli.AzureCLI{ Exec: testExecutor{ @@ -362,26 +258,9 @@ func (s *azSuite) TestShowCloud(c *gc.C) { "az cloud show -o json": { stdout: []byte(` { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, "isActive": true, "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } + "profile": "latest" } `[1:]), }, @@ -391,25 +270,9 @@ func (s *azSuite) TestShowCloud(c *gc.C) { cloud, err := azcli.ShowCloud("") c.Assert(err, jc.ErrorIsNil) c.Assert(cloud, jc.DeepEquals, &azurecli.Cloud{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, IsActive: true, Name: "AzureCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, }) } @@ -420,26 +283,9 @@ func (s *azSuite) TestShowCloudWithName(c *gc.C) { "az cloud show --name AzureUSGovernment -o json": { stdout: []byte(` { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.usgovcloudapi.net/", - "batchResourceId": "https://batch.core.usgovcloudapi.net/", - "gallery": "https://gallery.usgovcloudapi.net/", - "management": "https://management.core.usgovcloudapi.net/", - "resourceManager": "https://management.usgovcloudapi.net/", - "sqlManagement": "https://management.core.usgovcloudapi.net:8443/" - }, "isActive": false, "name": "AzureUSGovernment", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.usgovcloudapi.net", - "sqlServerHostname": ".database.usgovcloudapi.net", - "storageEndpoint": "core.usgovcloudapi.net" - } + "profile": "latest" } `[1:]), }, @@ -449,23 +295,9 @@ func (s *azSuite) TestShowCloudWithName(c *gc.C) { cloud, err := azcli.ShowCloud("AzureUSGovernment") c.Assert(err, jc.ErrorIsNil) c.Assert(cloud, jc.DeepEquals, &azurecli.Cloud{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.usgovcloudapi.net/", - BatchResourceID: "https://batch.core.usgovcloudapi.net/", - Management: "https://management.core.usgovcloudapi.net/", - ResourceManager: "https://management.usgovcloudapi.net/", - SQLManagement: "https://management.core.usgovcloudapi.net:8443/", - }, IsActive: false, Name: "AzureUSGovernment", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.usgovcloudapi.net", - SQLServerHostname: ".database.usgovcloudapi.net", - StorageEndpoint: "core.usgovcloudapi.net", - }, }) } @@ -484,81 +316,6 @@ func (s *azSuite) TestShowCloudError(c *gc.C) { c.Assert(cloud, gc.IsNil) } -func (s *azSuite) TestFindCloudsWithResourceManagerEndpoint(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az cloud list --query [?endpoints.resourceManager=='https://management.azure.com/'] -o json": { - stdout: []byte(` -[ - { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, - "isActive": true, - "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } - } -] -`[1:]), - }, - }, - }.Exec, - } - cloud, err := azcli.FindCloudsWithResourceManagerEndpoint("https://management.azure.com/") - c.Assert(err, jc.ErrorIsNil) - c.Assert(cloud, jc.DeepEquals, []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, - IsActive: true, - Name: "AzureCloud", - Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, - }}) -} - -func (s *azSuite) TestFindCloudsWithResourceManagerEndpointError(c *gc.C) { - azcli := azurecli.AzureCLI{ - Exec: testExecutor{ - commands: map[string]result{ - "az cloud list --query [?endpoints.resourceManager=='https://management.azure.com/'] -o json": { - error: errors.New("test error"), - }, - }, - }.Exec, - } - cloud, err := azcli.FindCloudsWithResourceManagerEndpoint("https://management.azure.com/") - c.Assert(err, gc.ErrorMatches, `execution failure: test error`) - c.Assert(cloud, gc.IsNil) -} - func (s *azSuite) TestListClouds(c *gc.C) { azcli := azurecli.AzureCLI{ Exec: testExecutor{ @@ -567,92 +324,24 @@ func (s *azSuite) TestListClouds(c *gc.C) { stdout: []byte(` [ { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.windows.net/", - "batchResourceId": "https://batch.core.windows.net/", - "gallery": "https://gallery.azure.com/", - "management": "https://management.core.windows.net/", - "resourceManager": "https://management.azure.com/", - "sqlManagement": "https://management.core.windows.net:8443/" - }, "isActive": true, "name": "AzureCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": "azuredatalakeanalytics.net", - "azureDatalakeStoreFileSystemEndpoint": "azuredatalakestore.net", - "keyvaultDns": ".vault.azure.net", - "sqlServerHostname": ".database.windows.net", - "storageEndpoint": "core.windows.net" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.chinacloudapi.cn", - "activeDirectoryGraphResourceId": "https://graph.chinacloudapi.cn/", - "activeDirectoryResourceId": "https://management.core.chinacloudapi.cn/", - "batchResourceId": "https://batch.chinacloudapi.cn/", - "gallery": "https://gallery.chinacloudapi.cn/", - "management": "https://management.core.chinacloudapi.cn/", - "resourceManager": "https://management.chinacloudapi.cn", - "sqlManagement": "https://management.core.chinacloudapi.cn:8443/" - }, "isActive": false, "name": "AzureChinaCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.azure.cn", - "sqlServerHostname": ".database.chinacloudapi.cn", - "storageEndpoint": "core.chinacloudapi.cn" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.com", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "activeDirectoryResourceId": "https://management.core.usgovcloudapi.net/", - "batchResourceId": "https://batch.core.usgovcloudapi.net/", - "gallery": "https://gallery.usgovcloudapi.net/", - "management": "https://management.core.usgovcloudapi.net/", - "resourceManager": "https://management.usgovcloudapi.net/", - "sqlManagement": "https://management.core.usgovcloudapi.net:8443/" - }, "isActive": false, "name": "AzureUSGovernment", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.usgovcloudapi.net", - "sqlServerHostname": ".database.usgovcloudapi.net", - "storageEndpoint": "core.usgovcloudapi.net" - } + "profile": "latest" }, { - "endpoints": { - "activeDirectory": "https://login.microsoftonline.de", - "activeDirectoryGraphResourceId": "https://graph.cloudapi.de/", - "activeDirectoryResourceId": "https://management.core.cloudapi.de/", - "batchResourceId": "https://batch.cloudapi.de/", - "gallery": "https://gallery.cloudapi.de/", - "management": "https://management.core.cloudapi.de/", - "resourceManager": "https://management.microsoftazure.de", - "sqlManagement": "https://management.core.cloudapi.de:8443/" - }, "isActive": false, "name": "AzureGermanCloud", - "profile": "latest", - "suffixes": { - "azureDatalakeAnalyticsCatalogAndJobEndpoint": null, - "azureDatalakeStoreFileSystemEndpoint": null, - "keyvaultDns": ".vault.microsoftazure.de", - "sqlServerHostname": ".database.cloudapi.de", - "storageEndpoint": "core.cloudapi.de" - } + "profile": "latest" } ] @@ -664,79 +353,21 @@ func (s *azSuite) TestListClouds(c *gc.C) { clouds, err := azcli.ListClouds() c.Assert(err, jc.ErrorIsNil) c.Assert(clouds, jc.DeepEquals, []azurecli.Cloud{{ - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.windows.net/", - BatchResourceID: "https://batch.core.windows.net/", - Management: "https://management.core.windows.net/", - ResourceManager: "https://management.azure.com/", - SQLManagement: "https://management.core.windows.net:8443/", - }, IsActive: true, Name: "AzureCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - AzureDatalakeAnalyticsCatalogAndJobEndpoint: "azuredatalakeanalytics.net", - AzureDatalakeStoreFileSystemEndpoint: "azuredatalakestore.net", - KeyvaultDNS: ".vault.azure.net", - SQLServerHostname: ".database.windows.net", - StorageEndpoint: "core.windows.net", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.chinacloudapi.cn", - ActiveDirectoryGraphResourceID: "https://graph.chinacloudapi.cn/", - ActiveDirectoryResourceID: "https://management.core.chinacloudapi.cn/", - BatchResourceID: "https://batch.chinacloudapi.cn/", - Management: "https://management.core.chinacloudapi.cn/", - ResourceManager: "https://management.chinacloudapi.cn", - SQLManagement: "https://management.core.chinacloudapi.cn:8443/", - }, IsActive: false, Name: "AzureChinaCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.azure.cn", - SQLServerHostname: ".database.chinacloudapi.cn", - StorageEndpoint: "core.chinacloudapi.cn", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.com", - ActiveDirectoryGraphResourceID: "https://graph.windows.net/", - ActiveDirectoryResourceID: "https://management.core.usgovcloudapi.net/", - BatchResourceID: "https://batch.core.usgovcloudapi.net/", - Management: "https://management.core.usgovcloudapi.net/", - ResourceManager: "https://management.usgovcloudapi.net/", - SQLManagement: "https://management.core.usgovcloudapi.net:8443/", - }, IsActive: false, Name: "AzureUSGovernment", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.usgovcloudapi.net", - SQLServerHostname: ".database.usgovcloudapi.net", - StorageEndpoint: "core.usgovcloudapi.net", - }, }, { - Endpoints: azurecli.CloudEndpoints{ - ActiveDirectory: "https://login.microsoftonline.de", - ActiveDirectoryGraphResourceID: "https://graph.cloudapi.de/", - ActiveDirectoryResourceID: "https://management.core.cloudapi.de/", - BatchResourceID: "https://batch.cloudapi.de/", - Management: "https://management.core.cloudapi.de/", - ResourceManager: "https://management.microsoftazure.de", - SQLManagement: "https://management.core.cloudapi.de:8443/", - }, IsActive: false, Name: "AzureGermanCloud", Profile: "latest", - Suffixes: azurecli.CloudSuffixes{ - KeyvaultDNS: ".vault.microsoftazure.de", - SQLServerHostname: ".database.cloudapi.de", - StorageEndpoint: "core.cloudapi.de", - }, }}) } diff --git a/provider/azure/internal/azurestorage/interface.go b/provider/azure/internal/azurestorage/interface.go deleted file mode 100644 index dca8e2e5526..00000000000 --- a/provider/azure/internal/azurestorage/interface.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azurestorage - -import ( - "github.com/Azure/azure-sdk-for-go/storage" - "github.com/juju/errors" -) - -// Client is an interface providing access to Azure storage services. -type Client interface { - // GetBlobService returns a BlobStorageClient which can operate - // on the blob service of the storage account. - GetBlobService() BlobStorageClient -} - -// BlobStorageClient is an interface providing access to Azure blob storage. -// -// This interface the subet of functionality provided by -// https://godoc.org/github.com/Azure/azure-sdk-for-go/storage#BlobStorageClient -// that is required by Juju. -type BlobStorageClient interface { - // GetContainerReference returns a Container object for the specified container name. - GetContainerReference(name string) Container -} - -// Container provides access to an Azure storage container. -type Container interface { - // Blobs returns the blobs in the container. - // - // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/List-Blobs - Blobs() ([]Blob, error) - - // Blob returns a Blob object for the specified blob name. - Blob(name string) Blob -} - -// Blob provides access to an Azure storage blob. -type Blob interface { - // Name returns the name of the blob. - Name() string - - // Properties returns the properties of the blob. - Properties() storage.BlobProperties - - // DeleteIfExists deletes the given blob from the specified container If the - // blob is deleted with this call, returns true. Otherwise returns false. - // - // See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Delete-Blob - DeleteIfExists(*storage.DeleteBlobOptions) (bool, error) -} - -// NewClientFunc is the type of the NewClient function. -type NewClientFunc func( - accountName, accountKey, blobServiceBaseURL, apiVersion string, - useHTTPS bool, -) (Client, error) - -// NewClient returns a Client that is backed by a storage.Client created with -// storage.NewClient -func NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion string, useHTTPS bool) (Client, error) { - client, err := storage.NewClient(accountName, accountKey, blobServiceBaseURL, apiVersion, useHTTPS) - if err != nil { - return nil, errors.Trace(err) - } - return clientWrapper{client}, nil -} - -type clientWrapper struct { - storage.Client -} - -// GetBlobService is part of the Client interface. -func (w clientWrapper) GetBlobService() BlobStorageClient { - return &blobStorageClient{w.Client.GetBlobService()} -} - -type blobStorageClient struct { - storage.BlobStorageClient -} - -// GetContainerReference is part of the BlobStorageClient interface. -func (c *blobStorageClient) GetContainerReference(name string) Container { - return container{c.BlobStorageClient.GetContainerReference(name)} -} - -type container struct { - *storage.Container -} - -// Blobs is part of the Container interface. -func (c container) Blobs() ([]Blob, error) { - //TODO(axw) handle pagination. - resp, err := c.Container.ListBlobs(storage.ListBlobsParameters{}) - if err != nil { - return nil, errors.Trace(err) - } - blobs := make([]Blob, len(resp.Blobs)) - for i := range blobs { - blobs[i] = blob{&resp.Blobs[i]} - } - return blobs, nil -} - -// Blob is part of the Container interface. -func (c container) Blob(name string) Blob { - return blob{c.Container.GetBlobReference(name)} -} - -type blob struct { - *storage.Blob -} - -// Name is part of the Blob interface. -func (b blob) Name() string { - return b.Blob.Name -} - -// Properties is part of the Blob interface. -func (b blob) Properties() storage.BlobProperties { - return b.Blob.Properties -} diff --git a/provider/azure/internal/azuretesting/json.go b/provider/azure/internal/azuretesting/json.go deleted file mode 100644 index 86a1ebb789c..00000000000 --- a/provider/azure/internal/azuretesting/json.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2020 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azuretesting - -import ( - "encoding/json" - "fmt" - "reflect" -) - -// JsonMarshalRaw does the same a json.Marshal, except that it does not -// use the MarshalJSON methods of the value being marshaled. If any types -// are specified in the allow set then those will use their MarshalJSON -// method. -// -// Many of the types in the Azure SDK have MarshalJSON which skip over -// fields that are marked as READ-ONLY, this is useful for the client, -// but a problem when pretending to be a server. -func JsonMarshalRaw(v interface{}, allow ...reflect.Type) ([]byte, error) { - allowed := make(map[reflect.Type]bool, len(allow)) - for _, a := range allow { - allowed[a] = true - } - if v != nil { - v = rawValueMaker{allowed}.rawValue(reflect.ValueOf(v)).Interface() - } - return json.Marshal(v) -} - -type rawValueMaker struct { - allowed map[reflect.Type]bool -} - -func (m rawValueMaker) rawValue(v reflect.Value) reflect.Value { - t := v.Type() - if m.allowed[t] { - return v - } - switch t.Kind() { - case reflect.Ptr: - return m.rawPointerValue(v) - case reflect.Struct: - return m.rawStructValue(v) - case reflect.Map: - return m.rawMapValue(v) - case reflect.Slice: - return m.rawSliceValue(v) - default: - return v - } -} - -func (m rawValueMaker) rawPointerValue(v reflect.Value) reflect.Value { - if v.IsNil() { - return v - } - rv := m.rawValue(v.Elem()) - if rv.CanAddr() { - return rv.Addr() - } - pv := reflect.New(rv.Type()) - pv.Elem().Set(rv) - return pv -} - -func (m rawValueMaker) rawStructValue(v reflect.Value) reflect.Value { - t := v.Type() - - fields := make([]reflect.StructField, 0, t.NumField()) - values := make([]reflect.Value, 0, t.NumField()) - - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - if sf.PkgPath != "" || sf.Tag.Get("json") == "-" { - // Skip fields that won't ever be marshaled. - continue - } - if tag, ok := sf.Tag.Lookup("json"); ok && tag == "" { - // Also skip fields with a present, but empty, json tag. - continue - } - rv := m.rawValue(v.Field(i)) - sf.Type = rv.Type() - sf.Anonymous = false - // Azure auth tokens use json.Number for strings and ints which confuses the SDK - // so send across the wire as string for affected fields. - if t.Name() == "Token" && (sf.Name == "ExpiresOn" || sf.Name == "ExpiresIn") { - val := fmt.Sprint(v.Field(i).Interface()) - if val != "" { - sf.Type = reflect.TypeOf("") - rv = reflect.ValueOf(fmt.Sprint(v.Field(i).Interface())) - } - } - fields = append(fields, sf) - values = append(values, rv) - } - - newT := reflect.StructOf(fields) - newV := reflect.New(newT).Elem() - - for i, v := range values { - newV.Field(i).Set(v) - } - - return newV -} - -var interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() - -func (m rawValueMaker) rawMapValue(v reflect.Value) reflect.Value { - newV := reflect.MakeMap(reflect.MapOf(v.Type().Key(), interfaceType)) - for _, key := range v.MapKeys() { - value := v.MapIndex(key) - newV.SetMapIndex(key, m.rawValue(value)) - } - - return newV -} - -func (m rawValueMaker) rawSliceValue(v reflect.Value) reflect.Value { - newV := reflect.MakeSlice(reflect.SliceOf(interfaceType), v.Len(), v.Len()) - for i := 0; i < v.Len(); i++ { - newV.Index(i).Set(m.rawValue(v.Index(i))) - } - return newV -} diff --git a/provider/azure/internal/azuretesting/recorder.go b/provider/azure/internal/azuretesting/recorder.go index c3723432ca9..7eba0743a63 100644 --- a/provider/azure/internal/azuretesting/recorder.go +++ b/provider/azure/internal/azuretesting/recorder.go @@ -10,7 +10,6 @@ import ( "sync" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" ) type RequestRecorderPolicy struct { @@ -41,33 +40,3 @@ func (p *RequestRecorderPolicy) Do(req *policy.Request) (*http.Response, error) return resp, err } - -// RequestRecorder returns an autorest.PrepareDecorator that records requests -// to ghe given slice. -func RequestRecorder(requests *[]*http.Request) autorest.PrepareDecorator { - if requests == nil { - return nil - } - var mu sync.Mutex - return func(p autorest.Preparer) autorest.Preparer { - return autorest.PreparerFunc(func(req *http.Request) (*http.Request, error) { - // Save the request body, since it will be consumed. - reqCopy := *req - if req.Body != nil { - var buf bytes.Buffer - if _, err := buf.ReadFrom(req.Body); err != nil { - return nil, err - } - if err := req.Body.Close(); err != nil { - return nil, err - } - reqCopy.Body = ioutil.NopCloser(&buf) - req.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes())) - } - mu.Lock() - *requests = append(*requests, &reqCopy) - mu.Unlock() - return req, nil - }) - } -} diff --git a/provider/azure/internal/azuretesting/senders.go b/provider/azure/internal/azuretesting/senders.go index 84f692345b8..99a6b1fba5e 100644 --- a/provider/azure/internal/azuretesting/senders.go +++ b/provider/azure/internal/azuretesting/senders.go @@ -5,15 +5,16 @@ package azuretesting import ( "context" + "encoding/json" "fmt" + "io" "net/http" "regexp" "sync" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/loggo" ) @@ -26,17 +27,130 @@ func (c *FakeCredential) GetToken(ctx context.Context, opts policy.TokenRequestO return azcore.AccessToken{Token: "FakeToken"}, nil } -// MockSender is a wrapper around autorest/mocks.Sender, extending it with -// request path checking to ease testing. +// Body implements acceptable body over a string. +type Body struct { + src []byte + buf []byte + isOpen bool +} + +// NewBody creates a new instance of Body. +func NewBody(s string) *Body { + return (&Body{src: []byte(s)}).reset() +} + +// Read reads into the passed byte slice and returns the bytes read. +func (body *Body) Read(b []byte) (n int, err error) { + if !body.IsOpen() { + return 0, fmt.Errorf("ERROR: Body has been closed") + } + if len(body.buf) == 0 { + return 0, io.EOF + } + n = copy(b, body.buf) + body.buf = body.buf[n:] + return n, nil +} + +// Close closes the body. +func (body *Body) Close() error { + if body.isOpen { + body.isOpen = false + } + return nil +} + +// IsOpen returns true if the Body has not been closed, false otherwise. +func (body *Body) IsOpen() bool { + return body.isOpen +} + +func (body *Body) reset() *Body { + body.isOpen = true + body.buf = body.src + return body +} + +// Length returns the number of bytes in the body. +func (body *Body) Length() int64 { + if body == nil { + return 0 + } + return int64(len(body.src)) +} + +// NewRequest instantiates a new request. +func NewRequest() *http.Request { + return NewRequestWithContent("") +} + +// NewRequestWithContent instantiates a new request using the passed string for the body content. +func NewRequestWithContent(c string) *http.Request { + r, _ := http.NewRequest("GET", "https://microsoft.com/a/b/c/", NewBody(c)) + return r +} + +// NewResponse instantiates a new response. +func NewResponse() *http.Response { + return NewResponseWithContent("") +} + +// NewResponseWithContent instantiates a new response with the passed string as the body content. +func NewResponseWithContent(c string) *http.Response { + return &http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: NewBody(c), + Request: NewRequest(), + } +} + +// NewResponseWithStatus instantiates a new response using the passed string and integer as the +// status and status code. +func NewResponseWithStatus(s string, c int) *http.Response { + resp := NewResponse() + resp.Status = s + resp.StatusCode = c + return resp +} + +// NewResponseWithBodyAndStatus instantiates a new response using the specified mock body, +// status and status code +func NewResponseWithBodyAndStatus(body *Body, c int, s string) *http.Response { + resp := NewResponse() + resp.Body = body + resp.ContentLength = body.Length() + resp.Status = s + resp.StatusCode = c + return resp +} + +type response struct { + r *http.Response + e error + d time.Duration +} + +// MockSender provides a mechanism to test requests and +// provide responses to/from the Azure cloud API. type MockSender struct { - *mocks.Sender + attempts int + responses []response + numResponses int + repeatResponse []int + err error + repeatError int // PathPattern, if non-empty, is assumed to be a regular expression // that must match the request path. PathPattern string } -func (s *MockSender) Do(req *http.Request) (*http.Response, error) { +// Do implements policy.Policy. +func (s *MockSender) Do(req *http.Request) (resp *http.Response, err error) { if s.PathPattern != "" { matched, err := regexp.MatchString(s.PathPattern, req.URL.Path) if err != nil { @@ -49,7 +163,89 @@ func (s *MockSender) Do(req *http.Request) (*http.Response, error) { ) } } - return s.Sender.Do(req) + s.attempts++ + + if len(s.responses) > 0 { + resp = s.responses[0].r + if resp != nil { + if b, ok := resp.Body.(*Body); ok { + b.reset() + } + } else { + err = s.responses[0].e + } + select { + case <-time.After(s.responses[0].d): + // do nothing + case <-req.Context().Done(): + err = req.Context().Err() + return + } + s.repeatResponse[0]-- + if s.repeatResponse[0] == 0 { + s.responses = s.responses[1:] + s.repeatResponse = s.repeatResponse[1:] + } + } else { + resp = NewResponse() + } + if resp != nil { + resp.Request = req + } + + if s.err != nil { + err = s.err + s.repeatError-- + if s.repeatError == 0 { + s.err = nil + } + } + + return +} + +// AppendResponse adds the passed http.Response to the response stack. +func (c *MockSender) AppendResponse(resp *http.Response) { + c.AppendAndRepeatResponse(resp, 1) +} + +// AppendAndRepeatResponse adds the passed http.Response to the response stack along with a +// repeat count. A negative repeat count will return the response for all remaining calls to Do. +func (c *MockSender) AppendAndRepeatResponse(resp *http.Response, repeat int) { + c.appendAndRepeat(response{r: resp}, repeat) +} + +func (c *MockSender) appendAndRepeat(resp response, repeat int) { + if c.responses == nil { + c.responses = []response{resp} + c.repeatResponse = []int{repeat} + } else { + c.responses = append(c.responses, resp) + c.repeatResponse = append(c.repeatResponse, repeat) + } + c.numResponses++ +} + +// Attempts returns the number of times Do was called. +func (c *MockSender) Attempts() int { + return c.attempts +} + +// SetError sets the error Do should return. +func (c *MockSender) SetError(err error) { + c.SetAndRepeatError(err, 1) +} + +// SetAndRepeatError sets the error Do should return and how many calls to Do will return the error. +// A negative repeat value will return the error for all remaining calls to Do. +func (c *MockSender) SetAndRepeatError(err error, repeat int) { + c.err = err + c.repeatError = repeat +} + +// NumResponses returns the number of responses that have been added to the sender. +func (c *MockSender) NumResponses() int { + return c.numResponses } const fakeTenantId = "11111111-1111-1111-1111-111111111111" @@ -58,13 +254,13 @@ const fakeTenantId = "11111111-1111-1111-1111-111111111111" // to JSON and sets it as the content. This function will panic if marshalling // fails. func NewSenderWithValue(v interface{}) *MockSender { - content, err := JsonMarshalRaw(v) + content, err := json.Marshal(v) if err != nil { panic(err) } - sender := &MockSender{Sender: mocks.NewSender()} - resp := mocks.NewResponseWithContent(string(content)) - mocks.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ + sender := &MockSender{} + resp := NewResponseWithContent(string(content)) + SetResponseHeaderValues(resp, "WWW-Authenticate", []string{ fmt.Sprintf( `authorization="https://testing.invalid/%s" scope="scope" resource="resource"`, fakeTenantId, @@ -74,14 +270,24 @@ func NewSenderWithValue(v interface{}) *MockSender { return sender } +// SetResponseHeaderValues adds a header containing all the passed string values. +func SetResponseHeaderValues(resp *http.Response, h string, values []string) { + if resp.Header == nil { + resp.Header = make(http.Header) + } + for _, v := range values { + resp.Header.Add(h, v) + } +} + // Senders is a Sender that includes a collection of Senders, which // will be called in sequence. -type Senders []autorest.Sender +type Senders []policy.Transporter func (s *Senders) Do(req *http.Request) (*http.Response, error) { logger.Debugf("Senders.Do(%s)", req.URL) if len(*s) == 0 { - response := mocks.NewResponseWithStatus("", http.StatusInternalServerError) + response := NewResponseWithStatus("", http.StatusInternalServerError) return response, fmt.Errorf("no sender for %q", req.URL) } sender := (*s)[0] @@ -95,7 +301,7 @@ func (s *Senders) Do(req *http.Request) (*http.Response, error) { // at a time. type SerialSender struct { mu sync.Mutex - s autorest.Sender + s policy.Transporter } func (s *SerialSender) Do(req *http.Request) (*http.Response, error) { @@ -104,6 +310,6 @@ func (s *SerialSender) Do(req *http.Request) (*http.Response, error) { return s.s.Do(req) } -func NewSerialSender(s autorest.Sender) *SerialSender { +func NewSerialSender(s policy.Transporter) *SerialSender { return &SerialSender{s: s} } diff --git a/provider/azure/internal/azuretesting/storage.go b/provider/azure/internal/azuretesting/storage.go deleted file mode 100644 index 2ea1cbe2adc..00000000000 --- a/provider/azure/internal/azuretesting/storage.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2015 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package azuretesting - -import ( - "github.com/Azure/azure-sdk-for-go/storage" - "github.com/juju/errors" - "github.com/juju/testing" - - "github.com/juju/juju/provider/azure/internal/azurestorage" -) - -type MockStorageClient struct { - testing.Stub - Containers map[string]azurestorage.Container -} - -// NewClient exists to satisfy users who want a NewClientFunc. -func (c *MockStorageClient) NewClient( - accountName, accountKey, blobServiceBaseURL, apiVersion string, - useHTTPS bool, -) (azurestorage.Client, error) { - c.AddCall("NewClient", accountName, accountKey, blobServiceBaseURL, apiVersion, useHTTPS) - return c, c.NextErr() -} - -func (c *MockStorageClient) GetBlobService() azurestorage.BlobStorageClient { - return c -} - -func (c *MockStorageClient) GetContainerReference(name string) azurestorage.Container { - c.MethodCall(c, "GetContainerReference", name) - container := c.Containers[name] - if container == nil { - container = notFoundContainer{name} - } - return container -} - -type MockStorageContainer struct { - testing.Stub - Blobs_ []azurestorage.Blob -} - -func (c *MockStorageContainer) Blobs() ([]azurestorage.Blob, error) { - c.MethodCall(c, "Blobs") - return c.Blobs_, c.NextErr() -} - -func (c *MockStorageContainer) Blob(name string) azurestorage.Blob { - c.MethodCall(c, "Blob", name) - for _, blob := range c.Blobs_ { - if blob.Name() == name { - return blob - } - } - return notFoundBlob{name: name} -} - -type MockStorageBlob struct { - testing.Stub - Name_ string - Properties_ storage.BlobProperties -} - -func (c *MockStorageBlob) Name() string { - return c.Name_ -} - -func (c *MockStorageBlob) Properties() storage.BlobProperties { - return c.Properties_ -} - -func (c *MockStorageBlob) DeleteIfExists(opts *storage.DeleteBlobOptions) (bool, error) { - c.MethodCall(c, "DeleteIfExists", opts) - return true, c.NextErr() -} - -type notFoundContainer struct { - name string -} - -func (c notFoundContainer) Blobs() ([]azurestorage.Blob, error) { - return nil, errors.NotFoundf("container %q", c.name) -} - -func (c notFoundContainer) Blob(name string) azurestorage.Blob { - return notFoundBlob{ - name: name, - deleteErr: errors.NotFoundf("container %q", c.name), - } -} - -type notFoundBlob struct { - name string - deleteErr error -} - -func (b notFoundBlob) Name() string { - return b.name -} - -func (notFoundBlob) Properties() storage.BlobProperties { - return storage.BlobProperties{} -} - -func (b notFoundBlob) DeleteIfExists(opts *storage.DeleteBlobOptions) (bool, error) { - // TODO(axw) should this return an error if the container doesn't exist? - return false, b.deleteErr -} diff --git a/provider/azure/internal/imageutils/images_test.go b/provider/azure/internal/imageutils/images_test.go index b953204f272..9d88232a70c 100644 --- a/provider/azure/internal/imageutils/images_test.go +++ b/provider/azure/internal/imageutils/images_test.go @@ -9,7 +9,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" "github.com/juju/utils/v3/arch" gc "gopkg.in/check.v1" @@ -24,7 +23,7 @@ import ( type imageutilsSuite struct { testing.BaseSuite - mockSender *mocks.Sender + mockSender *azuretesting.MockSender client *armcompute.VirtualMachineImagesClient callCtx *context.CloudCallContext } @@ -33,7 +32,7 @@ var _ = gc.Suite(&imageutilsSuite{}) func (s *imageutilsSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) - s.mockSender = mocks.NewSender() + s.mockSender = &azuretesting.MockSender{} opts := &arm.ClientOptions{ ClientOptions: policy.ClientOptions{ Transport: s.mockSender, @@ -46,7 +45,7 @@ func (s *imageutilsSuite) SetUpTest(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageLegacy(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent( + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent( `[{"name": "14.04.3"}, {"name": "14.04.1-LTS"}, {"name": "12.04.5"}]`, )) image, err := imageutils.SeriesImage(s.callCtx, "trusty", "released", "westus", s.client) @@ -60,7 +59,7 @@ func (s *imageutilsSuite) TestSeriesImageLegacy(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImage(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent( + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent( `[{"name": "20_04"}, {"name": "20_04-LTS"}, {"name": "19_04"}]`, )) image, err := imageutils.SeriesImage(s.callCtx, "focal", "released", "westus", s.client) @@ -74,7 +73,7 @@ func (s *imageutilsSuite) TestSeriesImage(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageInvalidSKU(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent( + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent( `[{"name": "14.04.invalid"}, {"name": "14.04.5-LTS"}]`, )) image, err := imageutils.SeriesImage(s.callCtx, "trusty", "released", "westus", s.client) @@ -106,27 +105,27 @@ func (s *imageutilsSuite) TestSeriesImageGenericLinux(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageStream(c *gc.C) { - s.mockSender.AppendAndRepeatResponse(mocks.NewResponseWithContent( + s.mockSender.AppendAndRepeatResponse(azuretesting.NewResponseWithContent( `[{"name": "14.04.2"}, {"name": "14.04.3-DAILY"}, {"name": "14.04.1-LTS"}]`), 2) s.assertImageId(c, "trusty", "daily", "Canonical:UbuntuServer:14.04.3-DAILY:latest") s.assertImageId(c, "trusty", "released", "Canonical:UbuntuServer:14.04.2:latest") } func (s *imageutilsSuite) TestSeriesImageNotFound(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent(`[]`)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent(`[]`)) image, err := imageutils.SeriesImage(s.callCtx, "trusty", "released", "westus", s.client) c.Assert(err, gc.ErrorMatches, "selecting SKU for trusty: Ubuntu SKUs for released stream not found") c.Assert(image, gc.IsNil) } func (s *imageutilsSuite) TestSeriesImageStreamNotFound(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithContent(`[{"name": "14.04-beta1"}]`)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithContent(`[{"name": "14.04-beta1"}]`)) _, err := imageutils.SeriesImage(s.callCtx, "trusty", "whatever", "westus", s.client) c.Assert(err, gc.ErrorMatches, "selecting SKU for trusty: Ubuntu SKUs for whatever stream not found") } func (s *imageutilsSuite) TestSeriesImageStreamThrewCredentialError(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized)) called := false s.callCtx.InvalidateCredentialFunc = func(string) error { called = true @@ -139,7 +138,7 @@ func (s *imageutilsSuite) TestSeriesImageStreamThrewCredentialError(c *gc.C) { } func (s *imageutilsSuite) TestSeriesImageStreamThrewNonCredentialError(c *gc.C) { - s.mockSender.AppendResponse(mocks.NewResponseWithStatus("308 Permanent Redirect", http.StatusPermanentRedirect)) + s.mockSender.AppendResponse(azuretesting.NewResponseWithStatus("308 Permanent Redirect", http.StatusPermanentRedirect)) called := false s.callCtx.InvalidateCredentialFunc = func(string) error { called = true diff --git a/provider/azure/internal/tracing/tracing.go b/provider/azure/internal/tracing/tracing.go index 8998be2b690..43bb16deffa 100644 --- a/provider/azure/internal/tracing/tracing.go +++ b/provider/azure/internal/tracing/tracing.go @@ -8,7 +8,6 @@ import ( "net/http/httputil" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/go-autorest/autorest" "github.com/juju/loggo" ) @@ -38,41 +37,3 @@ func (p *LoggingPolicy) Do(req *policy.Request) (*http.Response, error) { } return resp, err } - -// PrepareDecorator returns an autorest.PrepareDecorator that -// logs requests at trace level. -func PrepareDecorator(logger loggo.Logger) autorest.PrepareDecorator { - return func(p autorest.Preparer) autorest.Preparer { - return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { - if logger.IsTraceEnabled() { - dump, err := httputil.DumpRequest(r, true) - if err != nil { - logger.Tracef("failed to dump request: %v", err) - logger.Tracef("%+v", r) - } else { - logger.Tracef("%s", dump) - } - } - return p.Prepare(r) - }) - } -} - -// RespondDecorator returns an autorest.RespondDecorator that -// logs responses at trace level. -func RespondDecorator(logger loggo.Logger) autorest.RespondDecorator { - return func(r autorest.Responder) autorest.Responder { - return autorest.ResponderFunc(func(resp *http.Response) error { - if logger.IsTraceEnabled() { - dump, err := httputil.DumpResponse(resp, true) - if err != nil { - logger.Tracef("failed to dump response: %v", err) - logger.Tracef("%+v", resp) - } else { - logger.Tracef("%s", dump) - } - } - return r.Respond(resp) - }) - } -} diff --git a/provider/azure/internal/useragent/useragent.go b/provider/azure/internal/useragent/useragent.go deleted file mode 100644 index b8f44dca273..00000000000 --- a/provider/azure/internal/useragent/useragent.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2017 Canonical Ltd. -// Licensed under the AGPLv3, see LICENCE file for details. - -package useragent - -import ( - "github.com/Azure/go-autorest/autorest" - - "github.com/juju/juju/version" -) - -// JujuPrefix returns the User-Agent prefix set by Juju. -func JujuPrefix() string { - return "Juju/" + version.Current.String() -} - -// UpdateClient updates the UserAgent field of the given autorest.Client. -func UpdateClient(client *autorest.Client) { - if client.UserAgent == "" { - client.UserAgent = JujuPrefix() - } else { - client.UserAgent = JujuPrefix() + " " + client.UserAgent - } -} diff --git a/provider/azure/storage.go b/provider/azure/storage.go index e0e0b00d96c..3c61a5d5c25 100644 --- a/provider/azure/storage.go +++ b/provider/azure/storage.go @@ -4,25 +4,18 @@ package azure import ( - stdcontext "context" "fmt" - "net/http" "path" - "strings" "sync" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" - legacystorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage" - azurestorage "github.com/Azure/azure-sdk-for-go/storage" - "github.com/Azure/go-autorest/autorest" "github.com/juju/errors" "github.com/juju/names/v4" "github.com/juju/schema" "github.com/juju/juju/core/instance" "github.com/juju/juju/environs/context" - internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" "github.com/juju/juju/provider/azure/internal/errorutils" "github.com/juju/juju/storage" ) @@ -38,17 +31,6 @@ const ( // // See: https://azure.microsoft.com/en-gb/documentation/articles/virtual-machines-disks-vhds/ volumeSizeMaxGiB = 1023 - - // osDiskVHDContainer is the name of the blob container for VHDs - // backing OS disks. - osDiskVHDContainer = "osvhds" - - // dataDiskVHDContainer is the name of the blob container for VHDs - // backing data disks. - dataDiskVHDContainer = "datavhds" - - // vhdExtension is the filename extension we give to VHDs we create. - vhdExtension = ".vhd" ) // StorageProviderTypes implements storage.ProviderRegistry. @@ -140,15 +122,7 @@ func (e *azureStorageProvider) DefaultPools() []*storage.Config { // VolumeSource is part of the Provider interface. func (e *azureStorageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) { - // Check to see if the environment has a storage account, - // which means it uses unmanaged disks. All models created - // before Juju 2.3 will have a storage account already, so - // it's safe to do the check up front. - maybeStorageClient, maybeStorageAccount, err := e.env.maybeGetStorageClient(stdcontext.Background()) - if err != nil { - return nil, errors.Trace(err) - } - return &azureVolumeSource{e.env, maybeStorageAccount, maybeStorageClient}, nil + return &azureVolumeSource{e.env}, nil } // FilesystemSource is part of the Provider interface. @@ -157,9 +131,7 @@ func (e *azureStorageProvider) FilesystemSource(providerConfig *storage.Config) } type azureVolumeSource struct { - env *azureEnviron - maybeStorageAccount *legacystorage.Account - maybeStorageClient internalazurestorage.Client + env *azureEnviron } // CreateVolumes is specified on the storage.VolumeSource interface. @@ -171,11 +143,8 @@ func (v *azureVolumeSource) CreateVolumes(ctx context.ProviderCallContext, param continue } } - if v.maybeStorageClient == nil { - v.createManagedDiskVolumes(ctx, params, results) - return results, nil - } - return results, v.createUnmanagedDiskVolumes(ctx, params, results) + v.createManagedDiskVolumes(ctx, params, results) + return results, nil } // createManagedDiskVolumes creates volumes with associated managed disks. @@ -243,102 +212,9 @@ func (v *azureVolumeSource) createManagedDiskVolume(ctx context.ProviderCallCont return &volume, nil } -// createUnmanagedDiskVolumes creates volumes with associated unmanaged disks (blobs). -func (v *azureVolumeSource) createUnmanagedDiskVolumes(ctx context.ProviderCallContext, params []storage.VolumeParams, results []storage.CreateVolumesResult) error { - var instanceIds []instance.Id - for i, p := range params { - if results[i].Error != nil { - continue - } - instanceIds = append(instanceIds, p.Attachment.InstanceId) - } - if len(instanceIds) == 0 { - return nil - } - virtualMachines, err := v.virtualMachines(ctx, instanceIds) - if err != nil { - return errors.Annotate(err, "getting virtual machines") - } - // Update VirtualMachine objects in-memory, - // and then perform the updates all at once. - for i, p := range params { - if results[i].Error != nil { - continue - } - vm, ok := virtualMachines[p.Attachment.InstanceId] - if !ok { - continue - } - if vm.err != nil { - results[i].Error = vm.err - continue - } - volume, volumeAttachment, err := v.createUnmanagedDiskVolume(vm.vm, p) - if err != nil { - results[i].Error = err - vm.err = err - continue - } - results[i].Volume = volume - results[i].VolumeAttachment = volumeAttachment - } - - updateResults, err := v.updateVirtualMachines(ctx, virtualMachines, instanceIds) - if err != nil { - return errors.Annotate(err, "updating virtual machines") - } - for i, err := range updateResults { - if results[i].Error != nil || err == nil { - continue - } - results[i].Error = err - results[i].Volume = nil - results[i].VolumeAttachment = nil - } - return nil -} - -// createUnmanagedDiskVolume updates the provided VirtualMachine's -// StorageProfile with the parameters for creating a new unmanaged -// data disk. We don't actually interact with the Azure API until -// after all changes to the VirtualMachine are made. -func (v *azureVolumeSource) createUnmanagedDiskVolume( - vm *armcompute.VirtualMachine, - p storage.VolumeParams, -) (*storage.Volume, *storage.VolumeAttachment, error) { - - diskName := p.Tag.String() - sizeInGib := mibToGib(p.Size) - volumeAttachment, err := v.addDataDisk( - vm, - diskName, - p.Tag, - p.Attachment.Machine, - armcompute.DiskCreateOptionTypesEmpty, - to.Ptr(int32(sizeInGib)), - ) - if err != nil { - return nil, nil, errors.Trace(err) - } - // Data disks associate VHDs to machines. In Juju's storage model, - // the VHD is the volume and the disk is the volume attachment. - volume := storage.Volume{ - p.Tag, - storage.VolumeInfo{ - VolumeId: diskName, - Size: gibToMib(sizeInGib), - Persistent: true, - }, - } - return &volume, volumeAttachment, nil -} - // ListVolumes is specified on the storage.VolumeSource interface. func (v *azureVolumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) { - if v.maybeStorageClient == nil { - return v.listManagedDiskVolumes(ctx) - } - return v.listUnmanagedDiskVolumes(ctx) + return v.listManagedDiskVolumes(ctx) } func (v *azureVolumeSource) listManagedDiskVolumes(ctx context.ProviderCallContext) ([]string, error) { @@ -364,48 +240,9 @@ func (v *azureVolumeSource) listManagedDiskVolumes(ctx context.ProviderCallConte return volumeIds, nil } -func (v *azureVolumeSource) listUnmanagedDiskVolumes(ctx context.ProviderCallContext) ([]string, error) { - blobs, err := v.listBlobs(ctx) - if err != nil { - return nil, errors.Annotate(err, "listing volumes") - } - volumeIds := make([]string, 0, len(blobs)) - for _, blob := range blobs { - volumeId, ok := blobVolumeId(blob) - if !ok { - continue - } - volumeIds = append(volumeIds, volumeId) - } - return volumeIds, nil -} - -// listBlobs returns a list of blobs in the data-disk container. -func (v *azureVolumeSource) listBlobs(ctx context.ProviderCallContext) ([]internalazurestorage.Blob, error) { - blobsClient := v.maybeStorageClient.GetBlobService() - vhdContainer := blobsClient.GetContainerReference(dataDiskVHDContainer) - // TODO(axw) consider taking a set of IDs and computing the - // longest common prefix to pass in the parameters - blobs, err := vhdContainer.Blobs() - if err != nil { - err = errorutils.HandleCredentialError(err, ctx) - if err, ok := err.(azurestorage.AzureStorageServiceError); ok { - switch err.Code { - case "ContainerNotFound": - return nil, nil - } - } - return nil, errors.Annotate(err, "listing blobs") - } - return blobs, nil -} - // DescribeVolumes is specified on the storage.VolumeSource interface. func (v *azureVolumeSource) DescribeVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) { - if v.maybeStorageClient == nil { - return v.describeManagedDiskVolumes(ctx, volumeIds) - } - return v.describeUnmanagedDiskVolumes(ctx, volumeIds) + return v.describeManagedDiskVolumes(ctx, volumeIds) } func (v *azureVolumeSource) describeManagedDiskVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) { @@ -439,45 +276,9 @@ func (v *azureVolumeSource) describeManagedDiskVolumes(ctx context.ProviderCallC return results, nil } -func (v *azureVolumeSource) describeUnmanagedDiskVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) { - blobs, err := v.listBlobs(ctx) - if err != nil { - return nil, errors.Annotate(err, "listing volumes") - } - - byVolumeId := make(map[string]internalazurestorage.Blob) - for _, blob := range blobs { - volumeId, ok := blobVolumeId(blob) - if !ok { - continue - } - byVolumeId[volumeId] = blob - } - - results := make([]storage.DescribeVolumesResult, len(volumeIds)) - for i, volumeId := range volumeIds { - blob, ok := byVolumeId[volumeId] - if !ok { - results[i].Error = errors.NotFoundf("%s", volumeId) - continue - } - sizeInMib := blob.Properties().ContentLength / (1024 * 1024) - results[i].VolumeInfo = &storage.VolumeInfo{ - VolumeId: volumeId, - Size: uint64(sizeInMib), - Persistent: true, - } - } - - return results, nil -} - // DestroyVolumes is specified on the storage.VolumeSource interface. func (v *azureVolumeSource) DestroyVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { - if v.maybeStorageClient == nil { - return v.destroyManagedDiskVolumes(ctx, volumeIds) - } - return v.destroyUnmanagedDiskVolumes(ctx, volumeIds) + return v.destroyManagedDiskVolumes(ctx, volumeIds) } func (v *azureVolumeSource) destroyManagedDiskVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { @@ -499,16 +300,6 @@ func (v *azureVolumeSource) destroyManagedDiskVolumes(ctx context.ProviderCallCo }), nil } -func (v *azureVolumeSource) destroyUnmanagedDiskVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { - blobsClient := v.maybeStorageClient.GetBlobService() - vhdContainer := blobsClient.GetContainerReference(dataDiskVHDContainer) - return foreachVolume(volumeIds, func(volumeId string) error { - vhdBlob := vhdContainer.Blob(volumeId + vhdExtension) - _, err := vhdBlob.DeleteIfExists(nil) - return errorutils.HandleCredentialError(errors.Annotatef(err, "deleting blob %q", vhdBlob.Name()), ctx) - }), nil -} - func foreachVolume(volumeIds []string, f func(string) error) []error { results := make([]error, len(volumeIds)) var wg sync.WaitGroup @@ -663,17 +454,9 @@ func (v *azureVolumeSource) addDataDisk( CreateOption: to.Ptr(createOption), DiskSizeGB: diskSizeGB, } - if v.maybeStorageAccount == nil { - // This model uses managed disks. - diskResourceID := v.diskResourceID(diskName) - dataDisk.ManagedDisk = &armcompute.ManagedDiskParameters{ - ID: to.Ptr(diskResourceID), - } - } else { - // This model uses unmanaged disks. - dataDisksRoot := dataDiskVhdRoot(v.maybeStorageAccount) - vhdURI := dataDisksRoot + diskName + vhdExtension - dataDisk.Vhd = &armcompute.VirtualHardDisk{to.Ptr(vhdURI)} + diskResourceID := v.diskResourceID(diskName) + dataDisk.ManagedDisk = &armcompute.ManagedDiskParameters{ + ID: to.Ptr(diskResourceID), } if vm.Properties != nil { @@ -897,102 +680,3 @@ func mibToGib(m uint64) uint64 { func gibToMib(g uint64) uint64 { return g * 1024 } - -// dataDiskVhdRoot returns the URL to the blob container in which we store the -// VHDs for data disks for the environment. -func dataDiskVhdRoot(storageAccount *legacystorage.Account) string { - return blobContainerURL(storageAccount, dataDiskVHDContainer) -} - -// blobContainer returns the URL to the named blob container. -func blobContainerURL(storageAccount *legacystorage.Account, container string) string { - return fmt.Sprintf( - "%s%s/", - toValue(storageAccount.PrimaryEndpoints.Blob), - container, - ) -} - -// blobVolumeId returns the volume ID for a blob, and a boolean reporting -// whether or not the blob's name matches the scheme we use. -func blobVolumeId(blob internalazurestorage.Blob) (string, bool) { - blobName := blob.Name() - if !strings.HasSuffix(blobName, vhdExtension) { - return "", false - } - volumeId := blobName[:len(blobName)-len(vhdExtension)] - if _, err := names.ParseVolumeTag(volumeId); err != nil { - return "", false - } - return volumeId, true -} - -// getStorageClient returns a new storage client, given an environ config -// and a constructor. -func getStorageClient( - newClient internalazurestorage.NewClientFunc, - storageEndpoint string, - storageAccount *legacystorage.Account, - storageAccountKey *legacystorage.AccountKey, -) (internalazurestorage.Client, error) { - storageAccountName := toValue(storageAccount.Name) - const useHTTPS = true - return newClient( - storageAccountName, - toValue(storageAccountKey.Value), - storageEndpoint, - azurestorage.DefaultAPIVersion, - useHTTPS, - ) -} - -func isNotFoundResult(resp autorest.Response, err error) bool { - if resp.Response != nil && resp.StatusCode == http.StatusFound { - return true - } - var azureErr autorest.DetailedError - if errors.As(err, &azureErr) { - return azureErr.StatusCode == http.StatusNotFound - } - return false -} - -// getStorageAccountKey returns the key for the storage account. -func getStorageAccountKey( - ctx stdcontext.Context, - client legacystorage.AccountsClient, - resourceGroup, accountName string, -) (*legacystorage.AccountKey, error) { - logger.Debugf("getting keys for storage account %q", accountName) - listKeysResult, err := client.ListKeys(ctx, resourceGroup, accountName) - if err != nil { - if isNotFoundResult(listKeysResult.Response, err) { - return nil, errors.NewNotFound(err, "storage account keys not found") - } - return nil, errors.Annotate(err, "listing storage account keys") - } - if listKeysResult.Keys == nil { - return nil, errors.NotFoundf("storage account keys") - } - - // We need a storage key with full permissions. - var fullKey *legacystorage.AccountKey - for _, v := range *listKeysResult.Keys { - key := v - logger.Debugf("storage account key: %#v", key) - // At least some of the time, Azure returns the permissions - // in title-case, which does not match the constant. - if strings.ToUpper(string(key.Permissions)) != strings.ToUpper(string(legacystorage.Full)) { - continue - } - fullKey = &key - break - } - if fullKey == nil { - return nil, errors.NotFoundf( - "storage account key with %q permission", - legacystorage.Full, - ) - } - return fullKey, nil -} diff --git a/provider/azure/storage_test.go b/provider/azure/storage_test.go index c86e348364d..b2d6df1438e 100644 --- a/provider/azure/storage_test.go +++ b/provider/azure/storage_test.go @@ -11,17 +11,15 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" - autorestazure "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/mocks" "github.com/juju/errors" "github.com/juju/names/v4" jc "github.com/juju/testing/checkers" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" gc "gopkg.in/check.v1" "github.com/juju/juju/core/instance" "github.com/juju/juju/environs/context" "github.com/juju/juju/provider/azure" - internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" "github.com/juju/juju/provider/azure/internal/azuretesting" "github.com/juju/juju/storage" "github.com/juju/juju/testing" @@ -30,11 +28,9 @@ import ( type storageSuite struct { testing.BaseSuite - datavhdsContainer azuretesting.MockStorageContainer - storageClient azuretesting.MockStorageClient - provider storage.Provider - requests []*http.Request - sender azuretesting.Senders + provider storage.Provider + requests []*http.Request + sender azuretesting.Senders cloudCallCtx *context.CloudCallContext invalidCredential bool @@ -44,16 +40,9 @@ var _ = gc.Suite(&storageSuite{}) func (s *storageSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) - s.datavhdsContainer = azuretesting.MockStorageContainer{} - s.storageClient = azuretesting.MockStorageClient{ - Containers: map[string]internalazurestorage.Container{ - "datavhds": &s.datavhdsContainer, - }, - } s.requests = nil envProvider := newProvider(c, azure.ProviderConfig{ Sender: &s.sender, - NewStorageClient: s.storageClient.NewClient, RequestInspector: &azuretesting.RequestRecorderPolicy{Requests: &s.requests}, RandomWindowsAdminPassword: func() string { return "sorandom" }, CreateTokenCredential: func(appId, appPassword, tenantID string, opts azcore.ClientOptions) (azcore.TokenCredential, error) { @@ -84,20 +73,12 @@ func (s *storageSuite) volumeSource(c *gc.C, attrs ...testing.Attrs) storage.Vol storageConfig, err := storage.NewConfig("azure", "azure", nil) c.Assert(err, jc.ErrorIsNil) - s.sender = azuretesting.Senders{s.accountNotFoundSender()} + s.sender = azuretesting.Senders{} volumeSource, err := s.provider.VolumeSource(storageConfig) c.Assert(err, jc.ErrorIsNil) return volumeSource } -func (s *storageSuite) accountNotFoundSender() *mocks.Sender { - sender := mocks.NewSender() - sender.AppendResponse(mocks.NewResponseWithStatus( - "storage account not found", http.StatusNotFound, - )) - return sender -} - func (s *storageSuite) TestVolumeSource(c *gc.C) { vs := s.volumeSource(c) c.Assert(vs, gc.NotNil) @@ -204,6 +185,7 @@ func (s *storageSuite) TestCreateVolumes(c *gc.C) { "foo": to.Ptr("bar"), } return &armcompute.Disk{ + Name: to.Ptr(name), Location: to.Ptr("westus"), Tags: tags, SKU: &armcompute.DiskSKU{ @@ -224,8 +206,8 @@ func (s *storageSuite) TestCreateVolumes(c *gc.C) { } func (s *storageSuite) createSenderWithUnauthorisedStatusCode() { - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = azuretesting.Senders{unauthSender, unauthSender, unauthSender} } @@ -283,6 +265,7 @@ func (s *storageSuite) TestCreateVolumesWithInvalidCredential(c *gc.C) { "foo": to.Ptr("bar"), } return &armcompute.Disk{ + Name: to.Ptr(name), Location: to.Ptr("westus"), Tags: tags, Properties: &armcompute.DiskProperties{ @@ -333,7 +316,7 @@ func (s *storageSuite) TestListVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestListVolumesErrors(c *gc.C) { volumeSource := s.volumeSource(c) - sender := mocks.NewSender() + sender := &azuretesting.MockSender{} sender.SetAndRepeatError(errors.New("no disks for you"), -1) s.sender = azuretesting.Senders{ sender, @@ -379,9 +362,9 @@ func (s *storageSuite) TestDescribeVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestDescribeVolumesNotFound(c *gc.C) { volumeSource := s.volumeSource(c) - volumeSender := mocks.NewSender() - response := mocks.NewResponseWithBodyAndStatus( - mocks.NewBody("{}"), + volumeSender := &azuretesting.MockSender{} + response := azuretesting.NewResponseWithBodyAndStatus( + azuretesting.NewBody("{}"), http.StatusNotFound, "disk not found", ) @@ -397,7 +380,7 @@ func (s *storageSuite) TestDescribeVolumesNotFound(c *gc.C) { func (s *storageSuite) TestDestroyVolumes(c *gc.C) { volumeSource := s.volumeSource(c) - volume0Sender := azuretesting.NewSenderWithValue(&autorestazure.ServiceError{}) + volume0Sender := azuretesting.NewSenderWithValue(&odataerrors.ODataError{}) volume0Sender.PathPattern = `.*/Microsoft\.Compute/disks/volume-0` s.sender = azuretesting.Senders{volume0Sender} @@ -422,8 +405,8 @@ func (s *storageSuite) TestDestroyVolumesWithInvalidCredential(c *gc.C) { func (s *storageSuite) TestDestroyVolumesNotFound(c *gc.C) { volumeSource := s.volumeSource(c) - volume42Sender := mocks.NewSender() - volume42Sender.AppendResponse(mocks.NewResponseWithStatus( + volume42Sender := &azuretesting.MockSender{} + volume42Sender.AppendResponse(azuretesting.NewResponseWithStatus( "disk not found", http.StatusNotFound, )) s.sender = azuretesting.Senders{volume42Sender} @@ -541,6 +524,7 @@ func (s *storageSuite) TestAttachVolumes(c *gc.C) { }} assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: machine0DataDisks, @@ -623,6 +607,7 @@ func (s *storageSuite) TestDetachVolumes(c *gc.C) { c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: []*armcompute.DataDisk{ @@ -684,6 +669,7 @@ func (s *storageSuite) TestDetachVolumesFinal(c *gc.C) { c.Assert(s.requests[1].Method, gc.Equals, "PUT") // update machine-0 assertRequestBody(c, s.requests[1], &armcompute.VirtualMachine{ + Name: to.Ptr("machine-0"), Properties: &armcompute.VirtualMachineProperties{ StorageProfile: &armcompute.StorageProfile{ DataDisks: []*armcompute.DataDisk{}, diff --git a/provider/azure/upgrades_test.go b/provider/azure/upgrades_test.go index 02354cf39ed..df978a92b89 100644 --- a/provider/azure/upgrades_test.go +++ b/provider/azure/upgrades_test.go @@ -12,7 +12,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" - "github.com/Azure/go-autorest/autorest/mocks" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" @@ -170,8 +169,8 @@ func (s *environUpgradeSuite) TestEnvironUpgradeOperationCreateCommonDeploymentC trueString := "true" controllerTags["juju-is-controller"] = &trueString - unauthSender := mocks.NewSender() - unauthSender.AppendAndRepeatResponse(mocks.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) + unauthSender := &azuretesting.MockSender{} + unauthSender.AppendAndRepeatResponse(azuretesting.NewResponseWithStatus("401 Unauthorized", http.StatusUnauthorized), 3) s.sender = append(s.sender, unauthSender, unauthSender, unauthSender) c.Assert(s.invalidCredential, jc.IsFalse) diff --git a/provider/azure/utils.go b/provider/azure/utils.go index a6f5e1b7c91..903e1dcd6e5 100644 --- a/provider/azure/utils.go +++ b/provider/azure/utils.go @@ -67,10 +67,15 @@ func collectAPIVersions(ctx context.ProviderCallContext, client *armresources.Pr return result, nil } -func azureCloud(apiEndpoint, identityEndpoint string) azurecloud.Configuration { - // The AD graph API is deprecated. Use the new one. - if identityEndpoint == "https://graph.windows.net" { - identityEndpoint = azurecloud.AzurePublic.ActiveDirectoryAuthorityHost +func azureCloud(cloudName, apiEndpoint, identityEndpoint string) azurecloud.Configuration { + // Use well known cloud definitions from the SDk if possible. + switch cloudName { + case "azure": + return azurecloud.AzurePublic + case "azure-china": + return azurecloud.AzureChina + case "azure-gov": + return azurecloud.AzureGovernment } return azurecloud.Configuration{ ActiveDirectoryAuthorityHost: identityEndpoint,