| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 1 | package openstack | 
 | 2 |  | 
 | 3 | import ( | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 4 | 	"fmt" | 
 | 5 | 	"net/url" | 
| Ash Wilson | ed6a1d8 | 2014-09-03 12:01:00 -0400 | [diff] [blame] | 6 | 	"strings" | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 7 |  | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 8 | 	"github.com/rackspace/gophercloud" | 
| Ash Wilson | 11c9828 | 2014-09-08 16:05:10 -0400 | [diff] [blame] | 9 | 	identity2 "github.com/rackspace/gophercloud/openstack/identity/v2" | 
| Ash Wilson | b8401a7 | 2014-09-08 17:07:49 -0400 | [diff] [blame] | 10 | 	endpoints3 "github.com/rackspace/gophercloud/openstack/identity/v3/endpoints" | 
 | 11 | 	services3 "github.com/rackspace/gophercloud/openstack/identity/v3/services" | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 12 | 	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens" | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 13 | 	"github.com/rackspace/gophercloud/openstack/utils" | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 14 | ) | 
 | 15 |  | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 16 | const ( | 
 | 17 | 	v20 = "v2.0" | 
 | 18 | 	v30 = "v3.0" | 
 | 19 | ) | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 20 |  | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 21 | // 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. | 
 | 25 | func 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 Wilson | 8e434bf | 2014-09-08 17:10:04 -0400 | [diff] [blame^] | 34 | 	return &gophercloud.ProviderClient{IdentityEndpoint: normalized}, nil | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 35 | } | 
 | 36 |  | 
 | 37 | // AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 38 | // returns a Client instance that's ready to operate. | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 39 | // 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 Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 41 | func 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 Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 48 | 	if err != nil { | 
 | 49 | 		return nil, err | 
 | 50 | 	} | 
 | 51 | 	return client, nil | 
 | 52 | } | 
 | 53 |  | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 54 | // Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint. | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 55 | func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error { | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 56 | 	versions := []*utils.Version{ | 
 | 57 | 		&utils.Version{ID: v20, Priority: 20}, | 
 | 58 | 		&utils.Version{ID: v30, Priority: 30}, | 
 | 59 | 	} | 
 | 60 |  | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 61 | 	chosen, endpoint, err := utils.ChooseVersion(client.IdentityEndpoint, versions) | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 62 | 	if err != nil { | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 63 | 		return err | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 64 | 	} | 
 | 65 |  | 
| Ash Wilson | ed6a1d8 | 2014-09-03 12:01:00 -0400 | [diff] [blame] | 66 | 	if !strings.HasSuffix(endpoint, "/") { | 
 | 67 | 		endpoint = endpoint + "/" | 
 | 68 | 	} | 
 | 69 |  | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 70 | 	switch chosen.ID { | 
 | 71 | 	case v20: | 
| Ash Wilson | 11c9828 | 2014-09-08 16:05:10 -0400 | [diff] [blame] | 72 | 		v2Client := NewIdentityV2(client) | 
 | 73 | 		v2Client.Endpoint = endpoint | 
| Ash Wilson | 11c9828 | 2014-09-08 16:05:10 -0400 | [diff] [blame] | 74 |  | 
 | 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 Wilson | b8401a7 | 2014-09-08 17:07:49 -0400 | [diff] [blame] | 84 |  | 
| Ash Wilson | 11c9828 | 2014-09-08 16:05:10 -0400 | [diff] [blame] | 85 | 		client.TokenID = token.ID | 
| Ash Wilson | b8401a7 | 2014-09-08 17:07:49 -0400 | [diff] [blame] | 86 | 		client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { | 
 | 87 | 			return v2endpointLocator(v2Client, opts) | 
 | 88 | 		} | 
| Ash Wilson | 11c9828 | 2014-09-08 16:05:10 -0400 | [diff] [blame] | 89 |  | 
 | 90 | 		return nil | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 91 | 	case v30: | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 92 | 		// 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 Wilson | b8401a7 | 2014-09-08 17:07:49 -0400 | [diff] [blame] | 105 | 		client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { | 
 | 106 | 			return v3endpointLocator(v3Client, opts) | 
 | 107 | 		} | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 108 |  | 
 | 109 | 		return nil | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 110 | 	default: | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 111 | 		// The switch statement must be out of date from the versions list. | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 112 | 		return fmt.Errorf("Unrecognized identity version: %s", chosen.ID) | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 113 | 	} | 
 | 114 | } | 
 | 115 |  | 
