openstack_sd: Supporting application credential for authentication. (#4968)

* openstack_sd: Support application credentials for authentication.
Updated gophercloud

Signed-off-by: Kevin Bulebush <kmbulebu@gmail.com>
This commit is contained in:
Kevin Bulebush 2019-01-09 10:18:58 -05:00 committed by Brian Brazil
parent b8ede99767
commit 718344434c
56 changed files with 2981 additions and 991 deletions

View file

@ -50,20 +50,23 @@ var (
// SDConfig is the configuration for OpenStack based service discovery.
type SDConfig struct {
IdentityEndpoint string `yaml:"identity_endpoint"`
Username string `yaml:"username"`
UserID string `yaml:"userid"`
Password config_util.Secret `yaml:"password"`
ProjectName string `yaml:"project_name"`
ProjectID string `yaml:"project_id"`
DomainName string `yaml:"domain_name"`
DomainID string `yaml:"domain_id"`
Role Role `yaml:"role"`
Region string `yaml:"region"`
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
AllTenants bool `yaml:"all_tenants,omitempty"`
TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"`
IdentityEndpoint string `yaml:"identity_endpoint"`
Username string `yaml:"username"`
UserID string `yaml:"userid"`
Password config_util.Secret `yaml:"password"`
ProjectName string `yaml:"project_name"`
ProjectID string `yaml:"project_id"`
DomainName string `yaml:"domain_name"`
DomainID string `yaml:"domain_id"`
ApplicationCredentialName string `yaml:"application_credential_name"`
ApplicationCredentialID string `yaml:"application_credential_id"`
ApplicationCredentialSecret config_util.Secret `yaml:"application_credential_secret"`
Role Role `yaml:"role"`
Region string `yaml:"region"`
RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
Port int `yaml:"port"`
AllTenants bool `yaml:"all_tenants,omitempty"`
TLSConfig config_util.TLSConfig `yaml:"tls_config,omitempty"`
}
// OpenStackRole is role of the target in OpenStack.
@ -132,14 +135,17 @@ func NewDiscovery(conf *SDConfig, l log.Logger) (Discovery, error) {
}
} else {
opts = gophercloud.AuthOptions{
IdentityEndpoint: conf.IdentityEndpoint,
Username: conf.Username,
UserID: conf.UserID,
Password: string(conf.Password),
TenantName: conf.ProjectName,
TenantID: conf.ProjectID,
DomainName: conf.DomainName,
DomainID: conf.DomainID,
IdentityEndpoint: conf.IdentityEndpoint,
Username: conf.Username,
UserID: conf.UserID,
Password: string(conf.Password),
TenantName: conf.ProjectName,
TenantID: conf.ProjectID,
DomainName: conf.DomainName,
DomainID: conf.DomainID,
ApplicationCredentialID: conf.ApplicationCredentialID,
ApplicationCredentialName: conf.ApplicationCredentialName,
ApplicationCredentialSecret: string(conf.ApplicationCredentialSecret),
}
}
client, err := openstack.NewClient(opts.IdentityEndpoint)

View file

@ -540,6 +540,17 @@ region: <string>
[ project_name: <string> ]
[ project_id: <string> ]
# The application_credential_id or application_credential_name fields are
# required if using an application credential to authenticate. Some providers
# allow you to create an application credential to authenticate rather than a
# password.
[ application_credential_name: <string> ]
[ application_credential_id: <string> ]
# The application_credential_secret field is required if using an application
# credential to authenticate.
[ application_credential_secret: <secret> ]
# Whether the service discovery should list all instances for all projects.
# It is only relevant for the 'instance' role and usually requires admin permissions.
[ all_tenants: <boolean> | default: false ]
@ -978,7 +989,7 @@ endpoint: <string>
# A list of groups for which targets are retrieved. If omitted, all containers
# available to the requesting account are scraped.
groups:
groups:
[ - <string> ... ]
# The port to use for discovery and metric scraping.

2
go.mod
View file

@ -37,7 +37,7 @@ require (
github.com/google/gofuzz v0.0.0-20150304233714-bbcb9da2d746 // indirect
github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4 // indirect
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20171126203511-e4b8a938efae

4
go.sum
View file

@ -87,8 +87,8 @@ github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34 h1:mGdRet4qWdrDnNidFr
github.com/google/pprof v0.0.0-20180605153948-8b03ce837f34/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4 h1:yxHFSapGMUoyn+3v6LiJJxoJhvbDqIq8me0gAWehnSU=
github.com/googleapis/gnostic v0.0.0-20180520015035-48a0ecefe2e4/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 h1:Acc1d6mIuURCyYN6nkm1d7+Gycfq1+jUWdnBbTyGb6E=
github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9 h1:7TRGugCPfA2Mll6QT7cbhD1GXZwk7+1PUz8tYrOWXgQ=
github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=

View file

@ -1 +1,3 @@
**/*.swp
.idea
.vscode

View file

@ -7,11 +7,13 @@ install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports
go:
- 1.8
- tip
- "1.10"
- "tip"
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
before_script:
- go vet ./...
script:
- ./script/coverage
- ./script/format

98
vendor/github.com/gophercloud/gophercloud/.zuul.yaml generated vendored Normal file
View file

@ -0,0 +1,98 @@
- job:
name: gophercloud-unittest
parent: golang-test
description: |
Run gophercloud unit test
run: .zuul/playbooks/gophercloud-unittest/run.yaml
nodeset: ubuntu-xenial-ut
- job:
name: gophercloud-acceptance-test
parent: golang-test
description: |
Run gophercloud acceptance test on master branch
run: .zuul/playbooks/gophercloud-acceptance-test/run.yaml
- job:
name: gophercloud-acceptance-test-queens
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on queens branch
vars:
global_env:
OS_BRANCH: stable/queens
- job:
name: gophercloud-acceptance-test-rocky
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on rocky branch
vars:
global_env:
OS_BRANCH: stable/rocky
- job:
name: gophercloud-acceptance-test-pike
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on pike branch
vars:
global_env:
OS_BRANCH: stable/pike
- job:
name: gophercloud-acceptance-test-ocata
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on ocata branch
vars:
global_env:
OS_BRANCH: stable/ocata
- job:
name: gophercloud-acceptance-test-newton
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on newton branch
vars:
global_env:
OS_BRANCH: stable/newton
- job:
name: gophercloud-acceptance-test-mitaka
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on mitaka branch
vars:
global_env:
OS_BRANCH: stable/mitaka
nodeset: ubuntu-trusty
- project:
name: gophercloud/gophercloud
check:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test
recheck-mitaka:
jobs:
- gophercloud-acceptance-test-mitaka
recheck-newton:
jobs:
- gophercloud-acceptance-test-newton
recheck-ocata:
jobs:
- gophercloud-acceptance-test-ocata
recheck-pike:
jobs:
- gophercloud-acceptance-test-pike
recheck-queens:
jobs:
- gophercloud-acceptance-test-queens
recheck-rocky:
jobs:
- gophercloud-acceptance-test-rocky
periodic:
jobs:
- gophercloud-unittest
- gophercloud-acceptance-test

View file

@ -1,148 +0,0 @@
# Tips
## Implementing default logging and re-authentication attempts
You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
like the following and setting it as the provider client's HTTP Client (via the
`gophercloud.ProviderClient.HTTPClient` field):
```go
//...
// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
rt http.RoundTripper
numReauthAttempts int
}
// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
return http.Client{
Transport: &LogRoundTripper{
rt: http.DefaultTransport,
},
}
}
// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
glog.Infof("Request URL: %s\n", request.URL)
response, err := lrt.rt.RoundTrip(request)
if response == nil {
return nil, err
}
if response.StatusCode == http.StatusUnauthorized {
if lrt.numReauthAttempts == 3 {
return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
}
lrt.numReauthAttempts++
}
glog.Debugf("Response Status: %s\n", response.Status)
return response, nil
}
endpoint := "https://127.0.0.1/auth"
pc := openstack.NewClient(endpoint)
pc.HTTPClient = newHTTPClient()
//...
```
## Implementing custom objects
OpenStack request/response objects may differ among variable names or types.
### Custom request objects
To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
example, to pass in
```go
type MyCreateServerOpts struct {
Name string
Size int
}
```
to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
```go
func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{
"name": o.Name,
"size": o.Size,
}, nil
}
```
create an instance of your custom options object, and pass it to `servers.Create`:
```go
// ...
myOpts := MyCreateServerOpts{
Name: "s1",
Size: "100",
}
server, err := servers.Create(computeClient, myOpts).Extract()
// ...
```
### Custom response objects
Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
combined to create a custom object:
```go
// ...
type MyVolume struct {
volumes.Volume
tenantattr.VolumeExt
}
var v struct {
MyVolume `json:"volume"`
}
err := volumes.Get(client, volID).ExtractInto(&v)
// ...
```
## Overriding default `UnmarshalJSON` method
For some response objects, a field may be a custom type or may be allowed to take on
different types. In these cases, overriding the default `UnmarshalJSON` method may be
necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
method on the type:
```go
// ...
type MyVolume struct {
ID string `json: "id"`
TimeCreated time.Time `json: "-"`
}
func (r *MyVolume) UnmarshalJSON(b []byte) error {
type tmp MyVolume
var s struct {
tmp
TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Volume(s.tmp)
r.TimeCreated = time.Time(s.CreatedAt)
return err
}
// ...
```

View file

@ -1,32 +0,0 @@
# Compute
## Floating IPs
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
## Security Groups
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
## Servers
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
```golang
rebootOpts := &servers.RebootOpts{
Type: servers.SoftReboot,
}
res := servers.Reboot(client, server.ID, rebootOpts)
```
# Identity
## V3
### Tokens
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
`time.Time`

View file

@ -127,7 +127,7 @@ new resource in the `server` variable (a
## Advanced Usage
Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
Have a look at the [FAQ](./docs/FAQ.md) for some tips on customizing the way Gophercloud works.
## Backwards-Compatibility Guarantees
@ -140,4 +140,20 @@ See the [contributing guide](./.github/CONTRIBUTING.md).
## Help and feedback
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/issues).
to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues).
## Thank You
We'd like to extend special thanks and appreciation to the following:
### OpenLab
<a href="http://openlabtesting.org/"><img src="./docs/assets/openlab.png" width="600px"></a>
OpenLab is providing a full CI environment to test each PR and merge for a variety of OpenStack releases.
### VEXXHOST
<a href="https://vexxhost.com/"><img src="./docs/assets/vexxhost.png" width="600px"></a>
VEXXHOST is providing their services to assist with the development and testing of Gophercloud.

View file

@ -1,74 +0,0 @@
## On Pull Requests
- Before you start a PR there needs to be a Github issue and a discussion about it
on that issue with a core contributor, even if it's just a 'SGTM'.
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
- A PR's description must contain link(s) to the line(s) in the OpenStack
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
are not good enough. The link(s) should be to a non-`master` branch. For example,
a pull request implementing the creation of a Neutron v2 subnet might put the
following link in the description:
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
From that link, a reviewer (or user) can verify the fields in the request/response
objects in the PR.
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
- Forcing PRs to be small can have the effect of users submitting PRs in a hierarchical chain, with
one depending on the next. If a PR depends on another one, it should have a [Pending #PRNUM]
prefix in the PR title. In addition, it will be the PR submitter's responsibility to remove the
[Pending #PRNUM] tag once the PR has been updated with the merged, dependent PR. That will
let reviewers know it is ready to review.
- A PR should be small. Even if you intend on implementing an entire
service, a PR should only be one route of that service
(e.g. create server or get server, but not both).
- Unless explicitly asked, do not squash commits in the middle of a review; only
append. It makes it difficult for the reviewer to see what's changed from one
review to the next.
## On Code
- In re design: follow as closely as is reasonable the code already in the library.
Most operations (e.g. create, delete) admit the same design.
- Unit tests and acceptance (integration) tests must be written to cover each PR.
Tests for operations with several options (e.g. list, create) should include all
the options in the tests. This will allow users to verify an operation on their
own infrastructure and see an example of usage.
- If in doubt, ask in-line on the PR.
### File Structure
- The following should be used in most cases:
- `requests.go`: contains all the functions that make HTTP requests and the
types associated with the HTTP request (parameters for URL, body, etc)
- `results.go`: contains all the response objects and their methods
- `urls.go`: contains the endpoints to which the requests are made
### Naming
- For methods on a type in `results.go`, the receiver should be named `r` and the
variable into which it will be unmarshalled `s`.
- Functions in `requests.go`, with the exception of functions that return a
`pagination.Pager`, should be named returns of the name `r`.
- Functions in `requests.go` that accept request bodies should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Map`
(eg `ToPortCreateMap`).
- Functions in `requests.go` that accept query strings should accept as their
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
This `interface` should have at the least a method named `To<Resource><Action>Query`
(eg `ToServerListQuery`).

View file

@ -9,25 +9,45 @@ ProviderClient representing an active session on that provider.
Its fields are the union of those recognized by each identity implementation and
provider.
An example of manually providing authentication information:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
Username: "{username}",
Password: "{password}",
TenantID: "{tenant_id}",
}
provider, err := openstack.AuthenticatedClient(opts)
An example of using AuthOptionsFromEnv(), where the environment variables can
be read from a file, such as a standard openrc file:
opts, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(opts)
*/
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
//
// The IdentityEndpoint is typically referred to as the "auth_url" or
// "OS_AUTH_URL" in the information provided by the cloud operator.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
// control panel to discover your account's username. In Identity V3, either
// UserID or a combination of Username and DomainID or DomainName are needed.
Username string `json:"username,omitempty"`
UserID string `json:"id,omitempty"`
UserID string `json:"-"`
Password string `json:"password,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainID string `json:"-"`
DomainName string `json:"name,omitempty"`
// The TenantID and TenantName fields are optional for the Identity V2 API.
@ -39,7 +59,7 @@ type AuthOptions struct {
// If DomainID or DomainName are provided, they will also apply to TenantName.
// It is not currently possible to authenticate with Username and a Domain
// and scope to a Project in a different Domain by using TenantName. To
// accomplish that, the ProjectID will need to be provided to the TenantID
// accomplish that, the ProjectID will need to be provided as the TenantID
// option.
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
@ -50,15 +70,34 @@ type AuthOptions struct {
// false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false.
//
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
// NOTE: The reauth function will try to re-authenticate endlessly if left
// unchecked. The way to limit the number of attempts is to provide a custom
// HTTP client to the provider client and provide a transport that implements
// the RoundTripper interface and stores the number of failed retries. For an
// example of this, see here:
// https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
// Scope determines the scoping of the authentication request.
Scope *AuthScope `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
}
// AuthScope allows a created token to be limited to a specific domain or project.
type AuthScope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
@ -109,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password"`
Password string `json:"password,omitempty"`
Domain *domainReq `json:"domain,omitempty"`
}
@ -121,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
ID string `json:"id"`
}
type applicationCredentialReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
User *userReq `json:"user,omitempty"`
Secret *string `json:"secret,omitempty"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
}
type authReq struct {
@ -138,6 +185,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
// Populate the request structure based on the provided arguments. Create and return an error
// if insufficient or incompatible information is present.
var req request
var userRequest userReq
if opts.Password == "" {
if opts.TokenID != "" {
@ -161,8 +209,49 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID,
}
} else if opts.ApplicationCredentialID != "" {
// Configure the request for ApplicationCredentialID authentication.
// https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
// There are three kinds of possible application_credential requests
// 1. application_credential id + secret
// 2. application_credential name + secret + user_id
// 3. application_credential name + secret + username + domain_id / domain_name
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
ID: &opts.ApplicationCredentialID,
Secret: &opts.ApplicationCredentialSecret,
}
} else if opts.ApplicationCredentialName != "" {
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
// make sure that only one of DomainName or DomainID were provided
if opts.DomainID == "" && opts.DomainName == "" {
return nil, ErrDomainIDOrDomainName{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
if opts.DomainID != "" {
userRequest = userReq{
Name: &opts.Username,
Domain: &domainReq{ID: &opts.DomainID},
}
} else if opts.DomainName != "" {
userRequest = userReq{
Name: &opts.Username,
Domain: &domainReq{Name: &opts.DomainName},
}
}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
Name: &opts.ApplicationCredentialName,
User: &userRequest,
Secret: &opts.ApplicationCredentialSecret,
}
} else {
// If no password or token ID are available, authentication can't continue.
// If no password or token ID or ApplicationCredential are available, authentication can't continue.
return nil, ErrMissingPassword{}
}
} else {
@ -241,82 +330,85 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
}
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
var scope struct {
ProjectID string
ProjectName string
DomainID string
DomainName string
}
if opts.TenantID != "" {
scope.ProjectID = opts.TenantID
} else {
if opts.TenantName != "" {
scope.ProjectName = opts.TenantName
scope.DomainID = opts.DomainID
scope.DomainName = opts.DomainName
// For backwards compatibility.
// If AuthOptions.Scope was not set, try to determine it.
// This works well for common scenarios.
if opts.Scope == nil {
opts.Scope = new(AuthScope)
if opts.TenantID != "" {
opts.Scope.ProjectID = opts.TenantID
} else {
if opts.TenantName != "" {
opts.Scope.ProjectName = opts.TenantName
opts.Scope.DomainID = opts.DomainID
opts.Scope.DomainName = opts.DomainName
}
}
}
if scope.ProjectName != "" {
if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if scope.DomainID == "" && scope.DomainName == "" {
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
if scope.ProjectID != "" {
if opts.Scope.ProjectID != "" {
return nil, ErrScopeProjectIDOrProjectName{}
}
if scope.DomainID != "" {
if opts.Scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"id": &scope.DomainID},
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
},
}, nil
}
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &scope.ProjectName,
"domain": map[string]interface{}{"name": &scope.DomainName},
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
},
}, nil
}
} else if scope.ProjectID != "" {
} else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if scope.DomainID != "" {
if opts.Scope.DomainID != "" {
return nil, ErrScopeProjectIDAlone{}
}
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
return nil, ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &scope.ProjectID,
"id": &opts.Scope.ProjectID,
},
}, nil
} else if scope.DomainID != "" {
} else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if scope.DomainName != "" {
if opts.Scope.DomainName != "" {
return nil, ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &scope.DomainID,
"id": &opts.Scope.DomainID,
},
}, nil
} else if opts.Scope.DomainName != "" {
// DomainName
return map[string]interface{}{
"domain": map[string]interface{}{
"name": &opts.Scope.DomainName,
},
}, nil
} else if scope.DomainName != "" {
return nil, ErrScopeDomainName{}
}
return nil, nil

View file

@ -3,11 +3,17 @@ Package gophercloud provides a multi-vendor interface to OpenStack-compatible
clouds. The library has a three-level hierarchy: providers, services, and
resources.
Provider structs represent the service providers that offer and manage a
collection of services. The IdentityEndpoint is typically refered to as
"auth_url" in information provided by the cloud operator. Additionally,
the cloud may refer to TenantID or TenantName as project_id and project_name.
These are defined like so:
Authenticating with Providers
Provider structs represent the cloud providers that offer and manage a
collection of services. You will generally want to create one Provider
client per OpenStack cloud.
Use your OpenStack credentials to create a Provider client. The
IdentityEndpoint is typically refered to as "auth_url" or "OS_AUTH_URL" in
information provided by the cloud operator. Additionally, the cloud may refer to
TenantID or TenantName as project_id and project_name. Credentials are
specified like so:
opts := gophercloud.AuthOptions{
IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
@ -18,6 +24,16 @@ These are defined like so:
provider, err := openstack.AuthenticatedClient(opts)
You may also use the openstack.AuthOptionsFromEnv() helper function. This
function reads in standard environment variables frequently found in an
OpenStack `openrc` file. Again note that Gophercloud currently uses "tenant"
instead of "project".
opts, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(opts)
Service Clients
Service structs are specific to a provider and handle all of the logic and
operations for a particular OpenStack service. Examples of services include:
Compute, Object Storage, Block Storage. In order to define one, you need to
@ -27,6 +43,8 @@ pass in the parent provider, like so:
client := openstack.NewComputeV2(provider, opts)
Resources
Resource structs are the domain models that services make use of in order
to work with and represent the state of API resources:
@ -62,6 +80,12 @@ of results:
return true, nil
})
If you want to obtain the entire collection of pages without doing any
intermediary processing on each page, you can use the AllPages method:
allPages, err := servers.List(client, nil).AllPages()
allServers, err := servers.ExtractServers(allPages)
This top-level package contains utility functions and data types that are used
throughout the provider and service packages. Of particular note for end users
are the AuthOptions and EndpointOpts structs.

View file

@ -27,7 +27,7 @@ const (
// unambiguously identify one, and only one, endpoint within the catalog.
//
// Usually, these are passed to service client factory functions in a provider
// package, like "rackspace.NewComputeV2()".
// package, like "openstack.NewComputeV2()".
type EndpointOpts struct {
// Type [required] is the service type for the client (e.g., "compute",
// "object-store"). Generally, this will be supplied by the service client

View file

@ -1,6 +1,9 @@
package gophercloud
import "fmt"
import (
"fmt"
"strings"
)
// BaseError is an error type that all other error types embed.
type BaseError struct {
@ -43,6 +46,33 @@ func (e ErrInvalidInput) Error() string {
return e.choseErrString()
}
// ErrMissingEnvironmentVariable is the error when environment variable is required
// in a particular situation but not provided by the user
type ErrMissingEnvironmentVariable struct {
BaseError
EnvironmentVariable string
}
func (e ErrMissingEnvironmentVariable) Error() string {
e.DefaultErrString = fmt.Sprintf("Missing environment variable [%s]", e.EnvironmentVariable)
return e.choseErrString()
}
// ErrMissingAnyoneOfEnvironmentVariables is the error when anyone of the environment variables
// is required in a particular situation but not provided by the user
type ErrMissingAnyoneOfEnvironmentVariables struct {
BaseError
EnvironmentVariables []string
}
func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
e.DefaultErrString = fmt.Sprintf(
"Missing one of the following environment variables [%s]",
strings.Join(e.EnvironmentVariables, ", "),
)
return e.choseErrString()
}
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
@ -72,6 +102,11 @@ type ErrDefault401 struct {
ErrUnexpectedResponseCode
}
// ErrDefault403 is the default error type returned on a 403 HTTP response code.
type ErrDefault403 struct {
ErrUnexpectedResponseCode
}
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
type ErrDefault404 struct {
ErrUnexpectedResponseCode
@ -103,11 +138,22 @@ type ErrDefault503 struct {
}
func (e ErrDefault400) Error() string {
return "Invalid request due to incorrect syntax or missing required parameters."
e.DefaultErrString = fmt.Sprintf(
"Bad request with: [%s %s], error message: %s",
e.Method, e.URL, e.Body,
)
return e.choseErrString()
}
func (e ErrDefault401) Error() string {
return "Authentication failed"
}
func (e ErrDefault403) Error() string {
e.DefaultErrString = fmt.Sprintf(
"Request forbidden: [%s %s], error message: %s",
e.Method, e.URL, e.Body,
)
return e.choseErrString()
}
func (e ErrDefault404) Error() string {
return "Resource not found"
}
@ -141,6 +187,12 @@ type Err401er interface {
Error401(ErrUnexpectedResponseCode) error
}
// Err403er is the interface resource error types implement to override the error message
// from a 403 error.
type Err403er interface {
Error403(ErrUnexpectedResponseCode) error
}
// Err404er is the interface resource error types implement to override the error message
// from a 404 error.
type Err404er interface {
@ -393,16 +445,16 @@ func (e ErrScopeProjectIDAlone) Error() string {
return "ProjectID must be supplied alone in a Scope"
}
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
type ErrScopeDomainName struct{ BaseError }
func (e ErrScopeDomainName) Error() string {
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
}
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope"
}
// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name
type ErrAppCredMissingSecret struct{ BaseError }
func (e ErrAppCredMissingSecret) Error() string {
return "You must provide an Application Credential Secret"
}

View file

@ -8,10 +8,27 @@ import (
var nilOptions = gophercloud.AuthOptions{}
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
/*
AuthOptionsFromEnv fills out an identity.AuthOptions structure with the
settings found on the various OpenStack OS_* environment variables.
The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME.
Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must have settings,
or an error will result. OS_TENANT_ID, OS_TENANT_NAME, OS_PROJECT_ID, and
OS_PROJECT_NAME are optional.
OS_TENANT_ID and OS_TENANT_NAME are mutually exclusive to OS_PROJECT_ID and
OS_PROJECT_NAME. If OS_PROJECT_ID and OS_PROJECT_NAME are set, they will
still be referred as "tenant" in Gophercloud.
To use this function, first set the OS_* environment variables (for example,
by sourcing an `openrc` file), then:
opts, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(opts)
*/
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
authURL := os.Getenv("OS_AUTH_URL")
username := os.Getenv("OS_USERNAME")
@ -21,31 +38,60 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID")
applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME")
applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET")
// If OS_PROJECT_ID is set, overwrite tenantID with the value.
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
tenantID = v
}
// If OS_PROJECT_NAME is set, overwrite tenantName with the value.
if v := os.Getenv("OS_PROJECT_NAME"); v != "" {
tenantName = v
}
if authURL == "" {
err := gophercloud.ErrMissingInput{Argument: "authURL"}
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_AUTH_URL",
}
return nilOptions, err
}
if username == "" && userID == "" {
err := gophercloud.ErrMissingInput{Argument: "username"}
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERNAME", "OS_USERID"},
}
return nilOptions, err
}
if password == "" {
err := gophercloud.ErrMissingInput{Argument: "password"}
if password == "" && applicationCredentialID == "" && applicationCredentialName == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err
}
if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
}
return nilOptions, err
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
ApplicationCredentialID: applicationCredentialID,
ApplicationCredentialName: applicationCredentialName,
ApplicationCredentialSecret: applicationCredentialSecret,
}
return ao, nil

View file

@ -2,7 +2,6 @@ package openstack
import (
"fmt"
"net/url"
"reflect"
"github.com/gophercloud/gophercloud"
@ -12,43 +11,66 @@ import (
)
const (
v20 = "v2.0"
v30 = "v3.0"
// v2 represents Keystone v2.
// It should never increase beyond 2.0.
v2 = "v2.0"
// v3 represents Keystone v3.
// The version can be anything from v3 to v3.x.
v3 = "v3"
)
// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function instead.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
/*
NewClient prepares an unauthenticated ProviderClient instance.
Most users will probably prefer using the AuthenticatedClient function
instead.
This is useful if you wish to explicitly control the version of the identity
service that's used for authentication explicitly, for example.
A basic example of using this would be:
ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.NewClient(ao.IdentityEndpoint)
client, err := openstack.NewIdentityV3(provider, gophercloud.EndpointOpts{})
*/
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
u, err := url.Parse(endpoint)
base, err := utils.BaseEndpoint(endpoint)
if err != nil {
return nil, err
}
hadPath := u.Path != ""
u.Path, u.RawQuery, u.Fragment = "", "", ""
base := u.String()
endpoint = gophercloud.NormalizeURL(endpoint)
base = gophercloud.NormalizeURL(base)
if hadPath {
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: endpoint,
}, nil
}
p := new(gophercloud.ProviderClient)
p.IdentityBase = base
p.IdentityEndpoint = endpoint
p.UseTokenLock()
return &gophercloud.ProviderClient{
IdentityBase: base,
IdentityEndpoint: "",
}, nil
return p, nil
}
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
// returns a Client instance that's ready to operate.
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
// the most recent identity service available to proceed.
/*
AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint
specified by the options, acquires a token, and returns a Provider Client
instance that's ready to operate.
If the full path to a versioned identity endpoint was specified (example:
http://example.com:5000/v3), that path will be used as the endpoint to query.
If a versionless endpoint was specified (example: http://example.com:5000/),
the endpoint will be queried to determine which versions of the identity service
are available, then chooses the most recent or most supported version.
Example:
ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(ao)
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
*/
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
client, err := NewClient(options.IdentityEndpoint)
if err != nil {
@ -62,11 +84,12 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider
return client, nil
}
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
// Authenticate or re-authenticate against the most recent identity service
// supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
versions := []*utils.Version{
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
{ID: v30, Priority: 30, Suffix: "/v3/"},
{ID: v2, Priority: 20, Suffix: "/v2.0/"},
{ID: v3, Priority: 30, Suffix: "/v3/"},
}
chosen, endpoint, err := utils.ChooseVersion(client, versions)
@ -75,9 +98,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
}
switch chosen.ID {
case v20:
case v2:
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
case v30:
case v3:
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
default:
// The switch statement must be out of date from the versions list.
@ -123,9 +146,21 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
}
if options.AllowReauth {
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.ReauthFunc = nil
tac.TokenID = ""
tao := options
tao.AllowReauth = false
client.ReauthFunc = func() error {
client.TokenID = ""
return v2auth(client, endpoint, options, eo)
err := v2auth(&tac, endpoint, tao, eo)
if err != nil {
return err
}
client.TokenID = tac.TokenID
return nil
}
}
client.TokenID = token.ID
@ -167,9 +202,32 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
client.TokenID = token.ID
if opts.CanReauth() {
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.ReauthFunc = nil
tac.TokenID = ""
var tao tokens3.AuthOptionsBuilder
switch ot := opts.(type) {
case *gophercloud.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
case *tokens3.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
default:
tao = opts
}
client.ReauthFunc = func() error {
client.TokenID = ""
return v3auth(client, endpoint, opts, eo)
err := v3auth(&tac, endpoint, tao, eo)
if err != nil {
return err
}
client.TokenID = tac.TokenID
return nil
}
}
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
@ -179,7 +237,8 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return nil
}
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
// NewIdentityV2 creates a ServiceClient that may be used to interact with the
// v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v2.0/"
clientType := "identity"
@ -199,7 +258,8 @@ func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
}, nil
}
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
// NewIdentityV3 creates a ServiceClient that may be used to access the v3
// identity service.
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
endpoint := client.IdentityBase + "v3/"
clientType := "identity"
@ -212,6 +272,19 @@ func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOp
}
}
// Ensure endpoint still has a suffix of v3.
// This is because EndpointLocator might have found a versionless
// endpoint or the published endpoint is still /v2.0. In both
// cases, we need to fix the endpoint to point to /v3.
base, err := utils.BaseEndpoint(endpoint)
if err != nil {
return nil, err
}
base = gophercloud.NormalizeURL(base)
endpoint = base + "v3/"
return &gophercloud.ServiceClient{
ProviderClient: client,
Endpoint: endpoint,
@ -232,33 +305,43 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO
return sc, nil
}
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1
// object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "object-store")
}
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute
// package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "compute")
}
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network
// package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "network")
sc.ResourceBase = sc.Endpoint + "v2.0/"
return sc, err
}
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1
// block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "volume")
}
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2 block storage service.
// NewBlockStorageV2 creates a ServiceClient that may be used to access the v2
// block storage service.
func NewBlockStorageV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "volumev2")
}
// NewBlockStorageV3 creates a ServiceClient that may be used to access the v3 block storage service.
func NewBlockStorageV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "volumev3")
}
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "sharev2")
@ -270,7 +353,8 @@ func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (
return initClientOpts(client, eo, "cdn")
}
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1
// orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "orchestration")
}
@ -280,16 +364,64 @@ func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*
return initClientOpts(client, eo, "database")
}
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS service.
// NewDNSV2 creates a ServiceClient that may be used to access the v2 DNS
// service.
func NewDNSV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "dns")
sc.ResourceBase = sc.Endpoint + "v2/"
return sc, err
}
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service.
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2
// image service.
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "image")
sc.ResourceBase = sc.Endpoint + "v2/"
return sc, err
}
// NewLoadBalancerV2 creates a ServiceClient that may be used to access the v2
// load balancer service.
func NewLoadBalancerV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "load-balancer")
sc.ResourceBase = sc.Endpoint + "v2.0/"
return sc, err
}
// NewClusteringV1 creates a ServiceClient that may be used with the v1 clustering
// package.
func NewClusteringV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "clustering")
}
// NewMessagingV2 creates a ServiceClient that may be used with the v2 messaging
// service.
func NewMessagingV2(client *gophercloud.ProviderClient, clientID string, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "messaging")
sc.MoreHeaders = map[string]string{"Client-ID": clientID}
return sc, err
}
// NewContainerV1 creates a ServiceClient that may be used with v1 container package
func NewContainerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container")
}
// NewKeyManagerV1 creates a ServiceClient that may be used with the v1 key
// manager service.
func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
sc, err := initClientOpts(client, eo, "key-manager")
sc.ResourceBase = sc.Endpoint + "v1/"
return sc, err
}
// NewContainerInfraV1 creates a ServiceClient that may be used with the v1 container infra management
// package.
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container-infra")
}
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "workflowv2")
}

View file

@ -1,3 +1,68 @@
// Package floatingips provides the ability to manage floating ips through
// nova-network
/*
Package floatingips provides the ability to manage floating ips through the
Nova API.
This API has been deprecated and will be removed from a future release of the
Nova API service.
For environements that support this extension, this package can be used
regardless of if either Neutron or nova-network is used as the cloud's network
service.
Example to List Floating IPs
allPages, err := floatingips.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allFloatingIPs, err := floatingips.ExtractFloatingIPs(allPages)
if err != nil {
panic(err)
}
for _, fip := range allFloatingIPs {
fmt.Printf("%+v\n", fip)
}
Example to Create a Floating IP
createOpts := floatingips.CreateOpts{
Pool: "nova",
}
fip, err := floatingips.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Floating IP
err := floatingips.Delete(computeClient, "floatingip-id").ExtractErr()
if err != nil {
panic(err)
}
Example to Associate a Floating IP With a Server
associateOpts := floatingips.AssociateOpts{
FloatingIP: "10.10.10.2",
}
err := floatingips.AssociateInstance(computeClient, "server-id", associateOpts).ExtractErr()
if err != nil {
panic(err)
}
Example to Disassociate a Floating IP From a Server
disassociateOpts := floatingips.DisassociateOpts{
FloatingIP: "10.10.10.2",
}
err := floatingips.DisassociateInstance(computeClient, "server-id", disassociateOpts).ExtractErr()
if err != nil {
panic(err)
}
*/
package floatingips

View file

@ -12,15 +12,15 @@ func List(client *gophercloud.ServiceClient) pagination.Pager {
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
// CreateOpts struct in this package does.
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToFloatingIPCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies a Floating IP allocation request
// CreateOpts specifies a Floating IP allocation request.
type CreateOpts struct {
// Pool is the pool of floating IPs to allocate one from
// Pool is the pool of Floating IPs to allocate one from.
Pool string `json:"pool" required:"true"`
}
@ -29,7 +29,7 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// Create requests the creation of a new floating IP
// Create requests the creation of a new Floating IP.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFloatingIPCreateMap()
if err != nil {
@ -42,29 +42,30 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// Get returns data about a previously created FloatingIP.
// Get returns data about a previously created Floating IP.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// Delete requests the deletion of a previous allocated FloatingIP.
// Delete requests the deletion of a previous allocated Floating IP.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// AssociateOptsBuilder is the interface types must satfisfy to be used as
// Associate options
// AssociateOptsBuilder allows extensions to add additional parameters to the
// Associate request.
type AssociateOptsBuilder interface {
ToFloatingIPAssociateMap() (map[string]interface{}, error)
}
// AssociateOpts specifies the required information to associate a floating IP with an instance
// AssociateOpts specifies the required information to associate a Floating IP with an instance
type AssociateOpts struct {
// FloatingIP is the floating IP to associate with an instance
// FloatingIP is the Floating IP to associate with an instance.
FloatingIP string `json:"address" required:"true"`
// FixedIP is an optional fixed IP address of the server
// FixedIP is an optional fixed IP address of the server.
FixedIP string `json:"fixed_address,omitempty"`
}
@ -73,7 +74,7 @@ func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, er
return gophercloud.BuildRequestBody(opts, "addFloatingIp")
}
// AssociateInstance pairs an allocated floating IP with an instance.
// AssociateInstance pairs an allocated Floating IP with a server.
func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) {
b, err := opts.ToFloatingIPAssociateMap()
if err != nil {
@ -84,23 +85,24 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts
return
}
// DisassociateOptsBuilder is the interface types must satfisfy to be used as
// Disassociate options
// DisassociateOptsBuilder allows extensions to add additional parameters to
// the Disassociate request.
type DisassociateOptsBuilder interface {
ToFloatingIPDisassociateMap() (map[string]interface{}, error)
}
// DisassociateOpts specifies the required information to disassociate a floating IP with an instance
// DisassociateOpts specifies the required information to disassociate a
// Floating IP with a server.
type DisassociateOpts struct {
FloatingIP string `json:"address" required:"true"`
}
// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts.
// ToFloatingIPDisassociateMap constructs a request body from DisassociateOpts.
func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
}
// DisassociateInstance decouples an allocated floating IP from an instance
// DisassociateInstance decouples an allocated Floating IP from an instance
func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) {
b, err := opts.ToFloatingIPDisassociateMap()
if err != nil {

View file

@ -8,21 +8,21 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// A FloatingIP is an IP that can be associated with an instance
// A FloatingIP is an IP that can be associated with a server.
type FloatingIP struct {
// ID is a unique ID of the Floating IP
ID string `json:"-"`
// FixedIP is the IP of the instance related to the Floating IP
// FixedIP is a specific IP on the server to pair the Floating IP with.
FixedIP string `json:"fixed_ip,omitempty"`
// InstanceID is the ID of the instance that is using the Floating IP
// InstanceID is the ID of the server that is using the Floating IP.
InstanceID string `json:"instance_id"`
// IP is the actual Floating IP
// IP is the actual Floating IP.
IP string `json:"ip"`
// Pool is the pool of floating IPs that this floating IP belongs to
// Pool is the pool of Floating IPs that this Floating IP belongs to.
Pool string `json:"pool"`
}
@ -49,8 +49,7 @@ func (r *FloatingIP) UnmarshalJSON(b []byte) error {
return err
}
// FloatingIPPage stores a single, only page of FloatingIPs
// results from a List call.
// FloatingIPPage stores a single page of FloatingIPs from a List call.
type FloatingIPPage struct {
pagination.SinglePageBase
}
@ -61,8 +60,7 @@ func (page FloatingIPPage) IsEmpty() (bool, error) {
return len(va) == 0, err
}
// ExtractFloatingIPs interprets a page of results as a slice of
// FloatingIPs.
// ExtractFloatingIPs interprets a page of results as a slice of FloatingIPs.
func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
var s struct {
FloatingIPs []FloatingIP `json:"floating_ips"`
@ -86,32 +84,32 @@ func (r FloatingIPResult) Extract() (*FloatingIP, error) {
return s.FloatingIP, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a FloatingIP.
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a FloatingIP.
type CreateResult struct {
FloatingIPResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a FloatingIP.
// GetResult is the response from a Get operation. Call its Extract method to
// interpret it as a FloatingIP.
type GetResult struct {
FloatingIPResult
}
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
// DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// AssociateResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
// AssociateResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type AssociateResult struct {
gophercloud.ErrResult
}
// DisassociateResult is the response from a Delete operation. Call its Extract method to determine if
// the call succeeded or failed.
// DisassociateResult is the response from a Delete operation. Call its
// ExtractErr method to determine if the call succeeded or failed.
type DisassociateResult struct {
gophercloud.ErrResult
}

View file

@ -1,3 +1,51 @@
// Package hypervisors gives information and control of the os-hypervisors
// portion of the compute API
/*
Package hypervisors returns details about list of hypervisors, shows details for a hypervisor
and shows summary statistics for all hypervisors over all compute nodes in the OpenStack cloud.
Example of Show Hypervisor Details
hypervisorID := 42
hypervisor, err := hypervisors.Get(computeClient, 42).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", hypervisor)
Example of Retrieving Details of All Hypervisors
allPages, err := hypervisors.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allHypervisors, err := hypervisors.ExtractHypervisors(allPages)
if err != nil {
panic(err)
}
for _, hypervisor := range allHypervisors {
fmt.Printf("%+v\n", hypervisor)
}
Example of Show Hypervisor Statistics
hypervisorsStatistics, err := hypervisors.GetStatistics(computeClient).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", hypervisorsStatistics)
Example of Show Hypervisor Uptime
hypervisorID := 42
hypervisorUptime, err := hypervisors.GetUptime(computeClient, hypervisorID).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", hypervisorUptime)
*/
package hypervisors

View file

@ -1,6 +1,8 @@
package hypervisors
import (
"strconv"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@ -11,3 +13,29 @@ func List(client *gophercloud.ServiceClient) pagination.Pager {
return HypervisorPage{pagination.SinglePageBase(r)}
})
}
// Statistics makes a request against the API to get hypervisors statistics.
func GetStatistics(client *gophercloud.ServiceClient) (r StatisticsResult) {
_, r.Err = client.Get(hypervisorsStatisticsURL(client), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get makes a request against the API to get details for specific hypervisor.
func Get(client *gophercloud.ServiceClient, hypervisorID int) (r HypervisorResult) {
v := strconv.Itoa(hypervisorID)
_, r.Err = client.Get(hypervisorsGetURL(client, v), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// GetUptime makes a request against the API to get uptime for specific hypervisor.
func GetUptime(client *gophercloud.ServiceClient, hypervisorID int) (r UptimeResult) {
v := strconv.Itoa(hypervisorID)
_, r.Err = client.Get(hypervisorsUptimeURL(client, v), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View file

@ -4,15 +4,18 @@ import (
"encoding/json"
"fmt"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Topology represents a CPU Topology.
type Topology struct {
Sockets int `json:"sockets"`
Cores int `json:"cores"`
Threads int `json:"threads"`
}
// CPUInfo represents CPU information of the hypervisor.
type CPUInfo struct {
Vendor string `json:"vendor"`
Arch string `json:"arch"`
@ -21,59 +24,82 @@ type CPUInfo struct {
Topology Topology `json:"topology"`
}
// Service represents a Compute service running on the hypervisor.
type Service struct {
Host string `json:"host"`
ID int `json:"id"`
DisabledReason string `json:"disabled_reason"`
}
// Hypervisor represents a hypervisor in the OpenStack cloud.
type Hypervisor struct {
// A structure that contains cpu information like arch, model, vendor, features and topology
// A structure that contains cpu information like arch, model, vendor,
// features and topology.
CPUInfo CPUInfo `json:"-"`
// The current_workload is the number of tasks the hypervisor is responsible for.
// This will be equal or greater than the number of active VMs on the system
// (it can be greater when VMs are being deleted and the hypervisor is still cleaning up).
// The current_workload is the number of tasks the hypervisor is responsible
// for. This will be equal or greater than the number of active VMs on the
// system (it can be greater when VMs are being deleted and the hypervisor is
// still cleaning up).
CurrentWorkload int `json:"current_workload"`
// Status of the hypervisor, either "enabled" or "disabled"
// Status of the hypervisor, either "enabled" or "disabled".
Status string `json:"status"`
// State of the hypervisor, either "up" or "down"
// State of the hypervisor, either "up" or "down".
State string `json:"state"`
// Actual free disk on this hypervisor in GB
// DiskAvailableLeast is the actual free disk on this hypervisor,
// measured in GB.
DiskAvailableLeast int `json:"disk_available_least"`
// The hypervisor's IP address
// HostIP is the hypervisor's IP address.
HostIP string `json:"host_ip"`
// The free disk remaining on this hypervisor in GB
// FreeDiskGB is the free disk remaining on the hypervisor, measured in GB.
FreeDiskGB int `json:"-"`
// The free RAM in this hypervisor in MB
// FreeRAMMB is the free RAM in the hypervisor, measured in MB.
FreeRamMB int `json:"free_ram_mb"`
// The hypervisor host name
// HypervisorHostname is the hostname of the hypervisor.
HypervisorHostname string `json:"hypervisor_hostname"`
// The hypervisor type
// HypervisorType is the type of hypervisor.
HypervisorType string `json:"hypervisor_type"`
// The hypervisor version
// HypervisorVersion is the version of the hypervisor.
HypervisorVersion int `json:"-"`
// Unique ID of the hypervisor
// ID is the unique ID of the hypervisor.
ID int `json:"id"`
// The disk in this hypervisor in GB
// LocalGB is the disk space in the hypervisor, measured in GB.
LocalGB int `json:"-"`
// The disk used in this hypervisor in GB
// LocalGBUsed is the used disk space of the hypervisor, measured in GB.
LocalGBUsed int `json:"local_gb_used"`
// The memory of this hypervisor in MB
// MemoryMB is the total memory of the hypervisor, measured in MB.
MemoryMB int `json:"memory_mb"`
// The memory used in this hypervisor in MB
// MemoryMBUsed is the used memory of the hypervisor, measured in MB.
MemoryMBUsed int `json:"memory_mb_used"`
// The number of running vms on this hypervisor
// RunningVMs is the The number of running vms on the hypervisor.
RunningVMs int `json:"running_vms"`
// The hypervisor service object
// Service is the service this hypervisor represents.
Service Service `json:"service"`
// The number of vcpu in this hypervisor
// VCPUs is the total number of vcpus on the hypervisor.
VCPUs int `json:"vcpus"`
// The number of vcpu used in this hypervisor
// VCPUsUsed is the number of used vcpus on the hypervisor.
VCPUsUsed int `json:"vcpus_used"`
}
func (r *Hypervisor) UnmarshalJSON(b []byte) error {
type tmp Hypervisor
var s struct {
tmp
@ -90,9 +116,9 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error {
*r = Hypervisor(s.tmp)
// Newer versions pass the CPU into around as the correct types, this just needs
// converting and copying into place. Older versions pass CPU info around as a string
// and can simply be unmarshalled by the json parser
// Newer versions return the CPU info as the correct type.
// Older versions return the CPU info as a string and need to be
// unmarshalled by the json parser.
var tmpb []byte
switch t := s.CPUInfo.(type) {
@ -112,7 +138,8 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error {
return err
}
// These fields may be passed in in scientific notation
// These fields may be returned as a scientific notation, so they need
// converted to int.
switch t := s.HypervisorVersion.(type) {
case int:
r.HypervisorVersion = t
@ -143,15 +170,19 @@ func (r *Hypervisor) UnmarshalJSON(b []byte) error {
return nil
}
// HypervisorPage represents a single page of all Hypervisors from a List
// request.
type HypervisorPage struct {
pagination.SinglePageBase
}
// IsEmpty determines whether or not a HypervisorPage is empty.
func (page HypervisorPage) IsEmpty() (bool, error) {
va, err := ExtractHypervisors(page)
return len(va) == 0, err
}
// ExtractHypervisors interprets a page of results as a slice of Hypervisors.
func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) {
var h struct {
Hypervisors []Hypervisor `json:"hypervisors"`
@ -159,3 +190,101 @@ func ExtractHypervisors(p pagination.Page) ([]Hypervisor, error) {
err := (p.(HypervisorPage)).ExtractInto(&h)
return h.Hypervisors, err
}
type HypervisorResult struct {
gophercloud.Result
}
// Extract interprets any HypervisorResult as a Hypervisor, if possible.
func (r HypervisorResult) Extract() (*Hypervisor, error) {
var s struct {
Hypervisor Hypervisor `json:"hypervisor"`
}
err := r.ExtractInto(&s)
return &s.Hypervisor, err
}
// Statistics represents a summary statistics for all enabled
// hypervisors over all compute nodes in the OpenStack cloud.
type Statistics struct {
// The number of hypervisors.
Count int `json:"count"`
// The current_workload is the number of tasks the hypervisor is responsible for
CurrentWorkload int `json:"current_workload"`
// The actual free disk on this hypervisor(in GB).
DiskAvailableLeast int `json:"disk_available_least"`
// The free disk remaining on this hypervisor(in GB).
FreeDiskGB int `json:"free_disk_gb"`
// The free RAM in this hypervisor(in MB).
FreeRamMB int `json:"free_ram_mb"`
// The disk in this hypervisor(in GB).
LocalGB int `json:"local_gb"`
// The disk used in this hypervisor(in GB).
LocalGBUsed int `json:"local_gb_used"`
// The memory of this hypervisor(in MB).
MemoryMB int `json:"memory_mb"`
// The memory used in this hypervisor(in MB).
MemoryMBUsed int `json:"memory_mb_used"`
// The total number of running vms on all hypervisors.
RunningVMs int `json:"running_vms"`
// The number of vcpu in this hypervisor.
VCPUs int `json:"vcpus"`
// The number of vcpu used in this hypervisor.
VCPUsUsed int `json:"vcpus_used"`
}
type StatisticsResult struct {
gophercloud.Result
}
// Extract interprets any StatisticsResult as a Statistics, if possible.
func (r StatisticsResult) Extract() (*Statistics, error) {
var s struct {
Stats Statistics `json:"hypervisor_statistics"`
}
err := r.ExtractInto(&s)
return &s.Stats, err
}
// Uptime represents uptime and additional info for a specific hypervisor.
type Uptime struct {
// The hypervisor host name provided by the Nova virt driver.
// For the Ironic driver, it is the Ironic node uuid.
HypervisorHostname string `json:"hypervisor_hostname"`
// The id of the hypervisor.
ID int `json:"id"`
// The state of the hypervisor. One of up or down.
State string `json:"state"`
// The status of the hypervisor. One of enabled or disabled.
Status string `json:"status"`
// The total uptime of the hypervisor and information about average load.
Uptime string `json:"uptime"`
}
type UptimeResult struct {
gophercloud.Result
}
// Extract interprets any UptimeResult as a Uptime, if possible.
func (r UptimeResult) Extract() (*Uptime, error) {
var s struct {
Uptime Uptime `json:"hypervisor"`
}
err := r.ExtractInto(&s)
return &s.Uptime, err
}

View file

@ -5,3 +5,15 @@ import "github.com/gophercloud/gophercloud"
func hypervisorsListDetailURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-hypervisors", "detail")
}
func hypervisorsStatisticsURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("os-hypervisors", "statistics")
}
func hypervisorsGetURL(c *gophercloud.ServiceClient, hypervisorID string) string {
return c.ServiceURL("os-hypervisors", hypervisorID)
}
func hypervisorsUptimeURL(c *gophercloud.ServiceClient, hypervisorID string) string {
return c.ServiceURL("os-hypervisors", hypervisorID, "uptime")
}

View file

@ -1,7 +1,137 @@
// Package flavors provides information and interaction with the flavor API
// resource in the OpenStack Compute service.
//
// A flavor is an available hardware configuration for a server. Each flavor
// has a unique combination of disk space, memory capacity and priority for CPU
// time.
/*
Package flavors provides information and interaction with the flavor API
in the OpenStack Compute service.
A flavor is an available hardware configuration for a server. Each flavor
has a unique combination of disk space, memory capacity and priority for CPU
time.
Example to List Flavors
listOpts := flavors.ListOpts{
AccessType: flavors.PublicAccess,
}
allPages, err := flavors.ListDetail(computeClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allFlavors, err := flavors.ExtractFlavors(allPages)
if err != nil {
panic(err)
}
for _, flavor := range allFlavors {
fmt.Printf("%+v\n", flavor)
}
Example to Create a Flavor
createOpts := flavors.CreateOpts{
ID: "1",
Name: "m1.tiny",
Disk: gophercloud.IntToPointer(1),
RAM: 512,
VCPUs: 1,
RxTxFactor: 1.0,
}
flavor, err := flavors.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to List Flavor Access
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
allPages, err := flavors.ListAccesses(computeClient, flavorID).AllPages()
if err != nil {
panic(err)
}
allAccesses, err := flavors.ExtractAccesses(allPages)
if err != nil {
panic(err)
}
for _, access := range allAccesses {
fmt.Printf("%+v", access)
}
Example to Grant Access to a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
accessOpts := flavors.AddAccessOpts{
Tenant: "15153a0979884b59b0592248ef947921",
}
accessList, err := flavors.AddAccess(computeClient, flavor.ID, accessOpts).Extract()
if err != nil {
panic(err)
}
Example to Remove/Revoke Access to a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
accessOpts := flavors.RemoveAccessOpts{
Tenant: "15153a0979884b59b0592248ef947921",
}
accessList, err := flavors.RemoveAccess(computeClient, flavor.ID, accessOpts).Extract()
if err != nil {
panic(err)
}
Example to Create Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
createOpts := flavors.ExtraSpecsOpts{
"hw:cpu_policy": "CPU-POLICY",
"hw:cpu_thread_policy": "CPU-THREAD-POLICY",
}
createdExtraSpecs, err := flavors.CreateExtraSpecs(computeClient, flavorID, createOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", createdExtraSpecs)
Example to Get Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
extraSpecs, err := flavors.ListExtraSpecs(computeClient, flavorID).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", extraSpecs)
Example to Update Extra Specs for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
updateOpts := flavors.ExtraSpecsOpts{
"hw:cpu_thread_policy": "CPU-THREAD-POLICY-UPDATED",
}
updatedExtraSpec, err := flavors.UpdateExtraSpec(computeClient, flavorID, updateOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", updatedExtraSpec)
Example to Delete an Extra Spec for a Flavor
flavorID := "e91758d6-a54a-4778-ad72-0c73a1cb695b"
err := flavors.DeleteExtraSpec(computeClient, flavorID, "hw:cpu_thread_policy").ExtractErr()
if err != nil {
panic(err)
}
*/
package flavors

View file

@ -11,45 +11,65 @@ type ListOptsBuilder interface {
ToFlavorListQuery() (string, error)
}
// AccessType maps to OpenStack's Flavor.is_public field. Although the is_public field is boolean, the
// request options are ternary, which is why AccessType is a string. The following values are
// allowed:
//
// PublicAccess (the default): Returns public flavors and private flavors associated with that project.
// PrivateAccess (admin only): Returns private flavors, across all projects.
// AllAccess (admin only): Returns public and private flavors across all projects.
//
// The AccessType arguement is optional, and if it is not supplied, OpenStack returns the PublicAccess
// flavors.
/*
AccessType maps to OpenStack's Flavor.is_public field. Although the is_public
field is boolean, the request options are ternary, which is why AccessType is
a string. The following values are allowed:
The AccessType arguement is optional, and if it is not supplied, OpenStack
returns the PublicAccess flavors.
*/
type AccessType string
const (
PublicAccess AccessType = "true"
// PublicAccess returns public flavors and private flavors associated with
// that project.
PublicAccess AccessType = "true"
// PrivateAccess (admin only) returns private flavors, across all projects.
PrivateAccess AccessType = "false"
AllAccess AccessType = "None"
// AllAccess (admin only) returns public and private flavors across all
// projects.
AllAccess AccessType = "None"
)
// ListOpts helps control the results returned by the List() function.
// For example, a flavor with a minDisk field of 10 will not be returned if you specify MinDisk set to 20.
// Typically, software will use the last ID of the previous call to List to set the Marker for the current call.
type ListOpts struct {
/*
ListOpts filters the results returned by the List() function.
For example, a flavor with a minDisk field of 10 will not be returned if you
specify MinDisk set to 20.
// ChangesSince, if provided, instructs List to return only those things which have changed since the timestamp provided.
Typically, software will use the last ID of the previous call to List to set
the Marker for the current call.
*/
type ListOpts struct {
// ChangesSince, if provided, instructs List to return only those things which
// have changed since the timestamp provided.
ChangesSince string `q:"changes-since"`
// MinDisk and MinRAM, if provided, elides flavors which do not meet your criteria.
// MinDisk and MinRAM, if provided, elides flavors which do not meet your
// criteria.
MinDisk int `q:"minDisk"`
MinRAM int `q:"minRam"`
// SortDir allows to select sort direction.
// It can be "asc" or "desc" (default).
SortDir string `q:"sort_dir"`
// SortKey allows to sort by one of the flavors attributes.
// Default is flavorid.
SortKey string `q:"sort_key"`
// Marker and Limit control paging.
// Marker instructs List where to start listing from.
Marker string `q:"marker"`
// Limit instructs List to refrain from sending excessively large lists of flavors.
// Limit instructs List to refrain from sending excessively large lists of
// flavors.
Limit int `q:"limit"`
// AccessType, if provided, instructs List which set of flavors to return. If IsPublic not provided,
// flavors for the current project are returned.
// AccessType, if provided, instructs List which set of flavors to return.
// If IsPublic not provided, flavors for the current project are returned.
AccessType AccessType `q:"is_public"`
}
@ -60,8 +80,8 @@ func (opts ListOpts) ToFlavorListQuery() (string, error) {
}
// ListDetail instructs OpenStack to provide a list of flavors.
// You may provide criteria by which List curtails its results for easier processing.
// See ListOpts for more details.
// You may provide criteria by which List curtails its results for easier
// processing.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(client)
if opts != nil {
@ -80,31 +100,42 @@ type CreateOptsBuilder interface {
ToFlavorCreateMap() (map[string]interface{}, error)
}
// CreateOpts is passed to Create to create a flavor
// Source:
// https://github.com/openstack/nova/blob/stable/newton/nova/api/openstack/compute/schemas/flavor_manage.py#L20
// CreateOpts specifies parameters used for creating a flavor.
type CreateOpts struct {
// Name is the name of the flavor.
Name string `json:"name" required:"true"`
// memory size, in MBs
RAM int `json:"ram" required:"true"`
// RAM is the memory of the flavor, measured in MB.
RAM int `json:"ram" required:"true"`
// VCPUs is the number of vcpus for the flavor.
VCPUs int `json:"vcpus" required:"true"`
// disk size, in GBs
Disk *int `json:"disk" required:"true"`
ID string `json:"id,omitempty"`
// non-zero, positive
Swap *int `json:"swap,omitempty"`
// Disk the amount of root disk space, measured in GB.
Disk *int `json:"disk" required:"true"`
// ID is a unique ID for the flavor.
ID string `json:"id,omitempty"`
// Swap is the amount of swap space for the flavor, measured in MB.
Swap *int `json:"swap,omitempty"`
// RxTxFactor alters the network bandwidth of a flavor.
RxTxFactor float64 `json:"rxtx_factor,omitempty"`
IsPublic *bool `json:"os-flavor-access:is_public,omitempty"`
// ephemeral disk size, in GBs, non-zero, positive
// IsPublic flags a flavor as being available to all projects or not.
IsPublic *bool `json:"os-flavor-access:is_public,omitempty"`
// Ephemeral is the amount of ephemeral disk space, measured in GB.
Ephemeral *int `json:"OS-FLV-EXT-DATA:ephemeral,omitempty"`
}
// ToFlavorCreateMap satisfies the CreateOptsBuilder interface
func (opts *CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) {
// ToFlavorCreateMap constructs a request body from CreateOpts.
func (opts CreateOpts) ToFlavorCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "flavor")
}
// Create a flavor
// Create requests the creation of a new flavor.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToFlavorCreateMap()
if err != nil {
@ -117,14 +148,177 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
// Use ExtractFlavor to convert its result into a Flavor.
// Get retrieves details of a single flavor. Use ExtractFlavor to convert its
// result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// IDFromName is a convienience function that returns a flavor's ID given its name.
// Delete deletes the specified flavor ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// ListAccesses retrieves the tenants which have access to a flavor.
func ListAccesses(client *gophercloud.ServiceClient, id string) pagination.Pager {
url := accessURL(client, id)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AccessPage{pagination.SinglePageBase(r)}
})
}
// AddAccessOptsBuilder allows extensions to add additional parameters to the
// AddAccess requests.
type AddAccessOptsBuilder interface {
ToFlavorAddAccessMap() (map[string]interface{}, error)
}
// AddAccessOpts represents options for adding access to a flavor.
type AddAccessOpts struct {
// Tenant is the project/tenant ID to grant access.
Tenant string `json:"tenant"`
}
// ToFlavorAddAccessMap constructs a request body from AddAccessOpts.
func (opts AddAccessOpts) ToFlavorAddAccessMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "addTenantAccess")
}
// AddAccess grants a tenant/project access to a flavor.
func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsBuilder) (r AddAccessResult) {
b, err := opts.ToFlavorAddAccessMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// RemoveAccessOptsBuilder allows extensions to add additional parameters to the
// RemoveAccess requests.
type RemoveAccessOptsBuilder interface {
ToFlavorRemoveAccessMap() (map[string]interface{}, error)
}
// RemoveAccessOpts represents options for removing access to a flavor.
type RemoveAccessOpts struct {
// Tenant is the project/tenant ID to grant access.
Tenant string `json:"tenant"`
}
// ToFlavorRemoveAccessMap constructs a request body from RemoveAccessOpts.
func (opts RemoveAccessOpts) ToFlavorRemoveAccessMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "removeTenantAccess")
}
// RemoveAccess removes/revokes a tenant/project access to a flavor.
func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAccessOptsBuilder) (r RemoveAccessResult) {
b, err := opts.ToFlavorRemoveAccessMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ExtraSpecs requests all the extra-specs for the given flavor ID.
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
_, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
return
}
func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) {
_, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil)
return
}
// CreateExtraSpecsOptsBuilder allows extensions to add additional parameters to the
// CreateExtraSpecs requests.
type CreateExtraSpecsOptsBuilder interface {
ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error)
}
// ExtraSpecsOpts is a map that contains key-value pairs.
type ExtraSpecsOpts map[string]string
// ToFlavorExtraSpecsCreateMap assembles a body for a Create request based on
// the contents of ExtraSpecsOpts.
func (opts ExtraSpecsOpts) ToFlavorExtraSpecsCreateMap() (map[string]interface{}, error) {
return map[string]interface{}{"extra_specs": opts}, nil
}
// CreateExtraSpecs will create or update the extra-specs key-value pairs for
// the specified Flavor.
func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts CreateExtraSpecsOptsBuilder) (r CreateExtraSpecsResult) {
b, err := opts.ToFlavorExtraSpecsCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// UpdateExtraSpecOptsBuilder allows extensions to add additional parameters to
// the Update request.
type UpdateExtraSpecOptsBuilder interface {
ToFlavorExtraSpecUpdateMap() (map[string]string, string, error)
}
// ToFlavorExtraSpecUpdateMap assembles a body for an Update request based on
// the contents of a ExtraSpecOpts.
func (opts ExtraSpecsOpts) ToFlavorExtraSpecUpdateMap() (map[string]string, string, error) {
if len(opts) != 1 {
err := gophercloud.ErrInvalidInput{}
err.Argument = "flavors.ExtraSpecOpts"
err.Info = "Must have 1 and only one key-value pair"
return nil, "", err
}
var key string
for k := range opts {
key = k
}
return opts, key, nil
}
// UpdateExtraSpec will updates the value of the specified flavor's extra spec
// for the key in opts.
func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts UpdateExtraSpecOptsBuilder) (r UpdateExtraSpecResult) {
b, key, err := opts.ToFlavorExtraSpecUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// DeleteExtraSpec will delete the key-value pair with the given key for the given
// flavor ID.
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
_, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// IDFromName is a convienience function that returns a flavor's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""

View file

@ -12,16 +12,26 @@ type commonResult struct {
gophercloud.Result
}
// CreateResult is the response of a Get operations. Call its Extract method to
// interpret it as a Flavor.
type CreateResult struct {
commonResult
}
// GetResult temporarily holds the response from a Get call.
// GetResult is the response of a Get operations. Call its Extract method to
// interpret it as a Flavor.
type GetResult struct {
commonResult
}
// Extract provides access to the individual Flavor returned by the Get and Create functions.
// DeleteResult is the result from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// Extract provides access to the individual Flavor returned by the Get and
// Create functions.
func (r commonResult) Extract() (*Flavor, error) {
var s struct {
Flavor *Flavor `json:"flavor"`
@ -30,24 +40,35 @@ func (r commonResult) Extract() (*Flavor, error) {
return s.Flavor, err
}
// Flavor records represent (virtual) hardware configurations for server resources in a region.
// Flavor represent (virtual) hardware configurations for server resources
// in a region.
type Flavor struct {
// The Id field contains the flavor's unique identifier.
// For example, this identifier will be useful when specifying which hardware configuration to use for a new server instance.
// ID is the flavor's unique ID.
ID string `json:"id"`
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
// Disk is the amount of root disk, measured in GB.
Disk int `json:"disk"`
RAM int `json:"ram"`
// The Name field provides a human-readable moniker for the flavor.
Name string `json:"name"`
// RAM is the amount of memory, measured in MB.
RAM int `json:"ram"`
// Name is the name of the flavor.
Name string `json:"name"`
// RxTxFactor describes bandwidth alterations of the flavor.
RxTxFactor float64 `json:"rxtx_factor"`
// Swap indicates how much space is reserved for swap.
// If not provided, this field will be set to 0.
Swap int `json:"swap"`
// Swap is the amount of swap space, measured in MB.
Swap int `json:"-"`
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
VCPUs int `json:"vcpus"`
// IsPublic indicates whether the flavor is public.
IsPublic bool `json:"is_public"`
IsPublic bool `json:"os-flavor-access:is_public"`
// Ephemeral is the amount of ephemeral disk space, measured in GB.
Ephemeral int `json:"OS-FLV-EXT-DATA:ephemeral"`
}
func (r *Flavor) UnmarshalJSON(b []byte) error {
@ -82,18 +103,19 @@ func (r *Flavor) UnmarshalJSON(b []byte) error {
return nil
}
// FlavorPage contains a single page of the response from a List call.
// FlavorPage contains a single page of all flavors from a ListDetails call.
type FlavorPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines if a page contains any results.
// IsEmpty determines if a FlavorPage contains any results.
func (page FlavorPage) IsEmpty() (bool, error) {
flavors, err := ExtractFlavors(page)
return len(flavors) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
// NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (page FlavorPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"flavors_links"`
@ -105,7 +127,8 @@ func (page FlavorPage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
// ExtractFlavors provides access to the list of flavors in a page acquired
// from the ListDetail operation.
func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
var s struct {
Flavors []Flavor `json:"flavors"`
@ -113,3 +136,117 @@ func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
err := (r.(FlavorPage)).ExtractInto(&s)
return s.Flavors, err
}
// AccessPage contains a single page of all FlavorAccess entries for a flavor.
type AccessPage struct {
pagination.SinglePageBase
}
// IsEmpty indicates whether an AccessPage is empty.
func (page AccessPage) IsEmpty() (bool, error) {
v, err := ExtractAccesses(page)
return len(v) == 0, err
}
// ExtractAccesses interprets a page of results as a slice of FlavorAccess.
func ExtractAccesses(r pagination.Page) ([]FlavorAccess, error) {
var s struct {
FlavorAccesses []FlavorAccess `json:"flavor_access"`
}
err := (r.(AccessPage)).ExtractInto(&s)
return s.FlavorAccesses, err
}
type accessResult struct {
gophercloud.Result
}
// AddAccessResult is the response of an AddAccess operation. Call its
// Extract method to interpret it as a slice of FlavorAccess.
type AddAccessResult struct {
accessResult
}
// RemoveAccessResult is the response of a RemoveAccess operation. Call its
// Extract method to interpret it as a slice of FlavorAccess.
type RemoveAccessResult struct {
accessResult
}
// Extract provides access to the result of an access create or delete.
// The result will be all accesses that the flavor has.
func (r accessResult) Extract() ([]FlavorAccess, error) {
var s struct {
FlavorAccesses []FlavorAccess `json:"flavor_access"`
}
err := r.ExtractInto(&s)
return s.FlavorAccesses, err
}
// FlavorAccess represents an ACL of tenant access to a specific Flavor.
type FlavorAccess struct {
// FlavorID is the unique ID of the flavor.
FlavorID string `json:"flavor_id"`
// TenantID is the unique ID of the tenant.
TenantID string `json:"tenant_id"`
}
// Extract interprets any extraSpecsResult as ExtraSpecs, if possible.
func (r extraSpecsResult) Extract() (map[string]string, error) {
var s struct {
ExtraSpecs map[string]string `json:"extra_specs"`
}
err := r.ExtractInto(&s)
return s.ExtraSpecs, err
}
// extraSpecsResult contains the result of a call for (potentially) multiple
// key-value pairs. Call its Extract method to interpret it as a
// map[string]interface.
type extraSpecsResult struct {
gophercloud.Result
}
// ListExtraSpecsResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type ListExtraSpecsResult struct {
extraSpecsResult
}
// CreateExtraSpecResult contains the result of a Create operation. Call its
// Extract method to interpret it as a map[string]interface.
type CreateExtraSpecsResult struct {
extraSpecsResult
}
// extraSpecResult contains the result of a call for individual a single
// key-value pair.
type extraSpecResult struct {
gophercloud.Result
}
// GetExtraSpecResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetExtraSpecResult struct {
extraSpecResult
}
// UpdateExtraSpecResult contains the result of an Update operation. Call its
// Extract method to interpret it as a map[string]interface.
type UpdateExtraSpecResult struct {
extraSpecResult
}
// DeleteExtraSpecResult contains the result of a Delete operation. Call its
// ExtractErr method to determine if the call succeeded or failed.
type DeleteExtraSpecResult struct {
gophercloud.ErrResult
}
// Extract interprets any extraSpecResult as an ExtraSpec, if possible.
func (r extraSpecResult) Extract() (map[string]string, error) {
var s map[string]string
err := r.ExtractInto(&s)
return s, err
}

