blob: 3bbff12ee7be29588f3b28971cf3f28612767fdf [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 Wilsone7da01c2014-09-09 12:31:06 -040034 if !strings.HasSuffix(endpoint, "/") {
35 endpoint = endpoint + "/"
36 }
Ash Wilson09694b92014-09-09 14:08:27 -040037 if !strings.HasSuffix(base, "/") {
38 base = base + "/"
39 }
Ash Wilsone7da01c2014-09-09 12:31:06 -040040
Ash Wilson09694b92014-09-09 14:08:27 -040041 if hadPath {
42 return &gophercloud.ProviderClient{
43 IdentityBase: base,
44 IdentityEndpoint: endpoint,
45 }, nil
46 }
47
48 return &gophercloud.ProviderClient{
49 IdentityBase: base,
50 IdentityEndpoint: "",
51 }, nil
Ash Wilsona87ee062014-09-03 11:26:06 -040052}
53
54// 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 -040055// returns a Client instance that's ready to operate.
Ash Wilson8ba82242014-08-28 15:38:55 -040056// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
57// the most recent identity service available to proceed.
Ash Wilsona87ee062014-09-03 11:26:06 -040058func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
59 client, err := NewClient(options.IdentityEndpoint)
60 if err != nil {
61 return nil, err
62 }
63
64 err = Authenticate(client, options)
Ash Wilsonccd020b2014-09-02 10:40:54 -040065 if err != nil {
66 return nil, err
67 }
68 return client, nil
69}
70
Ash Wilsonccd020b2014-09-02 10:40:54 -040071// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
Ash Wilsona87ee062014-09-03 11:26:06 -040072func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
Ash Wilson4dee1b82014-08-29 14:56:45 -040073 versions := []*utils.Version{
Ash Wilson09694b92014-09-09 14:08:27 -040074 &utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
75 &utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
Ash Wilson4dee1b82014-08-29 14:56:45 -040076 }
77
Ash Wilson09694b92014-09-09 14:08:27 -040078 chosen, endpoint, err := utils.ChooseVersion(client.IdentityBase, client.IdentityEndpoint, versions)
Ash Wilson4dee1b82014-08-29 14:56:45 -040079 if err != nil {
Ash Wilsonccd020b2014-09-02 10:40:54 -040080 return err
Ash Wilson4dee1b82014-08-29 14:56:45 -040081 }
82
83 switch chosen.ID {
84 case v20:
Ash Wilson09694b92014-09-09 14:08:27 -040085 return v2auth(client, endpoint, options)
Ash Wilson4dee1b82014-08-29 14:56:45 -040086 case v30:
Ash Wilson09694b92014-09-09 14:08:27 -040087 return v3auth(client, endpoint, options)
Ash Wilson4dee1b82014-08-29 14:56:45 -040088 default:
Ash Wilsonccd020b2014-09-02 10:40:54 -040089 // The switch statement must be out of date from the versions list.
Ash Wilsona87ee062014-09-03 11:26:06 -040090 return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
Ash Wilsonccd020b2014-09-02 10:40:54 -040091 }
92}
93
Ash Wilson09694b92014-09-09 14:08:27 -040094// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
95func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
96 return v2auth(client, "", options)
97}
98
99func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
100 v2Client := NewIdentityV2(client)
101 if endpoint != "" {
102 v2Client.Endpoint = endpoint
103 }
104
105 result, err := identity2.Authenticate(v2Client, options)
106 if err != nil {
107 return err
108 }
109
110 token, err := identity2.GetToken(result)
111 if err != nil {
112 return err
113 }
114
115 client.TokenID = token.ID
116 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
117 return v2endpointLocator(result, opts)
118 }
119
120 return nil
121}
122
Ash Wilson9d9876b2014-09-09 09:28:00 -0400123func v2endpointLocator(authResults identity2.AuthResults, opts gophercloud.EndpointOpts) (string, error) {
124 catalog, err := identity2.GetServiceCatalog(authResults)
125 if err != nil {
126 return "", err
127 }
128
129 entries, err := catalog.CatalogEntries()
130 if err != nil {
131 return "", err
132 }
133
134 // 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 -0400135 var endpoints = make([]identity2.Endpoint, 0, 1)
Ash Wilson9d9876b2014-09-09 09:28:00 -0400136 for _, entry := range entries {
Ash Wilsone7da01c2014-09-09 12:31:06 -0400137 if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
Ash Wilson9d9876b2014-09-09 09:28:00 -0400138 for _, endpoint := range entry.Endpoints {
139 if opts.Region == "" || endpoint.Region == opts.Region {
140 endpoints = append(endpoints, endpoint)
141 }
142 }
143 }
144 }
145
146 // Report an error if the options were ambiguous.
147 if len(endpoints) == 0 {
148 return "", gophercloud.ErrEndpointNotFound
149 }
150 if len(endpoints) > 1 {
151 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
152 }
153
154 // Extract the appropriate URL from the matching Endpoint.
155 for _, endpoint := range endpoints {
Ash Wilson0747d7e2014-09-09 14:27:35 -0400156 switch opts.Interface {
157 case gophercloud.InterfacePublic:
Ash Wilson9d9876b2014-09-09 09:28:00 -0400158 return endpoint.PublicURL, nil
Ash Wilson0747d7e2014-09-09 14:27:35 -0400159 case gophercloud.InterfaceInternal:
Ash Wilson9d9876b2014-09-09 09:28:00 -0400160 return endpoint.InternalURL, nil
161 default:
Ash Wilson0747d7e2014-09-09 14:27:35 -0400162 return "", fmt.Errorf("Unexpected interface in endpoint query: %s", opts.Interface)
Ash Wilson9d9876b2014-09-09 09:28:00 -0400163 }
164 }
165
Ash Wilsonb8401a72014-09-08 17:07:49 -0400166 return "", gophercloud.ErrEndpointNotFound
167}
168
Ash Wilson09694b92014-09-09 14:08:27 -0400169// AuthenticateV3 explicitly authenticates against the identity v3 service.
170func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
171 return v3auth(client, "", options)
172}
173
174func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
175 // Override the generated service endpoint with the one returned by the version endpoint.
176 v3Client := NewIdentityV3(client)
177 if endpoint != "" {
178 v3Client.Endpoint = endpoint
179 }
180
181 result, err := tokens3.Create(v3Client, options, nil)
182 if err != nil {
183 return err
184 }
185
186 client.TokenID, err = result.TokenID()
187 if err != nil {
188 return err
189 }
190 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
191 return v3endpointLocator(v3Client, opts)
192 }
193
194 return nil
195}
196
Ash Wilsonb8401a72014-09-08 17:07:49 -0400197func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
Ash Wilson0747d7e2014-09-09 14:27:35 -0400198 // Default Interface to InterfacePublic, if it isn't provided.
199 if opts.Interface == "" {
200 opts.Interface = gophercloud.InterfacePublic
Ash Wilsonb8401a72014-09-08 17:07:49 -0400201 }
202
203 // Discover the service we're interested in.
Ash Wilson1cd3e692014-09-09 11:01:47 -0400204 serviceResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
Ash Wilsonb8401a72014-09-08 17:07:49 -0400205 if err != nil {
206 return "", err
207 }
208
Ash Wilson1cd3e692014-09-09 11:01:47 -0400209 allServiceResults, err := gophercloud.AllPages(serviceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400210 if err != nil {
211 return "", err
212 }
Ash Wilson1cd3e692014-09-09 11:01:47 -0400213 allServices := services3.AsServices(allServiceResults)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400214
215 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400216 filtered := make([]services3.Service, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400217 for _, service := range allServices {
218 if service.Name == opts.Name {
219 filtered = append(filtered, service)
220 }
221 }
222 allServices = filtered
223 }
224
225 if len(allServices) == 0 {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400226 return "", gophercloud.ErrServiceNotFound
Ash Wilsonb8401a72014-09-08 17:07:49 -0400227 }
228 if len(allServices) > 1 {
229 return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
230 }
231
232 service := allServices[0]
233
234 // Enumerate the endpoints available for this service.
235 endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
Ash Wilson0747d7e2014-09-09 14:27:35 -0400236 Interface: opts.Interface,
Ash Wilsonb8401a72014-09-08 17:07:49 -0400237 ServiceID: service.ID,
238 })
239 if err != nil {
240 return "", err
241 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400242 allEndpoints, err := gophercloud.AllPages(endpointResults)
243 if err != nil {
244 return "", err
245 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400246 endpoints := endpoints3.AsEndpoints(allEndpoints)
247
248 if opts.Name != "" {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400249 filtered := make([]endpoints3.Endpoint, 0, 1)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400250 for _, endpoint := range endpoints {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400251 if opts.Region == "" || endpoint.Region == opts.Region {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400252 filtered = append(filtered, endpoint)
253 }
254 }
255 endpoints = filtered
256 }
257
258 if len(endpoints) == 0 {
259 return "", gophercloud.ErrEndpointNotFound
260 }
261 if len(endpoints) > 1 {
262 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
263 }
264
265 endpoint := endpoints[0]
266
267 return endpoint.URL, nil
268}
269
Ash Wilsona87ee062014-09-03 11:26:06 -0400270// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
271func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400272 v2Endpoint := client.IdentityBase + "v2.0/"
Ash Wilsonccd020b2014-09-02 10:40:54 -0400273
Ash Wilsona87ee062014-09-03 11:26:06 -0400274 return &gophercloud.ServiceClient{
275 Provider: client,
276 Endpoint: v2Endpoint,
Ash Wilson4dee1b82014-08-29 14:56:45 -0400277 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400278}
279
Ash Wilsona87ee062014-09-03 11:26:06 -0400280// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
281func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400282 v3Endpoint := client.IdentityBase + "v3/"
Ash Wilsona87ee062014-09-03 11:26:06 -0400283
284 return &gophercloud.ServiceClient{
285 Provider: client,
286 Endpoint: v3Endpoint,
287 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400288}
Ash Wilson1cd3e692014-09-09 11:01:47 -0400289
290// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
291func NewStorageV1(client *gophercloud.ProviderClient, region string) (*gophercloud.ServiceClient, error) {
292 url, err := client.EndpointLocator(gophercloud.EndpointOpts{Type: "object-store", Name: "swift"})
293 if err != nil {
294 return nil, err
295 }
296 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
297}