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 }