github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/maas/environprovider.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	stdcontext "context"
     8  	"fmt"
     9  	"net/url"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gomaasapi/v2"
    13  	"github.com/juju/jsonschema"
    14  	"github.com/juju/loggo"
    15  
    16  	"github.com/juju/juju/cloud"
    17  	"github.com/juju/juju/environs"
    18  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/environs/context"
    21  )
    22  
    23  var cloudSchema = &jsonschema.Schema{
    24  	Type:     []jsonschema.Type{jsonschema.ObjectType},
    25  	Required: []string{cloud.EndpointKey, cloud.AuthTypesKey},
    26  	// Order doesn't matter since there's only one thing to ask about.  Add
    27  	// order if this changes.
    28  	Properties: map[string]*jsonschema.Schema{
    29  		cloud.AuthTypesKey: {
    30  			// don't need a prompt, since there's only one choice.
    31  			Type: []jsonschema.Type{jsonschema.ArrayType},
    32  			Enum: []interface{}{[]string{string(cloud.OAuth1AuthType)}},
    33  		},
    34  		cloud.EndpointKey: {
    35  			Singular: "the API endpoint url",
    36  			Type:     []jsonschema.Type{jsonschema.StringType},
    37  			Format:   jsonschema.FormatURI,
    38  		},
    39  	},
    40  }
    41  
    42  // Logger for the MAAS provider.
    43  var logger = loggo.GetLogger("juju.provider.maas")
    44  
    45  type EnvironProvider struct {
    46  	environProviderCredentials
    47  
    48  	// GetCapabilities is a function that connects to MAAS to return its set of
    49  	// capabilities.
    50  	GetCapabilities Capabilities
    51  }
    52  
    53  var _ environs.EnvironProvider = (*EnvironProvider)(nil)
    54  
    55  var providerInstance EnvironProvider
    56  
    57  // Version is part of the EnvironProvider interface.
    58  func (EnvironProvider) Version() int {
    59  	return 0
    60  }
    61  
    62  func (EnvironProvider) Open(_ stdcontext.Context, args environs.OpenParams) (environs.Environ, error) {
    63  	logger.Debugf("opening model %q.", args.Config.Name())
    64  	if err := validateCloudSpec(args.Cloud); err != nil {
    65  		return nil, errors.Annotate(err, "validating cloud spec")
    66  	}
    67  	env, err := NewEnviron(args.Cloud, args.Config, nil)
    68  	if err != nil {
    69  		return nil, errors.Annotate(err, "creating MAAS environ")
    70  	}
    71  	return env, nil
    72  }
    73  
    74  // CloudSchema returns the schema for adding new clouds of this type.
    75  func (p EnvironProvider) CloudSchema() *jsonschema.Schema {
    76  	return cloudSchema
    77  }
    78  
    79  // Ping tests the connection to the cloud, to verify the endpoint is valid.
    80  func (p EnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error {
    81  	var err error
    82  	base, version, includesVersion := gomaasapi.SplitVersionedURL(endpoint)
    83  	if includesVersion {
    84  		err = p.checkMaas(base, version)
    85  		if err == nil {
    86  			return nil
    87  		}
    88  	} else {
    89  		// No version info in the endpoint - try both in preference order.
    90  		err = p.checkMaas(endpoint, apiVersion2)
    91  		if err == nil {
    92  			return nil
    93  		}
    94  	}
    95  	return errors.Annotatef(err, "No MAAS server running at %s", endpoint)
    96  }
    97  
    98  func (p EnvironProvider) checkMaas(endpoint, ver string) error {
    99  	c, err := gomaasapi.NewAnonymousClient(endpoint, ver)
   100  	if err != nil {
   101  		logger.Debugf("Can't create maas API %s client for %q: %v", ver, endpoint, err)
   102  		return errors.Trace(err)
   103  	}
   104  	maas := gomaasapi.NewMAAS(*c)
   105  	_, err = p.GetCapabilities(maas, endpoint)
   106  	return errors.Trace(err)
   107  }
   108  
   109  // PrepareConfig is specified in the EnvironProvider interface.
   110  func (p EnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   111  	if err := validateCloudSpec(args.Cloud); err != nil {
   112  		return nil, errors.Annotate(err, "validating cloud spec")
   113  	}
   114  	var attrs map[string]interface{}
   115  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   116  		attrs = map[string]interface{}{
   117  			config.StorageDefaultBlockSourceKey: maasStorageProviderType,
   118  		}
   119  	}
   120  	if len(attrs) == 0 {
   121  		return args.Config, nil
   122  	}
   123  	return args.Config.Apply(attrs)
   124  }
   125  
   126  // DetectRegions is specified in the environs.CloudRegionDetector interface.
   127  func (p EnvironProvider) DetectRegions() ([]cloud.Region, error) {
   128  	return nil, errors.NotFoundf("regions")
   129  }
   130  
   131  func validateCloudSpec(spec environscloudspec.CloudSpec) error {
   132  	if err := spec.Validate(); err != nil {
   133  		return errors.Trace(err)
   134  	}
   135  	if _, err := parseCloudEndpoint(spec.Endpoint); err != nil {
   136  		return errors.Annotate(err, "validating endpoint")
   137  	}
   138  	if spec.Credential == nil {
   139  		return errors.NotValidf("missing credential")
   140  	}
   141  	if authType := spec.Credential.AuthType(); authType != cloud.OAuth1AuthType {
   142  		return errors.NotSupportedf("%q auth-type", authType)
   143  	}
   144  	if _, err := parseOAuthToken(*spec.Credential); err != nil {
   145  		return errors.Annotate(err, "validating MAAS OAuth token")
   146  	}
   147  	return nil
   148  }
   149  
   150  func parseCloudEndpoint(endpoint string) (server string, _ error) {
   151  	// For MAAS, the cloud endpoint may be either a full URL
   152  	// for the MAAS server, or just the IP/host.
   153  	if endpoint == "" {
   154  		return "", errors.New("MAAS server not specified")
   155  	}
   156  	server = endpoint
   157  	if url, err := url.Parse(server); err != nil || url.Scheme == "" {
   158  		server = fmt.Sprintf("http://%s/MAAS", endpoint)
   159  		if _, err := url.Parse(server); err != nil {
   160  			return "", errors.NotValidf("endpoint %q", endpoint)
   161  		}
   162  	}
   163  	return server, nil
   164  }