blob: 39d39a89fa29329bfb0c877791423d353ce41619 [file] [log] [blame]
Ash Wilson8ba82242014-08-28 15:38:55 -04001package openstack
2
3import (
Ash Wilsona87ee062014-09-03 11:26:06 -04004 "fmt"
Ash Wilson09694b92014-09-09 14:08:27 -04005 "net/url"
Ash Wilsoned6a1d82014-09-03 12:01:00 -04006 "strings"
Ash Wilson4dee1b82014-08-29 14:56:45 -04007
Ash Wilson8ba82242014-08-28 15:38:55 -04008 "github.com/rackspace/gophercloud"
Ash Wilson11c98282014-09-08 16:05:10 -04009 identity2 "github.com/rackspace/gophercloud/openstack/identity/v2"
Ash Wilsonb8401a72014-09-08 17:07:49 -040010 endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
11 services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
Ash Wilsona87ee062014-09-03 11:26:06 -040012 tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
Ash Wilson4dee1b82014-08-29 14:56:45 -040013 "github.com/rackspace/gophercloud/openstack/utils"
Ash Wilson8ba82242014-08-28 15:38:55 -040014)
15
Ash Wilson4dee1b82014-08-29 14:56:45 -040016const (
17 v20 = "v2.0"
18 v30 = "v3.0"
19)
Ash Wilson8ba82242014-08-28 15:38:55 -040020
Ash Wilsona87ee062014-09-03 11:26:06 -040021// NewClient prepares an unauthenticated ProviderClient instance.
22// Most users will probably prefer using the AuthenticatedClient function instead.
23// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
24// for example.
25func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
Ash Wilson09694b92014-09-09 14:08:27 -040026 u, err := url.Parse(endpoint)
27 if err != nil {
28 return nil, err
29 }
30 hadPath := u.Path != ""
31 u.Path, u.RawQuery, u.Fragment = "", "", ""
32 base := u.String()
33
Ash Wilsonaca58d82014-09-10 17:00:35 -040034 endpoint = normalizeURL(endpoint)
35 base = normalizeURL(base)
Ash Wilsone7da01c2014-09-09 12:31:06 -040036
Ash Wilson09694b92014-09-09 14:08:27 -040037 if hadPath {
38 return &gophercloud.ProviderClient{
39 IdentityBase: base,
40 IdentityEndpoint: endpoint,
41 }, nil
42 }
43
44 return &gophercloud.ProviderClient{
45 IdentityBase: base,
46 IdentityEndpoint: "",
47 }, nil
Ash Wilsona87ee062014-09-03 11:26:06 -040048}
49
50// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
Ash Wilsonccd020b2014-09-02 10:40:54 -040051// returns a Client instance that's ready to operate.
Ash Wilson8ba82242014-08-28 15:38:55 -040052// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
53// the most recent identity service available to proceed.
Ash Wilsona87ee062014-09-03 11:26:06 -040054func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
55 client, err := NewClient(options.IdentityEndpoint)
56 if err != nil {
57 return nil, err
58 }
59
60 err = Authenticate(client, options)
Ash Wilsonccd020b2014-09-02 10:40:54 -040061 if err != nil {
62 return nil, err
63 }
64 return client, nil
65}
66
Ash Wilsonccd020b2014-09-02 10:40:54 -040067// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
Ash Wilsona87ee062014-09-03 11:26:06 -040068func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
Ash Wilson4dee1b82014-08-29 14:56:45 -040069 versions := []*utils.Version{
Ash Wilson09694b92014-09-09 14:08:27 -040070 &utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
71 &utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
Ash Wilson4dee1b82014-08-29 14:56:45 -040072 }
73
Ash Wilson09694b92014-09-09 14:08:27 -040074 chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions)
Ash Wilson4dee1b82014-08-29 14:56:45 -040075 if err != nil {
Ash Wilsonccd020b2014-09-02 10:40:54 -040076 return err
Ash Wilson4dee1b82014-08-29 14:56:45 -040077 }
78
79 switch chosen.ID {
80 case v20:
Ash Wilson09694b92014-09-09 14:08:27 -040081 return v2auth(client, endpoint, options)
Ash Wilson4dee1b82014-08-29 14:56:45 -040082 case v30:
Ash Wilson09694b92014-09-09 14:08:27 -040083 return v3auth(client, endpoint, options)
Ash Wilson4dee1b82014-08-29 14:56:45 -040084 default:
Ash Wilsonccd020b2014-09-02 10:40:54 -040085 // The switch statement must be out of date from the versions list.
Ash Wilsona87ee062014-09-03 11:26:06 -040086 return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
Ash Wilsonccd020b2014-09-02 10:40:54 -040087 }
88}
89
Ash Wilson09694b92014-09-09 14:08:27 -040090// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
91func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
92 return v2auth(client, "", options)
93}
94
95func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
96 v2Client := NewIdentityV2(client)
97 if endpoint != "" {
98 v2Client.Endpoint = endpoint
99 }
100
101 result, err := identity2.Authenticate(v2Client, options)
102 if err != nil {
103 return err
104 }
105
106 token, err := identity2.GetToken(result)
107 if err != nil {
108 return err
109 }
110
111 client.TokenID = token.ID
112 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
113 return v2endpointLocator(result, opts)
114 }
115
116 return nil
117}
118
Ash Wilson9d9876b2014-09-09 09:28:00 -0400119func v2endpointLocator(authResults identity2.AuthResults, opts gophercloud.EndpointOpts) (string, error) {
120 catalog, err := identity2.GetServiceCatalog(authResults)
121 if err != nil {
122 return "", err
123 }
124
125 entries, err := catalog.CatalogEntries()
126 if err != nil {
127 return "", err
128 }
129
130 // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
Ash Wilsone7da01c2014-09-09 12:31:06 -0400131 var endpoints = make([]identity2.Endpoint, 0, 1)
Ash Wilson9d9876b2014-09-09 09:28:00 -0400132 for _, entry := range entries {
Ash Wilsone7da01c2014-09-09 12:31:06 -0400133 if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
Ash Wilson9d9876b2014-09-09 09:28:00 -0400134 for _, endpoint := range entry.Endpoints {
135 if opts.Region == "" || endpoint.Region == opts.Region {
136 endpoints = append(endpoints, endpoint)
137 }
138 }
139 }
140 }
141
142 // Report an error if the options were ambiguous.
143 if len(endpoints) == 0 {
144 return "", gophercloud.ErrEndpointNotFound
145 }
146 if len(endpoints) > 1 {
147 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
148 }
149
150 // Extract the appropriate URL from the matching Endpoint.
151 for _, endpoint := range endpoints {
Ash Wilsonefac18b2014-09-10 14:44:42 -0400152 switch opts.Availability {
153 case gophercloud.AvailabilityPublic:
Ash Wilsonaca58d82014-09-10 17:00:35 -0400154 return normalizeURL(endpoint.PublicURL), nil
Ash Wilsonefac18b2014-09-10 14:44:42 -0400155 case gophercloud.AvailabilityInternal:
Ash Wilsonaca58d82014-09-10 17:00:35 -0400156 return normalizeURL(endpoint.InternalURL), nil
Ash Wilson9d9876b2014-09-09 09:28:00 -0400157 default:
Ash Wilsonefac18b2014-09-10 14:44:42 -0400158 return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
Ash Wilson9d9876b2014-09-09 09:28:00 -0400159 }
160 }
161
Ash Wilsonb8401a72014-09-08 17:07:49 -0400162 return "", gophercloud.ErrEndpointNotFound
163}
164
Ash Wilson09694b92014-09-09 14:08:27 -0400165// AuthenticateV3 explicitly authenticates against the identity v3 service.
166func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
167 return v3auth(client, "", options)
168}
169
170func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
171 // Override the generated service endpoint with the one returned by the version endpoint.
172 v3Client := NewIdentityV3(client)
173 if endpoint != "" {
174 v3Client.Endpoint = endpoint
175 }
176
177 result, err := tokens3.Create(v3Client, options, nil)
178 if err != nil {
179 return err
180 }
181
182 client.TokenID, err = result.TokenID()
183 if err != nil {
184 return err
185 }
186 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
187 return v3endpointLocator(v3Client, opts)
188 }
189
190 return nil
191}
192
Ash Wilsonb8401a72014-09-08 17:07:49 -0400193func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400194 // Discover the service we're interested in.
Ash Wilson1cd3e692014-09-09 11:01:47 -0400195 serviceResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
Ash Wilsonb8401a72014-09-08 17:07:49 -0400196 if err != nil {
197 return "", err
198 }
199
Ash Wilson1cd3e692014-09-09 11:01:47 -0400200 allServiceResults, err := gophercloud.AllPages(serviceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400201 if err != nil {
202 return "", err
203 }
Ash Wilson1cd3e692014-09-09 11:01:47 -0400204 allServices := services3.AsServices(allServiceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400205
206 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400207 filtered := make([]services3.Service, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400208 for _, service := range allServices {
209 if service.Name == opts.Name {
210 filtered = append(filtered, service)
211 }
212 }
213 allServices = filtered
214 }
215
216 if len(allServices) == 0 {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400217 return "", gophercloud.ErrServiceNotFound
Ash Wilsonb8401a72014-09-08 17:07:49 -0400218 }
219 if len(allServices) > 1 {
220 return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
221 }
222
223 service := allServices[0]
224
225 // Enumerate the endpoints available for this service.
226 endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
Ash Wilsonefac18b2014-09-10 14:44:42 -0400227 Availability: opts.Availability,
228 ServiceID: service.ID,
Ash Wilsonb8401a72014-09-08 17:07:49 -0400229 })
230 if err != nil {
231 return "", err
232 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400233 allEndpoints, err := gophercloud.AllPages(endpointResults)
234 if err != nil {
235 return "", err
236 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400237 endpoints := endpoints3.AsEndpoints(allEndpoints)
238
239 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400240 filtered := make([]endpoints3.Endpoint, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400241 for _, endpoint := range endpoints {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400242 if opts.Region == "" || endpoint.Region == opts.Region {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400243 filtered = append(filtered, endpoint)
244 }
245 }
246 endpoints = filtered
247 }
248
249 if len(endpoints) == 0 {
250 return "", gophercloud.ErrEndpointNotFound
251 }
252 if len(endpoints) > 1 {
253 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
254 }
255
256 endpoint := endpoints[0]
257
Ash Wilsonaca58d82014-09-10 17:00:35 -0400258 return normalizeURL(endpoint.URL), nil
259}
260
261// normalizeURL ensures that each endpoint URL has a closing `/`, as expected by ServiceClient.
262func normalizeURL(url string) string {
263 if !strings.HasSuffix(url, "/") {
264 return url + "/"
265 }
266 return url
Ash Wilsonb8401a72014-09-08 17:07:49 -0400267}
268
Ash Wilsona87ee062014-09-03 11:26:06 -0400269// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
270func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400271 v2Endpoint := client.IdentityBase + "v2.0/"
Ash Wilsonccd020b2014-09-02 10:40:54 -0400272
Ash Wilsona87ee062014-09-03 11:26:06 -0400273 return &gophercloud.ServiceClient{
274 Provider: client,
275 Endpoint: v2Endpoint,
Ash Wilson4dee1b82014-08-29 14:56:45 -0400276 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400277}
278
Ash Wilsona87ee062014-09-03 11:26:06 -0400279// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
280func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400281 v3Endpoint := client.IdentityBase + "v3/"
Ash Wilsona87ee062014-09-03 11:26:06 -0400282
283 return &gophercloud.ServiceClient{
284 Provider: client,
285 Endpoint: v3Endpoint,
286 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400287}
Ash Wilson1cd3e692014-09-09 11:01:47 -0400288
289// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
Jon Perritt509fbb62014-09-10 13:29:56 -0500290func NewStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
291 eo.ApplyDefaults("object-store")
292 url, err := client.EndpointLocator(eo)
Ash Wilson1cd3e692014-09-09 11:01:47 -0400293 if err != nil {
294 return nil, err
295 }
296 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
297}