Updated gophercloud to 7112fcd50da

This commit is contained in:
Rickard von Essen 2018-06-05 10:17:19 +02:00
parent 57b8d58d7e
commit dc78b30467
63 changed files with 2657 additions and 1162 deletions

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
@ -141,3 +141,19 @@ See the [contributing guide](./.github/CONTRIBUTING.md).
If you're struggling with something or have spotted a potential bug, feel free
to submit an issue to our [bug tracker](/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,12 +9,32 @@ 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
@ -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,28 @@ 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:"-"`
}
// 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
@ -241,82 +274,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,13 +445,6 @@ 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 }

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")
@ -22,18 +39,34 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
// 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"}
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err
}

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,45 @@ 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")
}

View File

@ -1,15 +0,0 @@
// Package extensions provides information and interaction with the different extensions available
// for an OpenStack service.
//
// The purpose of OpenStack API extensions is to:
//
// - Introduce new features in the API without requiring a version change.
// - Introduce vendor-specific niche functionality.
// - Act as a proving ground for experimental functionalities that might be included in a future
// version of the API.
//
// Extensions usually have tags that prevent conflicts with other extensions that define attributes
// or resources with the same names, and with core resources and attributes.
// Because an extension might not be supported by all plug-ins, its availability varies with deployments
// and the specific plug-in.
package extensions

View File

@ -1 +0,0 @@
package extensions

View File

@ -1,20 +0,0 @@
package extensions
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) {
_, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil)
return
}
// List returns a Pager which allows you to iterate over the full collection of extensions.
// It does not accept query parameters.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(c, ListExtensionURL(c), func(r pagination.PageResult) pagination.Page {
return ExtensionPage{pagination.SinglePageBase(r)}
})
}

View File

@ -1,53 +0,0 @@
package extensions
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// GetResult temporarily stores the result of a Get call.
// Use its Extract() method to interpret it as an Extension.
type GetResult struct {
gophercloud.Result
}
// Extract interprets a GetResult as an Extension.
func (r GetResult) Extract() (*Extension, error) {
var s struct {
Extension *Extension `json:"extension"`
}
err := r.ExtractInto(&s)
return s.Extension, err
}
// Extension is a struct that represents an OpenStack extension.
type Extension struct {
Updated string `json:"updated"`
Name string `json:"name"`
Links []interface{} `json:"links"`
Namespace string `json:"namespace"`
Alias string `json:"alias"`
Description string `json:"description"`
}
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
type ExtensionPage struct {
pagination.SinglePageBase
}
// IsEmpty checks whether an ExtensionPage struct is empty.
func (r ExtensionPage) IsEmpty() (bool, error) {
is, err := ExtractExtensions(r)
return len(is) == 0, err
}
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
// elements into a slice of Extension structs.
// In other words, a generic collection is mapped into a relevant slice.
func ExtractExtensions(r pagination.Page) ([]Extension, error) {
var s struct {
Extensions []Extension `json:"extensions"`
}
err := (r.(ExtensionPage)).ExtractInto(&s)
return s.Extensions, err
}

View File

@ -1,13 +0,0 @@
package extensions
import "github.com/gophercloud/gophercloud"
// ExtensionURL generates the URL for an extension resource by name.
func ExtensionURL(c *gophercloud.ServiceClient, name string) string {
return c.ServiceURL("extensions", name)
}
// ListExtensionURL generates the URL for the extensions resource collection.
func ListExtensionURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("extensions")
}

View File

@ -1,23 +0,0 @@
package extensions
import (
"github.com/gophercloud/gophercloud"
common "github.com/gophercloud/gophercloud/openstack/common/extensions"
"github.com/gophercloud/gophercloud/pagination"
)
// ExtractExtensions interprets a Page as a slice of Extensions.
func ExtractExtensions(page pagination.Page) ([]common.Extension, error) {
return common.ExtractExtensions(page)
}
// Get retrieves information for a specific extension using its alias.
func Get(c *gophercloud.ServiceClient, alias string) common.GetResult {
return common.Get(c, alias)
}
// List returns a Pager which allows you to iterate over the full collection of extensions.
// It does not accept query parameters.
func List(c *gophercloud.ServiceClient) pagination.Pager {
return common.List(c)
}

View File

@ -1,3 +0,0 @@
// Package extensions provides information and interaction with the
// different extensions available for the OpenStack Compute service.
package extensions

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,71 @@
// Package keypairs provides information and interaction with the Keypairs
// extension for the OpenStack Compute service.
/*
Package keypairs provides the ability to manage key pairs as well as create
servers with a specified key pair.
Example to List Key Pairs
allPages, err := keypairs.List(computeClient).AllPages()
if err != nil {
panic(err)
}
allKeyPairs, err := keypairs.ExtractKeyPairs(allPages)
if err != nil {
panic(err)
}
for _, kp := range allKeyPairs {
fmt.Printf("%+v\n", kp)
}
Example to Create a Key Pair
createOpts := keypairs.CreateOpts{
Name: "keypair-name",
}
keypair, err := keypairs.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("%+v", keypair)
Example to Import a Key Pair
createOpts := keypairs.CreateOpts{
Name: "keypair-name",
PublicKey: "public-key",
}
keypair, err := keypairs.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Key Pair
err := keypairs.Delete(computeClient, "keypair-name").ExtractErr()
if err != nil {
panic(err)
}
Example to Create a Server With a Key Pair
serverCreateOpts := servers.CreateOpts{
Name: "server_name",
ImageRef: "image-uuid",
FlavorRef: "flavor-uuid",
}
createOpts := keypairs.CreateOptsExt{
CreateOptsBuilder: serverCreateOpts,
KeyName: "keypair-name",
}
server, err := servers.Create(computeClient, createOpts).Extract()
if err != nil {
panic(err)
}
*/
package keypairs

View File

@ -9,11 +9,12 @@ import (
// CreateOptsExt adds a KeyPair option to the base CreateOpts.
type CreateOptsExt struct {
servers.CreateOptsBuilder
// KeyName is the name of the key pair.
KeyName string `json:"key_name,omitempty"`
}
// ToServerCreateMap adds the key_name and, optionally, key_data options to
// the base server creation options.
// ToServerCreateMap adds the key_name to the base server creation options.
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
if err != nil {
@ -37,18 +38,19 @@ 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 {
ToKeyPairCreateMap() (map[string]interface{}, error)
}
// CreateOpts specifies keypair creation or import parameters.
// CreateOpts specifies KeyPair creation or import parameters.
type CreateOpts struct {
// Name is a friendly name to refer to this KeyPair in other services.
Name string `json:"name" required:"true"`
// PublicKey [optional] is a pregenerated OpenSSH-formatted public key. If provided, this key
// will be imported and no new key will be created.
// PublicKey [optional] is a pregenerated OpenSSH-formatted public key.
// If provided, this key will be imported and no new key will be created.
PublicKey string `json:"public_key,omitempty"`
}
@ -57,8 +59,8 @@ func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "keypair")
}
// Create requests the creation of a new keypair on the server, or to import a pre-existing
// keypair.
// Create requests the creation of a new KeyPair on the server, or to import a
// pre-existing keypair.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToKeyPairCreateMap()
if err != nil {

View File

@ -5,29 +5,33 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// KeyPair is an SSH key known to the OpenStack Cloud that is available to be injected into
// servers.
// KeyPair is an SSH key known to the OpenStack Cloud that is available to be
// injected into servers.
type KeyPair struct {
// Name is used to refer to this keypair from other services within this region.
// Name is used to refer to this keypair from other services within this
// region.
Name string `json:"name"`
// Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer
// public key.
// Fingerprint is a short sequence of bytes that can be used to authenticate
// or validate a longer public key.
Fingerprint string `json:"fingerprint"`
// PublicKey is the public key from this pair, in OpenSSH format. "ssh-rsa AAAAB3Nz..."
// PublicKey is the public key from this pair, in OpenSSH format.
// "ssh-rsa AAAAB3Nz..."
PublicKey string `json:"public_key"`
// PrivateKey is the private key from this pair, in PEM format.
// "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." It is only present if this keypair was just
// returned from a Create call
// "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..."
// It is only present if this KeyPair was just returned from a Create call.
PrivateKey string `json:"private_key"`
// UserID is the user who owns this keypair.
// UserID is the user who owns this KeyPair.
UserID string `json:"user_id"`
}
// KeyPairPage stores a single, only page of KeyPair results from a List call.
// KeyPairPage stores a single page of all KeyPair results from a List call.
// Use the ExtractKeyPairs function to convert the results to a slice of
// KeyPairs.
type KeyPairPage struct {
pagination.SinglePageBase
}
@ -58,7 +62,8 @@ type keyPairResult struct {
gophercloud.Result
}
// Extract is a method that attempts to interpret any KeyPair resource response as a KeyPair struct.
// Extract is a method that attempts to interpret any KeyPair resource response
// as a KeyPair struct.
func (r keyPairResult) Extract() (*KeyPair, error) {
var s struct {
KeyPair *KeyPair `json:"keypair"`
@ -67,20 +72,20 @@ func (r keyPairResult) Extract() (*KeyPair, error) {
return s.KeyPair, err
}
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
// as a KeyPair.
// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a KeyPair.
type CreateResult struct {
keyPairResult
}
// GetResult is the response from a Get operation. Call its Extract method to interpret it
// as a KeyPair.
// GetResult is the response from a Get operation. Call its Extract method to
// interpret it as a KeyPair.
type GetResult struct {
keyPairResult
}
// 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
}

View File

@ -1,5 +1,19 @@
/*
Package startstop provides functionality to start and stop servers that have
been provisioned by the OpenStack Compute service.
Example to Stop and Start a Server
serverID := "47b6b7b7-568d-40e4-868c-d5c41735532e"
err := startstop.Stop(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
err := startstop.Start(computeClient, serverID).ExtractErr()
if err != nil {
panic(err)
}
*/
package startstop

View File

@ -7,13 +7,13 @@ func actionURL(client *gophercloud.ServiceClient, id string) string {
}
// Start is the operation responsible for starting a Compute server.
func Start(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Start(client *gophercloud.ServiceClient, id string) (r StartResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
return
}
// Stop is the operation responsible for stopping a Compute server.
func Stop(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
func Stop(client *gophercloud.ServiceClient, id string) (r StopResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
return
}

View File

@ -0,0 +1,15 @@
package startstop
import "github.com/gophercloud/gophercloud"
// StartResult is the response from a Start operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type StartResult struct {
gophercloud.ErrResult
}
// StopResult is the response from Stop operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type StopResult struct {
gophercloud.ErrResult
}

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,33 +11,44 @@ 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"`
@ -45,11 +56,12 @@ type ListOpts struct {
// 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 +72,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 +92,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 +140,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 is the amount of swap space, measured in MB.
Swap int `json:"swap"`
// 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
// 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 {
@ -542,8 +578,8 @@ func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder
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 +587,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 +622,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 +652,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 +669,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 +683,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,7 +751,8 @@ 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 := ""
@ -734,7 +783,8 @@ 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

View File

@ -32,54 +32,64 @@ 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
// RescueResult is the response from a Rescue operation. Call its ExtractErr
// method to determine if the call succeeded or failed.
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
}
// 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 +117,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
@ -133,45 +143,84 @@ func (r RescueResult) Extract() (string, error) {
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"`
}
@ -32,18 +33,22 @@ func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
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 describes struct types that can be accepted by the Create call.
// 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.
// 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")
}
@ -67,17 +72,21 @@ 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 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 server.
// 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"`
}
@ -100,7 +109,7 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
return
}
// Delete is the operation responsible for permanently deleting an API tenant.
// 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"`
@ -56,7 +57,7 @@ type tenantResult struct {
gophercloud.Result
}
// Extract interprets any tenantResults as a tenant.
// Extract interprets any tenantResults as a Tenant.
func (r tenantResult) Extract() (*Tenant, error) {
var s struct {
Tenant *Tenant `json:"tenant"`
@ -65,22 +66,26 @@ func (r tenantResult) Extract() (*Tenant, error) {
return s.Tenant, err
}
// GetResult temporarily contains the response from the Get call.
// GetResult is the response from a Get request. Call its Extract method to
// interpret it as a Tenant.
type GetResult struct {
tenantResult
}
// CreateResult temporarily contains the reponse from the Create call.
// CreateResult is the response from a Create request. Call its Extract method
// to interpret it as a Tenant.
type CreateResult struct {
tenantResult
}
// DeleteResult temporarily contains the response from the Delete call.
// 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 temporarily contains the response from the Update call.
// UpdateResult is the response from a Update request. Call its Extract method
// to interpret it as a Tenant.
type UpdateResult struct {
tenantResult
}

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

@ -10,20 +10,22 @@ type Scope struct {
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
@ -39,11 +41,11 @@ type AuthOptions struct {
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
@ -53,6 +55,7 @@ type AuthOptions struct {
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,
@ -67,68 +70,17 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
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 +93,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,7 +133,7 @@ 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, 404},
})

View File

@ -13,41 +13,50 @@ 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"`
}
// Domain provides information about the domain to which this token grants access.
// 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 on the API.
// User represents a user resource that exists in the Identity Service.
type User struct {
Domain Domain `json:"domain"`
ID string `json:"id"`
@ -67,7 +76,8 @@ type Project struct {
Name string `json:"name"`
}
// commonResult is the deferred result of a Create or a Get call.
// 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
}
@ -92,7 +102,8 @@ func (r commonResult) ExtractToken() (*Token, error) {
return &s, err
}
// 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 commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
var s ServiceCatalog
err := r.ExtractInto(&s)
@ -126,27 +137,31 @@ func (r commonResult) ExtractProject() (*Project, error) {
return s.Project, 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.
// 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,60 @@
/*
Package images enables management and retrieval of images from the OpenStack
Image Service.
Example to List Images
images.ListOpts{
Owner: "a7509e1ae65945fda83f3e52c6296017",
}
allPages, err := images.List(imagesClient, 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)
}
Example to Create an Image
createOpts := images.CreateOpts{
Name: "image_name",
Visibility: images.ImageVisibilityPrivate,
}
image, err := images.Create(imageClient, createOpts)
if err != nil {
panic(err)
}
Example to Update an Image
imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27"
updateOpts := images.UpdateOpts{
images.ReplaceImageName{
NewName: "new_name",
},
}
image, err := images.Update(imageClient, imageID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete an Image
imageID := "1bea47ed-f6a9-463b-b423-14b9cca9ad27"
err := images.Delete(imageClient, imageID).ExtractErr()
if err != nil {
panic(err)
}
*/
package images

View File

@ -1,6 +1,10 @@
package images
import (
"fmt"
"net/url"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@ -15,33 +19,106 @@ type ListOptsBuilder interface {
// the API. Filtering is achieved by passing in struct field values that map to
// the server attributes you want to see returned. Marker and Limit are used
// for pagination.
//http://developer.openstack.org/api-ref-image-v2.html
//
// http://developer.openstack.org/api-ref-image-v2.html
type ListOpts struct {
// ID is the ID of the image.
// Multiple IDs can be specified by constructing a string
// such as "in:uuid1,uuid2,uuid3".
ID string `q:"id"`
// Integer value for the limit of values to return.
Limit int `q:"limit"`
// UUID of the server at which you want to set a marker.
Marker string `q:"marker"`
Name string `q:"name"`
Visibility ImageVisibility `q:"visibility"`
// Name filters on the name of the image.
// Multiple names can be specified by constructing a string
// such as "in:name1,name2,name3".
Name string `q:"name"`
// Visibility filters on the visibility of the image.
Visibility ImageVisibility `q:"visibility"`
// MemberStatus filters on the member status of the image.
MemberStatus ImageMemberStatus `q:"member_status"`
Owner string `q:"owner"`
Status ImageStatus `q:"status"`
SizeMin int64 `q:"size_min"`
SizeMax int64 `q:"size_max"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tag string `q:"tag"`
// Owner filters on the project ID of the image.
Owner string `q:"owner"`
// Status filters on the status of the image.
// Multiple statuses can be specified by constructing a string
// such as "in:saving,queued".
Status ImageStatus `q:"status"`
// SizeMin filters on the size_min image property.
SizeMin int64 `q:"size_min"`
// SizeMax filters on the size_max image property.
SizeMax int64 `q:"size_max"`
// Sort sorts the results using the new style of sorting. See the OpenStack
// Image API reference for the exact syntax.
//
// Sort cannot be used with the classic sort options (sort_key and sort_dir).
Sort string `q:"sort"`
// SortKey will sort the results based on a specified image property.
SortKey string `q:"sort_key"`
// SortDir will sort the list results either ascending or decending.
SortDir string `q:"sort_dir"`
// Tags filters on specific image tags.
Tags []string `q:"tag"`
// CreatedAtQuery filters images based on their creation date.
CreatedAtQuery *ImageDateQuery
// UpdatedAtQuery filters images based on their updated date.
UpdatedAtQuery *ImageDateQuery
// ContainerFormat filters images based on the container_format.
// Multiple container formats can be specified by constructing a
// string such as "in:bare,ami".
ContainerFormat string `q:"container_format"`
// DiskFormat filters images based on the disk_format.
// Multiple disk formats can be specified by constructing a string
// such as "in:qcow2,iso".
DiskFormat string `q:"disk_format"`
}
// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
params := q.Query()
if opts.CreatedAtQuery != nil {
createdAt := opts.CreatedAtQuery.Date.Format(time.RFC3339)
if v := opts.CreatedAtQuery.Filter; v != "" {
createdAt = fmt.Sprintf("%s:%s", v, createdAt)
}
params.Add("created_at", createdAt)
}
if opts.UpdatedAtQuery != nil {
updatedAt := opts.UpdatedAtQuery.Date.Format(time.RFC3339)
if v := opts.UpdatedAtQuery.Filter; v != "" {
updatedAt = fmt.Sprintf("%s:%s", v, updatedAt)
}
params.Add("updated_at", updatedAt)
}
q = &url.URL{RawQuery: params.Encode()}
return q.String(), err
}
// List implements image list request
// List implements image list request.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listURL(c)
if opts != nil {
@ -56,14 +133,13 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
})
}
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
// The CreateOpts struct in this package does.
// CreateOptsBuilder allows extensions to add parameters to the Create request.
type CreateOptsBuilder interface {
// Returns value that can be passed to json.Marshal
ToImageCreateMap() (map[string]interface{}, error)
}
// CreateOpts implements CreateOptsBuilder
// CreateOpts represents options used to create an image.
type CreateOpts struct {
// Name is the name of the new image.
Name string `json:"name" required:"true"`
@ -118,7 +194,7 @@ func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
return b, nil
}
// Create implements create image request
// Create implements create image request.
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToImageCreateMap()
if err != nil {
@ -129,19 +205,19 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// Delete implements image delete request
// Delete implements image delete request.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
return
}
// Get implements image get request
// Get implements image get request.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// Update implements image updated request
// Update implements image updated request.
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToImageUpdateMap()
if err != nil {
@ -155,9 +231,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
return
}
// UpdateOptsBuilder implements UpdateOptsBuilder
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
// returns value implementing json.Marshaler which when marshaled matches the patch schema:
// returns value implementing json.Marshaler which when marshaled matches
// the patch schema:
// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
ToImageUpdateMap() ([]interface{}, error)
}
@ -165,7 +243,8 @@ type UpdateOptsBuilder interface {
// UpdateOpts implements UpdateOpts
type UpdateOpts []Patch
// ToImageUpdateMap builder
// ToImageUpdateMap assembles a request body based on the contents of
// UpdateOpts.
func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) {
m := make([]interface{}, len(opts))
for i, patch := range opts {
@ -175,18 +254,18 @@ func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) {
return m, nil
}
// Patch represents a single update to an existing image. Multiple updates to an image can be
// submitted at the same time.
// Patch represents a single update to an existing image. Multiple updates
// to an image can be submitted at the same time.
type Patch interface {
ToImagePatchMap() map[string]interface{}
}
// UpdateVisibility updated visibility
// UpdateVisibility represents an updated visibility property request.
type UpdateVisibility struct {
Visibility ImageVisibility
}
// ToImagePatchMap builder
// ToImagePatchMap assembles a request body based on UpdateVisibility.
func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",
@ -195,12 +274,12 @@ func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
}
}
// ReplaceImageName implements Patch
// ReplaceImageName represents an updated image_name property request.
type ReplaceImageName struct {
NewName string
}
// ToImagePatchMap builder
// ToImagePatchMap assembles a request body based on ReplaceImageName.
func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",
@ -209,12 +288,12 @@ func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
}
}
// ReplaceImageChecksum implements Patch
// ReplaceImageChecksum represents an updated checksum property request.
type ReplaceImageChecksum struct {
Checksum string
}
// ReplaceImageChecksum builder
// ReplaceImageChecksum assembles a request body based on ReplaceImageChecksum.
func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",
@ -223,12 +302,12 @@ func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} {
}
}
// ReplaceImageTags implements Patch
// ReplaceImageTags represents an updated tags property request.
type ReplaceImageTags struct {
NewTags []string
}
// ToImagePatchMap builder
// ToImagePatchMap assembles a request body based on ReplaceImageTags.
func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
return map[string]interface{}{
"op": "replace",

View File

@ -11,11 +11,9 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// Image model
// Does not include the literal image data; just metadata.
// returned by listing images, and by fetching a specific image.
// Image represents an image found in the OpenStack Image service.
type Image struct {
// ID is the image UUID
// ID is the image UUID.
ID string `json:"id"`
// Name is the human-readable display name for the image.
@ -34,16 +32,19 @@ type Image struct {
ContainerFormat string `json:"container_format"`
// DiskFormat is the format of the disk.
// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso.
// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
// and iso.
DiskFormat string `json:"disk_format"`
// MinDiskGigabytes is the amount of disk space in GB that is required to boot the image.
// MinDiskGigabytes is the amount of disk space in GB that is required to
// boot the image.
MinDiskGigabytes int `json:"min_disk"`
// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to boot the image.
// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to
// boot the image.
MinRAMMegabytes int `json:"min_ram"`
// Owner is the tenant the image belongs to.
// Owner is the tenant ID the image belongs to.
Owner string `json:"owner"`
// Protected is whether the image is deletable or not.
@ -52,7 +53,7 @@ type Image struct {
// Visibility defines who can see/use the image.
Visibility ImageVisibility `json:"visibility"`
// Checksum is the checksum of the data that's associated with the image
// Checksum is the checksum of the data that's associated with the image.
Checksum string `json:"checksum"`
// SizeBytes is the size of the data that's associated with the image.
@ -60,23 +61,27 @@ type Image struct {
// Metadata is a set of metadata associated with the image.
// Image metadata allow for meaningfully define the image properties
// and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
// and tags.
// See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
Metadata map[string]string `json:"metadata"`
// Properties is a set of key-value pairs, if any, that are associated with the image.
// Properties is a set of key-value pairs, if any, that are associated with
// the image.
Properties map[string]interface{} `json:"-"`
// CreatedAt is the date when the image has been created.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is the date when the last change has been made to the image or it's properties.
// UpdatedAt is the date when the last change has been made to the image or
// it's properties.
UpdatedAt time.Time `json:"updated_at"`
// File is the trailing path after the glance endpoint that represent the location
// of the image or the path to retrieve it.
// File is the trailing path after the glance endpoint that represent the
// location of the image or the path to retrieve it.
File string `json:"file"`
// Schema is the path to the JSON-schema that represent the image or image entity.
// Schema is the path to the JSON-schema that represent the image or image
// entity.
Schema string `json:"schema"`
// VirtualSize is the virtual size of the image
@ -97,7 +102,7 @@ func (r *Image) UnmarshalJSON(b []byte) error {
switch t := s.SizeBytes.(type) {
case nil:
return nil
r.SizeBytes = 0
case float32:
r.SizeBytes = int64(t)
case float64:
@ -131,38 +136,43 @@ func (r commonResult) Extract() (*Image, error) {
return s, err
}
// CreateResult represents the result of a Create operation
// CreateResult represents the result of a Create operation. Call its Extract
// method to interpret it as an Image.
type CreateResult struct {
commonResult
}
// UpdateResult represents the result of an Update operation
// UpdateResult represents the result of an Update operation. Call its Extract
// method to interpret it as an Image.
type UpdateResult struct {
commonResult
}
// GetResult represents the result of a Get operation
// GetResult represents the result of a Get operation. Call its Extract
// method to interpret it as an Image.
type GetResult struct {
commonResult
}
//DeleteResult model
// DeleteResult represents the result of a Delete operation. Call its
// ExtractErr method to interpret it as an Image.
type DeleteResult struct {
gophercloud.ErrResult
}
// ImagePage represents page
// ImagePage represents the results of a List request.
type ImagePage struct {
pagination.LinkedPageBase
}
// IsEmpty returns true if a page contains no Images results.
// IsEmpty returns true if an ImagePage contains no Images results.
func (r ImagePage) IsEmpty() (bool, error) {
images, err := ExtractImages(r)
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 (r ImagePage) NextPageURL() (string, error) {
var s struct {
Next string `json:"next"`
@ -179,7 +189,8 @@ func (r ImagePage) NextPageURL() (string, error) {
return nextPageURL(r.URL.String(), s.Next)
}
// ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities.
// ExtractImages interprets the results of a single page from a List() call,
// producing a slice of Image entities.
func ExtractImages(r pagination.Page) ([]Image, error) {
var s struct {
Images []Image `json:"images"`

View File

@ -1,5 +1,9 @@
package images
import (
"time"
)
// ImageStatus image statuses
// http://docs.openstack.org/developer/glance/statuses.html
type ImageStatus string
@ -9,7 +13,8 @@ const (
// been reserved for an image in the image registry.
ImageStatusQueued ImageStatus = "queued"
// ImageStatusSaving denotes that an images raw data is currently being uploaded to Glance
// ImageStatusSaving denotes that an images raw data is currently being
// uploaded to Glance
ImageStatusSaving ImageStatus = "saving"
// ImageStatusActive denotes an image that is fully available in Glance.
@ -23,16 +28,18 @@ const (
// The image information is retained in the image registry.
ImageStatusDeleted ImageStatus = "deleted"
// ImageStatusPendingDelete is similar to Delete, but the image is not yet deleted.
// ImageStatusPendingDelete is similar to Delete, but the image is not yet
// deleted.
ImageStatusPendingDelete ImageStatus = "pending_delete"
// ImageStatusDeactivated denotes that access to image data is not allowed to any non-admin user.
// ImageStatusDeactivated denotes that access to image data is not allowed to
// any non-admin user.
ImageStatusDeactivated ImageStatus = "deactivated"
)
// ImageVisibility denotes an image that is fully available in Glance.
// This occurs when the image data is uploaded, or the image size
// is explicitly set to zero on creation.
// This occurs when the image data is uploaded, or the image size is explicitly
// set to zero on creation.
// According to design
// https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design
type ImageVisibility string
@ -52,12 +59,13 @@ const (
// ImageVisibilityCommunity images:
// - all users can see and boot it
// - users with tenantId in the member-list of the image with member_status == 'accepted'
// have this image in their default image-list
// - users with tenantId in the member-list of the image with
// member_status == 'accepted' have this image in their default image-list.
ImageVisibilityCommunity ImageVisibility = "community"
)
// MemberStatus is a status for adding a new member (tenant) to an image member list.
// MemberStatus is a status for adding a new member (tenant) to an image
// member list.
type ImageMemberStatus string
const (
@ -73,3 +81,24 @@ const (
// ImageMemberStatusAll
ImageMemberStatusAll ImageMemberStatus = "all"
)
// ImageDateFilter represents a valid filter to use for filtering
// images by their date during a List.
type ImageDateFilter string
const (
FilterGT ImageDateFilter = "gt"
FilterGTE ImageDateFilter = "gte"
FilterLT ImageDateFilter = "lt"
FilterLTE ImageDateFilter = "lte"
FilterNEQ ImageDateFilter = "neq"
FilterEQ ImageDateFilter = "eq"
)
// ImageDateQuery represents a date field to be used for listing images.
// If no filter is specified, the query will act as though FilterEQ was
// set.
type ImageDateQuery struct {
Date time.Time
Filter ImageDateFilter
}

View File

@ -0,0 +1,58 @@
/*
Package members enables management and retrieval of image members.
Members are projects other than the image owner who have access to the image.
Example to List Members of an Image
imageID := "2b6cacd4-cfd6-4b95-8302-4c04ccf0be3f"
allPages, err := members.List(imageID).AllPages()
if err != nil {
panic(err)
}
allMembers, err := members.ExtractMembers(allPages)
if err != nil {
panic(err)
}
for _, member := range allMembers {
fmt.Printf("%+v\n", member)
}
Example to Add a Member to an Image
imageID := "2b6cacd4-cfd6-4b95-8302-4c04ccf0be3f"
projectID := "fc404778935a4cebaddcb4788fb3ff2c"
member, err := members.Create(imageClient, imageID, projectID).Extract()
if err != nil {
panic(err)
}
Example to Update the Status of a Member
imageID := "2b6cacd4-cfd6-4b95-8302-4c04ccf0be3f"
projectID := "fc404778935a4cebaddcb4788fb3ff2c"
updateOpts := members.UpdateOpts{
Status: "accepted",
}
member, err := members.Update(imageClient, imageID, projectID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Delete a Member from an Image
imageID := "2b6cacd4-cfd6-4b95-8302-4c04ccf0be3f"
projectID := "fc404778935a4cebaddcb4788fb3ff2c"
err := members.Delete(imageClient, imageID, projectID).ExtractErr()
if err != nil {
panic(err)
}
*/
package members

View File

@ -5,16 +5,24 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// Create member for specific image
//
// Preconditions
// The specified images must exist.
// You can only add a new member to an image which 'visibility' attribute is private.
// You must be the owner of the specified image.
// Synchronous Postconditions
// With correct permissions, you can see the member status of the image as pending through API calls.
//
// More details here: http://developer.openstack.org/api-ref-image-v2.html#createImageMember-v2
/*
Create member for specific image
Preconditions
* The specified images must exist.
* You can only add a new member to an image which 'visibility' attribute is
private.
* You must be the owner of the specified image.
Synchronous Postconditions
With correct permissions, you can see the member status of the image as
pending through API calls.
More details here:
http://developer.openstack.org/api-ref-image-v2.html#createImageMember-v2
*/
func Create(client *gophercloud.ServiceClient, id string, member string) (r CreateResult) {
b := map[string]interface{}{"member": member}
_, r.Err = client.Post(createMemberURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
@ -23,8 +31,7 @@ func Create(client *gophercloud.ServiceClient, id string, member string) (r Crea
return
}
// List members returns list of members for specifed image id
// More details: http://developer.openstack.org/api-ref-image-v2.html#listImageMembers-v2
// List members returns list of members for specifed image id.
func List(client *gophercloud.ServiceClient, id string) pagination.Pager {
return pagination.NewPager(client, listMembersURL(client, id), func(r pagination.PageResult) pagination.Page {
return MemberPage{pagination.SinglePageBase(r)}
@ -32,26 +39,24 @@ func List(client *gophercloud.ServiceClient, id string) pagination.Pager {
}
// Get image member details.
// More details: http://developer.openstack.org/api-ref-image-v2.html#getImageMember-v2
func Get(client *gophercloud.ServiceClient, imageID string, memberID string) (r DetailsResult) {
_, r.Err = client.Get(getMemberURL(client, imageID, memberID), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
return
}
// Delete membership for given image.
// Callee should be image owner
// More details: http://developer.openstack.org/api-ref-image-v2.html#deleteImageMember-v2
// Delete membership for given image. Callee should be image owner.
func Delete(client *gophercloud.ServiceClient, imageID string, memberID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204}})
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 {
ToImageMemberUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts implements UpdateOptsBuilder
// UpdateOpts represents options to an Update request.
type UpdateOpts struct {
Status string
}
@ -63,8 +68,7 @@ func (opts UpdateOpts) ToImageMemberUpdateMap() (map[string]interface{}, error)
}, nil
}
// Update function updates member
// More details: http://developer.openstack.org/api-ref-image-v2.html#updateImageMember-v2
// Update function updates member.
func Update(client *gophercloud.ServiceClient, imageID string, memberID string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToImageMemberUpdateMap()
if err != nil {

View File

@ -7,7 +7,7 @@ import (
"github.com/gophercloud/gophercloud/pagination"
)
// Member model
// Member represents a member of an Image.
type Member struct {
CreatedAt time.Time `json:"created_at"`
ImageID string `json:"image_id"`
@ -17,7 +17,7 @@ type Member struct {
UpdatedAt time.Time `json:"updated_at"`
}
// Extract Member model from request if possible
// Extract Member model from a request.
func (r commonResult) Extract() (*Member, error) {
var s *Member
err := r.ExtractInto(&s)
@ -29,7 +29,8 @@ type MemberPage struct {
pagination.SinglePageBase
}
// ExtractMembers returns a slice of Members contained in a single page of results.
// ExtractMembers returns a slice of Members contained in a single page
// of results.
func ExtractMembers(r pagination.Page) ([]Member, error) {
var s struct {
Members []Member `json:"members"`
@ -38,7 +39,7 @@ func ExtractMembers(r pagination.Page) ([]Member, error) {
return s.Members, err
}
// IsEmpty determines whether or not a page of Members contains any results.
// IsEmpty determines whether or not a MemberPage contains any results.
func (r MemberPage) IsEmpty() (bool, error) {
members, err := ExtractMembers(r)
return len(members) == 0, err
@ -48,22 +49,26 @@ type commonResult struct {
gophercloud.Result
}
// CreateResult result model
// CreateResult represents the result of a Create operation. Call its Extract
// method to interpret it as a Member.
type CreateResult struct {
commonResult
}
// DetailsResult model
// DetailsResult represents the result of a Get operation. Call its Extract
// method to interpret it as a Member.
type DetailsResult struct {
commonResult
}
// UpdateResult model
// UpdateResult represents the result of an Update operation. Call its Extract
// method to interpret it as a Member.
type UpdateResult struct {
commonResult
}
// DeleteResult model
// DeleteResult represents the result of a Delete operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,29 @@
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) {
var base string
u, err := url.Parse(endpoint)
if err != nil {
return base, err
}
u.RawQuery, u.Fragment = "", ""
versionRe := regexp.MustCompile("v[0-9.]+/?")
if version := versionRe.FindString(u.Path); version != "" {
base = strings.Replace(u.String(), version, "", -1)
} else {
base = u.String()
}
return base, 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

@ -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)

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,15 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{},
}
}
jsonTag := f.Tag.Get("json")
if jsonTag == "-" {
continue
}
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 +352,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 +438,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 {

View File

@ -78,6 +78,53 @@ 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)
newType := reflect.New(typeOfV).Elem()
for _, v := range m[label].([]interface{}) {
b, err := json.Marshal(v)
if err != nil {
return err
}
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
if err != nil {
return err
}
}
newSlice = reflect.Append(newSlice, newType)
}
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 +224,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 +345,27 @@ 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
}
/*
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)
}

106
vendor/vendor.json vendored
View File

@ -694,112 +694,100 @@
"revisionTime": "2015-01-27T13:39:51Z"
},
{
"checksumSHA1": "QTqcF26Y2e0SHe2Z+2wj+fedud4=",
"checksumSHA1": "qduT9GZUhXc00XoHEwLx16Xn9gM=",
"path": "github.com/gophercloud/gophercloud",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "b7g9TcU1OmW7e2UySYeOAmcfHpY=",
"path": "github.com/gophercloud/gophercloud/internal",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "24DO5BEQdFKNl1rfWgI2b4+ry5U=",
"checksumSHA1": "YDNvjFNQS+UxqZMLm/shFs7aLNU=",
"path": "github.com/gophercloud/gophercloud/openstack",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "Au6MAsI90lewLByg9n+Yjtdqdh8=",
"path": "github.com/gophercloud/gophercloud/openstack/common/extensions",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
},
{
"checksumSHA1": "4XWDCGMYqipwJymi9xJo9UffD7g=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
},
{
"checksumSHA1": "e7AW3YDVYJPKUjpqsB4AL9RRlTw=",
"checksumSHA1": "vFS5BwnCdQIfKm1nNWrR+ijsAZA=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "bx6QnHtpgB6nKmN4QRVKa5PszqY=",
"checksumSHA1": "lAQuKIqTuQ9JuMgN0pPkNtRH2RM=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "qBpGbX7LQMPATdO8XyQmU7IXDiI=",
"checksumSHA1": "qfVZltu1fYTYXS97WbjeLuLPgUc=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "vTyXSR+Znw7/o/70UBOWG0F09r8=",
"checksumSHA1": "1/1G6O0CUVYyTFF/IqzWThGyuPQ=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "Rnzx2YgOD41k8KoPA08tR992PxQ=",
"checksumSHA1": "CSnfH01hSas0bdc/3m/f5Rt6SFY=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/images",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "IjCvcaNnRW++hclt21WUkMYinaA=",
"checksumSHA1": "FKVrvZnE/223fpKVGPqaIX4JP9I=",
"path": "github.com/gophercloud/gophercloud/openstack/compute/v2/servers",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "S8bHmOP+NjtlYioJC89zIBVvhYc=",
"checksumSHA1": "oOJkelRgWx0NzUmxuI3kTS27gM0=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tenants",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "AvUU5En9YpG25iLlcAPDgcQODjI=",
"checksumSHA1": "z5NsqMZX3TLMzpmwzOOXE4M5D9w=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "rqE0NwmQ9qhXADXxg3DcuZ4A3wk=",
"checksumSHA1": "2PYxD2MOrbp4JCWy5794sEtDYD4=",
"path": "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "p2ivHupXGBmyHkusnob2NsbsCQk=",
"checksumSHA1": "Pop8rylL583hCZ0RjO+9TkrScCo=",
"path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "KA5YKF9TwIsTy9KssO27y+wk/6U=",
"checksumSHA1": "GFqX1Y5SpZvvyx0LPaP9D9Xp5k0=",
"path": "github.com/gophercloud/gophercloud/openstack/imageservice/v2/members",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "TDOZnaS0TO0NirpxV1QwPerAQTY=",
"checksumSHA1": "8KE4bJzhbFZKsYMxcRg6xLqqfTg=",
"path": "github.com/gophercloud/gophercloud/openstack/utils",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "YspETi3tOMvawKIT91HyuqaA5lM=",
"checksumSHA1": "jWdt1lN75UC0GrLG5Tmng+qP+ZI=",
"path": "github.com/gophercloud/gophercloud/pagination",
"revision": "95a28eb606def6aaaed082b6b82d3244b0552184",
"revisionTime": "2017-06-23T01:44:30Z"
"revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2",
"revisionTime": "2018-05-31T02:06:30Z"
},
{
"checksumSHA1": "xSmii71kfQASGNG2C8ttmHx9KTE=",