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 }