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 }