github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/provider.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package vsphere
     5  
     6  import (
     7  	"net/url"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/jsonschema"
    11  	"github.com/juju/loggo"
    12  	"golang.org/x/net/context"
    13  
    14  	"github.com/juju/juju/cloud"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/config"
    17  	callcontext "github.com/juju/juju/environs/context"
    18  )
    19  
    20  var logger = loggo.GetLogger("juju.provider.vmware")
    21  
    22  const (
    23  	// provider version 1 organises VMs into folders.
    24  	providerVersion1 = 1
    25  
    26  	currentProviderVersion = providerVersion1
    27  )
    28  
    29  type environProvider struct {
    30  	environProviderCredentials
    31  	dial DialFunc
    32  }
    33  
    34  // EnvironProviderConfig contains configuration for the EnvironProvider.
    35  type EnvironProviderConfig struct {
    36  	// Dial is a function used for dialing connections to vCenter/ESXi.
    37  	Dial DialFunc
    38  }
    39  
    40  // NewEnvironProvider returns a new environs.EnvironProvider that will
    41  // dial vSphere connectons with the given dial function.
    42  func NewEnvironProvider(config EnvironProviderConfig) environs.CloudEnvironProvider {
    43  	return &environProvider{
    44  		dial: config.Dial,
    45  	}
    46  }
    47  
    48  // Version implements environs.EnvironProvider.
    49  func (p *environProvider) Version() int {
    50  	return currentProviderVersion
    51  }
    52  
    53  // Open implements environs.EnvironProvider.
    54  func (p *environProvider) Open(args environs.OpenParams) (environs.Environ, error) {
    55  	if err := validateCloudSpec(args.Cloud); err != nil {
    56  		return nil, errors.Annotate(err, "validating cloud spec")
    57  	}
    58  	env, err := newEnviron(p, args.Cloud, args.Config)
    59  	return env, errors.Trace(err)
    60  }
    61  
    62  var cloudSchema = &jsonschema.Schema{
    63  	Type:     []jsonschema.Type{jsonschema.ObjectType},
    64  	Required: []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey},
    65  	Order:    []string{cloud.EndpointKey, cloud.AuthTypesKey, cloud.RegionsKey},
    66  	Properties: map[string]*jsonschema.Schema{
    67  		cloud.EndpointKey: {
    68  			Singular: "the vCenter address or URL",
    69  			Type:     []jsonschema.Type{jsonschema.StringType},
    70  			Format:   jsonschema.FormatURI,
    71  		},
    72  		cloud.AuthTypesKey: {
    73  			// don't need a prompt, since there's only one choice.
    74  			Type: []jsonschema.Type{jsonschema.ArrayType},
    75  			Enum: []interface{}{[]string{string(cloud.UserPassAuthType)}},
    76  		},
    77  		cloud.RegionsKey: {
    78  			Type:     []jsonschema.Type{jsonschema.ObjectType},
    79  			Singular: "datacenter",
    80  			Plural:   "datacenters",
    81  			AdditionalProperties: &jsonschema.Schema{
    82  				Type:          []jsonschema.Type{jsonschema.ObjectType},
    83  				MaxProperties: jsonschema.Int(0),
    84  			},
    85  		},
    86  	},
    87  }
    88  
    89  // CloudSchema returns the schema for adding new clouds of this type.
    90  func (p *environProvider) CloudSchema() *jsonschema.Schema {
    91  	return cloudSchema
    92  }
    93  
    94  const failedLoginMsg = "ServerFaultCode: Cannot complete login due to an incorrect user name or password."
    95  
    96  // Ping tests the connection to the cloud, to verify the endpoint is valid.
    97  func (p *environProvider) Ping(callCtx callcontext.ProviderCallContext, endpoint string) error {
    98  	// try to be smart and not punish people for adding or forgetting http
    99  	u, err := url.Parse(endpoint)
   100  	if err != nil {
   101  		return errors.New("Invalid endpoint format, please give a full url or IP/hostname.")
   102  	}
   103  	switch u.Scheme {
   104  	case "http", "https":
   105  		// good!
   106  	case "":
   107  		u, err = url.Parse("https://" + endpoint + "/sdk")
   108  		if err != nil {
   109  			return errors.New("Invalid endpoint format, please give a full url or IP/hostname.")
   110  		}
   111  	default:
   112  		return errors.New("Invalid endpoint format, please use an http or https URL.")
   113  	}
   114  
   115  	// Set a user, to force the dial function to perform a login. The login
   116  	// should fail, since there's no password set.
   117  	u.User = url.User("juju")
   118  
   119  	ctx := context.Background()
   120  	client, err := p.dial(ctx, u, "")
   121  	if err != nil {
   122  		if err.Error() == failedLoginMsg {
   123  			// This is our expected error for trying to log into
   124  			// vSphere without any creds, so return nil.
   125  			return nil
   126  		}
   127  		logger.Errorf("Unexpected error dialing vSphere connection: %v", err)
   128  		return errors.Errorf("No vCenter/ESXi available at %s", endpoint)
   129  	}
   130  	defer client.Close(ctx)
   131  
   132  	// We shouldn't get here, since we haven't set a password, but it is
   133  	// theoretically possible to have user="juju", password="".
   134  	return nil
   135  }
   136  
   137  // PrepareConfig implements environs.EnvironProvider.
   138  func (p *environProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   139  	if err := validateCloudSpec(args.Cloud); err != nil {
   140  		return nil, errors.Annotate(err, "validating cloud spec")
   141  	}
   142  	return args.Config, nil
   143  }
   144  
   145  // Validate implements environs.EnvironProvider.
   146  func (*environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   147  	if old == nil {
   148  		ecfg, err := newValidConfig(cfg)
   149  		if err != nil {
   150  			return nil, errors.Annotate(err, "invalid config")
   151  		}
   152  		return ecfg.Config, nil
   153  	}
   154  
   155  	ecfg, err := newValidConfig(old)
   156  	if err != nil {
   157  		return nil, errors.Annotate(err, "invalid base config")
   158  	}
   159  
   160  	if err := ecfg.update(cfg); err != nil {
   161  		return nil, errors.Annotate(err, "invalid config change")
   162  	}
   163  
   164  	return ecfg.Config, nil
   165  }
   166  
   167  func validateCloudSpec(spec environs.CloudSpec) error {
   168  	if err := spec.Validate(); err != nil {
   169  		return errors.Trace(err)
   170  	}
   171  	// TODO(axw) add validation of endpoint/region.
   172  	if spec.Credential == nil {
   173  		return errors.NotValidf("missing credential")
   174  	}
   175  	if authType := spec.Credential.AuthType(); authType != cloud.UserPassAuthType {
   176  		return errors.NotSupportedf("%q auth-type", authType)
   177  	}
   178  	return nil
   179  }