github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"fmt"
     8  	"net/url"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/gomaasapi"
    12  	"github.com/juju/jsonschema"
    13  	"github.com/juju/loggo"
    14  
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/environs/context"
    19  	"github.com/juju/juju/provider/common"
    20  )
    21  
    22  var cloudSchema = &jsonschema.Schema{
    23  	Type:     []jsonschema.Type{jsonschema.ObjectType},
    24  	Required: []string{cloud.EndpointKey, cloud.AuthTypesKey},
    25  	// Order doesn't matter since there's only one thing to ask about.  Add
    26  	// order if this changes.
    27  	Properties: map[string]*jsonschema.Schema{
    28  		cloud.AuthTypesKey: {
    29  			// don't need a prompt, since there's only one choice.
    30  			Type: []jsonschema.Type{jsonschema.ArrayType},
    31  			Enum: []interface{}{[]string{string(cloud.OAuth1AuthType)}},
    32  		},
    33  		cloud.EndpointKey: {
    34  			Singular: "the API endpoint url",
    35  			Type:     []jsonschema.Type{jsonschema.StringType},
    36  			Format:   jsonschema.FormatURI,
    37  		},
    38  	},
    39  }
    40  
    41  // Logger for the MAAS provider.
    42  var logger = loggo.GetLogger("juju.provider.maas")
    43  
    44  type MaasEnvironProvider struct {
    45  	environProviderCredentials
    46  
    47  	// GetCapabilities is a function that connects to MAAS to return its set of
    48  	// capabilities.
    49  	GetCapabilities MaasCapabilities
    50  }
    51  
    52  var _ environs.EnvironProvider = (*MaasEnvironProvider)(nil)
    53  
    54  var providerInstance MaasEnvironProvider
    55  
    56  // Version is part of the EnvironProvider interface.
    57  func (MaasEnvironProvider) Version() int {
    58  	return 0
    59  }
    60  
    61  func (MaasEnvironProvider) Open(args environs.OpenParams) (environs.Environ, error) {
    62  	logger.Debugf("opening model %q.", args.Config.Name())
    63  	if err := validateCloudSpec(args.Cloud); err != nil {
    64  		return nil, errors.Annotate(err, "validating cloud spec")
    65  	}
    66  	env, err := NewEnviron(args.Cloud, args.Config, nil)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return env, nil
    71  }
    72  
    73  var errAgentNameAlreadySet = errors.New(
    74  	"maas-agent-name is already set; this should not be set by hand")
    75  
    76  // CloudSchema returns the schema for adding new clouds of this type.
    77  func (p MaasEnvironProvider) CloudSchema() *jsonschema.Schema {
    78  	return cloudSchema
    79  }
    80  
    81  // Ping tests the connection to the cloud, to verify the endpoint is valid.
    82  func (p MaasEnvironProvider) Ping(ctx context.ProviderCallContext, endpoint string) error {
    83  	base, version, includesVersion := gomaasapi.SplitVersionedURL(endpoint)
    84  	if includesVersion {
    85  		err := p.checkMaas(base, version)
    86  		if err == nil {
    87  			return nil
    88  		}
    89  	} else {
    90  		// No version info in the endpoint - try both in preference order.
    91  		err := p.checkMaas(endpoint, apiVersion2)
    92  		if err == nil {
    93  			return nil
    94  		}
    95  		err = p.checkMaas(endpoint, apiVersion1)
    96  		if err == nil {
    97  			return nil
    98  		}
    99  	}
   100  	return errors.Errorf("No MAAS server running at %s", endpoint)
   101  }
   102  
   103  func (p MaasEnvironProvider) checkMaas(endpoint, ver string) error {
   104  	c, err := gomaasapi.NewAnonymousClient(endpoint, ver)
   105  	if err != nil {
   106  		logger.Debugf("Can't create maas API %s client for %q: %v", ver, endpoint, err)
   107  		return errors.Trace(err)
   108  	}
   109  	maas := gomaasapi.NewMAAS(*c)
   110  	_, err = p.GetCapabilities(maas, endpoint)
   111  	return errors.Trace(err)
   112  }
   113  
   114  // PrepareConfig is specified in the EnvironProvider interface.
   115  func (p MaasEnvironProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   116  	if err := validateCloudSpec(args.Cloud); err != nil {
   117  		return nil, errors.Annotate(err, "validating cloud spec")
   118  	}
   119  	var attrs map[string]interface{}
   120  	if _, ok := args.Config.StorageDefaultBlockSource(); !ok {
   121  		attrs = map[string]interface{}{
   122  			config.StorageDefaultBlockSourceKey: maasStorageProviderType,
   123  		}
   124  	}
   125  	if len(attrs) == 0 {
   126  		return args.Config, nil
   127  	}
   128  	return args.Config.Apply(attrs)
   129  }
   130  
   131  func verifyCredentials(env *maasEnviron, ctx context.ProviderCallContext) error {
   132  	// Verify we can connect to the server and authenticate.
   133  	if env.usingMAAS2() {
   134  		// The maas2 controller verifies credentials at creation time.
   135  		return nil
   136  	}
   137  	_, err := env.getMAASClient().GetSubObject("maas").CallGet("get_config", nil)
   138  	if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied {
   139  		logger.Debugf("authentication failed: %v", err)
   140  		return errors.New(`authentication failed.
   141  
   142  Please ensure the credentials are correct.`)
   143  	}
   144  	return nil
   145  }
   146  
   147  // DetectRegions is specified in the environs.CloudRegionDetector interface.
   148  func (p MaasEnvironProvider) DetectRegions() ([]cloud.Region, error) {
   149  	return nil, errors.NotFoundf("regions")
   150  }
   151  
   152  func validateCloudSpec(spec environs.CloudSpec) error {
   153  	if err := spec.Validate(); err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	if _, err := parseCloudEndpoint(spec.Endpoint); err != nil {
   157  		return errors.Annotate(err, "validating endpoint")
   158  	}
   159  	if spec.Credential == nil {
   160  		return errors.NotValidf("missing credential")
   161  	}
   162  	if authType := spec.Credential.AuthType(); authType != cloud.OAuth1AuthType {
   163  		return errors.NotSupportedf("%q auth-type", authType)
   164  	}
   165  	if _, err := parseOAuthToken(*spec.Credential); err != nil {
   166  		return errors.Annotate(err, "validating MAAS OAuth token")
   167  	}
   168  	return nil
   169  }
   170  
   171  func parseCloudEndpoint(endpoint string) (server string, _ error) {
   172  	// For MAAS, the cloud endpoint may be either a full URL
   173  	// for the MAAS server, or just the IP/host.
   174  	if endpoint == "" {
   175  		return "", errors.New("MAAS server not specified")
   176  	}
   177  	server = endpoint
   178  	if url, err := url.Parse(server); err != nil || url.Scheme == "" {
   179  		server = fmt.Sprintf("http://%s/MAAS", endpoint)
   180  		if _, err := url.Parse(server); err != nil {
   181  			return "", errors.NotValidf("endpoint %q", endpoint)
   182  		}
   183  	}
   184  	return server, nil
   185  }