View file

@ -15,3 +15,35 @@ func listURL(client *gophercloud.ServiceClient) string {
func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("flavors")
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id)
}
func accessURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-flavor-access")
}
func accessActionURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "action")
}
func extraSpecsListURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-extra_specs")
}
func extraSpecsGetURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}
func extraSpecsCreateURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("flavors", id, "os-extra_specs")
}
func extraSpecUpdateURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}
func extraSpecDeleteURL(client *gophercloud.ServiceClient, id, key string) string {
return client.ServiceURL("flavors", id, "os-extra_specs", key)
}

View file

@ -1,7 +1,32 @@
// Package images provides information and interaction with the image API
// resource in the OpenStack Compute service.
//
// An image is a collection of files used to create or rebuild a server.
// Operators provide a number of pre-built OS images by default. You may also
// create custom images from cloud servers you have launched.
/*
Package images provides information and interaction with the images through
the OpenStack Compute service.
This API is deprecated and will be removed from a future version of the Nova
API service.
An image is a collection of files used to create or rebuild a server.
Operators provide a number of pre-built OS images by default. You may also
create custom images from cloud servers you have launched.
Example to List Images
listOpts := images.ListOpts{
Limit: 2,
}
allPages, err := images.ListDetail(computeClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allImages, err := images.ExtractImages(allPages)
if err != nil {
panic(err)
}
for _, image := range allImages {
fmt.Printf("%+v\n", image)
}
*/
package images

