blob: 207ba13e7efa239704c02ceb618b0865ad30ba53 [file] [log] [blame]
Ash Wilson8ba82242014-08-28 15:38:55 -04001package openstack
2
3import (
Ash Wilsona87ee062014-09-03 11:26:06 -04004 "fmt"
5 "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) {
26 // Normalize the identity endpoint that's provided by trimming any path, query or fragment from the URL.
27 u, err := url.Parse(endpoint)
28 if err != nil {
29 return nil, err
30 }
31 u.Path, u.RawQuery, u.Fragment = "", "", ""
32 normalized := u.String()
33
Ash Wilson8e434bf2014-09-08 17:10:04 -040034 return &gophercloud.ProviderClient{IdentityEndpoint: normalized}, nil
Ash Wilsona87ee062014-09-03 11:26:06 -040035}
36
37// 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 -040038// returns a Client instance that's ready to operate.
Ash Wilson8ba82242014-08-28 15:38:55 -040039// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
40// the most recent identity service available to proceed.
Ash Wilsona87ee062014-09-03 11:26:06 -040041func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
42 client, err := NewClient(options.IdentityEndpoint)
43 if err != nil {
44 return nil, err
45 }
46
47 err = Authenticate(client, options)
Ash Wilsonccd020b2014-09-02 10:40:54 -040048 if err != nil {
49 return nil, err
50 }
51 return client, nil
52}
53
Ash Wilsonccd020b2014-09-02 10:40:54 -040054// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
Ash Wilsona87ee062014-09-03 11:26:06 -040055func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
Ash Wilson4dee1b82014-08-29 14:56:45 -040056 versions := []*utils.Version{
57 &utils.Version{ID: v20, Priority: 20},
58 &utils.Version{ID: v30, Priority: 30},
59 }
60
Ash Wilsona87ee062014-09-03 11:26:06 -040061 chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions)
Ash Wilson4dee1b82014-08-29 14:56:45 -040062 if err != nil {
Ash Wilsonccd020b2014-09-02 10:40:54 -040063 return err
Ash Wilson4dee1b82014-08-29 14:56:45 -040064 }
65
Ash Wilsoned6a1d82014-09-03 12:01:00 -040066 if !strings.HasSuffix(endpoint, "/") {
67 endpoint = endpoint + "/"
68 }
69
Ash Wilson4dee1b82014-08-29 14:56:45 -040070 switch chosen.ID {
71 case v20:
Ash Wilson11c98282014-09-08 16:05:10 -040072 v2Client := NewIdentityV2(client)
73 v2Client.Endpoint = endpoint
Ash Wilson11c98282014-09-08 16:05:10 -040074
75 result, err := identity2.Authenticate(v2Client, options)
76 if err != nil {
77 return err
78 }
79
80 token, err := identity2.GetToken(result)
81 if err != nil {
82 return err
83 }
Ash Wilsonb8401a72014-09-08 17:07:49 -040084
Ash Wilson11c98282014-09-08 16:05:10 -040085 client.TokenID = token.ID
Ash Wilsonb8401a72014-09-08 17:07:49 -040086 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
Ash Wilson9d9876b2014-09-09 09:28:00 -040087 return v2endpointLocator(result, opts)
Ash Wilsonb8401a72014-09-08 17:07:49 -040088 }
Ash Wilson11c98282014-09-08 16:05:10 -040089
90 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -040091 case v30:
Ash Wilsona87ee062014-09-03 11:26:06 -040092 // Override the generated service endpoint with the one returned by the version endpoint.
93 v3Client := NewIdentityV3(client)
94 v3Client.Endpoint = endpoint
95
96 result, err := tokens3.Create(v3Client, options, nil)
97 if err != nil {
98 return err
99 }
100
101 client.TokenID, err = result.TokenID()
102 if err != nil {
103 return err
104 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400105 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
106 return v3endpointLocator(v3Client, opts)
107 }
Ash Wilsona87ee062014-09-03 11:26:06 -0400108
109 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -0400110 default:
Ash Wilsonccd020b2014-09-02 10:40:54 -0400111 // The switch statement must be out of date from the versions list.
Ash Wilsona87ee062014-09-03 11:26:06 -0400112 return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
Ash Wilsonccd020b2014-09-02 10:40:54 -0400113 }
114}
115
Ash Wilson9d9876b2014-09-09 09:28:00 -0400116func v2endpointLocator(authResults identity2.AuthResults, opts gophercloud.EndpointOpts) (string, error) {
117 catalog, err := identity2.GetServiceCatalog(authResults)
118 if err != nil {
119 return "", err
120 }
121
122 entries, err := catalog.CatalogEntries()
123 if err != nil {
124 return "", err
125 }
126
127 // Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
128 var endpoints = make([]identity2.Endpoint, 0, 6)
129 for _, entry := range entries {
130 matched := true
131
132 if entry.Type != opts.Type {
133 matched = false
134 }
135
136 if opts.Name != "" && entry.Name != opts.Name {
137 matched = false
138 }
139
140 if matched {
141 for _, endpoint := range entry.Endpoints {
142 if opts.Region == "" || endpoint.Region == opts.Region {
143 endpoints = append(endpoints, endpoint)
144 }
145 }
146 }
147 }
148
149 // Report an error if the options were ambiguous.
150 if len(endpoints) == 0 {
151 return "", gophercloud.ErrEndpointNotFound
152 }
153 if len(endpoints) > 1 {
154 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
155 }
156
157 // Extract the appropriate URL from the matching Endpoint.
158 for _, endpoint := range endpoints {
159 switch opts.URLType {
160 case "public", "":
161 return endpoint.PublicURL, nil
162 case "private":
163 return endpoint.InternalURL, nil
164 default:
165 return "", fmt.Errorf("Unexpected URLType in endpoint query: %s", opts.URLType)
166 }
167 }
168
Ash Wilsonb8401a72014-09-08 17:07:49 -0400169 return "", gophercloud.ErrEndpointNotFound
170}
171
172func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
173 // Transform URLType into an Interface.
174 var endpointInterface = endpoints3.InterfacePublic
175 switch opts.URLType {
176 case "", "public":
177 endpointInterface = endpoints3.InterfacePublic
178 case "internal":
179 endpointInterface = endpoints3.InterfaceInternal
180 case "admin":
181 endpointInterface = endpoints3.InterfaceAdmin
182 default:
183 return "", fmt.Errorf("Unrecognized URLType: %s", opts.URLType)
184 }
185
186 // Discover the service we're interested in.
Ash Wilson1cd3e692014-09-09 11:01:47 -0400187 serviceResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
Ash Wilsonb8401a72014-09-08 17:07:49 -0400188 if err != nil {
189 return "", err
190 }
191
Ash Wilson1cd3e692014-09-09 11:01:47 -0400192 allServiceResults, err := gophercloud.AllPages(serviceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400193 if err != nil {
194 return "", err
195 }
Ash Wilson1cd3e692014-09-09 11:01:47 -0400196 allServices := services3.AsServices(allServiceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400197
198 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400199 filtered := make([]services3.Service, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400200 for _, service := range allServices {
201 if service.Name == opts.Name {
202 filtered = append(filtered, service)
203 }
204 }
205 allServices = filtered
206 }
207
208 if len(allServices) == 0 {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400209 return "", gophercloud.ErrServiceNotFound
Ash Wilsonb8401a72014-09-08 17:07:49 -0400210 }
211 if len(allServices) > 1 {
212 return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
213 }
214
215 service := allServices[0]
216
217 // Enumerate the endpoints available for this service.
218 endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
219 Interface: endpointInterface,
220 ServiceID: service.ID,
221 })
222 if err != nil {
223 return "", err
224 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400225 allEndpoints, err := gophercloud.AllPages(endpointResults)
226 if err != nil {
227 return "", err
228 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400229 endpoints := endpoints3.AsEndpoints(allEndpoints)
230
231 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400232 filtered := make([]endpoints3.Endpoint, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400233 for _, endpoint := range endpoints {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400234 if opts.Region == "" || endpoint.Region == opts.Region {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400235 filtered = append(filtered, endpoint)
236 }
237 }
238 endpoints = filtered
239 }
240
241 if len(endpoints) == 0 {
242 return "", gophercloud.ErrEndpointNotFound
243 }
244 if len(endpoints) > 1 {
245 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
246 }
247
248 endpoint := endpoints[0]
249
250 return endpoint.URL, nil
251}
252
Ash Wilsona87ee062014-09-03 11:26:06 -0400253// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
254func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilsoned6a1d82014-09-03 12:01:00 -0400255 v2Endpoint := client.IdentityEndpoint + "/v2.0/"
Ash Wilsonccd020b2014-09-02 10:40:54 -0400256
Ash Wilsona87ee062014-09-03 11:26:06 -0400257 return &gophercloud.ServiceClient{
258 Provider: client,
259 Endpoint: v2Endpoint,
Ash Wilson4dee1b82014-08-29 14:56:45 -0400260 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400261}
262
Ash Wilsona87ee062014-09-03 11:26:06 -0400263// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
264func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilsoned6a1d82014-09-03 12:01:00 -0400265 v3Endpoint := client.IdentityEndpoint + "/v3/"
Ash Wilsona87ee062014-09-03 11:26:06 -0400266
267 return &gophercloud.ServiceClient{
268 Provider: client,
269 Endpoint: v3Endpoint,
270 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400271}
Ash Wilson1cd3e692014-09-09 11:01:47 -0400272
273// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
274func NewStorageV1(client *gophercloud.ProviderClient, region string) (*gophercloud.ServiceClient, error) {
275 url, err := client.EndpointLocator(gophercloud.EndpointOpts{Type: "object-store", Name: "swift"})
276 if err != nil {
277 return nil, err
278 }
279 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
280}