github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/azure/environprovider.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 stdcontext "context" 8 9 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 10 azurecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" 11 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 "github.com/juju/jsonschema" 15 "github.com/juju/loggo" 16 17 "github.com/juju/juju/core/instance" 18 "github.com/juju/juju/environs" 19 environscloudspec "github.com/juju/juju/environs/cloudspec" 20 "github.com/juju/juju/environs/config" 21 "github.com/juju/juju/environs/context" 22 "github.com/juju/juju/provider/azure/internal/errorutils" 23 ) 24 25 const ( 26 // provider version 1 introduces the "common" deployment, 27 // which contains common resources such as the virtual 28 // network and network security group. 29 providerVersion1 = 1 30 31 currentProviderVersion = providerVersion1 32 ) 33 34 // Logger for the Azure provider. 35 var logger = loggo.GetLogger("juju.provider.azure") 36 37 // ProviderConfig contains configuration for the Azure providers. 38 type ProviderConfig struct { 39 // Sender is the autorest.Sender that will be used by Azure 40 // clients. If sender is nil, the default HTTP client sender 41 // will be used. Used for testing. 42 Sender policy.Transporter 43 44 // RequestInspector will be used to inspect Azure requests 45 // if it is non-nil. Used for testing. 46 RequestInspector policy.Policy 47 48 // Retry is set by tests to limit the default retries. 49 Retry policy.RetryOptions 50 51 // CreateTokenCredential is set by tests to create a token. 52 CreateTokenCredential func(appId, appPassword, tenantID string, opts azcore.ClientOptions) (azcore.TokenCredential, error) 53 54 // RetryClock is used for retrying some operations, like 55 // waiting for deployments to complete. 56 // 57 // Retries due to rate-limiting are handled by the go-autorest 58 // package, which uses "time" directly. We cannot mock the 59 // waiting in that case. 60 RetryClock clock.Clock 61 62 // GneerateSSHKey is a functio nused to generate a new SSH 63 // key pair for provisioning Linux machines. 64 GenerateSSHKey func(comment string) (private, public string, _ error) 65 66 // ServicePrincipalCreator is the interface used to create service principals. 67 ServicePrincipalCreator ServicePrincipalCreator 68 69 // AzureCLI is the interface the to Azure CLI (az) command. 70 AzureCLI AzureCLI 71 72 // LoadBalancerSkuName is the load balancer SKU name. 73 // Legal values are determined by the Azure SDK. 74 LoadBalancerSkuName string 75 } 76 77 // Validate validates the Azure provider configuration. 78 func (cfg ProviderConfig) Validate() error { 79 if cfg.RetryClock == nil { 80 return errors.NotValidf("nil RetryClock") 81 } 82 if cfg.GenerateSSHKey == nil { 83 return errors.NotValidf("nil GenerateSSHKey") 84 } 85 if cfg.ServicePrincipalCreator == nil { 86 return errors.NotValidf("nil ServicePrincipalCreator") 87 } 88 if cfg.AzureCLI == nil { 89 return errors.NotValidf("nil AzureCLI") 90 } 91 return nil 92 } 93 94 type azureEnvironProvider struct { 95 environProviderCredentials 96 97 config ProviderConfig 98 } 99 100 // NewEnvironProvider returns a new EnvironProvider for Azure. 101 func NewEnvironProvider(config ProviderConfig) (*azureEnvironProvider, error) { 102 if err := config.Validate(); err != nil { 103 return nil, errors.Annotate(err, "validating environ provider configuration") 104 } 105 return &azureEnvironProvider{ 106 environProviderCredentials: environProviderCredentials{ 107 servicePrincipalCreator: config.ServicePrincipalCreator, 108 azureCLI: config.AzureCLI, 109 transporter: config.Sender, 110 }, 111 config: config, 112 }, nil 113 } 114 115 // Version is part of the EnvironProvider interface. 116 func (prov *azureEnvironProvider) Version() int { 117 return currentProviderVersion 118 } 119 120 // Open is part of the EnvironProvider interface. 121 func (prov *azureEnvironProvider) Open(ctx stdcontext.Context, args environs.OpenParams) (environs.Environ, error) { 122 logger.Debugf("opening model %q", args.Config.Name()) 123 124 namespace, err := instance.NewNamespace(args.Config.UUID()) 125 if err != nil { 126 return nil, errors.Trace(err) 127 } 128 environ := &azureEnviron{ 129 provider: prov, 130 namespace: namespace, 131 } 132 133 // Config is needed before cloud spec. 134 if err := environ.SetConfig(args.Config); err != nil { 135 return nil, errors.Trace(err) 136 } 137 138 if err := environ.SetCloudSpec(ctx, args.Cloud); err != nil { 139 return nil, errors.Trace(err) 140 } 141 return environ, nil 142 } 143 144 // CloudSchema returns the schema used to validate input for add-cloud. Since 145 // this provider does not support custom clouds, this always returns nil. 146 func (p azureEnvironProvider) CloudSchema() *jsonschema.Schema { 147 return nil 148 } 149 150 // Ping tests the connection to the cloud, to verify the endpoint is valid. 151 func (p azureEnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 152 return errors.NotImplementedf("Ping") 153 } 154 155 // PrepareConfig is part of the EnvironProvider interface. 156 func (prov *azureEnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 157 if err := validateCloudSpec(args.Cloud); err != nil { 158 return nil, errors.Annotate(err, "validating cloud spec") 159 } 160 // Set the default block-storage source. 161 attrs := make(map[string]interface{}) 162 if _, ok := args.Config.StorageDefaultBlockSource(); !ok { 163 attrs[config.StorageDefaultBlockSourceKey] = azureStorageProviderType 164 } 165 if len(attrs) == 0 { 166 return args.Config, nil 167 } 168 return args.Config.Apply(attrs) 169 } 170 171 func validateCloudSpec(spec environscloudspec.CloudSpec) error { 172 if err := spec.Validate(); err != nil { 173 return errors.Trace(err) 174 } 175 if spec.Credential == nil { 176 return errors.NotValidf("missing credential") 177 } 178 if authType := spec.Credential.AuthType(); authType != clientCredentialsAuthType { 179 return errors.NotSupportedf("%q auth-type", authType) 180 } 181 return nil 182 } 183 184 // verifyCredentials issues a cheap, non-modifying request to Azure to 185 // verify the configured credentials. If verification fails, a user-friendly 186 // error will be returned, and the original error will be logged at debug 187 // level. 188 var verifyCredentials = func(e *azureEnviron, ctx context.ProviderCallContext) error { 189 // This is used at bootstrap - the ctx invalid credential callback will log 190 // a suitable message. 191 _, err := e.credential.GetToken(ctx, policy.TokenRequestOptions{ 192 Scopes: []string{e.clientOptions.Cloud.Services[azurecloud.ResourceManager].Audience + "/.default"}, 193 }) 194 return errorutils.HandleCredentialError(err, ctx) 195 }