From 5cffae477de008b200c2a1f46f6d13997e35a228 Mon Sep 17 00:00:00 2001 From: mabhi Date: Wed, 27 Sep 2023 10:49:07 +0530 Subject: [PATCH] Upgrade to new azure-go-sdk and refactor old go-autorest references Signed-off-by: mabhi --- go.mod | 17 +- go.sum | 32 ++- pkg/blockstorage/azure/auth.go | 75 +++-- pkg/blockstorage/azure/azuredisk.go | 380 +++++++++++++------------- pkg/blockstorage/azure/client.go | 137 +++++----- pkg/blockstorage/azure/client_test.go | 10 +- pkg/blockstorage/helpers.go | 141 ++++++++++ 7 files changed, 478 insertions(+), 314 deletions(-) diff --git a/go.mod b/go.mod index 23160d0f41d..b82038f3d9d 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,12 @@ replace ( // Direct and indirect dependencies are in separate require sections require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions v1.2.0 github.com/Azure/go-autorest/autorest v0.11.29 - github.com/Azure/go-autorest/autorest/adal v0.9.23 github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 - github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig v2.22.0+incompatible github.com/aws/aws-sdk-go v1.45.16 @@ -62,6 +64,7 @@ require ( sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/kustomize/kyaml v0.13.9 sigs.k8s.io/yaml v1.3.0 + ) require ( @@ -70,16 +73,16 @@ require ( cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.1 // indirect cloud.google.com/go/storage v1.30.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0 // indirect; pinned - current kopia build referenced here requires v0.3.0 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.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/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect @@ -105,6 +108,7 @@ require ( github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/btree v1.0.1 // indirect @@ -125,6 +129,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/klauspost/reedsolomon v1.11.7 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -145,6 +150,7 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rogpeppe/go-internal v1.6.1 // indirect @@ -188,4 +194,5 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + ) diff --git a/go.sum b/go.sum index d8d915f3604..5d40cf1231a 100644 --- a/go.sum +++ b/go.sum @@ -24,11 +24,19 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1 h1:Oj853U9kG+RLTCQXpjvOnrv0WaZHxgmZz1TlLywgOPY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.1/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2 h1:t5+QXLCK9SVi0PPdaY0PrFvYUo24KwA0QwxnaHRSVd4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.2/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1 h1:LNHhpdK7hzUcx/k1LIcuh5k7k1LGIWLQfCjaneSj7Fc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.1/go.mod h1:uE9zaUfEQT/nbQjVi2IblCG9iaLtZsuYZ8ne+PuQ02M= +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/compute/armcompute/v4 v4.2.1 h1:UPeCRD+XY7QlaGQte2EVI2iOcWvUYA2XY8w5T/8v0NQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4 v4.2.1/go.mod h1:oGV6NlB0cvi1ZbYRR2UN44QHxWFyGk+iylgD0qaMXjA= +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/network/armnetwork v1.0.0 h1:nBy98uKOIfun5z6wx6jwWLrULcM0+cjBalBFZlEZ7CA= +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/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/storage/azblob v1.0.0 h1:u/LLAOFgsMv7HmNL4Qufg58y+qElGOt5qv0z1mURkRY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.0.0/go.mod h1:2e8rMJtl2+2j+HXbTBwnyGpm5Nou7KhvSfxOq8JpTag= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= @@ -62,16 +70,14 @@ github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935 github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= 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.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 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.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 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/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= @@ -196,11 +202,12 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 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/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 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-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -337,6 +344,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 h1:nHHjmvjitIiyPlUHk/ofpgvBcNcawJLtf4PYHORLjAA= github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0/go.mod h1:YBCo4DoEeDndqvAn6eeu0vWM7QdXmHEeI9cFWplmBys= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -405,7 +413,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +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/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -607,6 +616,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/blockstorage/azure/auth.go b/pkg/blockstorage/azure/auth.go index 524330f7bbc..a1e0cf81384 100644 --- a/pkg/blockstorage/azure/auth.go +++ b/pkg/blockstorage/azure/auth.go @@ -2,13 +2,16 @@ package azure import ( "context" - - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure/auth" + _ "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/kanisterio/kanister/pkg/blockstorage" "github.com/pkg/errors" ) +const ActiveDirectory = "activeDirectory" + // currently avaialble types: https://docs.microsoft.com/en-us/azure/developer/go/azure-sdk-authorization // to be available with azidentity: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity#readme-credential-types // determine if the combination of creds are client secret creds @@ -25,6 +28,28 @@ func isMSICredsAvailable(config map[string]string) bool { config[blockstorage.AzureClientSecret] == "") } +type ClientCredentialsConfig struct { + ClientID string + ClientSecret string + TenantID string + AuxTenants []string + AADEndpoint string + Resource string +} + +// Defaults to Public Cloud and Resource Manager Endpoint. +func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig { + return ClientCredentialsConfig{ + ClientID: clientID, + ClientSecret: clientSecret, + TenantID: tenantID, + Resource: cloud.AzurePublic.Services[cloud.ResourceManager].Endpoint, + //Todo: find a replacement for the AADEndpoint in the new azure sdk + AADEndpoint: cloud.AzurePublic.Services[ActiveDirectory].Endpoint, + //azure.PublicCloud.ActiveDirectoryEndpoint, + } +} + // Public interface to authenticate with different Azure credentials type type AzureAuthenticator interface { Authenticate(creds map[string]string) error @@ -46,22 +71,21 @@ type MsiAuthenticator struct{} func (m *MsiAuthenticator) Authenticate(creds map[string]string) error { // check if MSI endpoint is available - if !adal.MSIAvailable(context.Background(), nil) { - return errors.New("MSI endpoint is not supported") - } - // create a service principal token - msiConfig := auth.NewMSIConfig() - if clientID, ok := creds[blockstorage.AzureClientID]; ok && clientID != "" { - msiConfig.ClientID = clientID + + clientID, ok := creds[blockstorage.AzureClientID] + if !ok || clientID == "" { + return errors.New("Failed to fetch azure clientID") } - spt, err := msiConfig.ServicePrincipalToken() + azClientID := azidentity.ClientID(clientID) + opts := azidentity.ManagedIdentityCredentialOptions{ID: azClientID} + cred, err := azidentity.NewManagedIdentityCredential(&opts) if err != nil { - return errors.Wrap(err, "Failed to create a service principal token") + return errors.Wrap(err, "Failed to create a identity credential") } - // network call to check for token - err = spt.Refresh() + _, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{}) + if err != nil { - return errors.Wrap(err, "Failed to refresh token") + return errors.Wrap(err, "Failed to create a service principal token") } // creds passed authentication return nil @@ -75,36 +99,35 @@ func (c *ClientSecretAuthenticator) Authenticate(creds map[string]string) error if err != nil { return errors.Wrap(err, "Failed to get Client Secret config") } - // create a service principal token - spt, err := credConfig.ServicePrincipalToken() + cred, err := azidentity.NewClientSecretCredential(credConfig.TenantID, credConfig.ClientID, credConfig.ClientSecret, nil) if err != nil { - return errors.Wrap(err, "Failed to create a service principal token") + return errors.Wrap(err, "Failed to create a identity credential") } - // network call to check for token - err = spt.Refresh() + _, err = cred.GetToken(context.Background(), policy.TokenRequestOptions{}) + if err != nil { - return errors.Wrap(err, "Failed to refresh token") + return errors.Wrap(err, "Failed to create a service principal token") } // creds passed authentication return nil } -func getCredConfigForAuth(config map[string]string) (auth.ClientCredentialsConfig, error) { +func getCredConfigForAuth(config map[string]string) (ClientCredentialsConfig, error) { tenantID, ok := config[blockstorage.AzureTenantID] if !ok { - return auth.ClientCredentialsConfig{}, errors.New("Cannot get tenantID from config") + return ClientCredentialsConfig{}, errors.New("Cannot get tenantID from config") } clientID, ok := config[blockstorage.AzureClientID] if !ok { - return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientID from config") + return ClientCredentialsConfig{}, errors.New("Cannot get clientID from config") } clientSecret, ok := config[blockstorage.AzureClientSecret] if !ok { - return auth.ClientCredentialsConfig{}, errors.New("Cannot get clientSecret from config") + return ClientCredentialsConfig{}, errors.New("Cannot get clientSecret from config") } - credConfig := auth.NewClientCredentialsConfig(clientID, clientSecret, tenantID) + credConfig := NewClientCredentialsConfig(clientID, clientSecret, tenantID) return credConfig, nil } diff --git a/pkg/blockstorage/azure/azuredisk.go b/pkg/blockstorage/azure/azuredisk.go index c5051b26ee1..01a77656858 100644 --- a/pkg/blockstorage/azure/azuredisk.go +++ b/pkg/blockstorage/azure/azuredisk.go @@ -7,16 +7,15 @@ package azure import ( "context" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions" "regexp" "strings" "time" - "github.com/Azure/azure-sdk-for-go/profiles/latest/compute/mgmt/skus" - "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" - azcompute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute" + azto "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/storage" - azto "github.com/Azure/go-autorest/autorest/to" - uuid "github.com/gofrs/uuid" + "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/kanisterio/kanister/pkg/blockstorage" @@ -25,7 +24,6 @@ import ( "github.com/kanisterio/kanister/pkg/field" "github.com/kanisterio/kanister/pkg/kube" "github.com/kanisterio/kanister/pkg/log" - "github.com/kanisterio/kanister/pkg/poll" ) var _ blockstorage.Provider = (*AdStorage)(nil) @@ -39,6 +37,8 @@ const ( copyBlobName = "copy-blob-%s.vhd" ) +type LocationZoneMap map[string]struct{} + // AdStorage describes the azure storage client type AdStorage struct { azCli *Client @@ -63,11 +63,11 @@ func (s *AdStorage) VolumeGet(ctx context.Context, id string, zone string) (*blo return nil, errors.Wrapf(err, "Failed to get info for volume with ID %s", id) } - disk, err := s.azCli.DisksClient.Get(ctx, rg, name) + diskResponse, err := s.azCli.DisksClient.Get(ctx, rg, name, nil) if err != nil { return nil, errors.Wrapf(err, "Failed to get volume, volumeID: %s", id) } - return s.VolumeParse(ctx, disk) + return s.VolumeParse(ctx, diskResponse.Disk) } func (s *AdStorage) VolumeCreate(ctx context.Context, volume blockstorage.Volume) (*blockstorage.Volume, error) { @@ -77,43 +77,40 @@ func (s *AdStorage) VolumeCreate(ctx context.Context, volume blockstorage.Volume return nil, errors.Wrap(err, "Failed to create UUID") } diskName := fmt.Sprintf(volumeNameFmt, diskId.String()) - diskProperties := &azcompute.DiskProperties{ - DiskSizeGB: azto.Int32Ptr(int32(blockstorage.SizeInGi(volume.SizeInBytes))), - CreationData: &azcompute.CreationData{ - CreateOption: azcompute.DiskCreateOption(azcompute.DiskCreateOptionTypesEmpty), + + diskProperties := &armcompute.DiskProperties{ + CreationData: &armcompute.CreationData{ + CreateOption: azto.Ptr(armcompute.DiskCreateOptionEmpty), }, + DiskSizeGB: blockstorage.Int32Ptr(int32(blockstorage.SizeInGi(volume.SizeInBytes))), } region, id, err := getLocationInfo(volume.Az) if err != nil { return nil, errors.Wrapf(err, "Could not get region from zone %s", volume.Az) } // TODO(ilya): figure out how to create SKUed disks - createDisk := azcompute.Disk{ - Name: azto.StringPtr(diskName), - Tags: *azto.StringMapPtr(tags), - Location: azto.StringPtr(region), - DiskProperties: diskProperties, + createdDisk := armcompute.Disk{ + Name: blockstorage.StringPtr(diskName), + Tags: *blockstorage.StringMapPtr(tags), + Location: blockstorage.StringPtr(region), + Properties: diskProperties, + SKU: &armcompute.DiskSKU{ + Name: azto.Ptr(armcompute.DiskStorageAccountTypesStandardLRS), + }, } if id != "" { - createDisk.Zones = azto.StringSlicePtr([]string{id}) - } - result, err := s.azCli.DisksClient.CreateOrUpdate(ctx, s.azCli.ResourceGroup, diskName, createDisk) - if err != nil { - return nil, err + createdDisk.Zones = blockstorage.SliceStringPtr([]string{id}) } - err = result.WaitForCompletionRef(ctx, s.azCli.DisksClient.Client) + + pollerResp, err := s.azCli.DisksClient.BeginCreateOrUpdate(ctx, s.azCli.ResourceGroup, diskName, createdDisk, nil) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "Could not create volume %s", diskName) } - disk, err := result.Result(*s.azCli.DisksClient) + resp, err := pollerResp.PollUntilDone(ctx, nil) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "Volume create %s polling error", diskName) } - - // Even though the 'CreateOrUpdate' call above returns a 'Disk' model, this is incomplete and - // requires a GET to populate correctly. - // See https://github.com/Azure/azure-sdk-for-go/issues/326 for the explanation why - return s.VolumeGet(ctx, azto.String(disk.ID), volume.Az) + return s.VolumeGet(ctx, blockstorage.String(resp.ID), volume.Az) } func (s *AdStorage) VolumeDelete(ctx context.Context, volume *blockstorage.Volume) error { @@ -121,11 +118,11 @@ func (s *AdStorage) VolumeDelete(ctx context.Context, volume *blockstorage.Volum if err != nil { return errors.Wrapf(err, "Error in deleting Volume with ID %s", volume.ID) } - result, err := s.azCli.DisksClient.Delete(ctx, rg, name) + poller, err := s.azCli.DisksClient.BeginDelete(ctx, rg, name, nil) if err != nil { return errors.Wrapf(err, "Error in deleting Volume with ID %s", volume.ID) } - err = result.WaitForCompletionRef(ctx, s.azCli.DisksClient.Client) + _, err = poller.PollUntilDone(ctx, nil) return errors.Wrapf(err, "Error in deleting Volume with ID %s", volume.ID) } @@ -154,38 +151,27 @@ func (s *AdStorage) SnapshotCopyWithArgs(ctx context.Context, from blockstorage. if err != nil { return nil, errors.Wrapf(err, "SnapshotsClient.Copy: Failure in parsing snapshot ID %s", from.ID) } - _, err = s.azCli.SnapshotsClient.Get(ctx, rg, name) + _, err = s.azCli.SnapshotsClient.Get(ctx, rg, name, nil) if err != nil { return nil, errors.Wrapf(err, "SnapshotsClient.Copy: Failed to get snapshot with ID %s", from.ID) } duration := int32(3600) - gad := azcompute.GrantAccessData{ - Access: azcompute.Read, + gad := armcompute.GrantAccessData{ + Access: azto.Ptr(armcompute.AccessLevelRead), DurationInSeconds: &duration, } - snapshotsGrantAccessFuture, err := s.azCli.SnapshotsClient.GrantAccess(ctx, rg, name, gad) + snapshotsGrantAccessPoller, err := s.azCli.SnapshotsClient.BeginGrantAccess(ctx, rg, name, gad, nil) if err != nil { return nil, errors.Wrapf(err, "Failed to grant read access to snapshot: %s", from.ID) } defer s.revokeAccess(ctx, rg, name, from.ID) - - err = poll.Wait(ctx, func(ctx context.Context) (bool, error) { - _, err := snapshotsGrantAccessFuture.Result(*s.azCli.SnapshotsClient) - if err != nil { - if strings.Contains(err.Error(), "asynchronous operation has not completed") { - return false, nil - } - return false, err - } - return true, nil - }) + snapshotGrantRes, err := snapshotsGrantAccessPoller.PollUntilDone(ctx, nil) if err != nil { - return nil, errors.Wrap(err, "SnapshotsClient.Copy failure to grant snapshot access") + return nil, errors.Wrap(err, "SnapshotsClient.Copy failure to grant snapshot access. Snapshot grant access poller failed to pull the result") } - accessURI, err := snapshotsGrantAccessFuture.Result(*s.azCli.SnapshotsClient) if err != nil { return nil, errors.Wrap(err, "SnapshotsClient.Copy failure to grant snapshot access") } @@ -209,7 +195,7 @@ func (s *AdStorage) SnapshotCopyWithArgs(ctx context.Context, from blockstorage. Timeout: uint(time), } } - err = blob.Copy(*accessURI.AccessSAS, copyOptions) + err = blob.Copy(*snapshotGrantRes.AccessSAS, copyOptions) if err != nil { return nil, errors.Wrapf(err, "Failed to copy disk to blob") } @@ -228,15 +214,15 @@ func (s *AdStorage) SnapshotCopyWithArgs(ctx context.Context, from blockstorage. } tags = blockstorage.SanitizeTags(ktags.GetTags(tags)) - createSnap := azcompute.Snapshot{ - Name: azto.StringPtr(snapName), - Location: azto.StringPtr(to.Region), - Tags: *azto.StringMapPtr(tags), - SnapshotProperties: &azcompute.SnapshotProperties{ - CreationData: &azcompute.CreationData{ - CreateOption: azcompute.Import, - StorageAccountID: azto.StringPtr(storageAccountID), - SourceURI: azto.StringPtr(blobURI), + createSnap := armcompute.Snapshot{ + Name: blockstorage.StringPtr(snapName), + Location: blockstorage.StringPtr(to.Region), + Tags: *blockstorage.StringMapPtr(tags), + Properties: &armcompute.SnapshotProperties{ + CreationData: &armcompute.CreationData{ + CreateOption: azto.Ptr(armcompute.DiskCreateOptionImport), + StorageAccountID: blockstorage.StringPtr(storageAccountID), + SourceURI: blockstorage.StringPtr(blobURI), }, }, } @@ -245,20 +231,15 @@ func (s *AdStorage) SnapshotCopyWithArgs(ctx context.Context, from blockstorage. if val, ok := args[blockstorage.AzureMigrateResourceGroup]; ok && val != "" { migrateResourceGroup = val } - result, err := s.azCli.SnapshotsClient.CreateOrUpdate(ctx, migrateResourceGroup, snapName, createSnap) + createSnapshotPoller, err := s.azCli.SnapshotsClient.BeginCreateOrUpdate(ctx, migrateResourceGroup, snapName, createSnap, nil) if err != nil { return nil, errors.Wrapf(err, "Failed to copy snapshot from source snapshot %v", from) } - err = result.WaitForCompletionRef(ctx, s.azCli.SnapshotsClient.Client) + createSnapRes, err := createSnapshotPoller.PollUntilDone(ctx, nil) if err != nil { - return nil, errors.Wrapf(err, "Failed to copy snapshot from source snapshot %v", from) + return nil, errors.Wrap(err, "Poller failed to retrieve snapshot") } - rs, err := result.Result(*s.azCli.SnapshotsClient) - if err != nil { - return nil, errors.Wrapf(err, "Error in getting result of Snapshot copy operation, snaphotName %s", snapName) - } - - snap, err := s.SnapshotGet(ctx, azto.String(rs.ID)) + snap, err := s.SnapshotGet(ctx, blockstorage.String(createSnapRes.ID)) if err != nil { return nil, errors.Wrapf(err, "Failed to Get Snapshot after create, snaphotName %s", snapName) } @@ -271,7 +252,15 @@ func isMigrateStorageAccountorKey(migrateStorageAccount, migrateStorageKey strin } func (s *AdStorage) revokeAccess(ctx context.Context, rg, name, ID string) { - _, err := s.azCli.SnapshotsClient.RevokeAccess(ctx, rg, name) + poller, err := s.azCli.SnapshotsClient.BeginRevokeAccess(ctx, rg, name, nil) + if err != nil { + log.Print("Failed to finish the revoke request", field.M{"error": err.Error()}) + } + _, err = poller.PollUntilDone(ctx, nil) + if err != nil { + log.Print("failed to pull the result", field.M{"error": err.Error()}) + } + if err != nil { log.Print("Failed to revoke access from snapshot", field.M{"snapshot": ID}) } @@ -295,36 +284,28 @@ func (s *AdStorage) SnapshotCreate(ctx context.Context, volume blockstorage.Volu if err != nil { return nil, errors.Wrapf(err, "Could not get region from zone %s", volume.Az) } - createSnap := azcompute.Snapshot{ - Name: azto.StringPtr(snapName), - Location: azto.StringPtr(region), - Tags: *azto.StringMapPtr(tags), - SnapshotProperties: &azcompute.SnapshotProperties{ - CreationData: &azcompute.CreationData{ - CreateOption: azcompute.Copy, - SourceResourceID: azto.StringPtr(volume.ID), + createSnap := armcompute.Snapshot{ + Name: blockstorage.StringPtr(snapName), + Location: blockstorage.StringPtr(region), + Tags: *blockstorage.StringMapPtr(tags), + Properties: &armcompute.SnapshotProperties{ + CreationData: &armcompute.CreationData{ + CreateOption: azto.Ptr(armcompute.DiskCreateOptionCopy), + SourceResourceID: blockstorage.StringPtr(volume.ID), }, }, } - result, err := s.azCli.SnapshotsClient.CreateOrUpdate(ctx, s.azCli.ResourceGroup, snapName, createSnap) + pollerResp, err := s.azCli.SnapshotsClient.BeginCreateOrUpdate(ctx, s.azCli.ResourceGroup, snapName, createSnap, nil) if err != nil { return nil, errors.Wrapf(err, "Failed to create snapshot for volume %v", volume) } - err = result.WaitForCompletionRef(ctx, s.azCli.SnapshotsClient.Client) - if err != nil { - return nil, errors.Wrapf(err, "Failed to create snapshot for volume %v", volume) - } - rs, err := result.Result(*s.azCli.SnapshotsClient) - if err != nil { - return nil, errors.Wrapf(err, "Error in getting result of Snapshot create operation, snaphotName %s", snapName) - } - - snap, err := s.SnapshotGet(ctx, azto.String(rs.ID)) + resp, err := pollerResp.PollUntilDone(ctx, nil) + blockSnapshot := s.snapshotParse(ctx, resp.Snapshot) if err != nil { return nil, errors.Wrapf(err, "Failed to Get Snapshot after create, snaphotName %s", snapName) } - snap.Volume = &volume - return snap, nil + blockSnapshot.Volume = &volume + return blockSnapshot, nil } func (s *AdStorage) SnapshotCreateWaitForCompletion(ctx context.Context, snap *blockstorage.Snapshot) error { @@ -362,12 +343,11 @@ func (s *AdStorage) SnapshotDelete(ctx context.Context, snapshot *blockstorage.S if err != nil { return errors.Wrapf(err, "SnapshotClient.Delete: Failure in parsing snapshot ID %s", snapshot.ID) } - result, err := s.azCli.SnapshotsClient.Delete(ctx, rg, name) + poller, err := s.azCli.SnapshotsClient.BeginDelete(ctx, rg, name, nil) if err != nil { return errors.Wrapf(err, "SnapshotClient.Delete: Failed to delete snapshot with ID %s", snapshot.ID) } - err = result.WaitForCompletionRef(ctx, s.azCli.SnapshotsClient.Client) - + _, err = poller.PollUntilDone(ctx, nil) return errors.Wrapf(err, "SnapshotClient.Delete: Error while waiting for snapshot with ID %s to get deleted", snapshot.ID) } @@ -376,78 +356,78 @@ func (s *AdStorage) SnapshotGet(ctx context.Context, id string) (*blockstorage.S if err != nil { return nil, errors.Wrapf(err, "SnapshotsClient.Get: Failure in parsing snapshot ID %s", id) } - snap, err := s.azCli.SnapshotsClient.Get(ctx, rg, name) + snapRes, err := s.azCli.SnapshotsClient.Get(ctx, rg, name, nil) if err != nil { return nil, errors.Wrapf(err, "SnapshotsClient.Get: Failed to get snapshot with ID %s", id) } - return s.snapshotParse(ctx, snap), nil + return s.snapshotParse(ctx, snapRes.Snapshot), nil } func (s *AdStorage) VolumeParse(ctx context.Context, volume interface{}) (*blockstorage.Volume, error) { - vol, ok := volume.(azcompute.Disk) + vol, ok := volume.(armcompute.Disk) if !ok { - return nil, errors.New(fmt.Sprintf("Volume is not of type *azcompute.Disk, volume: %v", volume)) + return nil, errors.New(fmt.Sprintf("Volume is not of type *armcompute.Disk, volume: %v", volume)) } encrypted := false - if vol.DiskProperties.EncryptionSettingsCollection != nil && - vol.DiskProperties.EncryptionSettingsCollection.Enabled != nil { - encrypted = *vol.DiskProperties.EncryptionSettingsCollection.Enabled + if vol.Properties.EncryptionSettingsCollection != nil && + vol.Properties.EncryptionSettingsCollection.Enabled != nil { + encrypted = *vol.Properties.EncryptionSettingsCollection.Enabled } tags := map[string]string{"": ""} if vol.Tags != nil { - tags = azto.StringMap(vol.Tags) + tags = blockstorage.StringMap(vol.Tags) } - az := azto.String(vol.Location) - if z := azto.StringSlice(vol.Zones); len(z) > 0 { - az = az + "-" + z[0] + az := blockstorage.String(vol.Location) + if z := vol.Zones; len(z) > 0 { + az = az + "-" + *(z[0]) } return &blockstorage.Volume{ Type: s.Type(), - ID: azto.String(vol.ID), + ID: blockstorage.String(vol.ID), Encrypted: encrypted, - SizeInBytes: azto.Int64(vol.DiskSizeBytes), + SizeInBytes: blockstorage.Int64(vol.Properties.DiskSizeBytes), Az: az, Tags: blockstorage.MapToKeyValue(tags), - VolumeType: string(vol.Sku.Name), - CreationTime: blockstorage.TimeStamp(vol.DiskProperties.TimeCreated.ToTime()), - Attributes: map[string]string{"Users": azto.String(vol.ManagedBy)}, + VolumeType: string(*vol.SKU.Name), + CreationTime: blockstorage.TimeStamp(*vol.Properties.TimeCreated), + Attributes: map[string]string{"Users": blockstorage.String(vol.ManagedBy)}, }, nil } func (s *AdStorage) SnapshotParse(ctx context.Context, snapshot interface{}) (*blockstorage.Snapshot, error) { - if snap, ok := snapshot.(azcompute.Snapshot); ok { + if snap, ok := snapshot.(armcompute.Snapshot); ok { return s.snapshotParse(ctx, snap), nil } - return nil, errors.New(fmt.Sprintf("Snapshot is not of type *azcompute.Snapshot, snapshot: %v", snapshot)) + return nil, errors.New(fmt.Sprintf("Snapshot is not of type *armcompute.Snapshot, snapshot: %v", snapshot)) } -func (s *AdStorage) snapshotParse(ctx context.Context, snap azcompute.Snapshot) *blockstorage.Snapshot { +func (s *AdStorage) snapshotParse(ctx context.Context, snap armcompute.Snapshot) *blockstorage.Snapshot { vol := &blockstorage.Volume{ Type: s.Type(), - ID: azto.String(snap.SnapshotProperties.CreationData.SourceResourceID), + ID: *snap.Properties.CreationData.SourceResourceID, } - snapCreationTime := *snap.TimeCreated + snapCreationTime := *snap.Properties.TimeCreated encrypted := false - if snap.SnapshotProperties.EncryptionSettingsCollection != nil && - snap.SnapshotProperties.EncryptionSettingsCollection.Enabled != nil { - encrypted = *snap.SnapshotProperties.EncryptionSettingsCollection.Enabled + if snap.Properties.EncryptionSettingsCollection != nil && + snap.Properties.EncryptionSettingsCollection.Enabled != nil { + encrypted = *snap.Properties.EncryptionSettingsCollection.Enabled } tags := map[string]string{} if snap.Tags != nil { - tags = azto.StringMap(snap.Tags) + tags = blockstorage.StringMap(snap.Tags) } return &blockstorage.Snapshot{ Encrypted: encrypted, - ID: azto.String(snap.ID), - Region: azto.String(snap.Location), - SizeInBytes: azto.Int64(snap.SnapshotProperties.DiskSizeBytes), + ID: *snap.ID, + Region: *snap.Location, + SizeInBytes: *snap.Properties.DiskSizeBytes, Tags: blockstorage.MapToKeyValue(tags), Type: s.Type(), Volume: vol, - CreationTime: blockstorage.TimeStamp(snapCreationTime.ToTime()), + CreationTime: blockstorage.TimeStamp(snapCreationTime), } } @@ -455,16 +435,19 @@ func (s *AdStorage) VolumesList(ctx context.Context, tags map[string]string, zon var vols []*blockstorage.Volume // (ilya): It looks like azure doesn't support search by tags // List does listing per Subscription - for diskList, err := s.azCli.DisksClient.ListComplete(ctx); diskList.NotDone(); err = diskList.Next() { + pager := s.azCli.DisksClient.NewListPager(nil) + for pager.More() { + page, err := pager.NextPage(ctx) if err != nil { return nil, errors.Wrap(err, "DisksClient.List in VolumesList") } - disk := diskList.Value() - vol, err := s.VolumeParse(ctx, disk) - if err != nil { - return nil, errors.Wrap(err, "DisksClient.List in VolumesList, failure in parsing Volume") + for _, disk := range page.Value { + vol, err := s.VolumeParse(ctx, disk) + if err != nil { + return nil, errors.Wrap(err, "DisksClient.List in VolumesList, failure in parsing Volume") + } + vols = append(vols, vol) } - vols = append(vols, vol) } return vols, nil } @@ -473,17 +456,20 @@ func (s *AdStorage) SnapshotsList(ctx context.Context, tags map[string]string) ( var snaps []*blockstorage.Snapshot // (ilya): It looks like azure doesn't support search by tags // List does listing per Subscription - for snapList, err := s.azCli.SnapshotsClient.ListComplete(ctx); snapList.NotDone(); err = snapList.Next() { + pager := s.azCli.SnapshotsClient.NewListPager(nil) + for pager.More() { + page, err := pager.NextPage(ctx) if err != nil { return nil, errors.Wrap(err, "SnapshotsClient.List in SnapshotsList") } - snap := snapList.Value() - k10Snap, err := s.SnapshotParse(ctx, snap) - if err != nil { - log.WithError(err).Print("Incorrect Snaphost type", field.M{"SnapshotID": snap.ID}) - continue + for _, snap := range page.Value { + k10Snap, err := s.SnapshotParse(ctx, snap) + if err != nil { + log.WithError(err).Print("Incorrect Snaphost type", field.M{"SnapshotID": snap.ID}) + continue + } + snaps = append(snaps, k10Snap) } - snaps = append(snaps, k10Snap) } snaps = blockstorage.FilterSnapshotsWithTags(snaps, blockstorage.SanitizeTags(tags)) return snaps, nil @@ -509,39 +495,36 @@ func (s *AdStorage) VolumeCreateFromSnapshot(ctx context.Context, snapshot block } diskName := fmt.Sprintf(volumeNameFmt, diskId.String()) tags = blockstorage.SanitizeTags(tags) - createDisk := azcompute.Disk{ - Name: azto.StringPtr(diskName), - Tags: *azto.StringMapPtr(tags), - Location: azto.StringPtr(region), - DiskProperties: &azcompute.DiskProperties{ - CreationData: &azcompute.CreationData{ - CreateOption: azcompute.Copy, - SourceResourceID: azto.StringPtr(snapshot.ID), + createDisk := armcompute.Disk{ + Name: blockstorage.StringPtr(diskName), + Tags: *blockstorage.StringMapPtr(tags), + Location: blockstorage.StringPtr(region), + Properties: &armcompute.DiskProperties{ + CreationData: &armcompute.CreationData{ + CreateOption: azto.Ptr(armcompute.DiskCreateOptionCopy), + SourceResourceID: blockstorage.StringPtr(snapshot.ID), }, }, } if id != "" { - createDisk.Zones = azto.StringSlicePtr([]string{id}) + createDisk.Zones = blockstorage.SliceStringPtr([]string{id}) } - for _, saType := range azcompute.PossibleDiskStorageAccountTypesValues() { + for _, saType := range armcompute.PossibleDiskStorageAccountTypesValues() { if string(saType) == snapshot.Volume.VolumeType { - createDisk.Sku = &azcompute.DiskSku{ - Name: saType, + createDisk.SKU = &armcompute.DiskSKU{ + Name: azto.Ptr(saType), } } } - result, err := s.azCli.DisksClient.CreateOrUpdate(ctx, s.azCli.ResourceGroup, diskName, createDisk) + poller, err := s.azCli.DisksClient.BeginCreateOrUpdate(ctx, s.azCli.ResourceGroup, diskName, createDisk, nil) if err != nil { return nil, errors.Wrapf(err, "DiskCLient.CreateOrUpdate in VolumeCreateFromSnapshot, diskName: %s, snapshotID: %s", diskName, snapshot.ID) } - if err = result.WaitForCompletionRef(ctx, s.azCli.DisksClient.Client); err != nil { - return nil, errors.Wrapf(err, "DiskCLient.CreateOrUpdate in VolumeCreateFromSnapshot, diskName: %s, snapshotID: %s", diskName, snapshot.ID) - } - disk, err := result.Result(*s.azCli.DisksClient) + resp, err := poller.PollUntilDone(ctx, nil) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "DiskCLient.CreateOrUpdate in VolumeCreateFromSnapshot, diskName: %s, snapshotID: %s", diskName, snapshot.ID) } - return s.VolumeGet(ctx, azto.String(disk.ID), snapshot.Volume.Az) + return s.VolumeGet(ctx, blockstorage.String(resp.ID), snapshot.Volume.Az) } func (s *AdStorage) getRegionAndZoneID(ctx context.Context, sourceRegion, volAz string) (string, string, error) { @@ -594,19 +577,19 @@ func (s *AdStorage) SetTags(ctx context.Context, resource interface{}, tags map[ if err != nil { return err } - snap, err := s.azCli.SnapshotsClient.Get(ctx, rg, name) + snap, err := s.azCli.SnapshotsClient.Get(ctx, rg, name, nil) if err != nil { return errors.Wrapf(err, "SnapshotsClient.Get in SetTags, snapshotID: %s", res.ID) } - tags = ktags.AddMissingTags(azto.StringMap(snap.Tags), ktags.GetTags(tags)) - snapProperties := azcompute.SnapshotUpdate{ - Tags: *azto.StringMapPtr(blockstorage.SanitizeTags(tags)), + tags = ktags.AddMissingTags(blockstorage.StringMap(snap.Tags), ktags.GetTags(tags)) + snapProperties := armcompute.SnapshotUpdate{ + Tags: *blockstorage.StringMapPtr(blockstorage.SanitizeTags(tags)), } - result, err := s.azCli.SnapshotsClient.Update(ctx, rg, name, snapProperties) + poller, err := s.azCli.SnapshotsClient.BeginUpdate(ctx, rg, name, snapProperties, nil) if err != nil { return errors.Wrapf(err, "SnapshotsClient.Update in SetTags, snapshotID: %s", name) } - err = result.WaitForCompletionRef(ctx, s.azCli.SnapshotsClient.Client) + _, err = poller.PollUntilDone(ctx, nil) return errors.Wrapf(err, "SnapshotsClient.Update in SetTags, snapshotID: %s", name) } case *blockstorage.Volume: @@ -615,20 +598,20 @@ func (s *AdStorage) SetTags(ctx context.Context, resource interface{}, tags map[ if err != nil { return err } - vol, err := s.azCli.DisksClient.Get(ctx, rg, volID) + vol, err := s.azCli.DisksClient.Get(ctx, rg, volID, nil) if err != nil { return errors.Wrapf(err, "DiskClient.Get in SetTags, volumeID: %s", volID) } - tags = ktags.AddMissingTags(azto.StringMap(vol.Tags), ktags.GetTags(tags)) + tags = ktags.AddMissingTags(blockstorage.StringMap(vol.Tags), ktags.GetTags(tags)) - diskProperties := azcompute.DiskUpdate{ - Tags: *azto.StringMapPtr(blockstorage.SanitizeTags(tags)), + diskProperties := armcompute.DiskUpdate{ + Tags: *blockstorage.StringMapPtr(blockstorage.SanitizeTags(tags)), } - result, err := s.azCli.DisksClient.Update(ctx, rg, volID, diskProperties) + poller, err := s.azCli.DisksClient.BeginUpdate(ctx, rg, volID, diskProperties, nil) if err != nil { return errors.Wrapf(err, "DiskClient.Update in SetTags, volumeID: %s", volID) } - err = result.WaitForCompletionRef(ctx, s.azCli.DisksClient.Client) + _, err = poller.PollUntilDone(ctx, nil) return errors.Wrapf(err, "DiskClient.Update in SetTags, volumeID: %s", volID) } default: @@ -678,39 +661,34 @@ func (s *AdStorage) SnapshotRestoreTargets(ctx context.Context, snapshot *blocks // dynamicRegionMapAzure derives a mapping from Regions to zones for Azure. Depends on subscriptionID func (s *AdStorage) dynamicRegionMapAzure(ctx context.Context) (map[string][]string, error) { - subscriptionsCLient := subscriptions.NewClientWithBaseURI(s.azCli.BaseURI) - subscriptionsCLient.Authorizer = s.azCli.Authorizer - llResp, err := subscriptionsCLient.ListLocations(ctx, s.azCli.SubscriptionID, nil) - if err != nil { - return nil, err - } - regionMap := make(map[string]map[string]struct{}) - for _, location := range *llResp.Value { - regionMap[*location.Name] = make(map[string]struct{}) - } + subscriptionsClient := s.azCli.SubscriptionsClient + regionMap := make(map[string]LocationZoneMap) - skuClient := skus.NewResourceSkusClientWithBaseURI(s.azCli.BaseURI, s.azCli.SubscriptionID) - skuClient.Authorizer = s.azCli.Authorizer - skuResults, err := skuClient.ListComplete(ctx) - if err != nil { - return nil, err - } - for skuResults.Value().Name != nil { - if skuResults.Value().ResourceType != nil && *skuResults.Value().ResourceType == "disks" { - for _, location := range *skuResults.Value().LocationInfo { - if val, ok := regionMap[*location.Location]; ok { - for _, zone := range *location.Zones { - val[zone] = struct{}{} - } - regionMap[*location.Location] = val - } - } + locationsPager := subscriptionsClient.NewListLocationsPager(s.azCli.SubscriptionID, &armsubscriptions.ClientListLocationsOptions{IncludeExtendedLocations: nil}) + for locationsPager.More() { + page, err := locationsPager.NextPage(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to advance page") } - if err = skuResults.NextWithContext(ctx); err != nil { - return nil, err + for _, location := range page.Value { + regionMap[*location.Name] = make(LocationZoneMap) } } + skusClient := s.azCli.SKUsClient + skusPager := skusClient.NewListPager(&armcompute.ResourceSKUsClientListOptions{Filter: nil, + IncludeExtendedLocations: nil}) + for skusPager.More() { + skuResults, err := skusPager.NextPage(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to advance page") + } + for _, skuResult := range skuResults.Value { + if skuResult.Name != nil && skuResult.ResourceType != nil && *skuResult.ResourceType == "disks" { + s.mapLocationToZone(skuResult, ®ionMap) + } + } + } // convert to map of []string regionMapResult := make(map[string][]string) for region, zoneSet := range regionMap { @@ -722,3 +700,15 @@ func (s *AdStorage) dynamicRegionMapAzure(ctx context.Context) (map[string][]str } return regionMapResult, nil } + +func (s *AdStorage) mapLocationToZone(skuResult *armcompute.ResourceSKU, regionMap *map[string]LocationZoneMap) { + var rm = *regionMap + for _, locationInfo := range skuResult.LocationInfo { + if val, ok := rm[*locationInfo.Location]; ok { + for _, zone := range locationInfo.Zones { + val[*zone] = struct{}{} + } + rm[*locationInfo.Location] = val + } + } +} diff --git a/pkg/blockstorage/azure/client.go b/pkg/blockstorage/azure/client.go index 86589b8af62..0e2fe0cd4b3 100644 --- a/pkg/blockstorage/azure/client.go +++ b/pkg/blockstorage/azure/client.go @@ -20,11 +20,10 @@ package azure import ( "context" - - "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-03-01/compute" - "github.com/Azure/go-autorest/autorest" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions" "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/kanisterio/kanister/pkg/blockstorage" "github.com/kanisterio/kanister/pkg/log" "github.com/pkg/errors" @@ -32,15 +31,26 @@ import ( // Client is a wrapper for Client client type Client struct { - SubscriptionID string - ResourceGroup string - BaseURI string - Authorizer *autorest.BearerAuthorizer - DisksClient *compute.DisksClient - SnapshotsClient *compute.SnapshotsClient + Cred *azidentity.DefaultAzureCredential + SubscriptionID string + ResourceGroup string + BaseURI string + //https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/main/sdk/resourcemanager/compute/disk/main.go + //https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/arm/compute#DisksClient + DisksClient *armcompute.DisksClient + //https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/main/sdk/resourcemanager/compute/snapshot/main.go + //https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/arm/compute#SnapshotsClient + SnapshotsClient *armcompute.SnapshotsClient + SKUsClient *armcompute.ResourceSKUsClient + SubscriptionsClient *armsubscriptions.Client } // NewClient returns a Client struct +var ( + computeClientFactory *armcompute.ClientFactory + subscriptionsClientFactory *armsubscriptions.ClientFactory +) + func NewClient(ctx context.Context, config map[string]string) (*Client, error) { var resourceGroup string var subscriptionID string @@ -64,87 +74,70 @@ func NewClient(ctx context.Context, config map[string]string) (*Client, error) { return nil, errors.Wrap(err, "Cannot get subscriptionID from instance metadata") } } + /* + if id, ok := config[blockstorage.AzureCloudEnvironmentID]; !ok || id == "" { + config[blockstorage.AzureCloudEnvironmentID] = azure.PublicCloud.Name + } + */ + /* + env, err := azure.EnvironmentFromName(config[blockstorage.AzureCloudEnvironmentID]) + if err != nil { + return nil, errors.Wrap(err, "Failed to fetch the cloud environment.") + } - if id, ok := config[blockstorage.AzureCloudEnvironmentID]; !ok || id == "" { - config[blockstorage.AzureCloudEnvironmentID] = azure.PublicCloud.Name - } + authorizer, err := getAuthorizer(env, config) + if err != nil { + return nil, err + } - env, err := azure.EnvironmentFromName(config[blockstorage.AzureCloudEnvironmentID]) + _, ok = config[blockstorage.AzureResurceMgrEndpoint] + if !ok { + config[blockstorage.AzureResurceMgrEndpoint] = env.ResourceManagerEndpoint + } + */ + cred, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { - return nil, errors.Wrap(err, "Failed to fetch the cloud environment.") + return nil, err } - - authorizer, err := getAuthorizer(env, config) + computeClientFactory, err = armcompute.NewClientFactory(subscriptionID, cred, nil) if err != nil { return nil, err } - _, ok = config[blockstorage.AzureResurceMgrEndpoint] - if !ok { - config[blockstorage.AzureResurceMgrEndpoint] = env.ResourceManagerEndpoint - } - - disksClient := compute.NewDisksClientWithBaseURI(config[blockstorage.AzureResurceMgrEndpoint], subscriptionID) - disksClient.Authorizer = authorizer - - snapshotsClient := compute.NewSnapshotsClientWithBaseURI(config[blockstorage.AzureResurceMgrEndpoint], subscriptionID) - snapshotsClient.Authorizer = authorizer - - return &Client{ - BaseURI: config[blockstorage.AzureResurceMgrEndpoint], - SubscriptionID: subscriptionID, - Authorizer: authorizer, - DisksClient: &disksClient, - SnapshotsClient: &snapshotsClient, - ResourceGroup: resourceGroup, - }, nil -} - -//nolint:unparam -func getAuthorizer(env azure.Environment, config map[string]string) (*autorest.BearerAuthorizer, error) { - if isClientCredsAvailable(config) { - return getClientCredsAuthorizer(env, config) - } else if isMSICredsAvailable(config) { - return getMSIsAuthorizer(config) - } - return nil, errors.New("Missing credentials, or credential type not supported") -} + subscriptionsClientFactory, err = armsubscriptions.NewClientFactory(cred, nil) -func getClientCredsAuthorizer(env azure.Environment, config map[string]string) (*autorest.BearerAuthorizer, error) { - credConfig, err := getCredConfig(env, config) if err != nil { - return nil, errors.Wrap(err, "Failed to get Azure Client Credentials Config") - } - a, err := credConfig.Authorizer() - if err != nil { - return nil, errors.Wrap(err, "Failed to get Azure Client Credentials authorizer") - } - ba, ok := a.(*autorest.BearerAuthorizer) - if !ok { - return nil, errors.New("Failed to get Azure authorizer") + return nil, err } - return ba, nil -} -func getMSIsAuthorizer(config map[string]string) (*autorest.BearerAuthorizer, error) { - msiConfig := auth.NewMSIConfig() - msiConfig.ClientID = config[blockstorage.AzureClientID] - a, err := msiConfig.Authorizer() + disksClient := computeClientFactory.NewDisksClient() + snapshotsClient := computeClientFactory.NewSnapshotsClient() + skusClient := computeClientFactory.NewResourceSKUsClient() + subscriptionsClient := subscriptionsClientFactory.NewClient() + if err != nil { - return nil, errors.Wrap(err, "Failed to get Azure MSI authorizer") - } - ba, ok := a.(*autorest.BearerAuthorizer) - if !ok { - return nil, errors.New("Failed to get Azure authorizer") + return nil, err } - return ba, nil + + return &Client{ + Cred: cred, + BaseURI: config[blockstorage.AzureResurceMgrEndpoint], + SubscriptionID: subscriptionID, + DisksClient: disksClient, + SnapshotsClient: snapshotsClient, + SKUsClient: skusClient, + SubscriptionsClient: subscriptionsClient, + ResourceGroup: resourceGroup, + }, nil } -func getCredConfig(env azure.Environment, config map[string]string) (auth.ClientCredentialsConfig, error) { +func getCredConfig(env azure.Environment, config map[string]string) (ClientCredentialsConfig, error) { credConfig, err := getCredConfigForAuth(config) if err != nil { - return auth.ClientCredentialsConfig{}, err + return ClientCredentialsConfig{}, err } + + //Todo: Find alternatives to azure.Environment var ok bool if credConfig.AADEndpoint, ok = config[blockstorage.AzureActiveDirEndpoint]; !ok || credConfig.AADEndpoint == "" { credConfig.AADEndpoint = env.ActiveDirectoryEndpoint diff --git a/pkg/blockstorage/azure/client_test.go b/pkg/blockstorage/azure/client_test.go index a49c1be5972..e486ff73f86 100644 --- a/pkg/blockstorage/azure/client_test.go +++ b/pkg/blockstorage/azure/client_test.go @@ -17,14 +17,13 @@ package azure import ( "context" "fmt" - "strings" - "testing" - "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/kanisterio/kanister/pkg/blockstorage" envconfig "github.com/kanisterio/kanister/pkg/config" . "gopkg.in/check.v1" + "strings" + "testing" ) // Hook up gocheck into the "go test" runner. @@ -50,10 +49,11 @@ func (s *ClientSuite) TestClient(c *C) { c.Assert(err, IsNil) c.Assert(azCli.SubscriptionID, NotNil) - c.Assert(azCli.Authorizer, NotNil) c.Assert(azCli.DisksClient, NotNil) c.Assert(azCli.SnapshotsClient, NotNil) - _, err = azCli.DisksClient.List(context.Background()) + c.Assert(azCli.DisksClient.NewListPager(nil), NotNil) + c.Assert(azCli.SKUsClient, NotNil) + c.Assert(azCli.SubscriptionsClient, NotNil) c.Assert(err, IsNil) } diff --git a/pkg/blockstorage/helpers.go b/pkg/blockstorage/helpers.go index 20ea680d192..da1ea4d96df 100644 --- a/pkg/blockstorage/helpers.go +++ b/pkg/blockstorage/helpers.go @@ -16,6 +16,7 @@ package blockstorage import ( "bytes" + azto "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" ktags "github.com/kanisterio/kanister/pkg/blockstorage/tags" ) @@ -105,3 +106,143 @@ func FilterSnapshotsWithTags(snapshots []*Snapshot, tags map[string]string) []*S } return result } + +// utility functions equivalent to old functions from package `go-autorest/autorest/to` + +// StringMapPtr returns a pointer to a map of string pointers built from the passed map of strings. +func StringMapPtr(ms map[string]string) *map[string]*string { + msp := make(map[string]*string, len(ms)) + for k, s := range ms { + msp[k] = azto.Ptr(s) + } + return &msp +} + +// StringMap returns a map of strings built from the map of string pointers. The empty string is +// used for nil pointers. +func StringMap(msp map[string]*string) map[string]string { + ms := make(map[string]string, len(msp)) + for k, sp := range msp { + if sp != nil { + ms[k] = *sp + } else { + ms[k] = "" + } + } + return ms +} + +// StringSlice returns a string slice value for the passed string slice pointer. It returns a nil +// slice if the pointer is nil. +func StringSlice(s *[]string) []string { + if s != nil { + return *s + } + return nil +} + +// StringSlicePtr returns a pointer to the passed string slice. +func StringSlicePtr(s []string) *[]string { + return azto.Ptr(s) +} + +// SliceStringPtr returns a slice of string pointers from the passed string slice. +func SliceStringPtr(s []string) []*string { + ms := make([]*string, len(s)) + for k, sp := range s { + ms[k] = azto.Ptr(sp) + } + return ms +} + +// Bool returns a bool value for the passed bool pointer. It returns false if the pointer is nil. +func Bool(b *bool) bool { + if b != nil { + return *b + } + return false +} + +// BoolPtr returns a pointer to the passed bool. +func BoolPtr(b bool) *bool { + return &b +} + +// Int returns an int value for the passed int pointer. It returns 0 if the pointer is nil. +func Int(i *int) int { + if i != nil { + return *i + } + return 0 +} + +// IntPtr returns a pointer to the passed int. +func IntPtr(i int) *int { + return &i +} + +// Int32 returns an int value for the passed int pointer. It returns 0 if the pointer is nil. +func Int32(i *int32) int32 { + if i != nil { + return *i + } + return 0 +} + +// Int32Ptr returns a pointer to the passed int32. +func Int32Ptr(i int32) *int32 { + return &i +} + +// Int64 returns an int value for the passed int pointer. It returns 0 if the pointer is nil. +func Int64(i *int64) int64 { + if i != nil { + return *i + } + return 0 +} + +// Int64Ptr returns a pointer to the passed int64. +func Int64Ptr(i int64) *int64 { + return &i +} + +// Float32 returns an int value for the passed int pointer. It returns 0.0 if the pointer is nil. +func Float32(i *float32) float32 { + if i != nil { + return *i + } + return 0.0 +} + +// Float32Ptr returns a pointer to the passed float32. +func Float32Ptr(i float32) *float32 { + return &i +} + +// Float64 returns an int value for the passed int pointer. It returns 0.0 if the pointer is nil. +func Float64(i *float64) float64 { + if i != nil { + return *i + } + return 0.0 +} + +// Float64Ptr returns a pointer to the passed float64. +func Float64Ptr(i float64) *float64 { + return &i +} + +// String returns a string value for the passed string pointer. It returns the empty string if the +// pointer is nil. +func String(s *string) string { + if s != nil { + return *s + } + return "" +} + +// StringPtr returns a pointer to the passed string. +func StringPtr(s string) *string { + return &s +}