| Ash Wilson | b8401a7 | 2014-09-08 17:07:49 -0400 | [diff] [blame] | 116 | func v2endpointLocator(v2Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) { | 
 | 117 | 	return "", gophercloud.ErrEndpointNotFound | 
 | 118 | } | 
 | 119 |  | 
 | 120 | func v3endpointLocator(v3Client *gophercloud.ServiceClient, opts gophercloud.EndpointOpts) (string, error) { | 
 | 121 | 	// Transform URLType into an Interface. | 
 | 122 | 	var endpointInterface = endpoints3.InterfacePublic | 
 | 123 | 	switch opts.URLType { | 
 | 124 | 	case "", "public": | 
 | 125 | 		endpointInterface = endpoints3.InterfacePublic | 
 | 126 | 	case "internal": | 
 | 127 | 		endpointInterface = endpoints3.InterfaceInternal | 
 | 128 | 	case "admin": | 
 | 129 | 		endpointInterface = endpoints3.InterfaceAdmin | 
 | 130 | 	default: | 
 | 131 | 		return "", fmt.Errorf("Unrecognized URLType: %s", opts.URLType) | 
 | 132 | 	} | 
 | 133 |  | 
 | 134 | 	// Discover the service we're interested in. | 
 | 135 | 	computeResults, err := services3.List(v3Client, services3.ListOpts{ServiceType: opts.Type}) | 
 | 136 | 	if err != nil { | 
 | 137 | 		return "", err | 
 | 138 | 	} | 
 | 139 |  | 
 | 140 | 	serviceResults, err := gophercloud.AllPages(computeResults) | 
 | 141 | 	if err != nil { | 
 | 142 | 		return "", err | 
 | 143 | 	} | 
 | 144 | 	allServices := services3.AsServices(serviceResults) | 
 | 145 |  | 
 | 146 | 	if opts.Name != "" { | 
 | 147 | 		filtered := make([]services3.Service, 1) | 
 | 148 | 		for _, service := range allServices { | 
 | 149 | 			if service.Name == opts.Name { | 
 | 150 | 				filtered = append(filtered, service) | 
 | 151 | 			} | 
 | 152 | 		} | 
 | 153 | 		allServices = filtered | 
 | 154 | 	} | 
 | 155 |  | 
 | 156 | 	if len(allServices) == 0 { | 
 | 157 | 		return "", gophercloud.ErrEndpointNotFound | 
 | 158 | 	} | 
 | 159 | 	if len(allServices) > 1 { | 
 | 160 | 		return "", fmt.Errorf("Discovered %d matching services: %#v", len(allServices), allServices) | 
 | 161 | 	} | 
 | 162 |  | 
 | 163 | 	service := allServices[0] | 
 | 164 |  | 
 | 165 | 	// Enumerate the endpoints available for this service. | 
 | 166 | 	endpointResults, err := endpoints3.List(v3Client, endpoints3.ListOpts{ | 
 | 167 | 		Interface: endpointInterface, | 
 | 168 | 		ServiceID: service.ID, | 
 | 169 | 	}) | 
 | 170 | 	if err != nil { | 
 | 171 | 		return "", err | 
 | 172 | 	} | 
 | 173 |  | 
 | 174 | 	allEndpoints, err := gophercloud.AllPages(endpointResults) | 
 | 175 | 	if err != nil { | 
 | 176 | 		return "", err | 
 | 177 | 	} | 
 | 178 |  | 
 | 179 | 	endpoints := endpoints3.AsEndpoints(allEndpoints) | 
 | 180 |  | 
 | 181 | 	if opts.Name != "" { | 
 | 182 | 		filtered := make([]endpoints3.Endpoint, 1) | 
 | 183 | 		for _, endpoint := range endpoints { | 
 | 184 | 			if endpoint.Region == opts.Region { | 
 | 185 | 				filtered = append(filtered, endpoint) | 
 | 186 | 			} | 
 | 187 | 		} | 
 | 188 | 		endpoints = filtered | 
 | 189 | 	} | 
 | 190 |  | 
 | 191 | 	if len(endpoints) == 0 { | 
 | 192 | 		return "", gophercloud.ErrEndpointNotFound | 
 | 193 | 	} | 
 | 194 | 	if len(endpoints) > 1 { | 
 | 195 | 		return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints) | 
 | 196 | 	} | 
 | 197 |  | 
 | 198 | 	endpoint := endpoints[0] | 
 | 199 |  | 
 | 200 | 	return endpoint.URL, nil | 
 | 201 | } | 
 | 202 |  | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 203 | // NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service. | 
 | 204 | func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient { | 
| Ash Wilson | ed6a1d8 | 2014-09-03 12:01:00 -0400 | [diff] [blame] | 205 | 	v2Endpoint := client.IdentityEndpoint + "/v2.0/" | 
| Ash Wilson | ccd020b | 2014-09-02 10:40:54 -0400 | [diff] [blame] | 206 |  | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 207 | 	return &gophercloud.ServiceClient{ | 
 | 208 | 		Provider: client, | 
 | 209 | 		Endpoint: v2Endpoint, | 
| Ash Wilson | 4dee1b8 | 2014-08-29 14:56:45 -0400 | [diff] [blame] | 210 | 	} | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 211 | } | 
 | 212 |  | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 213 | // NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service. | 
 | 214 | func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient { | 
| Ash Wilson | ed6a1d8 | 2014-09-03 12:01:00 -0400 | [diff] [blame] | 215 | 	v3Endpoint := client.IdentityEndpoint + "/v3/" | 
| Ash Wilson | a87ee06 | 2014-09-03 11:26:06 -0400 | [diff] [blame] | 216 |  | 
 | 217 | 	return &gophercloud.ServiceClient{ | 
 | 218 | 		Provider: client, | 
 | 219 | 		Endpoint: v3Endpoint, | 
 | 220 | 	} | 
| Ash Wilson | 8ba8224 | 2014-08-28 15:38:55 -0400 | [diff] [blame] | 221 | } |