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  }