View file

@ -6,26 +6,33 @@ import (
)
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
// ListDetail request.
type ListOptsBuilder interface {
ToImageListQuery() (string, error)
}
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
// ListOpts contain options filtering Images returned from a call to ListDetail.
type ListOpts struct {
// When the image last changed status (in date-time format).
// ChangesSince filters Images based on the last changed status (in date-time
// format).
ChangesSince string `q:"changes-since"`
// The number of Images to return.
// Limit limits the number of Images to return.
Limit int `q:"limit"`
// UUID of the Image at which to set a marker.
// Mark is an Image UUID at which to set a marker.
Marker string `q:"marker"`
// The name of the Image.
// Name is the name of the Image.
Name string `q:"name"`
// The name of the Server (in URL format).
// Server is the name of the Server (in URL format).
Server string `q:"server"`
// The current status of the Image.
// Status is the current status of the Image.
Status string `q:"status"`
// The value of the type of image (e.g. BASE, SERVER, ALL)
// Type is the type of image (e.g. BASE, SERVER, ALL).
Type string `q:"type"`
}
@ -50,8 +57,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat
})
}
// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
// Get returns data about a specific image by its ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
@ -63,7 +69,8 @@ func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
return
}
// IDFromName is a convienience function that returns an image's ID given its name.
// IDFromName is a convienience function that returns an image's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""

