blob: 64f22d90048435b04659ce0972ad3fde8fd6c73b [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 Wilson4dee1b82014-08-29 14:56:45 -04006
Ash Wilson8ba82242014-08-28 15:38:55 -04007 "github.com/rackspace/gophercloud"
Ash Wilson52fbd182014-10-03 13:48:06 -04008 tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
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 Wilson3c8cc772014-09-16 11:40:49 -040013 "github.com/rackspace/gophercloud/pagination"
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 Wilsona8440642014-10-07 09:55:58 -040034 endpoint = gophercloud.NormalizeURL(endpoint)
35 base = gophercloud.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
Ash Wilson6cc00702014-10-03 14:00:26 -0400101 result := tokens2.Create(v2Client, options)
Ash Wilson52fbd182014-10-03 13:48:06 -0400102
103 token, err := result.ExtractToken()
Ash Wilson09694b92014-09-09 14:08:27 -0400104 if err != nil {
105 return err
106 }
107
Ash Wilson52fbd182014-10-03 13:48:06 -0400108 catalog, err := result.ExtractServiceCatalog()
Ash Wilson09694b92014-09-09 14:08:27 -0400109 if err != nil {
110 return err
111 }
112
113 client.TokenID = token.ID
Ash Wilson405f3102014-10-07 10:21:46 -0400114 client.EndpointLocator = catalog.EndpointURL
Ash Wilson09694b92014-09-09 14:08:27 -0400115
116 return nil
117}
118
Ash Wilson09694b92014-09-09 14:08:27 -0400119// AuthenticateV3 explicitly authenticates against the identity v3 service.
120func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
121 return v3auth(client, "", options)
122}
123
124func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
125 // Override the generated service endpoint with the one returned by the version endpoint.
126 v3Client := NewIdentityV3(client)
127 if endpoint != "" {
128 v3Client.Endpoint = endpoint
129 }
130
Ash Wilson63b2a292014-10-02 09:29:06 -0400131 token, err := tokens3.Create(v3Client, options, nil).Extract()
Ash Wilson09694b92014-09-09 14:08:27 -0400132 if err != nil {
133 return err
134 }
Ash Wilson63b2a292014-10-02 09:29:06 -0400135 client.TokenID = token.ID
Ash Wilson09694b92014-09-09 14:08:27 -0400136
Ash Wilson09694b92014-09-09 14:08:27 -0400137 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
138 return v3endpointLocator(v3Client, opts)
139 }
140
141 return nil
142}
143
Ash Wilsonb8401a72014-09-08 17:07:49 -0400144func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400145 // Discover the service we're interested in.
Ash Wilson566613e2014-09-12 14:51:46 -0400146 var services = make([]services3.Service, 0, 1)
Ash Wilson6b35e502014-09-12 15:15:23 -0400147 servicePager := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
Ash Wilson3c8cc772014-09-16 11:40:49 -0400148 err := servicePager.EachPage(func(page pagination.Page) (bool, error) {
Ash Wilson566613e2014-09-12 14:51:46 -0400149 part, err := services3.ExtractServices(page)
150 if err != nil {
Ash Wilson6b35e502014-09-12 15:15:23 -0400151 return false, err
Ash Wilson566613e2014-09-12 14:51:46 -0400152 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400153
Ash Wilson566613e2014-09-12 14:51:46 -0400154 for _, service := range part {
Ash Wilsonb8401a72014-09-08 17:07:49 -0400155 if service.Name == opts.Name {
Ash Wilson566613e2014-09-12 14:51:46 -0400156 services = append(services, service)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400157 }
158 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400159
Ash Wilson6b35e502014-09-12 15:15:23 -0400160 return true, nil
Ash Wilsonb8401a72014-09-08 17:07:49 -0400161 })
162 if err != nil {
163 return "", err
164 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400165
Ash Wilson566613e2014-09-12 14:51:46 -0400166 if len(services) == 0 {
167 return "", gophercloud.ErrServiceNotFound
168 }
169 if len(services) > 1 {
170 return "", fmt.Errorf("Discovered %d matching services: %#v", len(services), services)
171 }
172 service := services[0]
173
174 // Enumerate the endpoints available for this service.
175 var endpoints []endpoints3.Endpoint
Ash Wilson6b35e502014-09-12 15:15:23 -0400176 endpointPager := endpoints3.List(v3Client, endpoints3.ListOpts{
Ash Wilson566613e2014-09-12 14:51:46 -0400177 Availability: opts.Availability,
178 ServiceID: service.ID,
Ash Wilson6b35e502014-09-12 15:15:23 -0400179 })
Ash Wilson3c8cc772014-09-16 11:40:49 -0400180 err = endpointPager.EachPage(func(page pagination.Page) (bool, error) {
Ash Wilson566613e2014-09-12 14:51:46 -0400181 part, err := endpoints3.ExtractEndpoints(page)
182 if err != nil {
Ash Wilson6b35e502014-09-12 15:15:23 -0400183 return false, err
Ash Wilson566613e2014-09-12 14:51:46 -0400184 }
185
186 for _, endpoint := range part {
Ash Wilson1cd3e692014-09-09 11:01:47 -0400187 if opts.Region == "" || endpoint.Region == opts.Region {
Ash Wilson566613e2014-09-12 14:51:46 -0400188 endpoints = append(endpoints, endpoint)
Ash Wilsonb8401a72014-09-08 17:07:49 -0400189 }
190 }
Ash Wilson566613e2014-09-12 14:51:46 -0400191
Ash Wilson6b35e502014-09-12 15:15:23 -0400192 return true, nil
Ash Wilson566613e2014-09-12 14:51:46 -0400193 })
194 if err != nil {
195 return "", err
Ash Wilsonb8401a72014-09-08 17:07:49 -0400196 }
197
198 if len(endpoints) == 0 {
199 return "", gophercloud.ErrEndpointNotFound
200 }
201 if len(endpoints) > 1 {
202 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
203 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400204 endpoint := endpoints[0]
205
Ash Wilsona8440642014-10-07 09:55:58 -0400206 return gophercloud.NormalizeURL(endpoint.URL), nil
Ash Wilsonb8401a72014-09-08 17:07:49 -0400207}
208
Ash Wilsona87ee062014-09-03 11:26:06 -0400209// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
210func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400211 v2Endpoint := client.IdentityBase + "v2.0/"
Ash Wilsonccd020b2014-09-02 10:40:54 -0400212
Ash Wilsona87ee062014-09-03 11:26:06 -0400213 return &gophercloud.ServiceClient{
214 Provider: client,
215 Endpoint: v2Endpoint,
Ash Wilson4dee1b82014-08-29 14:56:45 -0400216 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400217}
218
Ash Wilsona87ee062014-09-03 11:26:06 -0400219// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
220func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
Ash Wilson09694b92014-09-09 14:08:27 -0400221 v3Endpoint := client.IdentityBase + "v3/"
Ash Wilsona87ee062014-09-03 11:26:06 -0400222
223 return &gophercloud.ServiceClient{
224 Provider: client,
225 Endpoint: v3Endpoint,
226 }
Ash Wilson8ba82242014-08-28 15:38:55 -0400227}
Ash Wilson1cd3e692014-09-09 11:01:47 -0400228
229// NewStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
Jon Perritt509fbb62014-09-10 13:29:56 -0500230func NewStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
231 eo.ApplyDefaults("object-store")
232 url, err := client.EndpointLocator(eo)
Ash Wilson1cd3e692014-09-09 11:01:47 -0400233 if err != nil {
234 return nil, err
235 }
236 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
237}
Ash Wilson5e57c1b2014-09-17 09:24:46 -0400238
239// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
240func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
241 eo.ApplyDefaults("compute")
242 url, err := client.EndpointLocator(eo)
243 if err != nil {
244 return nil, err
245 }
246 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
247}
Ash Wilsonebc3d122014-09-24 13:44:05 -0400248
249// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
Jamie Hannaford7ea29582014-09-11 15:49:46 +0200250func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
251 eo.ApplyDefaults("network")
252 url, err := client.EndpointLocator(eo)
253 if err != nil {
254 return nil, err
255 }
Ash Wilson99541ab2014-10-06 17:32:39 -0400256 return &gophercloud.ServiceClient{
257 Provider: client,
258 Endpoint: url,
259 ResourceBase: url + "v2.0/",
260 }, nil
Jamie Hannaford7ea29582014-09-11 15:49:46 +0200261}
Jon Perrittc5ee85e2014-09-17 00:53:19 -0500262
263// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
264func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
265 eo.ApplyDefaults("volume")
266 url, err := client.EndpointLocator(eo)
267 if err != nil {
268 return nil, err
269 }
270 return &gophercloud.ServiceClient{Provider: client, Endpoint: url}, nil
271}