blob: d24405327aa4513d98610722044286abf43d2d07 [file] [log] [blame]
Ash Wilson8ba82242014-08-28 15:38:55 -04001package openstack
2
3import (
Ash Wilsona87ee062014-09-03 11:26:06 -04004 "fmt"
Ash Wilsoned6a1d82014-09-03 12:01:00 -04005 "strings"
Ash Wilson4dee1b82014-08-29 14:56:45 -04006
Ash Wilson8ba82242014-08-28 15:38:55 -04007 "github.com/rackspace/gophercloud"
Ash Wilson11c98282014-09-08 16:05:10 -04008 identity2 "github.com/rackspace/gophercloud/openstack/identity/v2"
Ash Wilsonb8401a72014-09-08 17:07:49 -04009 endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
10 services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
Ash Wilsona87ee062014-09-03 11:26:06 -040011 tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
Ash Wilson4dee1b82014-08-29 14:56:45 -040012 "github.com/rackspace/gophercloud/openstack/utils"
Ash Wilson8ba82242014-08-28 15:38:55 -040013)
14
Ash Wilson4dee1b82014-08-29 14:56:45 -040015const (
16 v20 = "v2.0"
17 v30 = "v3.0"
18)
Ash Wilson8ba82242014-08-28 15:38:55 -040019
Ash Wilsona87ee062014-09-03 11:26:06 -040020// NewClient prepares an unauthenticated ProviderClient instance.
21// Most users will probably prefer using the AuthenticatedClient function instead.
22// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
23// for example.
24func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
Ash Wilsone7da01c2014-09-09 12:31:06 -040025 if !strings.HasSuffix(endpoint, "/") {
26 endpoint = endpoint + "/"
27 }
28
Ash Wilsona0c4c842014-09-09 11:30:58 -040029 return &gophercloud.ProviderClient{IdentityEndpoint: endpoint}, nil
Ash Wilsona87ee062014-09-03 11:26:06 -040030}
31
32// 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 -040033// returns a Client instance that's ready to operate.
Ash Wilson8ba82242014-08-28 15:38:55 -040034// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
35// the most recent identity service available to proceed.
Ash Wilsona87ee062014-09-03 11:26:06 -040036func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
37 client, err := NewClient(options.IdentityEndpoint)
38 if err != nil {
39 return nil, err
40 }
41
42 err = Authenticate(client, options)
Ash Wilsonccd020b2014-09-02 10:40:54 -040043 if err != nil {
44 return nil, err
45 }
46 return client, nil
47}
48
Ash Wilsonccd020b2014-09-02 10:40:54 -040049// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
Ash Wilsona87ee062014-09-03 11:26:06 -040050func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
Ash Wilson4dee1b82014-08-29 14:56:45 -040051 versions := []*utils.Version{
52 &utils.Version{ID: v20, Priority: 20},
53 &utils.Version{ID: v30, Priority: 30},
54 }
55
Ash Wilsona87ee062014-09-03 11:26:06 -040056 chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions)
Ash Wilson4dee1b82014-08-29 14:56:45 -040057 if err != nil {
Ash Wilsonccd020b2014-09-02 10:40:54 -040058 return err
Ash Wilson4dee1b82014-08-29 14:56:45 -040059 }
60
61 switch chosen.ID {
62 case v20:
Ash Wilson11c98282014-09-08 16:05:10 -040063 v2Client := NewIdentityV2(client)
64 v2Client.Endpoint = endpoint
Ash Wilson11c98282014-09-08 16:05:10 -040065
66 result, err := identity2.Authenticate(v2Client, options)
67 if err != nil {
68 return err
69 }
70
71 token, err := identity2.GetToken(result)
72 if err != nil {
73 return err
74 }
Ash Wilsonb8401a72014-09-08 17:07:49 -040075
Ash Wilson11c98282014-09-08 16:05:10 -040076 client.TokenID = token.ID
Ash Wilsonb8401a72014-09-08 17:07:49 -040077 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
Ash Wilson9d9876b2014-09-09 09:28:00 -040078 return v2endpointLocator(result, opts)
Ash Wilsonb8401a72014-09-08 17:07:49 -040079 }
Ash Wilson11c98282014-09-08 16:05:10 -040080
81 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -040082 case v30:
Ash Wilsona87ee062014-09-03 11:26:06 -040083 // Override the generated service endpoint with the one returned by the version endpoint.
84 v3Client := NewIdentityV3(client)
85 v3Client.Endpoint = endpoint
86
87 result, err := tokens3.Create(v3Client, options, nil)
88 if err != nil {
89 return err
90 }
91
92 client.TokenID, err = result.TokenID()
93 if err != nil {
94 return err
95 }
Ash Wilsonb8401a72014-09-08 17:07:49 -040096 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
97 return v3endpointLocator(v3Client, opts)
98 }
Ash Wilsona87ee062014-09-03 11:26:06 -040099
100 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -0400101 default:
Ash Wilsonccd020b2014-09-02 10:40:54 -0400102 // The switch statement must be out of date from the versions list.
Ash Wilsona87ee062014-09-03 11:26:06 -0400103 return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
Ash Wilsonccd020b2014-09-02 10:40:54 -0400104 }
105}
106
Ash Wilson9d9876b2014-09-09 09:28:00 -0400107func v2endpointLocator(authResults identity2.AuthResults, opts gophercloud.EndpointOpts) (string, error) {
108 catalog, err := identity2.GetServiceCatalog(authResults)
109 if err != nil {
110 return "", err
111 }
112
113 entries, err := catalog.CatalogEntries()
114 if err != nil {
115 return "", err
116 }
117
118 // 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 -0400119 var endpoints = make([]identity2.Endpoint, 0, 1)
Ash Wilson9d9876b2014-09-09 09:28:00 -0400120 for _, entry := range entries {
Ash Wilsone7da01c2014-09-09 12:31:06 -0400121 if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
Ash Wilson9d9876b2014-09-09 09:28:00 -0400122 for _, endpoint := range entry.Endpoints {
123 if opts.Region == "" || endpoint.Region == opts.Region {
124 endpoints = append(endpoints, endpoint)
125 }
126 }
127 }
128 }
129
130 // Report an error if the options were ambiguous.
131 if len(endpoints) == 0 {
132 return "", gophercloud.ErrEndpointNotFound
133 }
134 if len(endpoints) > 1 {
135 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
136 }
137
138 // Extract the appropriate URL from the matching Endpoint.
139 for _, endpoint := range endpoints {
140 switch opts.URLType {
141 case "public", "":
142 return endpoint.PublicURL, nil
143 case "private":
144 return endpoint.InternalURL, nil
145 default:
146 return "", fmt.Errorf("Unexpected URLType in endpoint query: %s", opts.URLType)
147 }
148 }
149
Ash Wilsonb8401a72014-09-08 17:07:49 -0400150 return "", gophercloud.ErrEndpointNotFound
151}
152
153func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
154 // Transform URLType into an Interface.
155 var endpointInterface = endpoints3.InterfacePublic
156 switch opts.URLType {
157 case "", "public":
158 endpointInterface = endpoints3.InterfacePublic
159 case "internal":
160 endpointInterface = endpoints3.InterfaceInternal
161 case "admin":
162 endpointInterface = endpoints3.InterfaceAdmin
163 default:
164 return "", fmt.Errorf("Unrecognized URLType: %s", opts.URLType)
165 }
166
167 // Discover the service we're interested in.
Ash Wilson1cd3e692014-09-09 11:01:47 -0400168 serviceResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
Ash Wilsonb8401a72014-09-08 17:07:49 -0400169 if err != nil {
170 return "", err
171 }
172
Ash Wilson1cd3e692014-09-09 11:01:47 -0400173 allServiceResults, err := gophercloud.AllPages(serviceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400174 if err != nil {
175 return "", err
176 }
Ash Wilson1cd3e692014-09-09 11:01:47 -0400177 allServices := services3.AsServices(allServiceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400178
179 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400180 filtered := make([]services3.Service, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400181 for _, service := range allServices {
182 if service.Name == opts.Name {
183 filtered = append(filtered, service)
184 }
185 }
186 allServices = filtered
187 }
188
189 if len(allServices) == 0 {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400190 return "", gophercloud.ErrServiceNotFound
Ash Wilsonb8401a72014-09-08 17:07:49 -0400191 }
192 if len(allServices) > 1 {
193 return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
194 }
195
196 service := allServices[0]
197
198 // Enumerate the endpoints available for this service.
199 endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
200 Interface: endpointInterface,
201 ServiceID: service.ID,
202 })
203 if err != nil {
204 return "", err
205 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400206 allEndpoints, err := gophercloud.AllPages(endpointResults)
207 if err != nil {
208 return "", err
209 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400210 endpoints := endpoints3.AsEndpoints(allEndpoints)
211
212 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400213 filtered := make([]endpoints3.Endpoint, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400214 for _, endpoint := range endpoints {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400215 if opts.Region == "" || endpoint.Region == opts.Region {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400216 filtered = append(filtered, endpoint)
217 }
218 }
219 endpoints = filtered
220 }
221
222 if len(endpoints) == 0 {
223 return "", gophercloud.ErrEndpointNotFound
224 }
225 if len(endpoints) > 1 {
226 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
227 }
228
229 endpoint := endpoints[0]
230
231 return endpoint.URL, nil
232}
233
Ash Wilsona87ee062014-09-03 11:26:06 -0400234// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
235func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilsone7da01c2014-09-09 12:31:06 -0400236 v2Endpoint := client.IdentityEndpoint + "v2.0/"
Ash Wilsonccd020b2014-09-02 10:40:54 -0400237
Ash Wilsona87ee062014-09-03 11:26:06 -0400238 return &gophercloud.ServiceClient{
239 Provider: client,
240 Endpoint: v2Endpoint,
Ash Wilson4dee1b82014-08-29 14:56:45 -0400241 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400242}
243
Ash Wilsona87ee062014-09-03 11:26:06 -0400244// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
245func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilsone7da01c2014-09-09 12:31:06 -0400246 v3Endpoint := client.IdentityEndpoint + "v3/"
Ash Wilsona87ee062014-09-03 11:26:06 -0400247
248 return &gophercloud.ServiceClient{
249 Provider: client,
250 Endpoint: v3Endpoint,
251 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400252}
Ash Wilson1cd3e692014-09-09 11:01:47 -0400253
254// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
255func NewStorageV1(client *gophercloud.ProviderClient, region string) (*gophercloud.ServiceClient, error) {
256 url, err := client.EndpointLocator(gophercloud.EndpointOpts{Type: "object-store", Name: "swift"})
257 if err != nil {
258 return nil, err
259 }
260 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
261}