View file

@ -5,12 +5,14 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// GetResult temporarily stores a Get response.
// GetResult is the response from a Get operation. Call its Extract method to
// interpret it as an Image.
type GetResult struct {
gophercloud.Result
}
// DeleteResult represents the result of an image.Delete operation.
// DeleteResult is the result from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
@ -24,44 +26,53 @@ func (r GetResult) Extract() (*Image, error) {
return s.Image, err
}
// Image is used for JSON (un)marshalling.
// It provides a description of an OS image.
// Image represents an Image returned by the Compute API.
type Image struct {
// ID contains the image's unique identifier.
// ID is the unique ID of an image.
ID string
// Created is the date when the image was created.
Created string
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
// MinDisk is the minimum amount of disk a flavor must have to be able
// to create a server based on the image, measured in GB.
MinDisk int
MinRAM int
// MinRAM is the minimum amount of RAM a flavor must have to be able
// to create a server based on the image, measured in MB.
MinRAM int
// Name provides a human-readable moniker for the OS image.
Name string
// The Progress and Status fields indicate image-creation status.
// Any usable image will have 100% progress.
Progress int
Status string
// Status is the current status of the image.
Status string
// Update is the date when the image was updated.
Updated string
// Metadata provides free-form key/value pairs that further describe the
// image.
Metadata map[string]interface{}
}
// ImagePage contains a single page of results from a List operation.
// Use ExtractImages to convert it into a slice of usable structs.
// ImagePage contains a single page of all Images returne from a ListDetail
// operation. Use ExtractImages to convert it into a slice of usable structs.
type ImagePage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a page contains no Image results.
// IsEmpty returns true if an ImagePage contains no Image results.
func (page ImagePage) IsEmpty() (bool, error) {
images, err := ExtractImages(page)
return len(images) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
// NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (page ImagePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"images_links"`
@ -73,7 +84,8 @@ func (page ImagePage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractImages converts a page of List results into a slice of usable Image structs.
// ExtractImages converts a page of List results into a slice of usable Image
// structs.
func ExtractImages(r pagination.Page) ([]Image, error) {
var s struct {
Images []Image `json:"images"`

View file

@ -1,6 +1,115 @@
// Package servers provides information and interaction with the server API
// resource in the OpenStack Compute service.
//
// A server is a virtual machine instance in the compute system. In order for
// one to be provisioned, a valid flavor and image are required.
/*
Package servers provides information and interaction with the server API
resource in the OpenStack Compute service.
A server is a virtual machine instance in the compute system. In order for
one to be provisioned, a valid flavor and image are required.
Example to List Servers
listOpts := servers.ListOpts{
AllTenants: true,
}
allPages, err := servers.List(computeClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allServers, err := servers.ExtractServers(allPages)
if err != nil {
panic(err)
}
for _, server := range allServers {
fmt.Printf("%+v\n", server)
}
Example to Create a Server
createOpts := servers.CreateOpts{
Name: "server_name",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Server
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
err := servers.Delete(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
Example to Force Delete a Server
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
err := servers.ForceDelete(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
Example to Reboot a Server
rebootOpts := servers.RebootOpts{
Type: servers.SoftReboot,
}
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
err := servers.Reboot(computeClient, serverID, rebootOpts).ExtractErr()
if err != nil {
panic(err)
}
Example to Rebuild a Server
rebuildOpts := servers.RebuildOpts{
Name: "new_name",
ImageID: "image-uuid",
}
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
server, err := servers.Rebuilt(computeClient, serverID, rebuildOpts).Extract()
if err != nil {
panic(err)
}
Example to Resize a Server
resizeOpts := servers.ResizeOpts{
FlavorRef: "flavor-uuid",
}
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
err := servers.Resize(computeClient, serverID, resizeOpts).ExtractErr()
if err != nil {
panic(err)
}
err = servers.ConfirmResize(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
Example to Snapshot a Server
snapshotOpts := servers.CreateImageOpts{
Name: "snapshot_name",
}
serverID := "d9072956-1560-487c-97f2-18bdf65ec749"
image, err := servers.CreateImage(computeClient, serverID, snapshotOpts).ExtractImageID()
if err != nil {
panic(err)
}
*/
package servers

View file

@ -21,13 +21,13 @@ type ListOptsBuilder interface {
// the server attributes you want to see returned. Marker and Limit are used
// for pagination.
type ListOpts struct {
// A time/date stamp for when the server last changed status.
// ChangesSince is a time/date stamp for when the server last changed status.
ChangesSince string `q:"changes-since"`
// Name of the image in URL format.
// Image is the name of the image in URL format.
Image string `q:"image"`
// Name of the flavor in URL format.
// Flavor is the name of the flavor in URL format.
Flavor string `q:"flavor"`
// Name of the server as a string; can be queried with regular expressions.
@ -36,20 +36,25 @@ type ListOpts struct {
// underlying database server implemented for Compute.
Name string `q:"name"`
// Value of the status of the server so that you can filter on "ACTIVE" for example.
// Status is the value of the status of the server so that you can filter on
// "ACTIVE" for example.
Status string `q:"status"`
// Name of the host as a string.
// Host is the name of the host as a string.
Host string `q:"host"`
// UUID of the server at which you want to set a marker.
// Marker is a UUID of the server at which you want to set a marker.
Marker string `q:"marker"`
// Integer value for the limit of values to return.
// Limit is an integer value for the limit of values to return.
Limit int `q:"limit"`
// Bool to show all tenants
// AllTenants is a bool to show all tenants.
AllTenants bool `q:"all_tenants"`
// TenantID lists servers for a particular tenant.
// Setting "AllTenants = true" is required.
TenantID string `q:"tenant_id"`
}
// ToServerListQuery formats a ListOpts into a query string.
@ -73,15 +78,16 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToServerCreateMap() (map[string]interface{}, error)
}
// Network is used within CreateOpts to control a new server's network attachments.
// Network is used within CreateOpts to control a new server's network
// attachments.
type Network struct {
// UUID of a nova-network to attach to the newly provisioned server.
// UUID of a network to attach to the newly provisioned server.
// Required unless Port is provided.
UUID string
@ -89,19 +95,21 @@ type Network struct {
// Required unless UUID is provided.
Port string
// FixedIP [optional] specifies a fixed IPv4 address to be used on this network.
// FixedIP specifies a fixed IPv4 address to be used on this network.
FixedIP string
}
// Personality is an array of files that are injected into the server at launch.
type Personality []*File
// File is used within CreateOpts and RebuildOpts to inject a file into the server at launch.
// File implements the json.Marshaler interface, so when a Create or Rebuild operation is requested,
// json.Marshal will call File's MarshalJSON method.
// File is used within CreateOpts and RebuildOpts to inject a file into the
// server at launch.
// File implements the json.Marshaler interface, so when a Create or Rebuild
// operation is requested, json.Marshal will call File's MarshalJSON method.
type File struct {
// Path of the file
// Path of the file.
Path string
// Contents of the file. Maximum content size is 255 bytes.
Contents []byte
}
@ -123,13 +131,13 @@ type CreateOpts struct {
// Name is the name to assign to the newly launched server.
Name string `json:"name" required:"true"`
// ImageRef [optional; required if ImageName is not provided] is the ID or full
// URL to the image that contains the server's OS and initial state.
// ImageRef [optional; required if ImageName is not provided] is the ID or
// full URL to the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageRef string `json:"imageRef"`
// ImageName [optional; required if ImageRef is not provided] is the name of the
// image that contains the server's OS and initial state.
// ImageName [optional; required if ImageRef is not provided] is the name of
// the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageName string `json:"-"`
@ -141,7 +149,8 @@ type CreateOpts struct {
// the flavor that describes the server's specs.
FlavorName string `json:"-"`
// SecurityGroups lists the names of the security groups to which this server should belong.
// SecurityGroups lists the names of the security groups to which this server
// should belong.
SecurityGroups []string `json:"-"`
// UserData contains configuration information or scripts to use upon launch.
@ -152,10 +161,12 @@ type CreateOpts struct {
AvailabilityZone string `json:"availability_zone,omitempty"`
// Networks dictates how this server will be attached to available networks.
// By default, the server will be attached to all isolated networks for the tenant.
// By default, the server will be attached to all isolated networks for the
// tenant.
Networks []Network `json:"-"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the server.
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the
// server.
Metadata map[string]string `json:"metadata,omitempty"`
// Personality includes files to inject into the server at launch.
@ -166,7 +177,7 @@ type CreateOpts struct {
ConfigDrive *bool `json:"config_drive,omitempty"`
// AdminPass sets the root user password. If not set, a randomly-generated
// password will be created and returned in the rponse.
// password will be created and returned in the response.
AdminPass string `json:"adminPass,omitempty"`
// AccessIPv4 specifies an IPv4 address for the instance.
@ -180,7 +191,8 @@ type CreateOpts struct {
ServiceClient *gophercloud.ServiceClient `json:"-"`
}
// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
// ToServerCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
sc := opts.ServiceClient
opts.ServiceClient = nil
@ -274,13 +286,14 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// Delete requests that a server previously provisioned be removed from your account.
// Delete requests that a server previously provisioned be removed from your
// account.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// ForceDelete forces the deletion of a server
// ForceDelete forces the deletion of a server.
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
return
@ -294,12 +307,14 @@ func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
return
}
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
// UpdateOptsBuilder allows extensions to add additional attributes to the
// Update request.
type UpdateOptsBuilder interface {
ToServerUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts specifies the base attributes that may be updated on an existing server.
// UpdateOpts specifies the base attributes that may be updated on an existing
// server.
type UpdateOpts struct {
// Name changes the displayed name of the server.
// The server host name will *not* change.
@ -331,7 +346,8 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
return
}
// ChangeAdminPassword alters the administrator or root password for a specified server.
// ChangeAdminPassword alters the administrator or root password for a specified
// server.
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
b := map[string]interface{}{
"changePassword": map[string]string{
@ -354,33 +370,38 @@ const (
PowerCycle = HardReboot
)
// RebootOptsBuilder is an interface that options must satisfy in order to be
// used when rebooting a server instance
// RebootOptsBuilder allows extensions to add additional parameters to the
// reboot request.
type RebootOptsBuilder interface {
ToServerRebootMap() (map[string]interface{}, error)
}
// RebootOpts satisfies the RebootOptsBuilder interface
// RebootOpts provides options to the reboot request.
type RebootOpts struct {
// Type is the type of reboot to perform on the server.
Type RebootMethod `json:"type" required:"true"`
}
// ToServerRebootMap allows RebootOpts to satisfiy the RebootOptsBuilder
// interface
func (opts *RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
// ToServerRebootMap builds a body for the reboot request.
func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "reboot")
}
// Reboot requests that a given server reboot.
// Two methods exist for rebooting a server:
//
// HardReboot (aka PowerCycle) starts the server instance by physically cutting power to the machine, or if a VM,
// terminating it at the hypervisor level.
// It's done. Caput. Full stop.
// Then, after a brief while, power is rtored or the VM instance rtarted.
//
// SoftReboot (aka OSReboot) simply tells the OS to rtart under its own procedur.
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to rtart the machine.
/*
Reboot requests that a given server reboot.
Two methods exist for rebooting a server:
HardReboot (aka PowerCycle) starts the server instance by physically cutting
power to the machine, or if a VM, terminating it at the hypervisor level.
It's done. Caput. Full stop.
Then, after a brief while, power is rtored or the VM instance restarted.
SoftReboot (aka OSReboot) simply tells the OS to restart under its own
procedure.
E.g., in Linux, asking it to enter runlevel 6, or executing
"sudo shutdown -r now", or by asking Windows to rtart the machine.
*/
func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
b, err := opts.ToServerRebootMap()
if err != nil {
@ -391,31 +412,43 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
return
}
// RebuildOptsBuilder is an interface that allows extensions to override the
// default behaviour of rebuild options
// RebuildOptsBuilder allows extensions to provide additional parameters to the
// rebuild request.
type RebuildOptsBuilder interface {
ToServerRebuildMap() (map[string]interface{}, error)
}
// RebuildOpts represents the configuration options used in a server rebuild
// operation
// operation.
type RebuildOpts struct {
// The server's admin password
// AdminPass is the server's admin password
AdminPass string `json:"adminPass,omitempty"`
// The ID of the image you want your server to be provisioned on
ImageID string `json:"imageRef"`
// ImageID is the ID of the image you want your server to be provisioned on.
ImageID string `json:"imageRef"`
// ImageName is readable name of an image.
ImageName string `json:"-"`
// Name to set the server to
Name string `json:"name,omitempty"`
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
AccessIPv4 string `json:"accessIPv4,omitempty"`
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
AccessIPv6 string `json:"accessIPv6,omitempty"`
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
// Metadata [optional] contains key-value pairs (up to 255 bytes each)
// to attach to the server.
Metadata map[string]string `json:"metadata,omitempty"`
// Personality [optional] includes files to inject into the server at launch.
// Rebuild will base64-encode file contents for you.
Personality Personality `json:"personality,omitempty"`
Personality Personality `json:"personality,omitempty"`
// ServiceClient will allow calls to be made to retrieve an image or
// flavor ID by name.
ServiceClient *gophercloud.ServiceClient `json:"-"`
}
@ -458,31 +491,34 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
return
}
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
// a Resize request.
// ResizeOptsBuilder allows extensions to add additional parameters to the
// resize request.
type ResizeOptsBuilder interface {
ToServerResizeMap() (map[string]interface{}, error)
}
// ResizeOpts represents the configuration options used to control a Resize operation.
// ResizeOpts represents the configuration options used to control a Resize
// operation.
type ResizeOpts struct {
// FlavorRef is the ID of the flavor you wish your server to become.
FlavorRef string `json:"flavorRef" required:"true"`
}
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON request body for the
// Resize request.
// ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON
// request body for the Resize request.
func (opts ResizeOpts) ToServerResizeMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "resize")
}
// Resize instructs the provider to change the flavor of the server.
//
// Note that this implies rebuilding it.
//
// Unfortunately, one cannot pass rebuild parameters to the resize function.
// When the resize completes, the server will be in RESIZE_VERIFY state.
// While in this state, you can explore the use of the new server's configuration.
// If you like it, call ConfirmResize() to commit the resize permanently.
// Otherwise, call RevertResize() to restore the old configuration.
// When the resize completes, the server will be in VERIFY_RESIZE state.
// While in this state, you can explore the use of the new server's
// configuration. If you like it, call ConfirmResize() to commit the resize
// permanently. Otherwise, call RevertResize() to restore the old configuration.
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
b, err := opts.ToServerResizeMap()
if err != nil {
@ -509,41 +545,8 @@ func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult)
return
}
// RescueOptsBuilder is an interface that allows extensions to override the
// default structure of a Rescue request.
type RescueOptsBuilder interface {
ToServerRescueMap() (map[string]interface{}, error)
}
// RescueOpts represents the configuration options used to control a Rescue
// option.
type RescueOpts struct {
// AdminPass is the desired administrative password for the instance in
// RESCUE mode. If it's left blank, the server will generate a password.
AdminPass string `json:"adminPass,omitempty"`
}
// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
// request body for the Rescue request.
func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "rescue")
}
// Rescue instructs the provider to place the server into RESCUE mode.
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) {
b, err := opts.ToServerRescueMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
// Reset request.
// ResetMetadataOptsBuilder allows extensions to add additional parameters to
// the Reset request.
type ResetMetadataOptsBuilder interface {
ToMetadataResetMap() (map[string]interface{}, error)
}
@ -551,20 +554,23 @@ type ResetMetadataOptsBuilder interface {
// MetadataOpts is a map that contains key-value pairs.
type MetadataOpts map[string]string
// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts.
// ToMetadataResetMap assembles a body for a Reset request based on the contents
// of a MetadataOpts.
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}
// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts.
// ToMetadataUpdateMap assembles a body for an Update request based on the
// contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}
// ResetMetadata will create multiple new key-value pairs for the given server ID.
// Note: Using this operation will erase any already-existing metadata and create
// the new metadata provided. To keep any already-existing metadata, use the
// UpdateMetadatas or UpdateMetadata function.
// ResetMetadata will create multiple new key-value pairs for the given server
// ID.
// Note: Using this operation will erase any already-existing metadata and
// create the new metadata provided. To keep any already-existing metadata,
// use the UpdateMetadatas or UpdateMetadata function.
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
b, err := opts.ToMetadataResetMap()
if err != nil {
@ -583,15 +589,15 @@ func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult
return
}
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
// Create request.
// UpdateMetadataOptsBuilder allows extensions to add additional parameters to
// the Create request.
type UpdateMetadataOptsBuilder interface {
ToMetadataUpdateMap() (map[string]interface{}, error)
}
// UpdateMetadata updates (or creates) all the metadata specified by opts for the given server ID.
// This operation does not affect already-existing metadata that is not specified
// by opts.
// UpdateMetadata updates (or creates) all the metadata specified by opts for
// the given server ID. This operation does not affect already-existing metadata
// that is not specified by opts.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
b, err := opts.ToMetadataUpdateMap()
if err != nil {
@ -613,7 +619,8 @@ type MetadatumOptsBuilder interface {
// MetadatumOpts is a map of length one that contains a key-value pair.
type MetadatumOpts map[string]string
// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts.
// ToMetadatumCreateMap assembles a body for a Create request based on the
// contents of a MetadataumOpts.
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
if len(opts) != 1 {
err := gophercloud.ErrInvalidInput{}
@ -629,7 +636,8 @@ func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string
return metadatum, key, nil
}
// CreateMetadatum will create or update the key-value pair with the given key for the given server ID.
// CreateMetadatum will create or update the key-value pair with the given key
// for the given server ID.
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
b, key, err := opts.ToMetadatumCreateMap()
if err != nil {
@ -642,53 +650,60 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
return
}
// Metadatum requests the key-value pair with the given key for the given server ID.
// Metadatum requests the key-value pair with the given key for the given
// server ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
return
}
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
// DeleteMetadatum will delete the key-value pair with the given key for the
// given server ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
_, r.Err = client.Delete(metadatumURL(client, id, key), nil)
return
}
// ListAddresses makes a request against the API to list the servers IP addresses.
// ListAddresses makes a request against the API to list the servers IP
// addresses.
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
return AddressPage{pagination.SinglePageBase(r)}
})
}
// ListAddressesByNetwork makes a request against the API to list the servers IP addresses
// for the given network.
// ListAddressesByNetwork makes a request against the API to list the servers IP
// addresses for the given network.
func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
return NetworkAddressPage{pagination.SinglePageBase(r)}
})
}
// CreateImageOptsBuilder is the interface types must satisfy in order to be
// used as CreateImage options
// CreateImageOptsBuilder allows extensions to add additional parameters to the
// CreateImage request.
type CreateImageOptsBuilder interface {
ToServerCreateImageMap() (map[string]interface{}, error)
}
// CreateImageOpts satisfies the CreateImageOptsBuilder
// CreateImageOpts provides options to pass to the CreateImage request.
type CreateImageOpts struct {
// Name of the image/snapshot
// Name of the image/snapshot.
Name string `json:"name" required:"true"`
// Metadata contains key-value pairs (up to 255 bytes each) to attach to the created image.
// Metadata contains key-value pairs (up to 255 bytes each) to attach to
// the created image.
Metadata map[string]string `json:"metadata,omitempty"`
}
// ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
// ToServerCreateImageMap formats a CreateImageOpts structure into a request
// body.
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "createImage")
}
// CreateImage makes a request against the nova API to schedule an image to be created of the server
// CreateImage makes a request against the nova API to schedule an image to be
// created of the server
func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
b, err := opts.ToServerCreateImageMap()
if err != nil {
@ -703,11 +718,17 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO
return
}
// IDFromName is a convienience function that returns a server's ID given its name.
// IDFromName is a convienience function that returns a server's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := List(client, nil).AllPages()
listOpts := ListOpts{
Name: name,
}
allPages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
@ -734,8 +755,40 @@ func IDFromName(client *gophercloud.ServiceClient, name string) (string, error)
}
}
// GetPassword makes a request against the nova API to get the encrypted administrative password.
// GetPassword makes a request against the nova API to get the encrypted
// administrative password.
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil)
return
}
// ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be
// used as ShowConsoleOutput options
type ShowConsoleOutputOptsBuilder interface {
ToServerShowConsoleOutputMap() (map[string]interface{}, error)
}
// ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder
type ShowConsoleOutputOpts struct {
// The number of lines to fetch from the end of console log.
// All lines will be returned if this is not specified.
Length int `json:"length,omitempty"`
}
// ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body.
func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "os-getConsoleOutput")
}
// ShowConsoleOutput makes a request against the nova API to get console log from the server
func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) {
b, err := opts.ToServerShowConsoleOutputMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}

View file

@ -32,54 +32,73 @@ func ExtractServersInto(r pagination.Page, v interface{}) error {
return r.(ServerPage).Result.ExtractIntoSlicePtr(v, "servers")
}
// CreateResult temporarily contains the response from a Create call.
// CreateResult is the response from a Create operation. Call its Extract
// method to interpret it as a Server.
type CreateResult struct {
serverResult
}
// GetResult temporarily contains the response from a Get call.
// GetResult is the response from a Get operation. Call its Extract
// method to interpret it as a Server.
type GetResult struct {
serverResult
}
// UpdateResult temporarily contains the response from an Update call.
// UpdateResult is the response from an Update operation. Call its Extract
// method to interpret it as a Server.
type UpdateResult struct {
serverResult
}
// DeleteResult temporarily contains the response from a Delete call.
// DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// RebuildResult temporarily contains the response from a Rebuild call.
// RebuildResult is the response from a Rebuild operation. Call its Extract
// method to interpret it as a Server.
type RebuildResult struct {
serverResult
}
// ActionResult represents the result of server action operations, like reboot
// ActionResult represents the result of server action operations, like reboot.
// Call its ExtractErr method to determine if the action succeeded or failed.
type ActionResult struct {
gophercloud.ErrResult
}
// RescueResult represents the result of a server rescue operation
type RescueResult struct {
ActionResult
}
// CreateImageResult represents the result of an image creation operation
// CreateImageResult is the response from a CreateImage operation. Call its
// ExtractImageID method to retrieve the ID of the newly created image.
type CreateImageResult struct {
gophercloud.Result
}
// ShowConsoleOutputResult represents the result of console output from a server
type ShowConsoleOutputResult struct {
gophercloud.Result
}
// Extract will return the console output from a ShowConsoleOutput request.
func (r ShowConsoleOutputResult) Extract() (string, error) {
var s struct {
Output string `json:"output"`
}
err := r.ExtractInto(&s)
return s.Output, err
}
// GetPasswordResult represent the result of a get os-server-password operation.
// Call its ExtractPassword method to retrieve the password.
type GetPasswordResult struct {
gophercloud.Result
}
// ExtractPassword gets the encrypted password.
// If privateKey != nil the password is decrypted with the private key.
// If privateKey == nil the encrypted password is returned and can be decrypted with:
// If privateKey == nil the encrypted password is returned and can be decrypted
// with:
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
var s struct {
@ -107,7 +126,7 @@ func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (stri
return string(password), nil
}
// ExtractImageID gets the ID of the newly created server image from the header
// ExtractImageID gets the ID of the newly created server image from the header.
func (r CreateImageResult) ExtractImageID() (string, error) {
if r.Err != nil {
return "", r.Err
@ -124,54 +143,84 @@ func (r CreateImageResult) ExtractImageID() (string, error) {
return imageID, nil
}
// Extract interprets any RescueResult as an AdminPass, if possible.
func (r RescueResult) Extract() (string, error) {
var s struct {
AdminPass string `json:"adminPass"`
}
err := r.ExtractInto(&s)
return s.AdminPass, err
}
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
// Server represents a server/instance in the OpenStack cloud.
type Server struct {
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
// ID uniquely identifies this server amongst all other servers,
// including those not accessible to the current tenant.
ID string `json:"id"`
// TenantID identifies the tenant owning this server resource.
TenantID string `json:"tenant_id"`
// UserID uniquely identifies the user account owning the tenant.
UserID string `json:"user_id"`
// Name contains the human-readable name for the server.
Name string `json:"name"`
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
// Updated and Created contain ISO-8601 timestamps of when the state of the
// server last changed, and when it was created.
Updated time.Time `json:"updated"`
Created time.Time `json:"created"`
HostID string `json:"hostid"`
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
// HostID is the host where the server is located in the cloud.
HostID string `json:"hostid"`
// Status contains the current operational status of the server,
// such as IN_PROGRESS or ACTIVE.
Status string `json:"status"`
// Progress ranges from 0..100.
// A request made against the server completes only once Progress reaches 100.
Progress int `json:"progress"`
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server,
// suitable for remote access for administration.
AccessIPv4 string `json:"accessIPv4"`
AccessIPv6 string `json:"accessIPv6"`
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
// Image refers to a JSON object, which itself indicates the OS image used to
// deploy the server.
Image map[string]interface{} `json:"-"`
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
// Flavor refers to a JSON object, which itself indicates the hardware
// configuration of the deployed server.
Flavor map[string]interface{} `json:"flavor"`
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
// Addresses includes a list of all IP addresses assigned to the server,
// keyed by pool.
Addresses map[string]interface{} `json:"addresses"`
// Metadata includes a list of all user-specified key-value pairs attached to the server.
// Metadata includes a list of all user-specified key-value pairs attached
// to the server.
Metadata map[string]string `json:"metadata"`
// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
// Links includes HTTP references to the itself, useful for passing along to
// other APIs that might want a server reference.
Links []interface{} `json:"links"`
// KeyName indicates which public key was injected into the server on launch.
KeyName string `json:"key_name"`
// AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
// AdminPass will generally be empty (""). However, it will contain the
// administrative password chosen when provisioning a new server without a
// set AdminPass setting in the first place.
// Note that this is the ONLY time this field will be valid.
AdminPass string `json:"adminPass"`
// SecurityGroups includes the security groups that this instance has applied to it
// SecurityGroups includes the security groups that this instance has applied
// to it.
SecurityGroups []map[string]interface{} `json:"security_groups"`
// Fault contains failure information about a server.
Fault Fault `json:"fault"`
}
type Fault struct {
Code int `json:"code"`
Created time.Time `json:"created"`
Details string `json:"details"`
Message string `json:"message"`
}
func (r *Server) UnmarshalJSON(b []byte) error {
@ -200,9 +249,10 @@ func (r *Server) UnmarshalJSON(b []byte) error {
return err
}
// ServerPage abstracts the raw results of making a List() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned to the client, you may only safely access the
// data provided through the ExtractServers call.
// ServerPage abstracts the raw results of making a List() request against
// the API. As OpenStack extensions may freely alter the response bodies of
// structures returned to the client, you may only safely access the data
// provided through the ExtractServers call.
type ServerPage struct {
pagination.LinkedPageBase
}
@ -213,7 +263,8 @@ func (r ServerPage) IsEmpty() (bool, error) {
return len(s) == 0, err
}
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
// NextPageURL uses the response's embedded link reference to navigate to the
// next page of results.
func (r ServerPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"servers_links"`
@ -225,49 +276,59 @@ func (r ServerPage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
// ExtractServers interprets the results of a single page from a List() call,
// producing a slice of Server entities.
func ExtractServers(r pagination.Page) ([]Server, error) {
var s []Server
err := ExtractServersInto(r, &s)
return s, err
}
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
// MetadataResult contains the result of a call for (potentially) multiple
// key-value pairs. Call its Extract method to interpret it as a
// map[string]interface.
type MetadataResult struct {
gophercloud.Result
}
// GetMetadataResult temporarily contains the response from a metadata Get call.
// GetMetadataResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetMetadataResult struct {
MetadataResult
}
// ResetMetadataResult temporarily contains the response from a metadata Reset call.
// ResetMetadataResult contains the result of a Reset operation. Call its
// Extract method to interpret it as a map[string]interface.
type ResetMetadataResult struct {
MetadataResult
}
// UpdateMetadataResult temporarily contains the response from a metadata Update call.
// UpdateMetadataResult contains the result of an Update operation. Call its
// Extract method to interpret it as a map[string]interface.
type UpdateMetadataResult struct {
MetadataResult
}
// MetadatumResult contains the result of a call for individual a single key-value pair.
// MetadatumResult contains the result of a call for individual a single
// key-value pair.
type MetadatumResult struct {
gophercloud.Result
}
// GetMetadatumResult temporarily contains the response from a metadatum Get call.
// GetMetadatumResult contains the result of a Get operation. Call its Extract
// method to interpret it as a map[string]interface.
type GetMetadatumResult struct {
MetadatumResult
}
// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
// CreateMetadatumResult contains the result of a Create operation. Call its
// Extract method to interpret it as a map[string]interface.
type CreateMetadatumResult struct {
MetadatumResult
}
// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
// DeleteMetadatumResult contains the result of a Delete operation. Call its
// ExtractErr method to determine if the call succeeded or failed.
type DeleteMetadatumResult struct {
gophercloud.ErrResult
}
@ -296,9 +357,10 @@ type Address struct {
Address string `json:"addr"`
}
// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned
// to the client, you may only safely access the data provided through the ExtractAddresses call.
// AddressPage abstracts the raw results of making a ListAddresses() request
// against the API. As OpenStack extensions may freely alter the response bodies
// of structures returned to the client, you may only safely access the data
// provided through the ExtractAddresses call.
type AddressPage struct {
pagination.SinglePageBase
}
@ -309,8 +371,8 @@ func (r AddressPage) IsEmpty() (bool, error) {
return len(addresses) == 0, err
}
// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
// producing a map of addresses.
// ExtractAddresses interprets the results of a single page from a
// ListAddresses() call, producing a map of addresses.
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
var s struct {
Addresses map[string][]Address `json:"addresses"`
@ -319,9 +381,11 @@ func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
return s.Addresses, err
}
// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures returned
// to the client, you may only safely access the data provided through the ExtractAddresses call.
// NetworkAddressPage abstracts the raw results of making a
// ListAddressesByNetwork() request against the API.
// As OpenStack extensions may freely alter the response bodies of structures
// returned to the client, you may only safely access the data provided through
// the ExtractAddresses call.
type NetworkAddressPage struct {
pagination.SinglePageBase
}
@ -332,8 +396,8 @@ func (r NetworkAddressPage) IsEmpty() (bool, error) {
return len(addresses) == 0, err
}
// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
// producing a slice of addresses.
// ExtractNetworkAddresses interprets the results of a single page from a
// ListAddressesByNetwork() call, producing a slice of addresses.
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
var s map[string][]Address
err := (r.(NetworkAddressPage)).ExtractInto(&s)

View file

@ -2,8 +2,9 @@ package servers
import "github.com/gophercloud/gophercloud"
// WaitForStatus will continually poll a server until it successfully transitions to a specified
// status. It will do this for at most the number of seconds specified.
// WaitForStatus will continually poll a server until it successfully
// transitions to a specified status. It will do this for at most the number
// of seconds specified.
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
return gophercloud.WaitFor(secs, func() (bool, error) {
current, err := Get(c, id).Extract()

View file

@ -0,0 +1,14 @@
/*
Package openstack contains resources for the individual OpenStack projects
supported in Gophercloud. It also includes functions to authenticate to an
OpenStack cloud and for provisioning various service-level clients.
Example of Creating a Service Client
ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(ao)
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
*/
package openstack

View file

@ -6,12 +6,16 @@ import (
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
// during the v2 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
/*
V2EndpointURL discovers the endpoint URL for a specific service from a
ServiceCatalog acquired during the v2 identity service.
The specified EndpointOpts are used to identify a unique, unambiguous endpoint
to return. It's an error both when multiple endpoints match the provided
criteria and when none do. The minimum that can be specified is a Type, but you
will also often need to specify a Name and/or a Region depending on what's
available on your OpenStack deployment.
*/
func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Name if provided, and Region if provided.
var endpoints = make([]tokens2.Endpoint, 0, 1)
@ -54,12 +58,16 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt
return "", err
}
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
// during the v3 identity service. The specified EndpointOpts are used to identify a unique,
// unambiguous endpoint to return. It's an error both when multiple endpoints match the provided
// criteria and when none do. The minimum that can be specified is a Type, but you will also often
// need to specify a Name and/or a Region depending on what's available on your OpenStack
// deployment.
/*
V3EndpointURL discovers the endpoint URL for a specific service from a Catalog
acquired during the v3 identity service.
The specified EndpointOpts are used to identify a unique, unambiguous endpoint
to return. It's an error both when multiple endpoints match the provided
criteria and when none do. The minimum that can be specified is a Type, but you
will also often need to specify a Name and/or a Region depending on what's
available on your OpenStack deployment.
*/
func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpts) (string, error) {
// Extract Endpoints from the catalog entries that match the requested Type, Interface,
// Name if provided, and Region if provided.
@ -76,7 +84,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
return "", err
}
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
(opts.Region == "" || endpoint.Region == opts.Region) {
(opts.Region == "" || endpoint.Region == opts.Region || endpoint.RegionID == opts.Region) {
endpoints = append(endpoints, endpoint)
}
}

View file

@ -1,7 +1,65 @@
// Package tenants provides information and interaction with the
// tenants API resource for the OpenStack Identity service.
//
// See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
// and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
// for more information.
/*
Package tenants provides information and interaction with the
tenants API resource for the OpenStack Identity service.
See http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
and http://developer.openstack.org/api-ref-identity-v2.html#admin-tenants
for more information.
Example to List Tenants
listOpts := tenants.ListOpts{
Limit: 2,
}
allPages, err := tenants.List(identityClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allTenants, err := tenants.ExtractTenants(allPages)
if err != nil {
panic(err)
}
for _, tenant := range allTenants {
fmt.Printf("%+v\n", tenant)
}
Example to Create a Tenant
createOpts := tenants.CreateOpts{
Name: "tenant_name",
Description: "this is a tenant",
Enabled: gophercloud.Enabled,
}
tenant, err := tenants.Create(identityClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Update a Tenant
tenantID := "e6db6ed6277c461a853458589063b295"
updateOpts := tenants.UpdateOpts{
Description: "this is a new description",
Enabled: gophercloud.Disabled,
}
tenant, err := tenants.Update(identityClient, tenantID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Tenant
tenantID := "e6db6ed6277c461a853458589063b295"
err := tenants.Delete(identitYClient, tenantID).ExtractErr()
if err != nil {
panic(err)
}
*/
package tenants

View file

@ -9,6 +9,7 @@ import (
type ListOpts struct {
// Marker is the ID of the last Tenant on the previous page.
Marker string `q:"marker"`
// Limit specifies the page size.
Limit int `q:"limit"`
}
@ -27,3 +28,89 @@ func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// CreateOpts represents the options needed when creating new tenant.
type CreateOpts struct {
// Name is the name of the tenant.
Name string `json:"name" required:"true"`
// Description is the description of the tenant.
Description string `json:"description,omitempty"`
// Enabled sets the tenant status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`
}
// CreateOptsBuilder enables extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToTenantCreateMap() (map[string]interface{}, error)
}
// ToTenantCreateMap assembles a request body based on the contents of
// a CreateOpts.
func (opts CreateOpts) ToTenantCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "tenant")
}
// Create is the operation responsible for creating new tenant.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToTenantCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return
}
// Get requests details on a single tenant by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToTenantUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts specifies the base attributes that may be updated on an existing
// tenant.
type UpdateOpts struct {
// Name is the name of the tenant.
Name string `json:"name,omitempty"`
// Description is the description of the tenant.
Description string `json:"description,omitempty"`
// Enabled sets the tenant status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`
}
// ToTenantUpdateMap formats an UpdateOpts structure into a request body.
func (opts UpdateOpts) ToTenantUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "tenant")
}
// Update is the operation responsible for updating exist tenants by their TenantID.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToTenantUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Delete is the operation responsible for permanently deleting a tenant.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}

View file

@ -43,7 +43,8 @@ func (r TenantPage) NextPageURL() (string, error) {
return gophercloud.ExtractNextURL(s.Links)
}
// ExtractTenants returns a slice of Tenants contained in a single page of results.
// ExtractTenants returns a slice of Tenants contained in a single page of
// results.
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
var s struct {
Tenants []Tenant `json:"tenants"`
@ -51,3 +52,40 @@ func ExtractTenants(r pagination.Page) ([]Tenant, error) {
err := (r.(TenantPage)).ExtractInto(&s)
return s.Tenants, err
}
type tenantResult struct {
gophercloud.Result
}
// Extract interprets any tenantResults as a Tenant.
func (r tenantResult) Extract() (*Tenant, error) {
var s struct {
Tenant *Tenant `json:"tenant"`
}
err := r.ExtractInto(&s)
return s.Tenant, err
}
// GetResult is the response from a Get request. Call its Extract method to
// interpret it as a Tenant.
type GetResult struct {
tenantResult
}
// CreateResult is the response from a Create request. Call its Extract method
// to interpret it as a Tenant.
type CreateResult struct {
tenantResult
}
// DeleteResult is the response from a Get request. Call its ExtractErr method
// to determine if the call succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// UpdateResult is the response from a Update request. Call its Extract method
// to interpret it as a Tenant.
type UpdateResult struct {
tenantResult
}

View file

@ -5,3 +5,19 @@ import "github.com/gophercloud/gophercloud"
func listURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tenants")
}
func getURL(client *gophercloud.ServiceClient, tenantID string) string {
return client.ServiceURL("tenants", tenantID)
}
func createURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("tenants")
}
func deleteURL(client *gophercloud.ServiceClient, tenantID string) string {
return client.ServiceURL("tenants", tenantID)
}
func updateURL(client *gophercloud.ServiceClient, tenantID string) string {
return client.ServiceURL("tenants", tenantID)
}

View file

@ -1,5 +1,46 @@
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
/*
Package tokens provides information and interaction with the token API
resource for the OpenStack Identity service.
For more information, see:
http://developer.openstack.org/api-ref-identity-v2.html#identity-auth-v2
Example to Create an Unscoped Token from a Password
authOpts := gophercloud.AuthOptions{
Username: "user",
Password: "pass"
}
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token from a Tenant ID and Password
authOpts := gophercloud.AuthOptions{
Username: "user",
Password: "password",
TenantID: "fc394f2ab2df4114bde39905f800dc57"
}
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token from a Tenant Name and Password
authOpts := gophercloud.AuthOptions{
Username: "user",
Password: "password",
TenantName: "tenantname"
}
token, err := tokens.Create(identityClient, authOpts).ExtractToken()
if err != nil {
panic(err)
}
*/
package tokens

View file

@ -2,17 +2,21 @@ package tokens
import "github.com/gophercloud/gophercloud"
// PasswordCredentialsV2 represents the required options to authenticate
// with a username and password.
type PasswordCredentialsV2 struct {
Username string `json:"username" required:"true"`
Password string `json:"password" required:"true"`
}
// TokenCredentialsV2 represents the required options to authenticate
// with a token.
type TokenCredentialsV2 struct {
ID string `json:"id,omitempty" required:"true"`
}
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
// interface.
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the
// AuthOptionsBuilder interface.
type AuthOptionsV2 struct {
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
@ -23,15 +27,16 @@ type AuthOptionsV2 struct {
TenantID string `json:"tenantId,omitempty"`
TenantName string `json:"tenantName,omitempty"`
// TokenCredentials allows users to authenticate (possibly as another user) with an
// authentication token ID.
// TokenCredentials allows users to authenticate (possibly as another user)
// with an authentication token ID.
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
}
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
// AuthOptionsBuilder allows extensions to add additional parameters to the
// token create request.
type AuthOptionsBuilder interface {
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
// ToTokenCreateMap assembles the Create request body, returning an error
// if parameters are missing or inconsistent.
ToTokenV2CreateMap() (map[string]interface{}, error)
}
@ -47,8 +52,7 @@ type AuthOptions struct {
TokenID string
}
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v2 tokens package
// ToTokenV2CreateMap builds a token request body from the given AuthOptions.
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
v2Opts := AuthOptionsV2{
TenantID: opts.TenantID,
@ -74,9 +78,9 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
}
// Create authenticates to the identity service and attempts to acquire a Token.
// If successful, the CreateResult
// Generally, rather than interact with this call directly, end users should call openstack.AuthenticatedClient(),
// which abstracts all of the gory details about navigating service catalogs and such.
// Generally, rather than interact with this call directly, end users should
// call openstack.AuthenticatedClient(), which abstracts all of the gory details
// about navigating service catalogs and such.
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r CreateResult) {
b, err := auth.ToTokenV2CreateMap()
if err != nil {

View file

@ -7,20 +7,24 @@ import (
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
)
// Token provides only the most basic information related to an authentication token.
// Token provides only the most basic information related to an authentication
// token.
type Token struct {
// ID provides the primary means of identifying a user to the OpenStack API.
// OpenStack defines this field as an opaque value, so do not depend on its content.
// It is safe, however, to compare for equality.
// OpenStack defines this field as an opaque value, so do not depend on its
// content. It is safe, however, to compare for equality.
ID string
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the authentication token becomes invalid.
// After this point in time, future API requests made using this authentication token will respond with errors.
// Either the caller will need to reauthenticate manually, or more preferably, the caller should exploit automatic re-authentication.
// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the
// authentication token becomes invalid. After this point in time, future
// API requests made using this authentication token will respond with
// errors. Either the caller will need to reauthenticate manually, or more
// preferably, the caller should exploit automatic re-authentication.
// See the AuthOptions structure for more details.
ExpiresAt time.Time
// Tenant provides information about the tenant to which this token grants access.
// Tenant provides information about the tenant to which this token grants
// access.
Tenant tenants.Tenant
}
@ -38,13 +42,17 @@ type User struct {
}
// Endpoint represents a single API endpoint offered by a service.
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
// It provides the public and internal URLs, if supported, along with a region
// specifier, again if provided.
//
// The significance of the Region field will depend upon your provider.
//
// In addition, the interface offered by the service will have version information associated with it
// through the VersionId, VersionInfo, and VersionList fields, if provided or supported.
// In addition, the interface offered by the service will have version
// information associated with it through the VersionId, VersionInfo, and
// VersionList fields, if provided or supported.
//
// In all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
// In all cases, fields which aren't supported by the provider and service
// combined will assume a zero-value ("").
type Endpoint struct {
TenantID string `json:"tenantId"`
PublicURL string `json:"publicURL"`
@ -56,38 +64,44 @@ type Endpoint struct {
VersionList string `json:"versionList"`
}
// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, will have a single
// CatalogEntry representing it.
// CatalogEntry provides a type-safe interface to an Identity API V2 service
// catalog listing.
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
// Each class of service, such as cloud DNS or block storage services, will have
// a single CatalogEntry representing it.
//
// Note: when looking for the desired service, try, whenever possible, to key
// off the type field. Otherwise, you'll tie the representation of the service
// to a specific provider.
type CatalogEntry struct {
// Name will contain the provider-specified name for the service.
Name string `json:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
// Type will contain a type string if OpenStack defines a type for the
// service. Otherwise, for provider-specific services, the provider may assign
// their own type strings.
Type string `json:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
// Endpoints will let the caller iterate over all the different endpoints that
// may exist for the service.
Endpoints []Endpoint `json:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
// ServiceCatalog provides a view into the service catalog from a previous,
// successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
// CreateResult is the response from a Create request. Use ExtractToken() to
// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a
// service catalog.
type CreateResult struct {
gophercloud.Result
}
// GetResult is the deferred response from a Get call, which is the same with a Created token.
// Use ExtractUser() to interpret it as a User.
// GetResult is the deferred response from a Get call, which is the same with a
// Created token. Use ExtractUser() to interpret it as a User.
type GetResult struct {
CreateResult
}
@ -121,7 +135,8 @@ func (r CreateResult) ExtractToken() (*Token, error) {
}, nil
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s struct {
Access struct {

View file

@ -1,6 +1,108 @@
// Package tokens provides information and interaction with the token API
// resource for the OpenStack Identity service.
//
// For more information, see:
// http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
/*
Package tokens provides information and interaction with the token API
resource for the OpenStack Identity service.
For more information, see:
http://developer.openstack.org/api-ref-identity-v3.html#tokens-v3
Example to Create a Token From a Username and Password
authOptions := tokens.AuthOptions{
UserID: "username",
Password: "password",
}
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token From a Username, Password, and Domain
authOptions := tokens.AuthOptions{
UserID: "username",
Password: "password",
DomainID: "default",
}
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
authOptions = tokens.AuthOptions{
UserID: "username",
Password: "password",
DomainName: "default",
}
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token From a Token
authOptions := tokens.AuthOptions{
TokenID: "token_id",
}
token, err := tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token from a Username and Password with Project ID Scope
scope := tokens.Scope{
ProjectID: "0fe36e73809d46aeae6705c39077b1b3",
}
authOptions := tokens.AuthOptions{
Scope: &scope,
UserID: "username",
Password: "password",
}
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token from a Username and Password with Domain ID Scope
scope := tokens.Scope{
DomainID: "default",
}
authOptions := tokens.AuthOptions{
Scope: &scope,
UserID: "username",
Password: "password",
}
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to Create a Token from a Username and Password with Project Name Scope
scope := tokens.Scope{
ProjectName: "project_name",
DomainID: "default",
}
authOptions := tokens.AuthOptions{
Scope: &scope,
UserID: "username",
Password: "password",
}
token, err = tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
*/
package tokens

View file

@ -4,26 +4,28 @@ import "github.com/gophercloud/gophercloud"
// Scope allows a created token to be limited to a specific domain or project.
type Scope struct {
ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"`
ProjectName string `json:"scope.project.name,omitempty"`
DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"`
DomainName string `json:"scope.project.id,omitempty"`
ProjectID string
ProjectName string
DomainID string
DomainName string
}
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
// AuthOptionsBuilder provides the ability for extensions to add additional
// parameters to AuthOptions. Extensions must satisfy all required methods.
type AuthOptionsBuilder interface {
// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
// missing or inconsistent.
// ToTokenV3CreateMap assembles the Create request body, returning an error
// if parameters are missing or inconsistent.
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
ToTokenV3ScopeMap() (map[string]interface{}, error)
CanReauth() bool
}
// AuthOptions represents options for authenticating a user.
type AuthOptions struct {
// IdentityEndpoint specifies the HTTP endpoint that is required to work with
// the Identity API of the appropriate version. While it's ultimately needed by
// all of the identity services, it will often be populated by a provider-level
// function.
// the Identity API of the appropriate version. While it's ultimately needed
// by all of the identity services, it will often be populated by a
// provider-level function.
IdentityEndpoint string `json:"-"`
// Username is required if using Identity V2 API. Consult with your provider's
@ -36,99 +38,58 @@ type AuthOptions struct {
// At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"id,omitempty"`
DomainID string `json:"-"`
DomainName string `json:"name,omitempty"`
// AllowReauth should be set to true if you grant permission for Gophercloud to
// cache your credentials in memory, and to allow Gophercloud to attempt to
// re-authenticate automatically if/when your token expires. If you set it to
// false, it will not cache these settings, but re-authentication will not be
// possible. This setting defaults to false.
// AllowReauth should be set to true if you grant permission for Gophercloud
// to cache your credentials in memory, and to allow Gophercloud to attempt
// to re-authenticate automatically if/when your token expires. If you set
// it to false, it will not cache these settings, but re-authentication will
// not be possible. This setting defaults to false.
AllowReauth bool `json:"-"`
// TokenID allows users to authenticate (possibly as another user) with an
// authentication token ID.
TokenID string `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
Scope Scope `json:"-"`
}
// ToTokenV3CreateMap builds a request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
gophercloudAuthOpts := gophercloud.AuthOptions{
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
ApplicationCredentialID: opts.ApplicationCredentialID,
ApplicationCredentialName: opts.ApplicationCredentialName,
ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
}
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
}
// ToTokenV3CreateMap builds a scope request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
if opts.Scope.ProjectID != "" {
return nil, gophercloud.ErrScopeProjectIDOrProjectName{}
}
scope := gophercloud.AuthScope(opts.Scope)
if opts.Scope.DomainID != "" {
// ProjectName + DomainID
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
},
}, nil
}
if opts.Scope.DomainName != "" {
// ProjectName + DomainName
return map[string]interface{}{
"project": map[string]interface{}{
"name": &opts.Scope.ProjectName,
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
},
}, nil
}
} else if opts.Scope.ProjectID != "" {
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
if opts.Scope.DomainID != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeProjectIDAlone{}
}
// ProjectID
return map[string]interface{}{
"project": map[string]interface{}{
"id": &opts.Scope.ProjectID,
},
}, nil
} else if opts.Scope.DomainID != "" {
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
}
// DomainID
return map[string]interface{}{
"domain": map[string]interface{}{
"id": &opts.Scope.DomainID,
},
}, nil
} else if opts.Scope.DomainName != "" {
return nil, gophercloud.ErrScopeDomainName{}
gophercloudAuthOpts := gophercloud.AuthOptions{
Scope: &scope,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
}
return nil, nil
return gophercloudAuthOpts.ToTokenV3ScopeMap()
}
func (opts *AuthOptions) CanReauth() bool {
@ -141,7 +102,8 @@ func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[
}
}
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
// Create authenticates and either generates a new token, or changes the Scope
// of an existing token.
func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResult) {
scope, err := opts.ToTokenV3ScopeMap()
if err != nil {
@ -180,9 +142,9 @@ func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
OkCodes: []int{200, 204, 400, 401, 403, 404},
OkCodes: []int{200, 204, 404},
})
if err != nil {
return false, err

View file

@ -13,35 +13,71 @@ import (
type Endpoint struct {
ID string `json:"id"`
Region string `json:"region"`
RegionID string `json:"region_id"`
Interface string `json:"interface"`
URL string `json:"url"`
}
// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing.
// Each class of service, such as cloud DNS or block storage services, could have multiple
// CatalogEntry representing it (one by interface type, e.g public, admin or internal).
// CatalogEntry provides a type-safe interface to an Identity API V3 service
// catalog listing. Each class of service, such as cloud DNS or block storage
// services, could have multiple CatalogEntry representing it (one by interface
// type, e.g public, admin or internal).
//
// Note: when looking for the desired service, try, whenever possible, to key off the type field.
// Otherwise, you'll tie the representation of the service to a specific provider.
// Note: when looking for the desired service, try, whenever possible, to key
// off the type field. Otherwise, you'll tie the representation of the service
// to a specific provider.
type CatalogEntry struct {
// Service ID
ID string `json:"id"`
// Name will contain the provider-specified name for the service.
Name string `json:"name"`
// Type will contain a type string if OpenStack defines a type for the service.
// Otherwise, for provider-specific services, the provider may assign their own type strings.
// Type will contain a type string if OpenStack defines a type for the
// service. Otherwise, for provider-specific services, the provider may
// assign their own type strings.
Type string `json:"type"`
// Endpoints will let the caller iterate over all the different endpoints that may exist for
// the service.
// Endpoints will let the caller iterate over all the different endpoints that
// may exist for the service.
Endpoints []Endpoint `json:"endpoints"`
}
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
// ServiceCatalog provides a view into the service catalog from a previous,
// successful authentication.
type ServiceCatalog struct {
Entries []CatalogEntry `json:"catalog"`
}
// commonResult is the deferred result of a Create or a Get call.
// Domain provides information about the domain to which this token grants
// access.
type Domain struct {
ID string `json:"id"`
Name string `json:"name"`
}
// User represents a user resource that exists in the Identity Service.
type User struct {
Domain Domain `json:"domain"`
ID string `json:"id"`
Name string `json:"name"`
}
// Role provides information about roles to which User is authorized.
type Role struct {
ID string `json:"id"`
Name string `json:"name"`
}
// Project provides information about project to which User is authorized.
type Project struct {
Domain Domain `json:"domain"`
ID string `json:"id"`
Name string `json:"name"`
}
// commonResult is the response from a request. A commonResult has various
// methods which can be used to extract different details about the result.
type commonResult struct {
gophercloud.Result
}
@ -66,34 +102,66 @@ func (r commonResult) ExtractToken() (*Token, error) {
return &s, err
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token.
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s ServiceCatalog
err := r.ExtractInto(&s)
return &s, err
}
// CreateResult defers the interpretation of a created token.
// Use ExtractToken() to interpret it as a Token, or ExtractServiceCatalog() to interpret it as a service catalog.
// ExtractUser returns the User that is the owner of the Token.
func (r commonResult) ExtractUser() (*User, error) {
var s struct {
User *User `json:"user"`
}
err := r.ExtractInto(&s)
return s.User, err
}
// ExtractRoles returns Roles to which User is authorized.
func (r commonResult) ExtractRoles() ([]Role, error) {
var s struct {
Roles []Role `json:"roles"`
}
err := r.ExtractInto(&s)
return s.Roles, err
}
// ExtractProject returns Project to which User is authorized.
func (r commonResult) ExtractProject() (*Project, error) {
var s struct {
Project *Project `json:"project"`
}
err := r.ExtractInto(&s)
return s.Project, err
}
// CreateResult is the response from a Create request. Use ExtractToken()
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
// as a service catalog.
type CreateResult struct {
commonResult
}
// GetResult is the deferred response from a Get call.
// GetResult is the response from a Get request. Use ExtractToken()
// to interpret it as a Token, or ExtractServiceCatalog() to interpret it
// as a service catalog.
type GetResult struct {
commonResult
}
// RevokeResult is the deferred response from a Revoke call.
// RevokeResult is response from a Revoke request.
type RevokeResult struct {
commonResult
}
// Token is a string that grants a user access to a controlled set of services in an OpenStack provider.
// Each Token is valid for a set length of time.
// Token is a string that grants a user access to a controlled set of services
// in an OpenStack provider. Each Token is valid for a set length of time.
type Token struct {
// ID is the issued token.
ID string `json:"id"`
// ExpiresAt is the timestamp at which this token will no longer be accepted.
ExpiresAt time.Time `json:"expires_at"`
}

View file

@ -0,0 +1,28 @@
package utils
import (
"net/url"
"regexp"
"strings"
)
// BaseEndpoint will return a URL without the /vX.Y
// portion of the URL.
func BaseEndpoint(endpoint string) (string, error) {
u, err := url.Parse(endpoint)
if err != nil {
return "", err
}
u.RawQuery, u.Fragment = "", ""
path := u.Path
versionRe := regexp.MustCompile("v[0-9.]+/?")
if version := versionRe.FindString(path); version != "" {
versionIndex := strings.Index(path, version)
u.Path = path[:versionIndex]
}
return u.String(), nil
}

View file

@ -68,11 +68,6 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*
return nil, "", err
}
byID := make(map[string]*Version)
for _, version := range recognized {
byID[version.ID] = version
}
var highest *Version
var endpoint string
@ -84,20 +79,22 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*
}
}
if matching, ok := byID[value.ID]; ok {
// Prefer a version that exactly matches the provided endpoint.
if href == identityEndpoint {
if href == "" {
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
for _, version := range recognized {
if strings.Contains(value.ID, version.ID) {
// Prefer a version that exactly matches the provided endpoint.
if href == identityEndpoint {
if href == "" {
return nil, "", fmt.Errorf("Endpoint missing in version %s response from %s", value.ID, client.IdentityBase)
}
return version, href, nil
}
return matching, href, nil
}
// Otherwise, find the highest-priority version with a whitelisted status.
if goodStatus[strings.ToLower(value.Status)] {
if highest == nil || matching.Priority > highest.Priority {
highest = matching
endpoint = href
// Otherwise, find the highest-priority version with a whitelisted status.
if goodStatus[strings.ToLower(value.Status)] {
if highest == nil || version.Priority > highest.Priority {
highest = version
endpoint = href
}
}
}
}

View file

@ -55,6 +55,6 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
return client.Get(url, nil, &gophercloud.RequestOpts{
MoreHeaders: headers,
OkCodes: []int{200, 204},
OkCodes: []int{200, 204, 300},
})
}

View file

@ -22,7 +22,6 @@ var (
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
// will need to implement.
type Page interface {
// NextPageURL generates the URL for the page of data that follows this collection.
// Return "" if no such page exists.
NextPageURL() (string, error)
@ -42,6 +41,8 @@ type Pager struct {
createPage func(r PageResult) Page
firstPage Page
Err error
// Headers supplies additional HTTP headers to populate on each paged request.
@ -90,9 +91,18 @@ func (p Pager) EachPage(handler func(Page) (bool, error)) error {
}
currentURL := p.initialURL
for {
currentPage, err := p.fetchNextPage(currentURL)
if err != nil {
return err
var currentPage Page
// if first page has already been fetched, no need to fetch it again
if p.firstPage != nil {
currentPage = p.firstPage
p.firstPage = nil
} else {
var err error
currentPage, err = p.fetchNextPage(currentURL)
if err != nil {
return err
}
}
empty, err := currentPage.IsEmpty()
@ -129,23 +139,26 @@ func (p Pager) AllPages() (Page, error) {
// body will contain the final concatenated Page body.
var body reflect.Value
// Grab a test page to ascertain the page body type.
testPage, err := p.fetchNextPage(p.initialURL)
// Grab a first page to ascertain the page body type.
firstPage, err := p.fetchNextPage(p.initialURL)
if err != nil {
return nil, err
}
// Store the page type so we can use reflection to create a new mega-page of
// that type.
pageType := reflect.TypeOf(testPage)
pageType := reflect.TypeOf(firstPage)
// if it's a single page, just return the testPage (first page)
// if it's a single page, just return the firstPage (first page)
if _, found := pageType.FieldByName("SinglePageBase"); found {
return testPage, nil
return firstPage, nil
}
// store the first page to avoid getting it twice
p.firstPage = firstPage
// Switch on the page body type. Recognized types are `map[string]interface{}`,
// `[]byte`, and `[]interface{}`.
switch pb := testPage.GetBody().(type) {
switch pb := firstPage.GetBody().(type) {
case map[string]interface{}:
// key is the map key for the page body if the body type is `map[string]interface{}`.
var key string

View file

@ -10,10 +10,28 @@ import (
"time"
)
// BuildRequestBody builds a map[string]interface from the given `struct`. If
// parent is not the empty string, the final map[string]interface returned will
// encapsulate the built one
//
/*
BuildRequestBody builds a map[string]interface from the given `struct`. If
parent is not an empty string, the final map[string]interface returned will
encapsulate the built one. For example:
disk := 1
createOpts := flavors.CreateOpts{
ID: "1",
Name: "m1.tiny",
Disk: &disk,
RAM: 512,
VCPUs: 1,
RxTxFactor: 1.0,
}
body, err := gophercloud.BuildRequestBody(createOpts, "flavor")
The above example can be run as-is, however it is recommended to look at how
BuildRequestBody is used within Gophercloud to more fully understand how it
fits within the request process as a whole rather than use it directly as shown
above.
*/
func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
optsValue := reflect.ValueOf(opts)
if optsValue.Kind() == reflect.Ptr {
@ -97,10 +115,31 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{},
}
}
jsonTag := f.Tag.Get("json")
if jsonTag == "-" {
continue
}
if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
sliceValue := v
if sliceValue.Kind() == reflect.Ptr {
sliceValue = sliceValue.Elem()
}
for i := 0; i < sliceValue.Len(); i++ {
element := sliceValue.Index(i)
if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
_, err := BuildRequestBody(element.Interface(), "")
if err != nil {
return nil, err
}
}
}
}
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
if zero {
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
if jsonTag := f.Tag.Get("json"); jsonTag != "" {
if jsonTag != "" {
jsonTagPieces := strings.Split(jsonTag, ",")
if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
if v.CanSet() {
@ -329,12 +368,20 @@ func BuildQueryString(opts interface{}) (*url.URL, error) {
params.Add(tags[0], v.Index(i).String())
}
}
case reflect.Map:
if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
var s []string
for _, k := range v.MapKeys() {
value := v.MapIndex(k).String()
s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
}
params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
}
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
// if the field has a 'required' tag, it can't have a zero-value
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
}
}
}
@ -407,10 +454,9 @@ func BuildHeaders(opts interface{}) (map[string]string, error) {
optsMap[tags[0]] = strconv.FormatBool(v.Bool())
}
} else {
// Otherwise, the field is not set.
if len(tags) == 2 && tags[1] == "required" {
// And the field is required. Return an error.
return optsMap, fmt.Errorf("Required header not set.")
// if the field has a 'required' tag, it can't have a zero-value
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
}
}
}

View file

@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"strings"
"sync"
)
// DefaultUserAgent is the default User-Agent string set in the request header.
@ -51,6 +52,8 @@ type ProviderClient struct {
IdentityEndpoint string
// TokenID is the ID of the most recently issued valid token.
// NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application.
// To safely read or write this value, call `Token` or `SetToken`, respectively
TokenID string
// EndpointLocator describes how this provider discovers the endpoints for
@ -68,16 +71,89 @@ type ProviderClient struct {
// authentication functions for different Identity service versions.
ReauthFunc func() error
Debug bool
mut *sync.RWMutex
reauthmut *reauthlock
}
type reauthlock struct {
sync.RWMutex
reauthing bool
}
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
// authenticated service requests.
func (client *ProviderClient) AuthenticatedHeaders() map[string]string {
if client.TokenID == "" {
return map[string]string{}
func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
if client.reauthmut != nil {
client.reauthmut.RLock()
if client.reauthmut.reauthing {
client.reauthmut.RUnlock()
return
}
client.reauthmut.RUnlock()
}
return map[string]string{"X-Auth-Token": client.TokenID}
t := client.Token()
if t == "" {
return
}
return map[string]string{"X-Auth-Token": t}
}
// UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token.
// If the application's ProviderClient is not used concurrently, this doesn't need to be called.
func (client *ProviderClient) UseTokenLock() {
client.mut = new(sync.RWMutex)
client.reauthmut = new(reauthlock)
}
// Token safely reads the value of the auth token from the ProviderClient. Applications should
// call this method to access the token instead of the TokenID field
func (client *ProviderClient) Token() string {
if client.mut != nil {
client.mut.RLock()
defer client.mut.RUnlock()
}
return client.TokenID
}
// SetToken safely sets the value of the auth token in the ProviderClient. Applications may
// use this method in a custom ReauthFunc
func (client *ProviderClient) SetToken(t string) {
if client.mut != nil {
client.mut.Lock()
defer client.mut.Unlock()
}
client.TokenID = t
}
// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
// called because of a 401 response, the caller may pass the previous token. In
// this case, the reauthentication can be skipped if another thread has already
// reauthenticated in the meantime. If no previous token is known, an empty
// string should be passed instead to force unconditional reauthentication.
func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.ReauthFunc == nil {
return nil
}
if client.mut == nil {
return client.ReauthFunc()
}
client.mut.Lock()
defer client.mut.Unlock()
client.reauthmut.Lock()
client.reauthmut.reauthing = true
client.reauthmut.Unlock()
if previousToken == "" || client.TokenID == previousToken {
err = client.ReauthFunc()
}
client.reauthmut.Lock()
client.reauthmut.reauthing = false
client.reauthmut.Unlock()
return
}
// RequestOpts customizes the behavior of the provider.Request() method.
@ -145,10 +221,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
}
req.Header.Set("Accept", applicationJSON)
for k, v := range client.AuthenticatedHeaders() {
req.Header.Add(k, v)
}
// Set the User-Agent header
req.Header.Set("User-Agent", client.UserAgent.Join())
@ -162,9 +234,16 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
}
}
// get latest token from client
for k, v := range client.AuthenticatedHeaders() {
req.Header.Set(k, v)
}
// Set connection parameter to close the connection immediately when we've got the response
req.Close = true
prereqtok := req.Header.Get("X-Auth-Token")
// Issue the request.
resp, err := client.HTTPClient.Do(req)
if err != nil {
@ -188,9 +267,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
if !ok {
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
//pc := make([]uintptr, 1)
//runtime.Callers(2, pc)
//f := runtime.FuncForPC(pc[0])
respErr := ErrUnexpectedResponseCode{
URL: url,
Method: method,
@ -198,7 +274,6 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
Actual: resp.StatusCode,
Body: body,
}
//respErr.Function = "gophercloud.ProviderClient.Request"
errType := options.ErrorContext
switch resp.StatusCode {
@ -209,7 +284,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
}
case http.StatusUnauthorized:
if client.ReauthFunc != nil {
err = client.ReauthFunc()
err = client.Reauthenticate(prereqtok)
if err != nil {
e := &ErrUnableToReauthenticate{}
e.ErrOriginal = respErr
@ -239,6 +314,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
if error401er, ok := errType.(Err401er); ok {
err = error401er.Error401(respErr)
}
case http.StatusForbidden:
err = ErrDefault403{respErr}
if error403er, ok := errType.(Err403er); ok {
err = error403er.Error403(respErr)
}
case http.StatusNotFound:
err = ErrDefault404{respErr}
if error404er, ok := errType.(Err404er); ok {
@ -298,7 +378,7 @@ func defaultOkCodes(method string) []int {
case method == "PUT":
return []int{201, 202}
case method == "PATCH":
return []int{200, 204}
return []int{200, 202, 204}
case method == "DELETE":
return []int{202, 204}
}

View file

@ -78,6 +78,77 @@ func (r Result) extractIntoPtr(to interface{}, label string) error {
return err
}
toValue := reflect.ValueOf(to)
if toValue.Kind() == reflect.Ptr {
toValue = toValue.Elem()
}
switch toValue.Kind() {
case reflect.Slice:
typeOfV := toValue.Type().Elem()
if typeOfV.Kind() == reflect.Struct {
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
if mSlice, ok := m[label].([]interface{}); ok {
for _, v := range mSlice {
// For each iteration of the slice, we create a new struct.
// This is to work around a bug where elements of a slice
// are reused and not overwritten when the same copy of the
// struct is used:
//
// https://github.com/golang/go/issues/21092
// https://github.com/golang/go/issues/24155
// https://play.golang.org/p/NHo3ywlPZli
newType := reflect.New(typeOfV).Elem()
b, err := json.Marshal(v)
if err != nil {
return err
}
// This is needed for structs with an UnmarshalJSON method.
// Technically this is just unmarshalling the response into
// a struct that is never used, but it's good enough to
// trigger the UnmarshalJSON method.
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
// Unmarshal is used rather than NewDecoder to also work
// around the above-mentioned bug.
err = json.Unmarshal(b, s)
if err != nil {
return err
}
}
newSlice = reflect.Append(newSlice, newType)
}
}
// "to" should now be properly modeled to receive the
// JSON response body and unmarshal into all the correct
// fields of the struct or composed extension struct
// at the end of this method.
toValue.Set(newSlice)
}
}
case reflect.Struct:
typeOfV := toValue.Type()
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
for i := 0; i < toValue.NumField(); i++ {
toField := toValue.Field(i)
if toField.Kind() == reflect.Struct {
s := toField.Addr().Interface()
err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
if err != nil {
return err
}
}
}
}
}
err = json.Unmarshal(b, &to)
return err
}
@ -177,9 +248,8 @@ type HeaderResult struct {
Result
}
// ExtractHeader will return the http.Header and error from the HeaderResult.
//
// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
// ExtractInto allows users to provide an object into which `Extract` will
// extract the http.Header headers of the result.
func (r HeaderResult) ExtractInto(to interface{}) error {
if r.Err != nil {
return r.Err
@ -299,6 +369,48 @@ func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
return nil
}
// RFC3339ZNoT is the time format used in Zun (Containers Service).
const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
type JSONRFC3339ZNoT time.Time
func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s == "" {
return nil
}
t, err := time.Parse(RFC3339ZNoT, s)
if err != nil {
return err
}
*jt = JSONRFC3339ZNoT(t)
return nil
}
// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
type JSONRFC3339ZNoTNoZ time.Time
func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if s == "" {
return nil
}
t, err := time.Parse(RFC3339ZNoTNoZ, s)
if err != nil {
return err
}
*jt = JSONRFC3339ZNoTNoZ(t)
return nil
}
/*
Link is an internal type to be used in packages of collection resources that are
paginated in a certain way.

View file

@ -28,6 +28,10 @@ type ServiceClient struct {
// The microversion of the service to use. Set this to use a particular microversion.
Microversion string
// MoreHeaders allows users (or Gophercloud) to set service-wide headers on requests. Put another way,
// values set in this field will be set on all the HTTP requests the service client sends.
MoreHeaders map[string]string
}
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
@ -108,15 +112,39 @@ func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Respon
return client.Request("DELETE", url, opts)
}
// Head calls `Request` with the "HEAD" HTTP verb.
func (client *ServiceClient) Head(url string, opts *RequestOpts) (*http.Response, error) {
if opts == nil {
opts = new(RequestOpts)
}
client.initReqOpts(url, nil, nil, opts)
return client.Request("HEAD", url, opts)
}
func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
switch client.Type {
case "compute":
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
case "sharev2":
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
case "volume":
opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
}
if client.Type != "" {
opts.MoreHeaders["OpenStack-API-Version"] = client.Type + " " + client.Microversion
}
}
// Request carries out the HTTP operation for the service client
func (client *ServiceClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
if len(client.MoreHeaders) > 0 {
if options == nil {
options = new(RequestOpts)
}
for k, v := range client.MoreHeaders {
options.MoreHeaders[k] = v
}
}
return client.ProviderClient.Request(method, url, options)
}

2
vendor/modules.txt vendored
View file

@ -90,7 +90,7 @@ github.com/google/pprof/profile
github.com/googleapis/gnostic/OpenAPIv2
github.com/googleapis/gnostic/compiler
github.com/googleapis/gnostic/extensions
# github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602
# github.com/gophercloud/gophercloud v0.0.0-20181206160319-9d88c34913a9
github.com/gophercloud/gophercloud
github.com/gophercloud/gophercloud/openstack
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips