blob: 3031fef62168b790ab231ebf55056cf6b613dfc0 [file] [log] [blame]
Ash Wilson8ba82242014-08-28 15:38:55 -04001package openstack
2
3import (
Ash Wilson4dee1b82014-08-29 14:56:45 -04004 "errors"
Ash Wilsona87ee062014-09-03 11:26:06 -04005 "fmt"
6 "net/url"
Ash Wilsoned6a1d82014-09-03 12:01:00 -04007 "strings"
Ash Wilson4dee1b82014-08-29 14:56:45 -04008
Ash Wilson8ba82242014-08-28 15:38:55 -04009 "github.com/rackspace/gophercloud"
Ash Wilson11c98282014-09-08 16:05:10 -040010 identity2 "github.com/rackspace/gophercloud/openstack/identity/v2"
Ash Wilsonb8401a72014-09-08 17:07:49 -040011 endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints"
12 services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services"
Ash Wilsona87ee062014-09-03 11:26:06 -040013 tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
Ash Wilson4dee1b82014-08-29 14:56:45 -040014 "github.com/rackspace/gophercloud/openstack/utils"
Ash Wilson8ba82242014-08-28 15:38:55 -040015)
16
Ash Wilson4dee1b82014-08-29 14:56:45 -040017const (
18 v20 = "v2.0"
19 v30 = "v3.0"
20)
Ash Wilson8ba82242014-08-28 15:38:55 -040021
Ash Wilsona87ee062014-09-03 11:26:06 -040022// NewClient prepares an unauthenticated ProviderClient instance.
23// Most users will probably prefer using the AuthenticatedClient function instead.
24// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
25// for example.
26func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
27 // Normalize the identity endpoint that's provided by trimming any path, query or fragment from the URL.
28 u, err := url.Parse(endpoint)
29 if err != nil {
30 return nil, err
31 }
32 u.Path, u.RawQuery, u.Fragment = "", "", ""
33 normalized := u.String()
34
35 return &gophercloud.ProviderClient{
36 IdentityEndpoint: normalized,
37 Reauthenticate: func() error {
38 return errors.New("Unable to reauthenticate before authenticating the first time.")
39 },
40 }, nil
41}
42
43// 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 -040044// returns a Client instance that's ready to operate.
Ash Wilson8ba82242014-08-28 15:38:55 -040045// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
46// the most recent identity service available to proceed.
Ash Wilsona87ee062014-09-03 11:26:06 -040047func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
48 client, err := NewClient(options.IdentityEndpoint)
49 if err != nil {
50 return nil, err
51 }
52
53 err = Authenticate(client, options)
Ash Wilsonccd020b2014-09-02 10:40:54 -040054 if err != nil {
55 return nil, err
56 }
57 return client, nil
58}
59
Ash Wilsonccd020b2014-09-02 10:40:54 -040060// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
Ash Wilsona87ee062014-09-03 11:26:06 -040061func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
Ash Wilson4dee1b82014-08-29 14:56:45 -040062 versions := []*utils.Version{
63 &utils.Version{ID: v20, Priority: 20},
64 &utils.Version{ID: v30, Priority: 30},
65 }
66
Ash Wilsona87ee062014-09-03 11:26:06 -040067 chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions)
Ash Wilson4dee1b82014-08-29 14:56:45 -040068 if err != nil {
Ash Wilsonccd020b2014-09-02 10:40:54 -040069 return err
Ash Wilson4dee1b82014-08-29 14:56:45 -040070 }
71
Ash Wilsoned6a1d82014-09-03 12:01:00 -040072 if !strings.HasSuffix(endpoint, "/") {
73 endpoint = endpoint + "/"
74 }
75
Ash Wilson4dee1b82014-08-29 14:56:45 -040076 switch chosen.ID {
77 case v20:
Ash Wilson11c98282014-09-08 16:05:10 -040078 v2Client := NewIdentityV2(client)
79 v2Client.Endpoint = endpoint
Ash Wilson11c98282014-09-08 16:05:10 -040080
81 result, err := identity2.Authenticate(v2Client, options)
82 if err != nil {
83 return err
84 }
85
86 token, err := identity2.GetToken(result)
87 if err != nil {
88 return err
89 }
Ash Wilsonb8401a72014-09-08 17:07:49 -040090
Ash Wilson11c98282014-09-08 16:05:10 -040091 client.TokenID = token.ID
Ash Wilsonb8401a72014-09-08 17:07:49 -040092 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
93 return v2endpointLocator(v2Client, opts)
94 }
Ash Wilson11c98282014-09-08 16:05:10 -040095
96 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -040097 case v30:
Ash Wilsona87ee062014-09-03 11:26:06 -040098 // Override the generated service endpoint with the one returned by the version endpoint.
99 v3Client := NewIdentityV3(client)
100 v3Client.Endpoint = endpoint
101
102 result, err := tokens3.Create(v3Client, options, nil)
103 if err != nil {
104 return err
105 }
106
107 client.TokenID, err = result.TokenID()
108 if err != nil {
109 return err
110 }
Ash Wilsonb8401a72014-09-08 17:07:49 -0400111 client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
112 return v3endpointLocator(v3Client, opts)
113 }
Ash Wilsona87ee062014-09-03 11:26:06 -0400114
115 return nil
Ash Wilson4dee1b82014-08-29 14:56:45 -0400116 default:
Ash Wilsonccd020b2014-09-02 10:40:54 -0400117 // The switch statement must be out of date from the versions list.
Ash Wilsona87ee062014-09-03 11:26:06 -0400118 return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
Ash Wilsonccd020b2014-09-02 10:40:54 -0400119 }
120}
121
Ash Wilsonb8401a72014-09-08 17:07:49 -0400122func v2endpointLocator(v2Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
123 return "", gophercloud.ErrEndpointNotFound
124}
125
126func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) {
127 // Transform URLType into an Interface.
128 var endpointInterface = endpoints3.InterfacePublic
129 switch opts.URLType {
130 case "", "public":
131 endpointInterface = endpoints3.InterfacePublic
132 case "internal":
133 endpointInterface = endpoints3.InterfaceInternal
134 case "admin":
135 endpointInterface = endpoints3.InterfaceAdmin
136 default:
137 return "", fmt.Errorf("Unrecognized URLType: %s", opts.URLType)
138 }
139
140 // Discover the service we're interested in.
141 computeResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type})
142 if err != nil {
143 return "", err
144 }
145
146 serviceResults, err := gophercloud.AllPages(computeResults)
147 if err != nil {
148 return "", err
149 }
150 allServices := services3.AsServices(serviceResults)
151
152 if opts.Name != "" {
153 filtered := make([]services3.Service, 1)
154 for _, service := range allServices {
155 if service.Name == opts.Name {
156 filtered = append(filtered, service)
157 }
158 }
159 allServices = filtered
160 }
161
162 if len(allServices) == 0 {
163 return "", gophercloud.ErrEndpointNotFound
164 }
165 if len(allServices) > 1 {
166 return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices)
167 }
168
169 service := allServices[0]
170
171 // Enumerate the endpoints available for this service.
172 endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{
173 Interface: endpointInterface,
174 ServiceID: service.ID,
175 })
176 if err != nil {
177 return "", err
178 }
179
180 allEndpoints, err := gophercloud.AllPages(endpointResults)
181 if err != nil {
182 return "", err
183 }
184
185 endpoints := endpoints3.AsEndpoints(allEndpoints)
186
187 if opts.Name != "" {
188 filtered := make([]endpoints3.Endpoint, 1)
189 for _, endpoint := range endpoints {
190 if endpoint.Region == opts.Region {
191 filtered = append(filtered, endpoint)
192 }
193 }
194 endpoints = filtered
195 }
196
197 if len(endpoints) == 0 {
198 return "", gophercloud.ErrEndpointNotFound
199 }
200 if len(endpoints) > 1 {
201 return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
202 }
203
204 endpoint := endpoints[0]
205
206 return endpoint.URL, nil
207}
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 Wilsoned6a1d82014-09-03 12:01:00 -0400211 v2Endpoint := client.IdentityEndpoint + "/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 Wilsoned6a1d82014-09-03 12:01:00 -0400221 v3Endpoint := client.IdentityEndpoint + "/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}