Merge pull request #9769 from fatmcgav/state_remote_swift_updates
state/remote/swift: Updates
This commit is contained in:
commit
866545738d
|
@ -3,14 +3,17 @@ package remote
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strconv"
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
"github.com/rackspace/gophercloud/openstack"
|
"github.com/gophercloud/gophercloud/openstack"
|
||||||
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers"
|
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/containers"
|
||||||
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/objects"
|
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TFSTATE_NAME = "tfstate.tf"
|
const TFSTATE_NAME = "tfstate.tf"
|
||||||
|
@ -49,13 +52,38 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
|
||||||
return fmt.Errorf("missing 'path' configuration")
|
return fmt.Errorf("missing 'path' configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := openstack.AuthenticatedClient(gophercloud.AuthOptions{
|
ao := gophercloud.AuthOptions{
|
||||||
IdentityEndpoint: os.Getenv("OS_AUTH_URL"),
|
IdentityEndpoint: os.Getenv("OS_AUTH_URL"),
|
||||||
Username: os.Getenv("OS_USERNAME"),
|
Username: os.Getenv("OS_USERNAME"),
|
||||||
TenantName: os.Getenv("OS_TENANT_NAME"),
|
TenantName: os.Getenv("OS_TENANT_NAME"),
|
||||||
Password: os.Getenv("OS_PASSWORD"),
|
Password: os.Getenv("OS_PASSWORD"),
|
||||||
})
|
DomainName: os.Getenv("OS_DOMAIN_NAME"),
|
||||||
|
DomainID: os.Getenv("OS_DOMAIN_ID"),
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := openstack.NewClient(ao.IdentityEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &tls.Config{}
|
||||||
|
insecure := false
|
||||||
|
if insecure_env := os.Getenv("OS_INSECURE"); insecure_env != "" {
|
||||||
|
insecure, err = strconv.ParseBool(insecure_env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if insecure {
|
||||||
|
log.Printf("[DEBUG] Insecure mode set")
|
||||||
|
config.InsecureSkipVerify = true
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
|
||||||
|
provider.HTTPClient.Transport = transport
|
||||||
|
|
||||||
|
err = openstack.Authenticate(provider, ao)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -70,12 +98,18 @@ func (c *SwiftClient) validateConfig(conf map[string]string) (err error) {
|
||||||
|
|
||||||
func (c *SwiftClient) Get() (*Payload, error) {
|
func (c *SwiftClient) Get() (*Payload, error) {
|
||||||
result := objects.Download(c.client, c.path, TFSTATE_NAME, nil)
|
result := objects.Download(c.client, c.path, TFSTATE_NAME, nil)
|
||||||
bytes, err := result.ExtractContent()
|
|
||||||
|
|
||||||
|
// Extract any errors from result
|
||||||
|
_, err := result.Extract()
|
||||||
|
|
||||||
|
// 404 response is to be expected if the object doesn't already exist!
|
||||||
|
if _, ok := err.(gophercloud.ErrDefault404); ok {
|
||||||
|
log.Printf("[DEBUG] Container doesn't exist to download.")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := result.ExtractContent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "but got 404 instead") {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +128,10 @@ func (c *SwiftClient) Put(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
result := objects.Create(c.client, c.path, TFSTATE_NAME, reader, nil)
|
createOpts := objects.CreateOpts{
|
||||||
|
Content: reader,
|
||||||
|
}
|
||||||
|
result := objects.Create(c.client, c.path, TFSTATE_NAME, createOpts)
|
||||||
|
|
||||||
return result.Err
|
return result.Err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
// GetOptsBuilder allows extensions to add additional headers to the Get
|
// GetOptsBuilder allows extensions to add additional headers to the Get
|
||||||
// request.
|
// request.
|
||||||
|
@ -23,31 +23,27 @@ func (opts GetOpts) ToAccountGetMap() (map[string]string, error) {
|
||||||
// custom metadata, call the ExtractMetadata method on the GetResult. To extract
|
// custom metadata, call the ExtractMetadata method on the GetResult. To extract
|
||||||
// all the headers that are returned (including the metadata), call the
|
// all the headers that are returned (including the metadata), call the
|
||||||
// ExtractHeader method on the GetResult.
|
// ExtractHeader method on the GetResult.
|
||||||
func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) GetResult {
|
func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) {
|
||||||
var res GetResult
|
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
headers, err := opts.ToAccountGetMap()
|
headers, err := opts.ToAccountGetMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
h[k] = v
|
h[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resp, err := c.Request("HEAD", getURL(c), &gophercloud.RequestOpts{
|
||||||
resp, err := c.Request("HEAD", getURL(c), gophercloud.RequestOpts{
|
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
OkCodes: []int{204},
|
OkCodes: []int{204},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOptsBuilder allows extensions to add additional headers to the Update
|
// UpdateOptsBuilder allows extensions to add additional headers to the Update
|
||||||
|
@ -80,28 +76,25 @@ func (opts UpdateOpts) ToAccountUpdateMap() (map[string]string, error) {
|
||||||
|
|
||||||
// Update is a function that creates, updates, or deletes an account's metadata.
|
// Update is a function that creates, updates, or deletes an account's metadata.
|
||||||
// To extract the headers returned, call the Extract method on the UpdateResult.
|
// To extract the headers returned, call the Extract method on the UpdateResult.
|
||||||
func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) UpdateResult {
|
func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||||
var res UpdateResult
|
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
headers, err := opts.ToAccountUpdateMap()
|
headers, err := opts.ToAccountUpdateMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
h[k] = v
|
h[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resp, err := c.Request("POST", updateURL(c), &gophercloud.RequestOpts{
|
||||||
resp, err := c.Request("POST", updateURL(c), gophercloud.RequestOpts{
|
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
OkCodes: []int{201, 202, 204},
|
OkCodes: []int{201, 202, 204},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
160
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go
generated
vendored
Normal file
160
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts/results.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateResult is returned from a call to the Update function.
|
||||||
|
type UpdateResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeader represents the headers returned in the response from an Update request.
|
||||||
|
type UpdateHeader struct {
|
||||||
|
ContentLength int64 `json:"-"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UpdateHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp UpdateHeader
|
||||||
|
var updateHeader *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &updateHeader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = UpdateHeader(updateHeader.tmp)
|
||||||
|
|
||||||
|
switch updateHeader.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(updateHeader.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Get. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the GetResult.
|
||||||
|
func (ur UpdateResult) Extract() (*UpdateHeader, error) {
|
||||||
|
var uh *UpdateHeader
|
||||||
|
err := ur.ExtractInto(&uh)
|
||||||
|
return uh, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader represents the headers returned in the response from a Get request.
|
||||||
|
type GetHeader struct {
|
||||||
|
BytesUsed int64 `json:"-"`
|
||||||
|
ContainerCount int64 `json:"-"`
|
||||||
|
ContentLength int64 `json:"-"`
|
||||||
|
ObjectCount int64 `json:"-"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"`
|
||||||
|
TempURLKey2 string `json:"X-Account-Meta-Temp-URL-Key-2"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *GetHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp GetHeader
|
||||||
|
var getHeader *struct {
|
||||||
|
tmp
|
||||||
|
BytesUsed string `json:"X-Account-Bytes-Used"`
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
ContainerCount string `json:"X-Account-Container-Count"`
|
||||||
|
ObjectCount string `json:"X-Account-Object-Count"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &getHeader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = GetHeader(getHeader.tmp)
|
||||||
|
|
||||||
|
switch getHeader.BytesUsed {
|
||||||
|
case "":
|
||||||
|
h.BytesUsed = 0
|
||||||
|
default:
|
||||||
|
h.BytesUsed, err = strconv.ParseInt(getHeader.BytesUsed, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("getHeader: ", getHeader.ContentLength)
|
||||||
|
switch getHeader.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(getHeader.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch getHeader.ObjectCount {
|
||||||
|
case "":
|
||||||
|
h.ObjectCount = 0
|
||||||
|
default:
|
||||||
|
h.ObjectCount, err = strconv.ParseInt(getHeader.ObjectCount, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch getHeader.ContainerCount {
|
||||||
|
case "":
|
||||||
|
h.ContainerCount = 0
|
||||||
|
default:
|
||||||
|
h.ContainerCount, err = strconv.ParseInt(getHeader.ContainerCount, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is returned from a call to the Get function.
|
||||||
|
type GetResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Get. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the GetResult.
|
||||||
|
func (r GetResult) Extract() (*GetHeader, error) {
|
||||||
|
var s *GetHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
|
||||||
|
// and returns the custom metatdata associated with the account.
|
||||||
|
func (r GetResult) ExtractMetadata() (map[string]string, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := make(map[string]string)
|
||||||
|
for k, v := range r.Header {
|
||||||
|
if strings.HasPrefix(k, "X-Account-Meta-") {
|
||||||
|
key := strings.TrimPrefix(k, "X-Account-Meta-")
|
||||||
|
metadata[key] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient) string {
|
func getURL(c *gophercloud.ServiceClient) string {
|
||||||
return c.Endpoint
|
return c.Endpoint
|
13
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/errors.go
generated
vendored
Normal file
13
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package objects
|
||||||
|
|
||||||
|
import "github.com/gophercloud/gophercloud"
|
||||||
|
|
||||||
|
// ErrWrongChecksum is the error when the checksum generated for an object
|
||||||
|
// doesn't match the ETAG header.
|
||||||
|
type ErrWrongChecksum struct {
|
||||||
|
gophercloud.BaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrWrongChecksum) Error() string {
|
||||||
|
return "Local checksum does not match API ETag header"
|
||||||
|
}
|
|
@ -1,19 +1,18 @@
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
"github.com/rackspace/gophercloud/openstack/objectstorage/v1/accounts"
|
"github.com/gophercloud/gophercloud/openstack/objectstorage/v1/accounts"
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the List
|
// ListOptsBuilder allows extensions to add additional parameters to the List
|
||||||
|
@ -42,10 +41,7 @@ type ListOpts struct {
|
||||||
// representing whether to list complete information for each object.
|
// representing whether to list complete information for each object.
|
||||||
func (opts ListOpts) ToObjectListParams() (bool, string, error) {
|
func (opts ListOpts) ToObjectListParams() (bool, string, error) {
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
if err != nil {
|
return opts.Full, q.String(), err
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
return opts.Full, q.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List is a function that retrieves all objects in a container. It also returns the details
|
// List is a function that retrieves all objects in a container. It also returns the details
|
||||||
|
@ -67,13 +63,11 @@ func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuild
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
|
||||||
p := ObjectPage{pagination.MarkerPageBase{PageResult: r}}
|
p := ObjectPage{pagination.MarkerPageBase{PageResult: r}}
|
||||||
p.MarkerPageBase.Owner = p
|
p.MarkerPageBase.Owner = p
|
||||||
return p
|
return p
|
||||||
}
|
})
|
||||||
|
|
||||||
pager := pagination.NewPager(c, url, createPage)
|
|
||||||
pager.Headers = headers
|
pager.Headers = headers
|
||||||
return pager
|
return pager
|
||||||
}
|
}
|
||||||
|
@ -113,47 +107,42 @@ func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, er
|
||||||
// Download is a function that retrieves the content and metadata for an object.
|
// Download is a function that retrieves the content and metadata for an object.
|
||||||
// To extract just the content, pass the DownloadResult response to the
|
// To extract just the content, pass the DownloadResult response to the
|
||||||
// ExtractContent function.
|
// ExtractContent function.
|
||||||
func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) DownloadResult {
|
func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) {
|
||||||
var res DownloadResult
|
|
||||||
|
|
||||||
url := downloadURL(c, containerName, objectName)
|
url := downloadURL(c, containerName, objectName)
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
headers, query, err := opts.ToObjectDownloadParams()
|
headers, query, err := opts.ToObjectDownloadParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
h[k] = v
|
h[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
url += query
|
url += query
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.Request("GET", url, gophercloud.RequestOpts{
|
resp, err := c.Get(url, nil, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
OkCodes: []int{200, 304},
|
OkCodes: []int{200, 304},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
res.Body = resp.Body
|
r.Body = resp.Body
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
|
return
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOptsBuilder allows extensions to add additional parameters to the
|
// CreateOptsBuilder allows extensions to add additional parameters to the
|
||||||
// Create request.
|
// Create request.
|
||||||
type CreateOptsBuilder interface {
|
type CreateOptsBuilder interface {
|
||||||
ToObjectCreateParams() (map[string]string, string, error)
|
ToObjectCreateParams() (io.Reader, map[string]string, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOpts is a structure that holds parameters for creating an object.
|
// CreateOpts is a structure that holds parameters for creating an object.
|
||||||
type CreateOpts struct {
|
type CreateOpts struct {
|
||||||
|
Content io.Reader
|
||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
CacheControl string `h:"Cache-Control"`
|
CacheControl string `h:"Cache-Control"`
|
||||||
ContentDisposition string `h:"Content-Disposition"`
|
ContentDisposition string `h:"Content-Disposition"`
|
||||||
|
@ -175,78 +164,60 @@ type CreateOpts struct {
|
||||||
|
|
||||||
// ToObjectCreateParams formats a CreateOpts into a query string and map of
|
// ToObjectCreateParams formats a CreateOpts into a query string and map of
|
||||||
// headers.
|
// headers.
|
||||||
func (opts CreateOpts) ToObjectCreateParams() (map[string]string, string, error) {
|
func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, string, error) {
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, nil, "", err
|
||||||
}
|
}
|
||||||
h, err := gophercloud.BuildHeaders(opts)
|
h, err := gophercloud.BuildHeaders(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, q.String(), err
|
return nil, nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range opts.Metadata {
|
for k, v := range opts.Metadata {
|
||||||
h["X-Object-Meta-"+k] = v
|
h["X-Object-Meta-"+k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
return h, q.String(), nil
|
hash := md5.New()
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
_, err = io.Copy(io.MultiWriter(hash, buf), opts.Content)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
localChecksum := fmt.Sprintf("%x", hash.Sum(nil))
|
||||||
|
h["ETag"] = localChecksum
|
||||||
|
|
||||||
|
return buf, h, q.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag
|
// Create is a function that creates a new object or replaces an existing object. If the returned response's ETag
|
||||||
// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times.
|
// header fails to match the local checksum, the failed request will automatically be retried up to a maximum of 3 times.
|
||||||
func Create(c *gophercloud.ServiceClient, containerName, objectName string, content io.ReadSeeker, opts CreateOptsBuilder) CreateResult {
|
func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) {
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
url := createURL(c, containerName, objectName)
|
url := createURL(c, containerName, objectName)
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
var b io.Reader
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
headers, query, err := opts.ToObjectCreateParams()
|
tmpB, headers, query, err := opts.ToObjectCreateParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
h[k] = v
|
h[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
url += query
|
url += query
|
||||||
|
b = tmpB
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := md5.New()
|
resp, err := c.Put(url, nil, nil, &gophercloud.RequestOpts{
|
||||||
bufioReader := bufio.NewReader(io.TeeReader(content, hash))
|
RawBody: b,
|
||||||
io.Copy(ioutil.Discard, bufioReader)
|
|
||||||
localChecksum := hash.Sum(nil)
|
|
||||||
|
|
||||||
h["ETag"] = fmt.Sprintf("%x", localChecksum)
|
|
||||||
|
|
||||||
_, err := content.Seek(0, 0)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
ropts := gophercloud.RequestOpts{
|
|
||||||
RawBody: content,
|
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
}
|
})
|
||||||
|
r.Err = err
|
||||||
resp, err := c.Request("PUT", url, ropts)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
if resp.Header.Get("ETag") == fmt.Sprintf("%x", localChecksum) {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
res.Err = fmt.Errorf("Local checksum does not match API ETag header")
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyOptsBuilder allows extensions to add additional parameters to the
|
// CopyOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
@ -262,14 +233,11 @@ type CopyOpts struct {
|
||||||
ContentDisposition string `h:"Content-Disposition"`
|
ContentDisposition string `h:"Content-Disposition"`
|
||||||
ContentEncoding string `h:"Content-Encoding"`
|
ContentEncoding string `h:"Content-Encoding"`
|
||||||
ContentType string `h:"Content-Type"`
|
ContentType string `h:"Content-Type"`
|
||||||
Destination string `h:"Destination,required"`
|
Destination string `h:"Destination" required:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToObjectCopyMap formats a CopyOpts into a map of headers.
|
// ToObjectCopyMap formats a CopyOpts into a map of headers.
|
||||||
func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
|
func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
|
||||||
if opts.Destination == "" {
|
|
||||||
return nil, fmt.Errorf("Required CopyOpts field 'Destination' not set.")
|
|
||||||
}
|
|
||||||
h, err := gophercloud.BuildHeaders(opts)
|
h, err := gophercloud.BuildHeaders(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -281,14 +249,12 @@ func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy is a function that copies one object to another.
|
// Copy is a function that copies one object to another.
|
||||||
func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) CopyResult {
|
func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) (r CopyResult) {
|
||||||
var res CopyResult
|
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
|
||||||
headers, err := opts.ToObjectCopyMap()
|
headers, err := opts.ToObjectCopyMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
|
@ -296,15 +262,15 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C
|
||||||
}
|
}
|
||||||
|
|
||||||
url := copyURL(c, containerName, objectName)
|
url := copyURL(c, containerName, objectName)
|
||||||
resp, err := c.Request("COPY", url, gophercloud.RequestOpts{
|
resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
OkCodes: []int{201},
|
OkCodes: []int{201},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteOptsBuilder allows extensions to add additional parameters to the
|
// DeleteOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
@ -321,32 +287,26 @@ type DeleteOpts struct {
|
||||||
// ToObjectDeleteQuery formats a DeleteOpts into a query string.
|
// ToObjectDeleteQuery formats a DeleteOpts into a query string.
|
||||||
func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
|
func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
if err != nil {
|
return q.String(), err
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is a function that deletes an object.
|
// Delete is a function that deletes an object.
|
||||||
func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) DeleteResult {
|
func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) {
|
||||||
var res DeleteResult
|
|
||||||
url := deleteURL(c, containerName, objectName)
|
url := deleteURL(c, containerName, objectName)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
query, err := opts.ToObjectDeleteQuery()
|
query, err := opts.ToObjectDeleteQuery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
url += query
|
url += query
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.Delete(url, nil)
|
resp, err := c.Delete(url, nil)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOptsBuilder allows extensions to add additional parameters to the
|
// GetOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
@ -364,35 +324,29 @@ type GetOpts struct {
|
||||||
// ToObjectGetQuery formats a GetOpts into a query string.
|
// ToObjectGetQuery formats a GetOpts into a query string.
|
||||||
func (opts GetOpts) ToObjectGetQuery() (string, error) {
|
func (opts GetOpts) ToObjectGetQuery() (string, error) {
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
q, err := gophercloud.BuildQueryString(opts)
|
||||||
if err != nil {
|
return q.String(), err
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get is a function that retrieves the metadata of an object. To extract just the custom
|
// Get is a function that retrieves the metadata of an object. To extract just the custom
|
||||||
// metadata, pass the GetResult response to the ExtractMetadata function.
|
// metadata, pass the GetResult response to the ExtractMetadata function.
|
||||||
func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) GetResult {
|
func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) {
|
||||||
var res GetResult
|
|
||||||
url := getURL(c, containerName, objectName)
|
url := getURL(c, containerName, objectName)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
query, err := opts.ToObjectGetQuery()
|
query, err := opts.ToObjectGetQuery()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
url += query
|
url += query
|
||||||
}
|
}
|
||||||
|
resp, err := c.Request("HEAD", url, &gophercloud.RequestOpts{
|
||||||
resp, err := c.Request("HEAD", url, gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 204},
|
OkCodes: []int{200, 204},
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
||||||
|
@ -426,31 +380,28 @@ func (opts UpdateOpts) ToObjectUpdateMap() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update is a function that creates, updates, or deletes an object's metadata.
|
// Update is a function that creates, updates, or deletes an object's metadata.
|
||||||
func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) UpdateResult {
|
func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||||
var res UpdateResult
|
|
||||||
h := make(map[string]string)
|
h := make(map[string]string)
|
||||||
|
|
||||||
if opts != nil {
|
if opts != nil {
|
||||||
headers, err := opts.ToObjectUpdateMap()
|
headers, err := opts.ToObjectUpdateMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
h[k] = v
|
h[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
url := updateURL(c, containerName, objectName)
|
url := updateURL(c, containerName, objectName)
|
||||||
resp, err := c.Request("POST", url, gophercloud.RequestOpts{
|
resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{
|
||||||
MoreHeaders: h,
|
MoreHeaders: h,
|
||||||
})
|
})
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
res.Header = resp.Header
|
r.Header = resp.Header
|
||||||
}
|
}
|
||||||
res.Err = err
|
r.Err = err
|
||||||
return res
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPMethod represents an HTTP method string (e.g. "GET").
|
// HTTPMethod represents an HTTP method string (e.g. "GET").
|
441
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go
generated
vendored
Normal file
441
vendor/github.com/gophercloud/gophercloud/openstack/objectstorage/v1/objects/results.go
generated
vendored
Normal file
|
@ -0,0 +1,441 @@
|
||||||
|
package objects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gophercloud/gophercloud"
|
||||||
|
"github.com/gophercloud/gophercloud/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Object is a structure that holds information related to a storage object.
|
||||||
|
type Object struct {
|
||||||
|
// Bytes is the total number of bytes that comprise the object.
|
||||||
|
Bytes int64 `json:"bytes"`
|
||||||
|
|
||||||
|
// ContentType is the content type of the object.
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
|
||||||
|
// Hash represents the MD5 checksum value of the object's content.
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
|
||||||
|
// LastModified is the RFC3339Milli time the object was last modified, represented
|
||||||
|
// as a string. For any given object (obj), this value may be parsed to a time.Time:
|
||||||
|
// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
|
||||||
|
LastModified gophercloud.JSONRFC3339MilliNoZ `json:"last_modified"`
|
||||||
|
|
||||||
|
// Name is the unique name for the object.
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectPage is a single page of objects that is returned from a call to the
|
||||||
|
// List function.
|
||||||
|
type ObjectPage struct {
|
||||||
|
pagination.MarkerPageBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if a ListResult contains no object names.
|
||||||
|
func (r ObjectPage) IsEmpty() (bool, error) {
|
||||||
|
names, err := ExtractNames(r)
|
||||||
|
return len(names) == 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMarker returns the last object name in a ListResult.
|
||||||
|
func (r ObjectPage) LastMarker() (string, error) {
|
||||||
|
names, err := ExtractNames(r)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(names) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return names[len(names)-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractInfo is a function that takes a page of objects and returns their full information.
|
||||||
|
func ExtractInfo(r pagination.Page) ([]Object, error) {
|
||||||
|
var s []Object
|
||||||
|
err := (r.(ObjectPage)).ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractNames is a function that takes a page of objects and returns only their names.
|
||||||
|
func ExtractNames(r pagination.Page) ([]string, error) {
|
||||||
|
casted := r.(ObjectPage)
|
||||||
|
ct := casted.Header.Get("Content-Type")
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(ct, "application/json"):
|
||||||
|
parsed, err := ExtractInfo(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]string, 0, len(parsed))
|
||||||
|
for _, object := range parsed {
|
||||||
|
names = append(names, object.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
case strings.HasPrefix(ct, "text/plain"):
|
||||||
|
names := make([]string, 0, 50)
|
||||||
|
|
||||||
|
body := string(r.(ObjectPage).Body.([]uint8))
|
||||||
|
for _, name := range strings.Split(body, "\n") {
|
||||||
|
if len(name) > 0 {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
case strings.HasPrefix(ct, "text/html"):
|
||||||
|
return []string{}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadHeader represents the headers returned in the response from a Download request.
|
||||||
|
type DownloadHeader struct {
|
||||||
|
AcceptRanges string `json:"Accept-Ranges"`
|
||||||
|
ContentDisposition string `json:"Content-Disposition"`
|
||||||
|
ContentEncoding string `json:"Content-Encoding"`
|
||||||
|
ContentLength int64 `json:"-"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
|
||||||
|
ETag string `json:"Etag"`
|
||||||
|
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
|
||||||
|
ObjectManifest string `json:"X-Object-Manifest"`
|
||||||
|
StaticLargeObject bool `json:"X-Static-Large-Object"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DownloadHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp DownloadHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = DownloadHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadResult is a *http.Response that is returned from a call to the Download function.
|
||||||
|
type DownloadResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
Body io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Download. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the DownloadResult.
|
||||||
|
func (r DownloadResult) Extract() (*DownloadHeader, error) {
|
||||||
|
var s *DownloadHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractContent is a function that takes a DownloadResult's io.Reader body
|
||||||
|
// and reads all available data into a slice of bytes. Please be aware that due
|
||||||
|
// the nature of io.Reader is forward-only - meaning that it can only be read
|
||||||
|
// once and not rewound. You can recreate a reader from the output of this
|
||||||
|
// function by using bytes.NewReader(downloadBytes)
|
||||||
|
func (r *DownloadResult) ExtractContent() ([]byte, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.Body.Close()
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeader represents the headers returned in the response from a Get request.
|
||||||
|
type GetHeader struct {
|
||||||
|
ContentDisposition string `json:"Content-Disposition"`
|
||||||
|
ContentEncoding string `json:"Content-Encoding"`
|
||||||
|
ContentLength int64 `json:"Content-Length"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
|
||||||
|
ETag string `json:"Etag"`
|
||||||
|
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
|
||||||
|
ObjectManifest string `json:"X-Object-Manifest"`
|
||||||
|
StaticLargeObject bool `json:"X-Static-Large-Object"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *GetHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp GetHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = GetHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResult is a *http.Response that is returned from a call to the Get function.
|
||||||
|
type GetResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Get. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the GetResult.
|
||||||
|
func (r GetResult) Extract() (*GetHeader, error) {
|
||||||
|
var s *GetHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
|
||||||
|
// and returns the custom metadata associated with the object.
|
||||||
|
func (r GetResult) ExtractMetadata() (map[string]string, error) {
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
metadata := make(map[string]string)
|
||||||
|
for k, v := range r.Header {
|
||||||
|
if strings.HasPrefix(k, "X-Object-Meta-") {
|
||||||
|
key := strings.TrimPrefix(k, "X-Object-Meta-")
|
||||||
|
metadata[key] = v[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHeader represents the headers returned in the response from a Create request.
|
||||||
|
type CreateHeader struct {
|
||||||
|
ContentLength int64 `json:"Content-Length"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
ETag string `json:"Etag"`
|
||||||
|
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CreateHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp CreateHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = CreateHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateResult represents the result of a create operation.
|
||||||
|
type CreateResult struct {
|
||||||
|
checksum string
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Create. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the CreateResult.
|
||||||
|
func (r CreateResult) Extract() (*CreateHeader, error) {
|
||||||
|
//if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) {
|
||||||
|
// return nil, ErrWrongChecksum{}
|
||||||
|
//}
|
||||||
|
var s *CreateHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateHeader represents the headers returned in the response from a Update request.
|
||||||
|
type UpdateHeader struct {
|
||||||
|
ContentLength int64 `json:"Content-Length"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UpdateHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp UpdateHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = UpdateHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateResult represents the result of an update operation.
|
||||||
|
type UpdateResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Update. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the UpdateResult.
|
||||||
|
func (r UpdateResult) Extract() (*UpdateHeader, error) {
|
||||||
|
var s *UpdateHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHeader represents the headers returned in the response from a Delete request.
|
||||||
|
type DeleteHeader struct {
|
||||||
|
ContentLength int64 `json:"Content-Length"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DeleteHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp DeleteHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = DeleteHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResult represents the result of a delete operation.
|
||||||
|
type DeleteResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Delete. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the DeleteResult.
|
||||||
|
func (r DeleteResult) Extract() (*DeleteHeader, error) {
|
||||||
|
var s *DeleteHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyHeader represents the headers returned in the response from a Copy request.
|
||||||
|
type CopyHeader struct {
|
||||||
|
ContentLength int64 `json:"Content-Length"`
|
||||||
|
ContentType string `json:"Content-Type"`
|
||||||
|
CopiedFrom string `json:"X-Copied-From"`
|
||||||
|
CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"`
|
||||||
|
Date gophercloud.JSONRFC1123 `json:"Date"`
|
||||||
|
ETag string `json:"Etag"`
|
||||||
|
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
|
||||||
|
TransID string `json:"X-Trans-Id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CopyHeader) UnmarshalJSON(b []byte) error {
|
||||||
|
type tmp CopyHeader
|
||||||
|
var hTmp *struct {
|
||||||
|
tmp
|
||||||
|
ContentLength string `json:"Content-Length"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(b, &hTmp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*h = CopyHeader(hTmp.tmp)
|
||||||
|
|
||||||
|
switch hTmp.ContentLength {
|
||||||
|
case "":
|
||||||
|
h.ContentLength = 0
|
||||||
|
default:
|
||||||
|
h.ContentLength, err = strconv.ParseInt(hTmp.ContentLength, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyResult represents the result of a copy operation.
|
||||||
|
type CopyResult struct {
|
||||||
|
gophercloud.HeaderResult
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract will return a struct of headers returned from a call to Copy. To obtain
|
||||||
|
// a map of headers, call the ExtractHeader method on the CopyResult.
|
||||||
|
func (r CopyResult) Extract() (*CopyHeader, error) {
|
||||||
|
var s *CopyHeader
|
||||||
|
err := r.ExtractInto(&s)
|
||||||
|
return s, err
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/rackspace/gophercloud"
|
"github.com/gophercloud/gophercloud"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient, container string) string {
|
func listURL(c *gophercloud.ServiceClient, container string) string {
|
|
@ -1,275 +0,0 @@
|
||||||
# Contributing to gophercloud
|
|
||||||
|
|
||||||
- [Getting started](#getting-started)
|
|
||||||
- [Tests](#tests)
|
|
||||||
- [Style guide](#basic-style-guide)
|
|
||||||
- [5 ways to get involved](#5-ways-to-get-involved)
|
|
||||||
|
|
||||||
## Setting up your git workspace
|
|
||||||
|
|
||||||
As a contributor you will need to setup your workspace in a slightly different
|
|
||||||
way than just downloading it. Here are the basic installation instructions:
|
|
||||||
|
|
||||||
1. Configure your `$GOPATH` and run `go get` as described in the main
|
|
||||||
[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
|
|
||||||
get dependencies for unit and acceptance tests.
|
|
||||||
|
|
||||||
2. Move into the directory that houses your local repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ${GOPATH}/src/github.com/rackspace/gophercloud
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Fork the `rackspace/gophercloud` repository and update your remote refs. You
|
|
||||||
will need to rename the `origin` remote branch to `upstream`, and add your
|
|
||||||
fork as `origin` instead:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git remote rename origin upstream
|
|
||||||
git remote add origin git@github.com/<my_username>/gophercloud
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Checkout the latest development branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout master
|
|
||||||
```
|
|
||||||
|
|
||||||
5. If you're working on something (discussed more in detail below), you will
|
|
||||||
need to checkout a new feature branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout -b my-new-feature
|
|
||||||
```
|
|
||||||
|
|
||||||
Another thing to bear in mind is that you will need to add a few extra
|
|
||||||
environment variables for acceptance tests - this is documented in our
|
|
||||||
[acceptance tests readme](/acceptance).
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
When working on a new or existing feature, testing will be the backbone of your
|
|
||||||
work since it helps uncover and prevent regressions in the codebase. There are
|
|
||||||
two types of test we use in gophercloud: unit tests and acceptance tests, which
|
|
||||||
are both described below.
|
|
||||||
|
|
||||||
### Unit tests
|
|
||||||
|
|
||||||
Unit tests are the fine-grained tests that establish and ensure the behaviour
|
|
||||||
of individual units of functionality. We usually test on an
|
|
||||||
operation-by-operation basis (an operation typically being an API action) with
|
|
||||||
the use of mocking to set up explicit expectations. Each operation will set up
|
|
||||||
its HTTP response expectation, and then test how the system responds when fed
|
|
||||||
this controlled, pre-determined input.
|
|
||||||
|
|
||||||
To make life easier, we've introduced a bunch of test helpers to simplify the
|
|
||||||
process of testing expectations with assertions:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud/testhelper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSomething(t *testing.T) {
|
|
||||||
result, err := Operation()
|
|
||||||
|
|
||||||
testhelper.AssertEquals(t, "foo", result.Bar)
|
|
||||||
testhelper.AssertNoErr(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSomethingElse(t *testing.T) {
|
|
||||||
testhelper.CheckEquals(t, "expected", "actual")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`AssertEquals` and `AssertNoErr` will throw a fatal error if a value does not
|
|
||||||
match an expected value or if an error has been declared, respectively. You can
|
|
||||||
also use `CheckEquals` and `CheckNoErr` for the same purpose; the only difference
|
|
||||||
being that `t.Errorf` is raised rather than `t.Fatalf`.
|
|
||||||
|
|
||||||
Here is a truncated example of mocked HTTP responses:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
fake "github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
// Setup the HTTP request multiplexer and server
|
|
||||||
th.SetupHTTP()
|
|
||||||
defer th.TeardownHTTP()
|
|
||||||
|
|
||||||
th.Mux.HandleFunc("/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Test we're using the correct HTTP method
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
|
|
||||||
// Test we're setting the auth token
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
// Set the appropriate headers for our mocked response
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
// Set the HTTP body
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"network": {
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"name": "private-network",
|
|
||||||
"admin_state_up": true,
|
|
||||||
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
|
|
||||||
"shared": true,
|
|
||||||
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Call our API operation
|
|
||||||
network, err := Get(fake.ServiceClient(), "d32019d3-bc6e-4319-9c1d-6722fc136a22").Extract()
|
|
||||||
|
|
||||||
// Assert no errors and equality
|
|
||||||
th.AssertNoErr(t, err)
|
|
||||||
th.AssertEquals(t, n.Status, "ACTIVE")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Acceptance tests
|
|
||||||
|
|
||||||
As we've already mentioned, unit tests have a very narrow and confined focus -
|
|
||||||
they test small units of behaviour. Acceptance tests on the other hand have a
|
|
||||||
far larger scope: they are fully functional tests that test the entire API of a
|
|
||||||
service in one fell swoop. They don't care about unit isolation or mocking
|
|
||||||
expectations, they instead do a full run-through and consequently test how the
|
|
||||||
entire system _integrates_ together. When an API satisfies expectations, it
|
|
||||||
proves by default that the requirements for a contract have been met.
|
|
||||||
|
|
||||||
Please be aware that acceptance tests will hit a live API - and may incur
|
|
||||||
service charges from your provider. Although most tests handle their own
|
|
||||||
teardown procedures, it is always worth manually checking that resources are
|
|
||||||
deleted after the test suite finishes.
|
|
||||||
|
|
||||||
### Running tests
|
|
||||||
|
|
||||||
To run all tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test -tags fixtures ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
To run all tests with verbose output:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test -v -tags fixtures ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
To run tests that match certain [build tags]():
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go test -tags "fixtures foo bar" ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
To run tests for a particular sub-package:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ./path/to/package && go test -tags fixtures .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Basic style guide
|
|
||||||
|
|
||||||
We follow the standard formatting recommendations and language idioms set out
|
|
||||||
in the [Effective Go](https://golang.org/doc/effective_go.html) guide. It's
|
|
||||||
definitely worth reading - but the relevant sections are
|
|
||||||
[formatting](https://golang.org/doc/effective_go.html#formatting)
|
|
||||||
and [names](https://golang.org/doc/effective_go.html#names).
|
|
||||||
|
|
||||||
## 5 ways to get involved
|
|
||||||
|
|
||||||
There are five main ways you can get involved in our open-source project, and
|
|
||||||
each is described briefly below. Once you've made up your mind and decided on
|
|
||||||
your fix, you will need to follow the same basic steps that all submissions are
|
|
||||||
required to adhere to:
|
|
||||||
|
|
||||||
1. [fork](https://help.github.com/articles/fork-a-repo/) the `rackspace/gophercloud` repository
|
|
||||||
2. checkout a [new branch](https://github.com/Kunena/Kunena-Forum/wiki/Create-a-new-branch-with-git-and-manage-branches)
|
|
||||||
3. submit your branch as a [pull request](https://help.github.com/articles/creating-a-pull-request/)
|
|
||||||
|
|
||||||
### 1. Providing feedback
|
|
||||||
|
|
||||||
On of the easiest ways to get readily involved in our project is to let us know
|
|
||||||
about your experiences using our SDK. Feedback like this is incredibly useful
|
|
||||||
to us, because it allows us to refine and change features based on what our
|
|
||||||
users want and expect of us. There are a bunch of ways to get in contact! You
|
|
||||||
can [ping us](https://developer.rackspace.com/support/) via e-mail, talk to us on irc
|
|
||||||
(#rackspace-dev on freenode), [tweet us](https://twitter.com/rackspace), or
|
|
||||||
submit an issue on our [bug tracker](/issues). Things you might like to tell us
|
|
||||||
are:
|
|
||||||
|
|
||||||
* how easy was it to start using our SDK?
|
|
||||||
* did it meet your expectations? If not, why not?
|
|
||||||
* did our documentation help or hinder you?
|
|
||||||
* what could we improve in general?
|
|
||||||
|
|
||||||
### 2. Fixing bugs
|
|
||||||
|
|
||||||
If you want to start fixing open bugs, we'd really appreciate that! Bug fixing
|
|
||||||
is central to any project. The best way to get started is by heading to our
|
|
||||||
[bug tracker](https://github.com/rackspace/gophercloud/issues) and finding open
|
|
||||||
bugs that you think nobody is working on. It might be useful to comment on the
|
|
||||||
thread to see the current state of the issue and if anybody has made any
|
|
||||||
breakthroughs on it so far.
|
|
||||||
|
|
||||||
### 3. Improving documentation
|
|
||||||
|
|
||||||
We have three forms of documentation:
|
|
||||||
|
|
||||||
* short README documents that briefly introduce a topic
|
|
||||||
* reference documentation on [godoc.org](http://godoc.org) that is automatically
|
|
||||||
generated from source code comments
|
|
||||||
* user documentation on our [homepage](http://gophercloud.io) that includes
|
|
||||||
getting started guides, installation guides and code samples
|
|
||||||
|
|
||||||
If you feel that a certain section could be improved - whether it's to clarify
|
|
||||||
ambiguity, correct a technical mistake, or to fix a grammatical error - please
|
|
||||||
feel entitled to do so! We welcome doc pull requests with the same childlike
|
|
||||||
enthusiasm as any other contribution!
|
|
||||||
|
|
||||||
### 4. Optimizing existing features
|
|
||||||
|
|
||||||
If you would like to improve or optimize an existing feature, please be aware
|
|
||||||
that we adhere to [semantic versioning](http://semver.org) - which means that
|
|
||||||
we cannot introduce breaking changes to the API without a major version change
|
|
||||||
(v1.x -> v2.x). Making that leap is a big step, so we encourage contributors to
|
|
||||||
refactor rather than rewrite. Running tests will prevent regression and avoid
|
|
||||||
the possibility of breaking somebody's current implementation.
|
|
||||||
|
|
||||||
Another tip is to keep the focus of your work as small as possible - try not to
|
|
||||||
introduce a change that affects lots and lots of files because it introduces
|
|
||||||
added risk and increases the cognitive load on the reviewers checking your
|
|
||||||
work. Change-sets which are easily understood and will not negatively impact
|
|
||||||
users are more likely to be integrated quickly.
|
|
||||||
|
|
||||||
Lastly, if you're seeking to optimize a particular operation, you should try to
|
|
||||||
demonstrate a negative performance impact - perhaps using go's inbuilt
|
|
||||||
[benchmark capabilities](http://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go).
|
|
||||||
|
|
||||||
### 5. Working on a new feature
|
|
||||||
|
|
||||||
If you've found something we've left out, definitely feel free to start work on
|
|
||||||
introducing that feature. It's always useful to open an issue or submit a pull
|
|
||||||
request early on to indicate your intent to a core contributor - this enables
|
|
||||||
quick/early feedback and can help steer you in the right direction by avoiding
|
|
||||||
known issues. It might also help you avoid losing time implementing something
|
|
||||||
that might not ever work. One tip is to prefix your Pull Request issue title
|
|
||||||
with [wip] - then people know it's a work in progress.
|
|
||||||
|
|
||||||
You must ensure that all of your work is well tested - both in terms of unit
|
|
||||||
and acceptance tests. Untested code will not be merged because it introduces
|
|
||||||
too much of a risk to end-users.
|
|
||||||
|
|
||||||
Happy hacking!
|
|
|
@ -1,13 +0,0 @@
|
||||||
Contributors
|
|
||||||
============
|
|
||||||
|
|
||||||
| Name | Email |
|
|
||||||
| ---- | ----- |
|
|
||||||
| Samuel A. Falvo II | <sam.falvo@rackspace.com>
|
|
||||||
| Glen Campbell | <glen.campbell@rackspace.com>
|
|
||||||
| Jesse Noller | <jesse.noller@rackspace.com>
|
|
||||||
| Jon Perritt | <jon.perritt@rackspace.com>
|
|
||||||
| Ash Wilson | <ash.wilson@rackspace.com>
|
|
||||||
| Jamie Hannaford | <jamie.hannaford@rackspace.com>
|
|
||||||
| Don Schenck | don.schenck@rackspace.com>
|
|
||||||
| Joe Topjian | <joe@topjian.net>
|
|
|
@ -1,191 +0,0 @@
|
||||||
Copyright 2012-2013 Rackspace, Inc.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
||||||
this file except in compliance with the License. You may obtain a copy of the
|
|
||||||
License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software distributed
|
|
||||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
||||||
specific language governing permissions and limitations under the License.
|
|
||||||
|
|
||||||
------
|
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
|
@ -1,160 +0,0 @@
|
||||||
# Gophercloud: an OpenStack SDK for Go
|
|
||||||
[![Build Status](https://travis-ci.org/rackspace/gophercloud.svg?branch=master)](https://travis-ci.org/rackspace/gophercloud) [![Coverage Status](https://coveralls.io/repos/rackspace/gophercloud/badge.png)](https://coveralls.io/r/rackspace/gophercloud)
|
|
||||||
|
|
||||||
Gophercloud is a flexible SDK that allows you to consume and work with OpenStack
|
|
||||||
clouds in a simple and idiomatic way using golang. Many services are supported,
|
|
||||||
including Compute, Block Storage, Object Storage, Networking, and Identity.
|
|
||||||
Each service API is backed with getting started guides, code samples, reference
|
|
||||||
documentation, unit tests and acceptance tests.
|
|
||||||
|
|
||||||
## Useful links
|
|
||||||
|
|
||||||
* [Gophercloud homepage](http://gophercloud.io)
|
|
||||||
* [Reference documentation](http://godoc.org/github.com/rackspace/gophercloud)
|
|
||||||
* [Getting started guides](http://gophercloud.io/docs)
|
|
||||||
* [Effective Go](https://golang.org/doc/effective_go.html)
|
|
||||||
|
|
||||||
## How to install
|
|
||||||
|
|
||||||
Before installing, you need to ensure that your [GOPATH environment variable](https://golang.org/doc/code.html#GOPATH)
|
|
||||||
is pointing to an appropriate directory where you want to install Gophercloud:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir $HOME/go
|
|
||||||
export GOPATH=$HOME/go
|
|
||||||
```
|
|
||||||
|
|
||||||
To protect yourself against changes in your dependencies, we highly recommend choosing a
|
|
||||||
[dependency management solution](https://github.com/golang/go/wiki/PackageManagementTools) for
|
|
||||||
your projects, such as [godep](https://github.com/tools/godep). Once this is set up, you can install
|
|
||||||
Gophercloud as a dependency like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/rackspace/gophercloud
|
|
||||||
|
|
||||||
# Edit your code to import relevant packages from "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
godep save ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
This will install all the source files you need into a `Godeps/_workspace` directory, which is
|
|
||||||
referenceable from your own source files when you use the `godep go` command.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
### Credentials
|
|
||||||
|
|
||||||
Because you'll be hitting an API, you will need to retrieve your OpenStack
|
|
||||||
credentials and either store them as environment variables or in your local Go
|
|
||||||
files. The first method is recommended because it decouples credential
|
|
||||||
information from source code, allowing you to push the latter to your version
|
|
||||||
control system without any security risk.
|
|
||||||
|
|
||||||
You will need to retrieve the following:
|
|
||||||
|
|
||||||
* username
|
|
||||||
* password
|
|
||||||
* tenant name or tenant ID
|
|
||||||
* a valid Keystone identity URL
|
|
||||||
|
|
||||||
For users that have the OpenStack dashboard installed, there's a shortcut. If
|
|
||||||
you visit the `project/access_and_security` path in Horizon and click on the
|
|
||||||
"Download OpenStack RC File" button at the top right hand corner, you will
|
|
||||||
download a bash file that exports all of your access details to environment
|
|
||||||
variables. To execute the file, run `source admin-openrc.sh` and you will be
|
|
||||||
prompted for your password.
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
Once you have access to your credentials, you can begin plugging them into
|
|
||||||
Gophercloud. The next step is authentication, and this is handled by a base
|
|
||||||
"Provider" struct. To get one, you can either pass in your credentials
|
|
||||||
explicitly, or tell Gophercloud to use environment variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Option 1: Pass in the values yourself
|
|
||||||
opts := gophercloud.AuthOptions{
|
|
||||||
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
|
|
||||||
Username: "{username}",
|
|
||||||
Password: "{password}",
|
|
||||||
TenantID: "{tenant_id}",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option 2: Use a utility function to retrieve all your environment variables
|
|
||||||
opts, err := openstack.AuthOptionsFromEnv()
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you have the `opts` variable, you can pass it in and get back a
|
|
||||||
`ProviderClient` struct:
|
|
||||||
|
|
||||||
```go
|
|
||||||
provider, err := openstack.AuthenticatedClient(opts)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `ProviderClient` is the top-level client that all of your OpenStack services
|
|
||||||
derive from. The provider contains all of the authentication details that allow
|
|
||||||
your Go code to access the API - such as the base URL and token ID.
|
|
||||||
|
|
||||||
### Provision a server
|
|
||||||
|
|
||||||
Once we have a base Provider, we inject it as a dependency into each OpenStack
|
|
||||||
service. In order to work with the Compute API, we need a Compute service
|
|
||||||
client; which can be created like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
client, err := openstack.NewComputeV2(provider, gophercloud.EndpointOpts{
|
|
||||||
Region: os.Getenv("OS_REGION_NAME"),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
We then use this `client` for any Compute API operation we want. In our case,
|
|
||||||
we want to provision a new server - so we invoke the `Create` method and pass
|
|
||||||
in the flavor ID (hardware specification) and image ID (operating system) we're
|
|
||||||
interested in:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
server, err := servers.Create(client, servers.CreateOpts{
|
|
||||||
Name: "My new server!",
|
|
||||||
FlavorRef: "flavor_id",
|
|
||||||
ImageRef: "image_id",
|
|
||||||
}).Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are unsure about what images and flavors are, you can read our [Compute
|
|
||||||
Getting Started guide](http://gophercloud.io/docs/compute). The above code
|
|
||||||
sample creates a new server with the parameters, and embodies the new resource
|
|
||||||
in the `server` variable (a
|
|
||||||
[`servers.Server`](http://godoc.org/github.com/rackspace/gophercloud) struct).
|
|
||||||
|
|
||||||
### Next steps
|
|
||||||
|
|
||||||
Cool! You've handled authentication, got your `ProviderClient` and provisioned
|
|
||||||
a new server. You're now ready to use more OpenStack services.
|
|
||||||
|
|
||||||
* [Getting started with Compute](http://gophercloud.io/docs/compute)
|
|
||||||
* [Getting started with Object Storage](http://gophercloud.io/docs/object-storage)
|
|
||||||
* [Getting started with Networking](http://gophercloud.io/docs/networking)
|
|
||||||
* [Getting started with Block Storage](http://gophercloud.io/docs/block-storage)
|
|
||||||
* [Getting started with Identity](http://gophercloud.io/docs/identity)
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Engaging the community and lowering barriers for contributors is something we
|
|
||||||
care a lot about. For this reason, we've taken the time to write a [contributing
|
|
||||||
guide](./CONTRIBUTING.md) for folks interested in getting involved in our project.
|
|
||||||
If you're not sure how you can get involved, feel free to submit an issue or
|
|
||||||
[contact us](https://developer.rackspace.com/support/). You don't need to be a
|
|
||||||
Go expert - all members of the community are welcome!
|
|
||||||
|
|
||||||
## Help and feedback
|
|
||||||
|
|
||||||
If you're struggling with something or have spotted a potential bug, feel free
|
|
||||||
to submit an issue to our [bug tracker](/issues) or [contact us directly](https://developer.rackspace.com/support/).
|
|
|
@ -1,338 +0,0 @@
|
||||||
# Upgrading to v1.0.0
|
|
||||||
|
|
||||||
With the arrival of this new major version increment, the unfortunate news is
|
|
||||||
that breaking changes have been introduced to existing services. The API
|
|
||||||
has been completely rewritten from the ground up to make the library more
|
|
||||||
extensible, maintainable and easy-to-use.
|
|
||||||
|
|
||||||
Below we've compiled upgrade instructions for the various services that
|
|
||||||
existed before. If you have a specific issue that is not addressed below,
|
|
||||||
please [submit an issue](/issues/new) or
|
|
||||||
[e-mail our support team](https://developer.rackspace.com/support/).
|
|
||||||
|
|
||||||
* [Authentication](#authentication)
|
|
||||||
* [Servers](#servers)
|
|
||||||
* [List servers](#list-servers)
|
|
||||||
* [Get server details](#get-server-details)
|
|
||||||
* [Create server](#create-server)
|
|
||||||
* [Resize server](#resize-server)
|
|
||||||
* [Reboot server](#reboot-server)
|
|
||||||
* [Update server](#update-server)
|
|
||||||
* [Rebuild server](#rebuild-server)
|
|
||||||
* [Change admin password](#change-admin-password)
|
|
||||||
* [Delete server](#delete-server)
|
|
||||||
* [Rescue server](#rescue-server)
|
|
||||||
* [Images and flavors](#images-and-flavors)
|
|
||||||
* [List images](#list-images)
|
|
||||||
* [List flavors](#list-flavors)
|
|
||||||
* [Create/delete image](#createdelete-image)
|
|
||||||
* [Other](#other)
|
|
||||||
* [List keypairs](#list-keypairs)
|
|
||||||
* [Create/delete keypair](#createdelete-keypair)
|
|
||||||
* [List IP addresses](#list-ip-addresses)
|
|
||||||
|
|
||||||
# Authentication
|
|
||||||
|
|
||||||
One of the major differences that this release introduces is the level of
|
|
||||||
sub-packaging to differentiate between services and providers. You now have
|
|
||||||
the option of authenticating with OpenStack and other providers (like Rackspace).
|
|
||||||
|
|
||||||
To authenticate with a vanilla OpenStack installation, you can either specify
|
|
||||||
your credentials like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack"
|
|
||||||
)
|
|
||||||
|
|
||||||
opts := gophercloud.AuthOptions{
|
|
||||||
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
|
|
||||||
Username: "{username}",
|
|
||||||
Password: "{password}",
|
|
||||||
TenantID: "{tenant_id}",
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or have them pulled in through environment variables, like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
opts, err := openstack.AuthOptionsFromEnv()
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you have your `AuthOptions` struct, you pass it in to get back a `Provider`,
|
|
||||||
like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
provider, err := openstack.AuthenticatedClient(opts)
|
|
||||||
```
|
|
||||||
|
|
||||||
This provider is the top-level structure that all services are created from.
|
|
||||||
|
|
||||||
# Servers
|
|
||||||
|
|
||||||
Before you can interact with the Compute API, you need to retrieve a
|
|
||||||
`gophercloud.ServiceClient`. To do this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Define your region, etc.
|
|
||||||
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
|
|
||||||
|
|
||||||
client, err := openstack.NewComputeV2(provider, opts)
|
|
||||||
```
|
|
||||||
|
|
||||||
## List servers
|
|
||||||
|
|
||||||
All operations that involve API collections (servers, flavors, images) now use
|
|
||||||
the `pagination.Pager` interface. This interface represents paginated entities
|
|
||||||
that can be iterated over.
|
|
||||||
|
|
||||||
Once you have a Pager, you can then pass a callback function into its `EachPage`
|
|
||||||
method, and this will allow you to traverse over the collection and execute
|
|
||||||
arbitrary functionality. So, an example with list servers:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// We have the option of filtering the server list. If we want the full
|
|
||||||
// collection, leave it as an empty struct or nil
|
|
||||||
opts := servers.ListOpts{Name: "server_1"}
|
|
||||||
|
|
||||||
// Retrieve a pager (i.e. a paginated collection)
|
|
||||||
pager := servers.List(client, opts)
|
|
||||||
|
|
||||||
// Define an anonymous function to be executed on each page's iteration
|
|
||||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
serverList, err := servers.ExtractServers(page)
|
|
||||||
|
|
||||||
// `s' will be a servers.Server struct
|
|
||||||
for _, s := range serverList {
|
|
||||||
fmt.Printf("We have a server. ID=%s, Name=%s", s.ID, s.Name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get server details
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
// Get the HTTP result
|
|
||||||
response := servers.Get(client, "server_id")
|
|
||||||
|
|
||||||
// Extract a Server struct from the response
|
|
||||||
server, err := response.Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
// Define our options
|
|
||||||
opts := servers.CreateOpts{
|
|
||||||
Name: "new_server",
|
|
||||||
FlavorRef: "flavorID",
|
|
||||||
ImageRef: "imageID",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get our response
|
|
||||||
response := servers.Create(client, opts)
|
|
||||||
|
|
||||||
// Extract
|
|
||||||
server, err := response.Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Change admin password
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
result := servers.ChangeAdminPassword(client, "server_id", "newPassword_&123")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resize server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
result := servers.Resize(client, "server_id", "new_flavor_id")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reboot server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
// You have a choice of two reboot methods: servers.SoftReboot or servers.HardReboot
|
|
||||||
result := servers.Reboot(client, "server_id", servers.SoftReboot)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
opts := servers.UpdateOpts{Name: "new_name"}
|
|
||||||
|
|
||||||
server, err := servers.Update(client, "server_id", opts).Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rebuild server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
// You have the option of specifying additional options
|
|
||||||
opts := RebuildOpts{
|
|
||||||
Name: "new_name",
|
|
||||||
AdminPass: "admin_password",
|
|
||||||
ImageID: "image_id",
|
|
||||||
Metadata: map[string]string{"owner": "me"},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := servers.Rebuild(client, "server_id", opts)
|
|
||||||
|
|
||||||
// You can extract a servers.Server struct from the HTTP response
|
|
||||||
server, err := result.Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Delete server
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
|
|
||||||
response := servers.Delete(client, "server_id")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rescue server
|
|
||||||
|
|
||||||
The server rescue extension for Compute is not currently supported.
|
|
||||||
|
|
||||||
# Images and flavors
|
|
||||||
|
|
||||||
## List images
|
|
||||||
|
|
||||||
As with listing servers (see above), you first retrieve a Pager, and then pass
|
|
||||||
in a callback over each page:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
|
||||||
)
|
|
||||||
|
|
||||||
// We have the option of filtering the image list. If we want the full
|
|
||||||
// collection, leave it as an empty struct
|
|
||||||
opts := images.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", Name: "Ubuntu 12.04"}
|
|
||||||
|
|
||||||
// Retrieve a pager (i.e. a paginated collection)
|
|
||||||
pager := images.List(client, opts)
|
|
||||||
|
|
||||||
// Define an anonymous function to be executed on each page's iteration
|
|
||||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
imageList, err := images.ExtractImages(page)
|
|
||||||
|
|
||||||
for _, i := range imageList {
|
|
||||||
// "i" will be an images.Image
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## List flavors
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// We have the option of filtering the flavor list. If we want the full
|
|
||||||
// collection, leave it as an empty struct
|
|
||||||
opts := flavors.ListOpts{ChangesSince: "2014-01-01T01:02:03Z", MinRAM: 4}
|
|
||||||
|
|
||||||
// Retrieve a pager (i.e. a paginated collection)
|
|
||||||
pager := flavors.List(client, opts)
|
|
||||||
|
|
||||||
// Define an anonymous function to be executed on each page's iteration
|
|
||||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
flavorList, err := networks.ExtractFlavors(page)
|
|
||||||
|
|
||||||
for _, f := range flavorList {
|
|
||||||
// "f" will be a flavors.Flavor
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create/delete image
|
|
||||||
|
|
||||||
Image management has been shifted to Glance, but unfortunately this service is
|
|
||||||
not supported as of yet. You can, however, list Compute images like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
|
||||||
|
|
||||||
// Retrieve a pager (i.e. a paginated collection)
|
|
||||||
pager := images.List(client, opts)
|
|
||||||
|
|
||||||
// Define an anonymous function to be executed on each page's iteration
|
|
||||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
imageList, err := images.ExtractImages(page)
|
|
||||||
|
|
||||||
for _, i := range imageList {
|
|
||||||
// "i" will be an images.Image
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
# Other
|
|
||||||
|
|
||||||
## List keypairs
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
|
||||||
|
|
||||||
// Retrieve a pager (i.e. a paginated collection)
|
|
||||||
pager := keypairs.List(client, opts)
|
|
||||||
|
|
||||||
// Define an anonymous function to be executed on each page's iteration
|
|
||||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
keyList, err := keypairs.ExtractKeyPairs(page)
|
|
||||||
|
|
||||||
for _, k := range keyList {
|
|
||||||
// "k" will be a keypairs.KeyPair
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create/delete keypairs
|
|
||||||
|
|
||||||
To create a new keypair, you need to specify its name and, optionally, a
|
|
||||||
pregenerated OpenSSH-formatted public key.
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
|
||||||
|
|
||||||
opts := keypairs.CreateOpts{
|
|
||||||
Name: "new_key",
|
|
||||||
PublicKey: "...",
|
|
||||||
}
|
|
||||||
|
|
||||||
response := keypairs.Create(client, opts)
|
|
||||||
|
|
||||||
key, err := response.Extract()
|
|
||||||
```
|
|
||||||
|
|
||||||
To delete an existing keypair:
|
|
||||||
|
|
||||||
```go
|
|
||||||
response := keypairs.Delete(client, "keypair_id")
|
|
||||||
```
|
|
||||||
|
|
||||||
## List IP addresses
|
|
||||||
|
|
||||||
This operation is not currently supported.
|
|
|
@ -1,55 +0,0 @@
|
||||||
package gophercloud
|
|
||||||
|
|
||||||
/*
|
|
||||||
AuthOptions stores information needed to authenticate to an OpenStack cluster.
|
|
||||||
You can populate one manually, or use a provider's AuthOptionsFromEnv() function
|
|
||||||
to read relevant information from the standard environment variables. Pass one
|
|
||||||
to a provider's AuthenticatedClient function to authenticate and obtain a
|
|
||||||
ProviderClient representing an active session on that provider.
|
|
||||||
|
|
||||||
Its fields are the union of those recognized by each identity implementation and
|
|
||||||
provider.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
IdentityEndpoint string
|
|
||||||
|
|
||||||
// Username is required if using Identity V2 API. Consult with your provider's
|
|
||||||
// control panel to discover your account's username. In Identity V3, either
|
|
||||||
// UserID or a combination of Username and DomainID or DomainName are needed.
|
|
||||||
Username, UserID string
|
|
||||||
|
|
||||||
// Exactly one of Password or APIKey is required for the Identity V2 and V3
|
|
||||||
// APIs. Consult with your provider's control panel to discover your account's
|
|
||||||
// preferred method of authentication.
|
|
||||||
Password, APIKey string
|
|
||||||
|
|
||||||
// At most one of DomainID and DomainName must be provided if using Username
|
|
||||||
// with Identity V3. Otherwise, either are optional.
|
|
||||||
DomainID, DomainName string
|
|
||||||
|
|
||||||
// The TenantID and TenantName fields are optional for the Identity V2 API.
|
|
||||||
// Some providers allow you to specify a TenantName instead of the TenantId.
|
|
||||||
// Some require both. Your provider's authentication policies will determine
|
|
||||||
// how these fields influence authentication.
|
|
||||||
TenantID, TenantName string
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// TokenID allows users to authenticate (possibly as another user) with an
|
|
||||||
// authentication token ID.
|
|
||||||
TokenID string
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package gophercloud
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// AuthResults [deprecated] is a leftover type from the v0.x days. It was
|
|
||||||
// intended to describe common functionality among identity service results, but
|
|
||||||
// is not actually used anywhere.
|
|
||||||
type AuthResults interface {
|
|
||||||
// TokenID returns the token's ID value from the authentication response.
|
|
||||||
TokenID() (string, error)
|
|
||||||
|
|
||||||
// ExpiresAt retrieves the token's expiration time.
|
|
||||||
ExpiresAt() (time.Time, error)
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
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. Examples of providers include: OpenStack, Rackspace,
|
|
||||||
HP. These are defined like so:
|
|
||||||
|
|
||||||
opts := gophercloud.AuthOptions{
|
|
||||||
IdentityEndpoint: "https://my-openstack.com:5000/v2.0",
|
|
||||||
Username: "{username}",
|
|
||||||
Password: "{password}",
|
|
||||||
TenantID: "{tenant_id}",
|
|
||||||
}
|
|
||||||
|
|
||||||
provider, err := openstack.AuthenticatedClient(opts)
|
|
||||||
|
|
||||||
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
|
|
||||||
pass in the parent provider, like so:
|
|
||||||
|
|
||||||
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
|
|
||||||
|
|
||||||
client := openstack.NewComputeV2(provider, opts)
|
|
||||||
|
|
||||||
Resource structs are the domain models that services make use of in order
|
|
||||||
to work with and represent the state of API resources:
|
|
||||||
|
|
||||||
server, err := servers.Get(client, "{serverId}").Extract()
|
|
||||||
|
|
||||||
Intermediate Result structs are returned for API operations, which allow
|
|
||||||
generic access to the HTTP headers, response body, and any errors associated
|
|
||||||
with the network transaction. To turn a result into a usable resource struct,
|
|
||||||
you must call the Extract method which is chained to the response, or an
|
|
||||||
Extract function from an applicable extension:
|
|
||||||
|
|
||||||
result := servers.Get(client, "{serverId}")
|
|
||||||
|
|
||||||
// Attempt to extract the disk configuration from the OS-DCF disk config
|
|
||||||
// extension:
|
|
||||||
config, err := diskconfig.ExtractGet(result)
|
|
||||||
|
|
||||||
All requests that enumerate a collection return a Pager struct that is used to
|
|
||||||
iterate through the results one page at a time. Use the EachPage method on that
|
|
||||||
Pager to handle each successive Page in a closure, then use the appropriate
|
|
||||||
extraction method from that request's package to interpret that Page as a slice
|
|
||||||
of results:
|
|
||||||
|
|
||||||
err := servers.List(client, nil).EachPage(func (page pagination.Page) (bool, error) {
|
|
||||||
s, err := servers.ExtractServers(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the []servers.Server slice.
|
|
||||||
|
|
||||||
// Return "false" or an error to prematurely stop fetching new pages.
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
package gophercloud
|
|
|
@ -1,92 +0,0 @@
|
||||||
package gophercloud
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrServiceNotFound is returned when no service in a service catalog matches
|
|
||||||
// the provided EndpointOpts. This is generally returned by provider service
|
|
||||||
// factory methods like "NewComputeV2()" and can mean that a service is not
|
|
||||||
// enabled for your account.
|
|
||||||
ErrServiceNotFound = errors.New("No suitable service could be found in the service catalog.")
|
|
||||||
|
|
||||||
// ErrEndpointNotFound is returned when no available endpoints match the
|
|
||||||
// provided EndpointOpts. This is also generally returned by provider service
|
|
||||||
// factory methods, and usually indicates that a region was specified
|
|
||||||
// incorrectly.
|
|
||||||
ErrEndpointNotFound = errors.New("No suitable endpoint could be found in the service catalog.")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Availability indicates to whom a specific service endpoint is accessible:
|
|
||||||
// the internet at large, internal networks only, or only to administrators.
|
|
||||||
// Different identity services use different terminology for these. Identity v2
|
|
||||||
// lists them as different kinds of URLs within the service catalog ("adminURL",
|
|
||||||
// "internalURL", and "publicURL"), while v3 lists them as "Interfaces" in an
|
|
||||||
// endpoint's response.
|
|
||||||
type Availability string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AvailabilityAdmin indicates that an endpoint is only available to
|
|
||||||
// administrators.
|
|
||||||
AvailabilityAdmin Availability = "admin"
|
|
||||||
|
|
||||||
// AvailabilityPublic indicates that an endpoint is available to everyone on
|
|
||||||
// the internet.
|
|
||||||
AvailabilityPublic Availability = "public"
|
|
||||||
|
|
||||||
// AvailabilityInternal indicates that an endpoint is only available within
|
|
||||||
// the cluster's internal network.
|
|
||||||
AvailabilityInternal Availability = "internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EndpointOpts specifies search criteria used by queries against an
|
|
||||||
// OpenStack service catalog. The options must contain enough information to
|
|
||||||
// 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()".
|
|
||||||
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
|
|
||||||
// function, but a user-given value will be honored if provided.
|
|
||||||
Type string
|
|
||||||
|
|
||||||
// Name [optional] is the service name for the client (e.g., "nova") as it
|
|
||||||
// appears in the service catalog. Services can have the same Type but a
|
|
||||||
// different Name, which is why both Type and Name are sometimes needed.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Region [required] is the geographic region in which the endpoint resides,
|
|
||||||
// generally specifying which datacenter should house your resources.
|
|
||||||
// Required only for services that span multiple regions.
|
|
||||||
Region string
|
|
||||||
|
|
||||||
// Availability [optional] is the visibility of the endpoint to be returned.
|
|
||||||
// Valid types include the constants AvailabilityPublic, AvailabilityInternal,
|
|
||||||
// or AvailabilityAdmin from this package.
|
|
||||||
//
|
|
||||||
// Availability is not required, and defaults to AvailabilityPublic. Not all
|
|
||||||
// providers or services offer all Availability options.
|
|
||||||
Availability Availability
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
EndpointLocator is an internal function to be used by provider implementations.
|
|
||||||
|
|
||||||
It provides an implementation that locates a single endpoint from a service
|
|
||||||
catalog for a specific ProviderClient based on user-provided EndpointOpts. The
|
|
||||||
provider then uses it to discover related ServiceClients.
|
|
||||||
*/
|
|
||||||
type EndpointLocator func(EndpointOpts) (string, error)
|
|
||||||
|
|
||||||
// ApplyDefaults is an internal method to be used by provider implementations.
|
|
||||||
//
|
|
||||||
// It sets EndpointOpts fields if not already set, including a default type.
|
|
||||||
// Currently, EndpointOpts.Availability defaults to the public endpoint.
|
|
||||||
func (eo *EndpointOpts) ApplyDefaults(t string) {
|
|
||||||
if eo.Type == "" {
|
|
||||||
eo.Type = t
|
|
||||||
}
|
|
||||||
if eo.Availability == "" {
|
|
||||||
eo.Availability = AvailabilityPublic
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package openstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
var nilOptions = gophercloud.AuthOptions{}
|
|
||||||
|
|
||||||
// ErrNoAuthUrl, ErrNoUsername, and ErrNoPassword errors indicate of the required OS_AUTH_URL, OS_USERNAME, or OS_PASSWORD
|
|
||||||
// environment variables, respectively, remain undefined. See the AuthOptions() function for more details.
|
|
||||||
var (
|
|
||||||
ErrNoAuthURL = fmt.Errorf("Environment variable OS_AUTH_URL needs to be set.")
|
|
||||||
ErrNoUsername = fmt.Errorf("Environment variable OS_USERNAME, OS_USERID, or OS_TOKEN needs to be set.")
|
|
||||||
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD or OS_TOKEN needs to be set.")
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuthOptionsFromEnv fills out an AuthOptions structure from the environment
|
|
||||||
// variables: OS_AUTH_URL, OS_USERNAME, OS_USERID, OS_PASSWORD, OS_TENANT_ID,
|
|
||||||
// OS_TENANT_NAME, OS_DOMAIN_ID, OS_DOMAIN_NAME, OS_TOKEN. It checks that
|
|
||||||
// (1) OS_AUTH_URL is set, (2) OS_USERNAME, OS_USERID, or OS_TOKEN is set,
|
|
||||||
// (3) OS_PASSWORD or OS_TOKEN is set.
|
|
||||||
func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
|
||||||
authURL := os.Getenv("OS_AUTH_URL")
|
|
||||||
username := os.Getenv("OS_USERNAME")
|
|
||||||
userID := os.Getenv("OS_USERID")
|
|
||||||
password := os.Getenv("OS_PASSWORD")
|
|
||||||
tenantID := os.Getenv("OS_TENANT_ID")
|
|
||||||
tenantName := os.Getenv("OS_TENANT_NAME")
|
|
||||||
domainID := os.Getenv("OS_DOMAIN_ID")
|
|
||||||
domainName := os.Getenv("OS_DOMAIN_NAME")
|
|
||||||
tokenID := os.Getenv("OS_TOKEN")
|
|
||||||
|
|
||||||
if authURL == "" {
|
|
||||||
return nilOptions, ErrNoAuthURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if username == "" && userID == "" && tokenID == "" {
|
|
||||||
return nilOptions, ErrNoUsername
|
|
||||||
}
|
|
||||||
|
|
||||||
if password == "" && tokenID == "" {
|
|
||||||
return nilOptions, ErrNoPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
ao := gophercloud.AuthOptions{
|
|
||||||
IdentityEndpoint: authURL,
|
|
||||||
UserID: userID,
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
TenantID: tenantID,
|
|
||||||
TenantName: tenantName,
|
|
||||||
DomainID: domainID,
|
|
||||||
DomainName: domainName,
|
|
||||||
TokenID: tokenID,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ao, nil
|
|
||||||
}
|
|
5
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/doc.go
generated
vendored
5
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/doc.go
generated
vendored
|
@ -1,5 +0,0 @@
|
||||||
// Package volumes provides information and interaction with volumes in the
|
|
||||||
// OpenStack Block Storage service. A volume is a detachable block storage
|
|
||||||
// device, akin to a USB hard drive. It can only be attached to one instance at
|
|
||||||
// a time.
|
|
||||||
package volumes
|
|
236
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go
generated
vendored
236
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/requests.go
generated
vendored
|
@ -1,236 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Create request.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToVolumeCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts contains options for creating a Volume. This object is passed to
|
|
||||||
// the volumes.Create function. For more information about these parameters,
|
|
||||||
// see the Volume object.
|
|
||||||
type CreateOpts struct {
|
|
||||||
// OPTIONAL
|
|
||||||
Availability string
|
|
||||||
// OPTIONAL
|
|
||||||
Description string
|
|
||||||
// OPTIONAL
|
|
||||||
Metadata map[string]string
|
|
||||||
// OPTIONAL
|
|
||||||
Name string
|
|
||||||
// REQUIRED
|
|
||||||
Size int
|
|
||||||
// OPTIONAL
|
|
||||||
SnapshotID, SourceVolID, ImageID string
|
|
||||||
// OPTIONAL
|
|
||||||
VolumeType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeCreateMap assembles a request body based on the contents of a
|
|
||||||
// CreateOpts.
|
|
||||||
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
|
|
||||||
v := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Size == 0 {
|
|
||||||
return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
|
|
||||||
}
|
|
||||||
v["size"] = opts.Size
|
|
||||||
|
|
||||||
if opts.Availability != "" {
|
|
||||||
v["availability_zone"] = opts.Availability
|
|
||||||
}
|
|
||||||
if opts.Description != "" {
|
|
||||||
v["display_description"] = opts.Description
|
|
||||||
}
|
|
||||||
if opts.ImageID != "" {
|
|
||||||
v["imageRef"] = opts.ImageID
|
|
||||||
}
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
v["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
if opts.Name != "" {
|
|
||||||
v["display_name"] = opts.Name
|
|
||||||
}
|
|
||||||
if opts.SourceVolID != "" {
|
|
||||||
v["source_volid"] = opts.SourceVolID
|
|
||||||
}
|
|
||||||
if opts.SnapshotID != "" {
|
|
||||||
v["snapshot_id"] = opts.SnapshotID
|
|
||||||
}
|
|
||||||
if opts.VolumeType != "" {
|
|
||||||
v["volume_type"] = opts.VolumeType
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"volume": v}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create will create a new Volume based on the values in CreateOpts. To extract
|
|
||||||
// the Volume object from the response, call the Extract method on the
|
|
||||||
// CreateResult.
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToVolumeCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 201},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete will delete the existing Volume with the provided ID.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
|
||||||
// from the response, call the Extract method on the GetResult.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the List
|
|
||||||
// request.
|
|
||||||
type ListOptsBuilder interface {
|
|
||||||
ToVolumeListQuery() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
|
|
||||||
// function.
|
|
||||||
type ListOpts struct {
|
|
||||||
// admin-only option. Set it to true to see all tenant volumes.
|
|
||||||
AllTenants bool `q:"all_tenants"`
|
|
||||||
// List only volumes that contain Metadata.
|
|
||||||
Metadata map[string]string `q:"metadata"`
|
|
||||||
// List only volumes that have Name as the display name.
|
|
||||||
Name string `q:"name"`
|
|
||||||
// List only volumes that have a status of Status.
|
|
||||||
Status string `q:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeListQuery formats a ListOpts into a query string.
|
|
||||||
func (opts ListOpts) ToVolumeListQuery() (string, error) {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns Volumes optionally limited by the conditions provided in ListOpts.
|
|
||||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|
||||||
url := listURL(client)
|
|
||||||
if opts != nil {
|
|
||||||
query, err := opts.ToVolumeListQuery()
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += query
|
|
||||||
}
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return ListResult{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Update request.
|
|
||||||
type UpdateOptsBuilder interface {
|
|
||||||
ToVolumeUpdateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOpts contain options for updating an existing Volume. This object is passed
|
|
||||||
// to the volumes.Update function. For more information about the parameters, see
|
|
||||||
// the Volume object.
|
|
||||||
type UpdateOpts struct {
|
|
||||||
// OPTIONAL
|
|
||||||
Name string
|
|
||||||
// OPTIONAL
|
|
||||||
Description string
|
|
||||||
// OPTIONAL
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeUpdateMap assembles a request body based on the contents of an
|
|
||||||
// UpdateOpts.
|
|
||||||
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
|
|
||||||
v := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Description != "" {
|
|
||||||
v["display_description"] = opts.Description
|
|
||||||
}
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
v["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
if opts.Name != "" {
|
|
||||||
v["display_name"] = opts.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"volume": v}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update will update the Volume with provided information. To extract the updated
|
|
||||||
// Volume from the response, call the Extract method on the UpdateResult.
|
|
||||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
|
|
||||||
var res UpdateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToVolumeUpdateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Put(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
|
||||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
|
||||||
volumeCount := 0
|
|
||||||
volumeID := ""
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("A volume name must be provided.")
|
|
||||||
}
|
|
||||||
pager := List(client, nil)
|
|
||||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
volumeList, err := ExtractVolumes(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range volumeList {
|
|
||||||
if s.Name == name {
|
|
||||||
volumeCount++
|
|
||||||
volumeID = s.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch volumeCount {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("Unable to find volume: %s", name)
|
|
||||||
case 1:
|
|
||||||
return volumeID, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Found %d volumes matching %s", volumeCount, name)
|
|
||||||
}
|
|
||||||
}
|
|
113
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go
generated
vendored
113
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/results.go
generated
vendored
|
@ -1,113 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Volume contains all the information associated with an OpenStack Volume.
|
|
||||||
type Volume struct {
|
|
||||||
// Current status of the volume.
|
|
||||||
Status string `mapstructure:"status"`
|
|
||||||
|
|
||||||
// Human-readable display name for the volume.
|
|
||||||
Name string `mapstructure:"display_name"`
|
|
||||||
|
|
||||||
// Instances onto which the volume is attached.
|
|
||||||
Attachments []map[string]interface{} `mapstructure:"attachments"`
|
|
||||||
|
|
||||||
// This parameter is no longer used.
|
|
||||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
|
||||||
|
|
||||||
// Indicates whether this is a bootable volume.
|
|
||||||
Bootable string `mapstructure:"bootable"`
|
|
||||||
|
|
||||||
// The date when this volume was created.
|
|
||||||
CreatedAt string `mapstructure:"created_at"`
|
|
||||||
|
|
||||||
// Human-readable description for the volume.
|
|
||||||
Description string `mapstructure:"display_description"`
|
|
||||||
|
|
||||||
// The type of volume to create, either SATA or SSD.
|
|
||||||
VolumeType string `mapstructure:"volume_type"`
|
|
||||||
|
|
||||||
// The ID of the snapshot from which the volume was created
|
|
||||||
SnapshotID string `mapstructure:"snapshot_id"`
|
|
||||||
|
|
||||||
// The ID of another block storage volume from which the current volume was created
|
|
||||||
SourceVolID string `mapstructure:"source_volid"`
|
|
||||||
|
|
||||||
// Arbitrary key-value pairs defined by the user.
|
|
||||||
Metadata map[string]string `mapstructure:"metadata"`
|
|
||||||
|
|
||||||
// Unique identifier for the volume.
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Size of the volume in GB.
|
|
||||||
Size int `mapstructure:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult contains the response body and error from a Create request.
|
|
||||||
type CreateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult contains the response body and error from a Get request.
|
|
||||||
type GetResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult contains the response body and error from a Delete request.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListResult is a pagination.pager that is returned from a call to the List function.
|
|
||||||
type ListResult struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if a ListResult contains no Volumes.
|
|
||||||
func (r ListResult) IsEmpty() (bool, error) {
|
|
||||||
volumes, err := ExtractVolumes(r)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(volumes) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
|
|
||||||
func ExtractVolumes(page pagination.Page) ([]Volume, error) {
|
|
||||||
var response struct {
|
|
||||||
Volumes []Volume `json:"volumes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(page.(ListResult).Body, &response)
|
|
||||||
return response.Volumes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateResult contains the response body and error from an Update request.
|
|
||||||
type UpdateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
type commonResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract will get the Volume object out of the commonResult object.
|
|
||||||
func (r commonResult) Extract() (*Volume, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
Volume *Volume `json:"volume"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &res)
|
|
||||||
|
|
||||||
return res.Volume, err
|
|
||||||
}
|
|
23
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/urls.go
generated
vendored
23
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/urls.go
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL("volumes")
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return createURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL("volumes", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(c, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(c, id)
|
|
||||||
}
|
|
22
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/util.go
generated
vendored
22
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes/util.go
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WaitForStatus will continually poll the resource, checking for a particular
|
|
||||||
// status. It will do this for the amount of seconds defined.
|
|
||||||
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
|
||||||
return gophercloud.WaitFor(secs, func() (bool, error) {
|
|
||||||
current, err := Get(c, id).Extract()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if current.Status == status {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
}
|
|
5
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/doc.go
generated
vendored
5
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/doc.go
generated
vendored
|
@ -1,5 +0,0 @@
|
||||||
// Package volumes provides information and interaction with volumes in the
|
|
||||||
// OpenStack Block Storage service. A volume is a detachable block storage
|
|
||||||
// device, akin to a USB hard drive. It can only be attached to one instance at
|
|
||||||
// a time.
|
|
||||||
package volumes
|
|
204
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/fixtures.go
generated
vendored
204
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/fixtures.go
generated
vendored
|
@ -1,204 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
fake "github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MockListResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/volumes/detail", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"volumes": [
|
|
||||||
{
|
|
||||||
"volume_type": "lvmdriver-1",
|
|
||||||
"created_at": "2015-09-17T03:35:03.000000",
|
|
||||||
"bootable": "false",
|
|
||||||
"name": "vol-001",
|
|
||||||
"os-vol-mig-status-attr:name_id": null,
|
|
||||||
"consistencygroup_id": null,
|
|
||||||
"source_volid": null,
|
|
||||||
"os-volume-replication:driver_data": null,
|
|
||||||
"multiattach": false,
|
|
||||||
"snapshot_id": null,
|
|
||||||
"replication_status": "disabled",
|
|
||||||
"os-volume-replication:extended_status": null,
|
|
||||||
"encrypted": false,
|
|
||||||
"os-vol-host-attr:host": null,
|
|
||||||
"availability_zone": "nova",
|
|
||||||
"attachments": [
|
|
||||||
{
|
|
||||||
"attachment_id": "03987cd1-0ad5-40d1-9b2a-7cc48295d4fa",
|
|
||||||
"id": "47e9ecc5-4045-4ee3-9a4b-d859d546a0cf",
|
|
||||||
"volume_id": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
|
||||||
"server_id": "d1c4788b-9435-42e2-9b81-29f3be1cd01f",
|
|
||||||
"host_name": "stack",
|
|
||||||
"device": "/dev/vdc"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"id": "289da7f8-6440-407c-9fb4-7db01ec49164",
|
|
||||||
"size": 75,
|
|
||||||
"user_id": "ff1ce52c03ab433aaba9108c2e3ef541",
|
|
||||||
"os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459",
|
|
||||||
"os-vol-mig-status-attr:migstat": null,
|
|
||||||
"metadata": {"foo": "bar"},
|
|
||||||
"status": "available",
|
|
||||||
"description": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"volume_type": "lvmdriver-1",
|
|
||||||
"created_at": "2015-09-17T03:32:29.000000",
|
|
||||||
"bootable": "false",
|
|
||||||
"name": "vol-002",
|
|
||||||
"os-vol-mig-status-attr:name_id": null,
|
|
||||||
"consistencygroup_id": null,
|
|
||||||
"source_volid": null,
|
|
||||||
"os-volume-replication:driver_data": null,
|
|
||||||
"multiattach": false,
|
|
||||||
"snapshot_id": null,
|
|
||||||
"replication_status": "disabled",
|
|
||||||
"os-volume-replication:extended_status": null,
|
|
||||||
"encrypted": false,
|
|
||||||
"os-vol-host-attr:host": null,
|
|
||||||
"availability_zone": "nova",
|
|
||||||
"attachments": [],
|
|
||||||
"id": "96c3bda7-c82a-4f50-be73-ca7621794835",
|
|
||||||
"size": 75,
|
|
||||||
"user_id": "ff1ce52c03ab433aaba9108c2e3ef541",
|
|
||||||
"os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459",
|
|
||||||
"os-vol-mig-status-attr:migstat": null,
|
|
||||||
"metadata": {},
|
|
||||||
"status": "available",
|
|
||||||
"description": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MockGetResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"volume": {
|
|
||||||
"volume_type": "lvmdriver-1",
|
|
||||||
"created_at": "2015-09-17T03:32:29.000000",
|
|
||||||
"bootable": "false",
|
|
||||||
"name": "vol-001",
|
|
||||||
"os-vol-mig-status-attr:name_id": null,
|
|
||||||
"consistencygroup_id": null,
|
|
||||||
"source_volid": null,
|
|
||||||
"os-volume-replication:driver_data": null,
|
|
||||||
"multiattach": false,
|
|
||||||
"snapshot_id": null,
|
|
||||||
"replication_status": "disabled",
|
|
||||||
"os-volume-replication:extended_status": null,
|
|
||||||
"encrypted": false,
|
|
||||||
"os-vol-host-attr:host": null,
|
|
||||||
"availability_zone": "nova",
|
|
||||||
"attachments": [{
|
|
||||||
"attachment_id": "dbce64e3-f3b9-4423-a44f-a2b15deffa1b",
|
|
||||||
"id": "3eafc6f5-ed74-456d-90fb-f253f594dbae",
|
|
||||||
"volume_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
|
|
||||||
"server_id": "d1c4788b-9435-42e2-9b81-29f3be1cd01f",
|
|
||||||
"host_name": "stack",
|
|
||||||
"device": "/dev/vdd"
|
|
||||||
}],
|
|
||||||
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
|
|
||||||
"size": 75,
|
|
||||||
"user_id": "ff1ce52c03ab433aaba9108c2e3ef541",
|
|
||||||
"os-vol-tenant-attr:tenant_id": "304dc00909ac4d0da6c62d816bcb3459",
|
|
||||||
"os-vol-mig-status-attr:migstat": null,
|
|
||||||
"metadata": {},
|
|
||||||
"status": "available",
|
|
||||||
"description": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MockCreateResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/volumes", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
th.TestHeader(t, r, "Content-Type", "application/json")
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"volume": {
|
|
||||||
"name": "vol-001",
|
|
||||||
"size": 75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"volume": {
|
|
||||||
"size": 75,
|
|
||||||
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
|
|
||||||
"metadata": {},
|
|
||||||
"created_at": "2015-09-17T03:32:29.044216",
|
|
||||||
"encrypted": false,
|
|
||||||
"bootable": "false",
|
|
||||||
"availability_zone": "nova",
|
|
||||||
"attachments": [],
|
|
||||||
"user_id": "ff1ce52c03ab433aaba9108c2e3ef541",
|
|
||||||
"status": "creating",
|
|
||||||
"description": null,
|
|
||||||
"volume_type": "lvmdriver-1",
|
|
||||||
"name": "vol-001",
|
|
||||||
"replication_status": "disabled",
|
|
||||||
"consistencygroup_id": null,
|
|
||||||
"source_volid": null,
|
|
||||||
"snapshot_id": null,
|
|
||||||
"multiattach": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MockDeleteResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func MockUpdateResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/volumes/d32019d3-bc6e-4319-9c1d-6722fc136a22", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "PUT")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"volume": {
|
|
||||||
"name": "vol-002"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
251
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/requests.go
generated
vendored
251
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/requests.go
generated
vendored
|
@ -1,251 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Create request.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToVolumeCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts contains options for creating a Volume. This object is passed to
|
|
||||||
// the volumes.Create function. For more information about these parameters,
|
|
||||||
// see the Volume object.
|
|
||||||
type CreateOpts struct {
|
|
||||||
// The availability zone [OPTIONAL]
|
|
||||||
AvailabilityZone string
|
|
||||||
// ConsistencyGroupID is the ID of a consistency group [OPTINAL]
|
|
||||||
ConsistencyGroupID string
|
|
||||||
// The volume description [OPTIONAL]
|
|
||||||
Description string
|
|
||||||
// One or more metadata key and value pairs to associate with the volume [OPTIONAL]
|
|
||||||
Metadata map[string]string
|
|
||||||
// The volume name [OPTIONAL]
|
|
||||||
Name string
|
|
||||||
// The size of the volume, in gibibytes (GiB) [REQUIRED]
|
|
||||||
Size int
|
|
||||||
// the ID of the existing volume snapshot [OPTIONAL]
|
|
||||||
SnapshotID string
|
|
||||||
// SourceReplica is a UUID of an existing volume to replicate with [OPTIONAL]
|
|
||||||
SourceReplica string
|
|
||||||
// the ID of the existing volume [OPTIONAL]
|
|
||||||
SourceVolID string
|
|
||||||
// The ID of the image from which you want to create the volume.
|
|
||||||
// Required to create a bootable volume.
|
|
||||||
ImageID string
|
|
||||||
// The associated volume type [OPTIONAL]
|
|
||||||
VolumeType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeCreateMap assembles a request body based on the contents of a
|
|
||||||
// CreateOpts.
|
|
||||||
func (opts CreateOpts) ToVolumeCreateMap() (map[string]interface{}, error) {
|
|
||||||
v := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Size == 0 {
|
|
||||||
return nil, fmt.Errorf("Required CreateOpts field 'Size' not set.")
|
|
||||||
}
|
|
||||||
v["size"] = opts.Size
|
|
||||||
|
|
||||||
if opts.AvailabilityZone != "" {
|
|
||||||
v["availability_zone"] = opts.AvailabilityZone
|
|
||||||
}
|
|
||||||
if opts.ConsistencyGroupID != "" {
|
|
||||||
v["consistencygroup_id"] = opts.ConsistencyGroupID
|
|
||||||
}
|
|
||||||
if opts.Description != "" {
|
|
||||||
v["description"] = opts.Description
|
|
||||||
}
|
|
||||||
if opts.ImageID != "" {
|
|
||||||
v["imageRef"] = opts.ImageID
|
|
||||||
}
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
v["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
if opts.Name != "" {
|
|
||||||
v["name"] = opts.Name
|
|
||||||
}
|
|
||||||
if opts.SourceReplica != "" {
|
|
||||||
v["source_replica"] = opts.SourceReplica
|
|
||||||
}
|
|
||||||
if opts.SourceVolID != "" {
|
|
||||||
v["source_volid"] = opts.SourceVolID
|
|
||||||
}
|
|
||||||
if opts.SnapshotID != "" {
|
|
||||||
v["snapshot_id"] = opts.SnapshotID
|
|
||||||
}
|
|
||||||
if opts.VolumeType != "" {
|
|
||||||
v["volume_type"] = opts.VolumeType
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"volume": v}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create will create a new Volume based on the values in CreateOpts. To extract
|
|
||||||
// the Volume object from the response, call the Extract method on the
|
|
||||||
// CreateResult.
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToVolumeCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{202},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete will delete the existing Volume with the provided ID.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get retrieves the Volume with the provided ID. To extract the Volume object
|
|
||||||
// from the response, call the Extract method on the GetResult.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the List
|
|
||||||
// request.
|
|
||||||
type ListOptsBuilder interface {
|
|
||||||
ToVolumeListQuery() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOpts holds options for listing Volumes. It is passed to the volumes.List
|
|
||||||
// function.
|
|
||||||
type ListOpts struct {
|
|
||||||
// admin-only option. Set it to true to see all tenant volumes.
|
|
||||||
AllTenants bool `q:"all_tenants"`
|
|
||||||
// List only volumes that contain Metadata.
|
|
||||||
Metadata map[string]string `q:"metadata"`
|
|
||||||
// List only volumes that have Name as the display name.
|
|
||||||
Name string `q:"name"`
|
|
||||||
// List only volumes that have a status of Status.
|
|
||||||
Status string `q:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeListQuery formats a ListOpts into a query string.
|
|
||||||
func (opts ListOpts) ToVolumeListQuery() (string, error) {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns Volumes optionally limited by the conditions provided in ListOpts.
|
|
||||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|
||||||
url := listURL(client)
|
|
||||||
if opts != nil {
|
|
||||||
query, err := opts.ToVolumeListQuery()
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += query
|
|
||||||
}
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return ListResult{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Update request.
|
|
||||||
type UpdateOptsBuilder interface {
|
|
||||||
ToVolumeUpdateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOpts contain options for updating an existing Volume. This object is passed
|
|
||||||
// to the volumes.Update function. For more information about the parameters, see
|
|
||||||
// the Volume object.
|
|
||||||
type UpdateOpts struct {
|
|
||||||
// OPTIONAL
|
|
||||||
Name string
|
|
||||||
// OPTIONAL
|
|
||||||
Description string
|
|
||||||
// OPTIONAL
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeUpdateMap assembles a request body based on the contents of an
|
|
||||||
// UpdateOpts.
|
|
||||||
func (opts UpdateOpts) ToVolumeUpdateMap() (map[string]interface{}, error) {
|
|
||||||
v := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Description != "" {
|
|
||||||
v["description"] = opts.Description
|
|
||||||
}
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
v["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
if opts.Name != "" {
|
|
||||||
v["name"] = opts.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"volume": v}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update will update the Volume with provided information. To extract the updated
|
|
||||||
// Volume from the response, call the Extract method on the UpdateResult.
|
|
||||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
|
|
||||||
var res UpdateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToVolumeUpdateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Put(updateURL(client, id), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
|
||||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
|
||||||
volumeCount := 0
|
|
||||||
volumeID := ""
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("A volume name must be provided.")
|
|
||||||
}
|
|
||||||
pager := List(client, nil)
|
|
||||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
volumeList, err := ExtractVolumes(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range volumeList {
|
|
||||||
if s.Name == name {
|
|
||||||
volumeCount++
|
|
||||||
volumeID = s.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch volumeCount {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("Unable to find volume: %s", name)
|
|
||||||
case 1:
|
|
||||||
return volumeID, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Found %d volumes matching %s", volumeCount, name)
|
|
||||||
}
|
|
||||||
}
|
|
137
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/results.go
generated
vendored
137
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/results.go
generated
vendored
|
@ -1,137 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Volume contains all the information associated with an OpenStack Volume.
|
|
||||||
type Volume struct {
|
|
||||||
// Instances onto which the volume is attached.
|
|
||||||
Attachments []map[string]interface{} `mapstructure:"attachments"`
|
|
||||||
|
|
||||||
// AvailabilityZone is which availability zone the volume is in.
|
|
||||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
|
||||||
|
|
||||||
// Indicates whether this is a bootable volume.
|
|
||||||
Bootable string `mapstructure:"bootable"`
|
|
||||||
|
|
||||||
// ConsistencyGroupID is the consistency group ID.
|
|
||||||
ConsistencyGroupID string `mapstructure:"consistencygroup_id"`
|
|
||||||
|
|
||||||
// The date when this volume was created.
|
|
||||||
CreatedAt string `mapstructure:"created_at"`
|
|
||||||
|
|
||||||
// Human-readable description for the volume.
|
|
||||||
Description string `mapstructure:"description"`
|
|
||||||
|
|
||||||
// Encrypted denotes if the volume is encrypted.
|
|
||||||
Encrypted bool `mapstructure:"encrypted"`
|
|
||||||
|
|
||||||
// Human-readable display name for the volume.
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
|
|
||||||
// The type of volume to create, either SATA or SSD.
|
|
||||||
VolumeType string `mapstructure:"volume_type"`
|
|
||||||
|
|
||||||
// ReplicationDriverData contains data about the replication driver.
|
|
||||||
ReplicationDriverData string `mapstructure:"os-volume-replication:driver_data"`
|
|
||||||
|
|
||||||
// ReplicationExtendedStatus contains extended status about replication.
|
|
||||||
ReplicationExtendedStatus string `mapstructure:"os-volume-replication:extended_status"`
|
|
||||||
|
|
||||||
// ReplicationStatus is the status of replication.
|
|
||||||
ReplicationStatus string `mapstructure:"replication_status"`
|
|
||||||
|
|
||||||
// The ID of the snapshot from which the volume was created
|
|
||||||
SnapshotID string `mapstructure:"snapshot_id"`
|
|
||||||
|
|
||||||
// The ID of another block storage volume from which the current volume was created
|
|
||||||
SourceVolID string `mapstructure:"source_volid"`
|
|
||||||
|
|
||||||
// Current status of the volume.
|
|
||||||
Status string `mapstructure:"status"`
|
|
||||||
|
|
||||||
// TenantID is the id of the project that owns the volume.
|
|
||||||
TenantID string `mapstructure:"os-vol-tenant-attr:tenant_id"`
|
|
||||||
|
|
||||||
// Arbitrary key-value pairs defined by the user.
|
|
||||||
Metadata map[string]string `mapstructure:"metadata"`
|
|
||||||
|
|
||||||
// Multiattach denotes if the volume is multi-attach capable.
|
|
||||||
Multiattach bool `mapstructure:"multiattach"`
|
|
||||||
|
|
||||||
// Unique identifier for the volume.
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Size of the volume in GB.
|
|
||||||
Size int `mapstructure:"size"`
|
|
||||||
|
|
||||||
// UserID is the id of the user who created the volume.
|
|
||||||
UserID string `mapstructure:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult contains the response body and error from a Create request.
|
|
||||||
type CreateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult contains the response body and error from a Get request.
|
|
||||||
type GetResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult contains the response body and error from a Delete request.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListResult is a pagination.pager that is returned from a call to the List function.
|
|
||||||
type ListResult struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if a ListResult contains no Volumes.
|
|
||||||
func (r ListResult) IsEmpty() (bool, error) {
|
|
||||||
volumes, err := ExtractVolumes(r)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(volumes) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractVolumes extracts and returns Volumes. It is used while iterating over a volumes.List call.
|
|
||||||
func ExtractVolumes(page pagination.Page) ([]Volume, error) {
|
|
||||||
var response struct {
|
|
||||||
Volumes []Volume `json:"volumes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(page.(ListResult).Body, &response)
|
|
||||||
return response.Volumes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateResult contains the response body and error from an Update request.
|
|
||||||
type UpdateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
type commonResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract will get the Volume object out of the commonResult object.
|
|
||||||
func (r commonResult) Extract() (*Volume, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
Volume *Volume `json:"volume"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &res)
|
|
||||||
|
|
||||||
return res.Volume, err
|
|
||||||
}
|
|
23
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/urls.go
generated
vendored
23
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/urls.go
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL("volumes")
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL("volumes", "detail")
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL("volumes", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(c, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(c, id)
|
|
||||||
}
|
|
22
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/util.go
generated
vendored
22
vendor/github.com/rackspace/gophercloud/openstack/blockstorage/v2/volumes/util.go
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
package volumes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WaitForStatus will continually poll the resource, checking for a particular
|
|
||||||
// status. It will do this for the amount of seconds defined.
|
|
||||||
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
|
||||||
return gophercloud.WaitFor(secs, func() (bool, error) {
|
|
||||||
current, err := Get(c, id).Extract()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if current.Status == status {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,346 +0,0 @@
|
||||||
package openstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
|
|
||||||
tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
v20 = "v2.0"
|
|
||||||
v30 = "v3.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
u.RawQuery, u.Fragment = "", ""
|
|
||||||
|
|
||||||
// Base is url with path
|
|
||||||
endpoint = gophercloud.NormalizeURL(endpoint)
|
|
||||||
base := gophercloud.NormalizeURL(u.String())
|
|
||||||
|
|
||||||
path := u.Path
|
|
||||||
if !strings.HasSuffix(path, "/") {
|
|
||||||
path = path + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := strings.Split(path[0:len(path)-1], "/")
|
|
||||||
for index,version := range(parts) {
|
|
||||||
if 2 <= len(version) && len(version) <= 4 && strings.HasPrefix(version, "v") {
|
|
||||||
_, err := strconv.ParseFloat(version[1:], 64)
|
|
||||||
if err == nil {
|
|
||||||
// post version suffixes in path are not supported
|
|
||||||
// version must be on the last index
|
|
||||||
if index < len(parts) - 1 {
|
|
||||||
return nil, fmt.Errorf("Path suffixes (after version) are not supported.")
|
|
||||||
}
|
|
||||||
switch version {
|
|
||||||
case "v2.0", "v3":
|
|
||||||
// valid version found, strip from base
|
|
||||||
return &gophercloud.ProviderClient{
|
|
||||||
IdentityBase: base[0:len(base)-len(version)-1],
|
|
||||||
IdentityEndpoint: endpoint,
|
|
||||||
}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Invalid identity endpoint version %v. Supported versions: v2.0, v3", version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gophercloud.ProviderClient{
|
|
||||||
IdentityBase: base,
|
|
||||||
IdentityEndpoint: "",
|
|
||||||
}, 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.
|
|
||||||
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
|
||||||
client, err := NewClient(options.IdentityEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Authenticate(client, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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/"},
|
|
||||||
}
|
|
||||||
|
|
||||||
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch chosen.ID {
|
|
||||||
case v20:
|
|
||||||
return v2auth(client, endpoint, options)
|
|
||||||
case v30:
|
|
||||||
return v3auth(client, endpoint, options)
|
|
||||||
default:
|
|
||||||
// The switch statement must be out of date from the versions list.
|
|
||||||
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
|
|
||||||
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
|
||||||
return v2auth(client, "", options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
|
|
||||||
v2Client := NewIdentityV2(client)
|
|
||||||
if endpoint != "" {
|
|
||||||
v2Client.Endpoint = endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
result := tokens2.Create(v2Client, tokens2.AuthOptions{AuthOptions: options})
|
|
||||||
|
|
||||||
token, err := result.ExtractToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
catalog, err := result.ExtractServiceCatalog()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.AllowReauth {
|
|
||||||
client.ReauthFunc = func() error {
|
|
||||||
client.TokenID = ""
|
|
||||||
return v2auth(client, endpoint, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.TokenID = token.ID
|
|
||||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
|
||||||
return V2EndpointURL(catalog, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticateV3 explicitly authenticates against the identity v3 service.
|
|
||||||
func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
|
||||||
return v3auth(client, "", options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
|
|
||||||
// Override the generated service endpoint with the one returned by the version endpoint.
|
|
||||||
v3Client := NewIdentityV3(client)
|
|
||||||
if endpoint != "" {
|
|
||||||
v3Client.Endpoint = endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the auth options to a local variable that we can change. `options`
|
|
||||||
// needs to stay as-is for reauth purposes
|
|
||||||
v3Options := options
|
|
||||||
|
|
||||||
var scope *tokens3.Scope
|
|
||||||
if options.TenantID != "" {
|
|
||||||
scope = &tokens3.Scope{
|
|
||||||
ProjectID: options.TenantID,
|
|
||||||
}
|
|
||||||
v3Options.TenantID = ""
|
|
||||||
v3Options.TenantName = ""
|
|
||||||
} else {
|
|
||||||
if options.TenantName != "" {
|
|
||||||
scope = &tokens3.Scope{
|
|
||||||
ProjectName: options.TenantName,
|
|
||||||
DomainID: options.DomainID,
|
|
||||||
DomainName: options.DomainName,
|
|
||||||
}
|
|
||||||
v3Options.TenantName = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := tokens3.Create(v3Client, v3Options, scope)
|
|
||||||
|
|
||||||
token, err := result.ExtractToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
catalog, err := result.ExtractServiceCatalog()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client.TokenID = token.ID
|
|
||||||
|
|
||||||
if options.AllowReauth {
|
|
||||||
client.ReauthFunc = func() error {
|
|
||||||
client.TokenID = ""
|
|
||||||
return v3auth(client, endpoint, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
|
||||||
return V3EndpointURL(catalog, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
|
|
||||||
func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
|
|
||||||
v2Endpoint := client.IdentityBase + "v2.0/"
|
|
||||||
|
|
||||||
return &gophercloud.ServiceClient{
|
|
||||||
ProviderClient: client,
|
|
||||||
Endpoint: v2Endpoint,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
|
|
||||||
func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
|
|
||||||
v3Endpoint := client.IdentityBase + "v3/"
|
|
||||||
|
|
||||||
return &gophercloud.ServiceClient{
|
|
||||||
ProviderClient: client,
|
|
||||||
Endpoint: v3Endpoint,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIdentityAdminV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("identity")
|
|
||||||
eo.Availability = gophercloud.AvailabilityAdmin
|
|
||||||
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force using v2 API
|
|
||||||
if strings.Contains(url, "/v3") {
|
|
||||||
url = strings.Replace(url, "/v3", "/v2.0", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIdentityAdminV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("identity")
|
|
||||||
eo.Availability = gophercloud.AvailabilityAdmin
|
|
||||||
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force using v3 API
|
|
||||||
if strings.Contains(url, "/v2.0") {
|
|
||||||
url = strings.Replace(url, "/v2.0", "/v3", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
eo.ApplyDefaults("object-store")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
|
|
||||||
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("compute")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
|
|
||||||
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("network")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{
|
|
||||||
ProviderClient: client,
|
|
||||||
Endpoint: url,
|
|
||||||
ResourceBase: url + "v2.0/",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
eo.ApplyDefaults("volume")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
eo.ApplyDefaults("volumev2")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
|
|
||||||
// CDN service.
|
|
||||||
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("cdn")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
eo.ApplyDefaults("orchestration")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
|
|
||||||
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
|
||||||
eo.ApplyDefaults("database")
|
|
||||||
url, err := client.EndpointLocator(eo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
package bootfromvolume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SourceType represents the type of medium being used to create the volume.
|
|
||||||
type SourceType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
Volume SourceType = "volume"
|
|
||||||
Snapshot SourceType = "snapshot"
|
|
||||||
Image SourceType = "image"
|
|
||||||
Blank SourceType = "blank"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BlockDevice is a structure with options for booting a server instance
|
|
||||||
// from a volume. The volume may be created from an image, snapshot, or another
|
|
||||||
// volume.
|
|
||||||
type BlockDevice struct {
|
|
||||||
// BootIndex [optional] is the boot index. It defaults to 0.
|
|
||||||
BootIndex int `json:"boot_index"`
|
|
||||||
|
|
||||||
// DeleteOnTermination [optional] specifies whether or not to delete the attached volume
|
|
||||||
// when the server is deleted. Defaults to `false`.
|
|
||||||
DeleteOnTermination bool `json:"delete_on_termination"`
|
|
||||||
|
|
||||||
// DestinationType [optional] is the type that gets created. Possible values are "volume"
|
|
||||||
// and "local".
|
|
||||||
DestinationType string `json:"destination_type"`
|
|
||||||
|
|
||||||
// GuestFormat [optional] specifies the format of the block device.
|
|
||||||
GuestFormat string `json:"guest_format"`
|
|
||||||
|
|
||||||
// SourceType [required] must be one of: "volume", "snapshot", "image".
|
|
||||||
SourceType SourceType `json:"source_type"`
|
|
||||||
|
|
||||||
// UUID [required] is the unique identifier for the volume, snapshot, or image (see above)
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
|
|
||||||
// VolumeSize [optional] is the size of the volume to create (in gigabytes).
|
|
||||||
VolumeSize int `json:"volume_size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsExt is a structure that extends the server `CreateOpts` structure
|
|
||||||
// by allowing for a block device mapping.
|
|
||||||
type CreateOptsExt struct {
|
|
||||||
servers.CreateOptsBuilder
|
|
||||||
BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerCreateMap adds the block device mapping option to the base server
|
|
||||||
// creation options.
|
|
||||||
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
|
|
||||||
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.BlockDevice) == 0 {
|
|
||||||
return nil, errors.New("Required fields UUID and SourceType not set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
serverMap := base["server"].(map[string]interface{})
|
|
||||||
|
|
||||||
blockDevice := make([]map[string]interface{}, len(opts.BlockDevice))
|
|
||||||
|
|
||||||
for i, bd := range opts.BlockDevice {
|
|
||||||
if string(bd.SourceType) == "" {
|
|
||||||
return nil, errors.New("SourceType must be one of: volume, image, snapshot.")
|
|
||||||
}
|
|
||||||
|
|
||||||
blockDevice[i] = make(map[string]interface{})
|
|
||||||
|
|
||||||
blockDevice[i]["source_type"] = bd.SourceType
|
|
||||||
blockDevice[i]["boot_index"] = strconv.Itoa(bd.BootIndex)
|
|
||||||
blockDevice[i]["delete_on_termination"] = strconv.FormatBool(bd.DeleteOnTermination)
|
|
||||||
if bd.VolumeSize > 0 {
|
|
||||||
blockDevice[i]["volume_size"] = strconv.Itoa(bd.VolumeSize)
|
|
||||||
}
|
|
||||||
if bd.UUID != "" {
|
|
||||||
blockDevice[i]["uuid"] = bd.UUID
|
|
||||||
}
|
|
||||||
if bd.DestinationType != "" {
|
|
||||||
blockDevice[i]["destination_type"] = bd.DestinationType
|
|
||||||
}
|
|
||||||
if bd.GuestFormat != "" {
|
|
||||||
blockDevice[i]["guest_format"] = bd.GuestFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
serverMap["block_device_mapping_v2"] = blockDevice
|
|
||||||
|
|
||||||
return base, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create requests the creation of a server from the given block device mapping.
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) servers.CreateResult {
|
|
||||||
var res servers.CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToServerCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete imageName and flavorName that come from ToServerCreateMap().
|
|
||||||
// As of Liberty, Boot From Volume is failing if they are passed.
|
|
||||||
delete(reqBody["server"].(map[string]interface{}), "imageName")
|
|
||||||
delete(reqBody["server"].(map[string]interface{}), "flavorName")
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 202},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package bootfromvolume
|
|
||||||
|
|
||||||
import (
|
|
||||||
os "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateResult temporarily contains the response from a Create call.
|
|
||||||
type CreateResult struct {
|
|
||||||
os.CreateResult
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package bootfromvolume
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL("os-volumes_boot")
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package floatingip provides the ability to manage floating ips through
|
|
||||||
// nova-network
|
|
||||||
package floatingip
|
|
|
@ -1,193 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package floatingip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOutput is a sample response to a List call.
|
|
||||||
const ListOutput = `
|
|
||||||
{
|
|
||||||
"floating_ips": [
|
|
||||||
{
|
|
||||||
"fixed_ip": null,
|
|
||||||
"id": 1,
|
|
||||||
"instance_id": null,
|
|
||||||
"ip": "10.10.10.1",
|
|
||||||
"pool": "nova"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fixed_ip": "166.78.185.201",
|
|
||||||
"id": 2,
|
|
||||||
"instance_id": "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
|
||||||
"ip": "10.10.10.2",
|
|
||||||
"pool": "nova"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// GetOutput is a sample response to a Get call.
|
|
||||||
const GetOutput = `
|
|
||||||
{
|
|
||||||
"floating_ip": {
|
|
||||||
"fixed_ip": "166.78.185.201",
|
|
||||||
"id": 2,
|
|
||||||
"instance_id": "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
|
||||||
"ip": "10.10.10.2",
|
|
||||||
"pool": "nova"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CreateOutput is a sample response to a Post call
|
|
||||||
const CreateOutput = `
|
|
||||||
{
|
|
||||||
"floating_ip": {
|
|
||||||
"fixed_ip": null,
|
|
||||||
"id": 1,
|
|
||||||
"instance_id": null,
|
|
||||||
"ip": "10.10.10.1",
|
|
||||||
"pool": "nova"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// FirstFloatingIP is the first result in ListOutput.
|
|
||||||
var FirstFloatingIP = FloatingIP{
|
|
||||||
ID: "1",
|
|
||||||
IP: "10.10.10.1",
|
|
||||||
Pool: "nova",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondFloatingIP is the first result in ListOutput.
|
|
||||||
var SecondFloatingIP = FloatingIP{
|
|
||||||
FixedIP: "166.78.185.201",
|
|
||||||
ID: "2",
|
|
||||||
InstanceID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
|
||||||
IP: "10.10.10.2",
|
|
||||||
Pool: "nova",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedFloatingIPsSlice is the slice of results that should be parsed
|
|
||||||
// from ListOutput, in the expected order.
|
|
||||||
var ExpectedFloatingIPsSlice = []FloatingIP{FirstFloatingIP, SecondFloatingIP}
|
|
||||||
|
|
||||||
// CreatedFloatingIP is the parsed result from CreateOutput.
|
|
||||||
var CreatedFloatingIP = FloatingIP{
|
|
||||||
ID: "1",
|
|
||||||
IP: "10.10.10.1",
|
|
||||||
Pool: "nova",
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
|
||||||
func HandleListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, ListOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGetSuccessfully configures the test server to respond to a Get request
|
|
||||||
// for an existing floating ip
|
|
||||||
func HandleGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-floating-ips/2", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, GetOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleCreateSuccessfully configures the test server to respond to a Create request
|
|
||||||
// for a new floating ip
|
|
||||||
func HandleCreateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-floating-ips", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"pool": "nova"
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, CreateOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDeleteSuccessfully configures the test server to respond to a Delete request for a
|
|
||||||
// an existing floating ip
|
|
||||||
func HandleDeleteSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-floating-ips/1", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAssociateSuccessfully configures the test server to respond to a Post request
|
|
||||||
// to associate an allocated floating IP
|
|
||||||
func HandleAssociateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"addFloatingIp": {
|
|
||||||
"address": "10.10.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFixedAssociateSucessfully configures the test server to respond to a Post request
|
|
||||||
// to associate an allocated floating IP with a specific fixed IP address
|
|
||||||
func HandleAssociateFixedSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"addFloatingIp": {
|
|
||||||
"address": "10.10.10.2",
|
|
||||||
"fixed_address": "166.78.185.201"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDisassociateSuccessfully configures the test server to respond to a Post request
|
|
||||||
// to disassociate an allocated floating IP
|
|
||||||
func HandleDisassociateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"removeFloatingIp": {
|
|
||||||
"address": "10.10.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
package floatingip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List returns a Pager that allows you to iterate over a collection of FloatingIPs.
|
|
||||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
|
||||||
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
|
|
||||||
return FloatingIPsPage{pagination.SinglePageBase(r)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
|
|
||||||
// CreateOpts struct in this package does.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToFloatingIPCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts specifies a Floating IP allocation request
|
|
||||||
type CreateOpts struct {
|
|
||||||
// Pool is the pool of floating IPs to allocate one from
|
|
||||||
Pool string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssociateOpts specifies the required information to associate or disassociate a floating IP to an instance
|
|
||||||
type AssociateOpts struct {
|
|
||||||
// ServerID is the UUID of the server
|
|
||||||
ServerID string
|
|
||||||
|
|
||||||
// FixedIP is an optional fixed IP address of the server
|
|
||||||
FixedIP string
|
|
||||||
|
|
||||||
// FloatingIP is the floating IP to associate with an instance
|
|
||||||
FloatingIP string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
|
|
||||||
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
|
|
||||||
if opts.Pool == "" {
|
|
||||||
return nil, errors.New("Missing field required for floating IP creation: Pool")
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"pool": opts.Pool}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToAssociateMap constructs a request body from AssociateOpts.
|
|
||||||
func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
|
|
||||||
if opts.ServerID == "" {
|
|
||||||
return nil, errors.New("Required field missing for floating IP association: ServerID")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.FloatingIP == "" {
|
|
||||||
return nil, errors.New("Required field missing for floating IP association: FloatingIP")
|
|
||||||
}
|
|
||||||
|
|
||||||
associateInfo := map[string]interface{}{
|
|
||||||
"serverId": opts.ServerID,
|
|
||||||
"floatingIp": opts.FloatingIP,
|
|
||||||
"fixedIp": opts.FixedIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
return associateInfo, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create requests the creation of a new floating IP
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToFloatingIPCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns data about a previously created FloatingIP.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete requests the deletion of a previous allocated FloatingIP.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// association / disassociation
|
|
||||||
|
|
||||||
// Associate pairs an allocated floating IP with an instance
|
|
||||||
// Deprecated. Use AssociateInstance.
|
|
||||||
func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult {
|
|
||||||
var res AssociateResult
|
|
||||||
|
|
||||||
addFloatingIp := make(map[string]interface{})
|
|
||||||
addFloatingIp["address"] = fip
|
|
||||||
reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssociateInstance pairs an allocated floating IP with an instance.
|
|
||||||
func AssociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) AssociateResult {
|
|
||||||
var res AssociateResult
|
|
||||||
|
|
||||||
associateInfo, err := opts.ToAssociateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
addFloatingIp := make(map[string]interface{})
|
|
||||||
addFloatingIp["address"] = associateInfo["floatingIp"].(string)
|
|
||||||
|
|
||||||
// fixedIp is not required
|
|
||||||
if associateInfo["fixedIp"] != "" {
|
|
||||||
addFloatingIp["fixed_address"] = associateInfo["fixedIp"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverId := associateInfo["serverId"].(string)
|
|
||||||
|
|
||||||
reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
|
|
||||||
_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disassociate decouples an allocated floating IP from an instance
|
|
||||||
// Deprecated. Use DisassociateInstance.
|
|
||||||
func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult {
|
|
||||||
var res DisassociateResult
|
|
||||||
|
|
||||||
removeFloatingIp := make(map[string]interface{})
|
|
||||||
removeFloatingIp["address"] = fip
|
|
||||||
reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisassociateInstance decouples an allocated floating IP from an instance
|
|
||||||
func DisassociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) DisassociateResult {
|
|
||||||
var res DisassociateResult
|
|
||||||
|
|
||||||
associateInfo, err := opts.ToAssociateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFloatingIp := make(map[string]interface{})
|
|
||||||
removeFloatingIp["address"] = associateInfo["floatingIp"].(string)
|
|
||||||
reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
|
|
||||||
|
|
||||||
serverId := associateInfo["serverId"].(string)
|
|
||||||
|
|
||||||
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package floatingip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A FloatingIP is an IP that can be associated with an instance
|
|
||||||
type FloatingIP struct {
|
|
||||||
// ID is a unique ID of the Floating IP
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// FixedIP is the IP of the instance related to the Floating IP
|
|
||||||
FixedIP string `mapstructure:"fixed_ip,omitempty"`
|
|
||||||
|
|
||||||
// InstanceID is the ID of the instance that is using the Floating IP
|
|
||||||
InstanceID string `mapstructure:"instance_id"`
|
|
||||||
|
|
||||||
// IP is the actual Floating IP
|
|
||||||
IP string `mapstructure:"ip"`
|
|
||||||
|
|
||||||
// Pool is the pool of floating IPs that this floating IP belongs to
|
|
||||||
Pool string `mapstructure:"pool"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloatingIPsPage stores a single, only page of FloatingIPs
|
|
||||||
// results from a List call.
|
|
||||||
type FloatingIPsPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a FloatingIPsPage is empty.
|
|
||||||
func (page FloatingIPsPage) IsEmpty() (bool, error) {
|
|
||||||
va, err := ExtractFloatingIPs(page)
|
|
||||||
return len(va) == 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractFloatingIPs interprets a page of results as a slice of
|
|
||||||
// FloatingIPs.
|
|
||||||
func ExtractFloatingIPs(page pagination.Page) ([]FloatingIP, error) {
|
|
||||||
casted := page.(FloatingIPsPage).Body
|
|
||||||
var response struct {
|
|
||||||
FloatingIPs []FloatingIP `mapstructure:"floating_ips"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(casted, &response)
|
|
||||||
|
|
||||||
return response.FloatingIPs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type FloatingIPResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a method that attempts to interpret any FloatingIP resource
|
|
||||||
// response as a FloatingIP struct.
|
|
||||||
func (r FloatingIPResult) Extract() (*FloatingIP, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
FloatingIP *FloatingIP `json:"floating_ip" mapstructure:"floating_ip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(r.Body, &res)
|
|
||||||
return res.FloatingIP, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
type GetResult struct {
|
|
||||||
FloatingIPResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult is the response from a Delete operation. Call its Extract 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.
|
|
||||||
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.
|
|
||||||
type DisassociateResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package floatingip
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const resourcePath = "os-floating-ips"
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(resourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL(resourcePath, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return getURL(c, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serverURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return c.ServiceURL("servers/" + serverId + "/action")
|
|
||||||
}
|
|
||||||
|
|
||||||
func associateURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return serverURL(c, serverId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func disassociateURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return serverURL(c, serverId)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package keypairs provides information and interaction with the Keypairs
|
|
||||||
// extension for the OpenStack Compute service.
|
|
||||||
package keypairs
|
|
171
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/fixtures.go
generated
vendored
171
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/fixtures.go
generated
vendored
|
@ -1,171 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package keypairs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOutput is a sample response to a List call.
|
|
||||||
const ListOutput = `
|
|
||||||
{
|
|
||||||
"keypairs": [
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"fingerprint": "15:b0:f8:b3:f9:48:63:71:cf:7b:5b:38:6d:44:2d:4a",
|
|
||||||
"name": "firstkey",
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+Eo/RZRngaGTkFs7I62ZjsIlO79KklKbMXi8F+KITD4bVQHHn+kV+4gRgkgCRbdoDqoGfpaDFs877DYX9n4z6FrAIZ4PES8TNKhatifpn9NdQYWA+IkU8CuvlEKGuFpKRi/k7JLos/gHi2hy7QUwgtRvcefvD/vgQZOVw/mGR9Q== Generated by Nova\n"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"fingerprint": "35:9d:d0:c3:4a:80:d3:d8:86:f1:ca:f7:df:c4:f9:d8",
|
|
||||||
"name": "secondkey",
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7DUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5Q== Generated by Nova\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// GetOutput is a sample response to a Get call.
|
|
||||||
const GetOutput = `
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+Eo/RZRngaGTkFs7I62ZjsIlO79KklKbMXi8F+KITD4bVQHHn+kV+4gRgkgCRbdoDqoGfpaDFs877DYX9n4z6FrAIZ4PES8TNKhatifpn9NdQYWA+IkU8CuvlEKGuFpKRi/k7JLos/gHi2hy7QUwgtRvcefvD/vgQZOVw/mGR9Q== Generated by Nova\n",
|
|
||||||
"name": "firstkey",
|
|
||||||
"fingerprint": "15:b0:f8:b3:f9:48:63:71:cf:7b:5b:38:6d:44:2d:4a"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CreateOutput is a sample response to a Create call.
|
|
||||||
const CreateOutput = `
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"fingerprint": "35:9d:d0:c3:4a:80:d3:d8:86:f1:ca:f7:df:c4:f9:d8",
|
|
||||||
"name": "createdkey",
|
|
||||||
"private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7\nDUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ\n9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5QIDAQAB\nAoGAE5XO1mDhORy9COvsg+kYPUhB1GsCYxh+v88wG7HeFDKBY6KUc/Kxo6yoGn5T\nTjRjekyi2KoDZHz4VlIzyZPwFS4I1bf3oCunVoAKzgLdmnTtvRNMC5jFOGc2vUgP\n9bSyRj3S1R4ClVk2g0IDeagko/jc8zzLEYuIK+fbkds79YECQQDt3vcevgegnkga\ntF4NsDmmBPRkcSHCqrANP/7vFcBQN3czxeYYWX3DK07alu6GhH1Y4sHbdm616uU0\nll7xbDzxAkEAzAtN2IyftNygV2EGiaGgqLyo/tD9+Vui2qCQplqe4jvWh/5Sparl\nOjmKo+uAW+hLrLVMnHzRWxbWU8hirH5FNQJATO+ZxCK4etXXAnQmG41NCAqANWB2\nB+2HJbH2NcQ2QHvAHUm741JGn/KI/aBlo7KEjFRDWUVUB5ji64BbUwCsMQJBAIku\nLGcjnBf/oLk+XSPZC2eGd2Ph5G5qYmH0Q2vkTx+wtTn3DV+eNsDfgMtWAJVJ5t61\ngU1QSXyhLPVlKpnnxuUCQC+xvvWjWtsLaFtAsZywJiqLxQzHts8XLGZptYJ5tLWV\nrtmYtBcJCN48RrgQHry/xWYeA4K/AFQpXfNPgprQ96Q=\n-----END RSA PRIVATE KEY-----\n",
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7DUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5Q== Generated by Nova\n",
|
|
||||||
"user_id": "fake"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// ImportOutput is a sample response to a Create call that provides its own public key.
|
|
||||||
const ImportOutput = `
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"fingerprint": "1e:2c:9b:56:79:4b:45:77:f9:ca:7a:98:2c:b0:d5:3c",
|
|
||||||
"name": "importedkey",
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated by Nova",
|
|
||||||
"user_id": "fake"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// FirstKeyPair is the first result in ListOutput.
|
|
||||||
var FirstKeyPair = KeyPair{
|
|
||||||
Name: "firstkey",
|
|
||||||
Fingerprint: "15:b0:f8:b3:f9:48:63:71:cf:7b:5b:38:6d:44:2d:4a",
|
|
||||||
PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+Eo/RZRngaGTkFs7I62ZjsIlO79KklKbMXi8F+KITD4bVQHHn+kV+4gRgkgCRbdoDqoGfpaDFs877DYX9n4z6FrAIZ4PES8TNKhatifpn9NdQYWA+IkU8CuvlEKGuFpKRi/k7JLos/gHi2hy7QUwgtRvcefvD/vgQZOVw/mGR9Q== Generated by Nova\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondKeyPair is the second result in ListOutput.
|
|
||||||
var SecondKeyPair = KeyPair{
|
|
||||||
Name: "secondkey",
|
|
||||||
Fingerprint: "35:9d:d0:c3:4a:80:d3:d8:86:f1:ca:f7:df:c4:f9:d8",
|
|
||||||
PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7DUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5Q== Generated by Nova\n",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedKeyPairSlice is the slice of results that should be parsed from ListOutput, in the expected
|
|
||||||
// order.
|
|
||||||
var ExpectedKeyPairSlice = []KeyPair{FirstKeyPair, SecondKeyPair}
|
|
||||||
|
|
||||||
// CreatedKeyPair is the parsed result from CreatedOutput.
|
|
||||||
var CreatedKeyPair = KeyPair{
|
|
||||||
Name: "createdkey",
|
|
||||||
Fingerprint: "35:9d:d0:c3:4a:80:d3:d8:86:f1:ca:f7:df:c4:f9:d8",
|
|
||||||
PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7DUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5Q== Generated by Nova\n",
|
|
||||||
PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQC9mC3WZN9UGLxgPBpP7H5jZMc6pKwOoSgre8yun6REFktn/Kz7\nDUt9jaR1UJyRzHxITfCfAIgSxPdGqB/oF1suMyWgu5i0625vavLB5z5kC8Hq3qZJ\n9zJO1poE1kyD+htiTtPWJ88e12xuH2XB/CZN9OpEiF98hAagiOE0EnOS5QIDAQAB\nAoGAE5XO1mDhORy9COvsg+kYPUhB1GsCYxh+v88wG7HeFDKBY6KUc/Kxo6yoGn5T\nTjRjekyi2KoDZHz4VlIzyZPwFS4I1bf3oCunVoAKzgLdmnTtvRNMC5jFOGc2vUgP\n9bSyRj3S1R4ClVk2g0IDeagko/jc8zzLEYuIK+fbkds79YECQQDt3vcevgegnkga\ntF4NsDmmBPRkcSHCqrANP/7vFcBQN3czxeYYWX3DK07alu6GhH1Y4sHbdm616uU0\nll7xbDzxAkEAzAtN2IyftNygV2EGiaGgqLyo/tD9+Vui2qCQplqe4jvWh/5Sparl\nOjmKo+uAW+hLrLVMnHzRWxbWU8hirH5FNQJATO+ZxCK4etXXAnQmG41NCAqANWB2\nB+2HJbH2NcQ2QHvAHUm741JGn/KI/aBlo7KEjFRDWUVUB5ji64BbUwCsMQJBAIku\nLGcjnBf/oLk+XSPZC2eGd2Ph5G5qYmH0Q2vkTx+wtTn3DV+eNsDfgMtWAJVJ5t61\ngU1QSXyhLPVlKpnnxuUCQC+xvvWjWtsLaFtAsZywJiqLxQzHts8XLGZptYJ5tLWV\nrtmYtBcJCN48RrgQHry/xWYeA4K/AFQpXfNPgprQ96Q=\n-----END RSA PRIVATE KEY-----\n",
|
|
||||||
UserID: "fake",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportedKeyPair is the parsed result from ImportOutput.
|
|
||||||
var ImportedKeyPair = KeyPair{
|
|
||||||
Name: "importedkey",
|
|
||||||
Fingerprint: "1e:2c:9b:56:79:4b:45:77:f9:ca:7a:98:2c:b0:d5:3c",
|
|
||||||
PublicKey: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated by Nova",
|
|
||||||
UserID: "fake",
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
|
||||||
func HandleListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, ListOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGetSuccessfully configures the test server to respond to a Get request for "firstkey".
|
|
||||||
func HandleGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-keypairs/firstkey", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, GetOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleCreateSuccessfully configures the test server to respond to a Create request for a new
|
|
||||||
// keypair called "createdkey".
|
|
||||||
func HandleCreateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{ "keypair": { "name": "createdkey" } }`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, CreateOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleImportSuccessfully configures the test server to respond to an Import request for an
|
|
||||||
// existing keypair called "importedkey".
|
|
||||||
func HandleImportSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-keypairs", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"keypair": {
|
|
||||||
"name": "importedkey",
|
|
||||||
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDx8nkQv/zgGgB4rMYmIf+6A4l6Rr+o/6lHBQdW5aYd44bd8JttDCE/F/pNRr0lRE+PiqSPO8nDPHw0010JeMH9gYgnnFlyY3/OcJ02RhIPyyxYpv9FhY+2YiUkpwFOcLImyrxEsYXpD/0d3ac30bNH6Sw9JD9UZHYcpSxsIbECHw== Generated by Nova"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, ImportOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDeleteSuccessfully configures the test server to respond to a Delete request for a
|
|
||||||
// keypair called "deletedkey".
|
|
||||||
func HandleDeleteSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-keypairs/deletedkey", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
102
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go
generated
vendored
102
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/requests.go
generated
vendored
|
@ -1,102 +0,0 @@
|
||||||
package keypairs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateOptsExt adds a KeyPair option to the base CreateOpts.
|
|
||||||
type CreateOptsExt struct {
|
|
||||||
servers.CreateOptsBuilder
|
|
||||||
KeyName string `json:"key_name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerCreateMap adds the key_name and, optionally, key_data options to
|
|
||||||
// the base server creation options.
|
|
||||||
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
|
|
||||||
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.KeyName == "" {
|
|
||||||
return base, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
serverMap := base["server"].(map[string]interface{})
|
|
||||||
serverMap["key_name"] = opts.KeyName
|
|
||||||
|
|
||||||
return base, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a Pager that allows you to iterate over a collection of KeyPairs.
|
|
||||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
|
||||||
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
|
|
||||||
return KeyPairPage{pagination.SinglePageBase(r)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
|
|
||||||
// CreateOpts struct in this package does.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToKeyPairCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts specifies keypair creation or import parameters.
|
|
||||||
type CreateOpts struct {
|
|
||||||
// Name [required] is a friendly name to refer to this KeyPair in other services.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToKeyPairCreateMap constructs a request body from CreateOpts.
|
|
||||||
func (opts CreateOpts) ToKeyPairCreateMap() (map[string]interface{}, error) {
|
|
||||||
if opts.Name == "" {
|
|
||||||
return nil, errors.New("Missing field required for keypair creation: Name")
|
|
||||||
}
|
|
||||||
|
|
||||||
keypair := make(map[string]interface{})
|
|
||||||
keypair["name"] = opts.Name
|
|
||||||
if opts.PublicKey != "" {
|
|
||||||
keypair["public_key"] = opts.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"keypair": keypair}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToKeyPairCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns public data about a previously uploaded KeyPair.
|
|
||||||
func Get(client *gophercloud.ServiceClient, name string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, name), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete requests the deletion of a previous stored KeyPair from the server.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, name string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, name), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package keypairs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyPair is an SSH key known to the OpenStack cluster 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 string `mapstructure:"name"`
|
|
||||||
|
|
||||||
// Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer
|
|
||||||
// public key.
|
|
||||||
Fingerprint string `mapstructure:"fingerprint"`
|
|
||||||
|
|
||||||
// PublicKey is the public key from this pair, in OpenSSH format. "ssh-rsa AAAAB3Nz..."
|
|
||||||
PublicKey string `mapstructure:"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
|
|
||||||
PrivateKey string `mapstructure:"private_key"`
|
|
||||||
|
|
||||||
// UserID is the user who owns this keypair.
|
|
||||||
UserID string `mapstructure:"user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyPairPage stores a single, only page of KeyPair results from a List call.
|
|
||||||
type KeyPairPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a KeyPairPage is empty.
|
|
||||||
func (page KeyPairPage) IsEmpty() (bool, error) {
|
|
||||||
ks, err := ExtractKeyPairs(page)
|
|
||||||
return len(ks) == 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractKeyPairs interprets a page of results as a slice of KeyPairs.
|
|
||||||
func ExtractKeyPairs(page pagination.Page) ([]KeyPair, error) {
|
|
||||||
type pair struct {
|
|
||||||
KeyPair KeyPair `mapstructure:"keypair"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp struct {
|
|
||||||
KeyPairs []pair `mapstructure:"keypairs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(page.(KeyPairPage).Body, &resp)
|
|
||||||
results := make([]KeyPair, len(resp.KeyPairs))
|
|
||||||
for i, pair := range resp.KeyPairs {
|
|
||||||
results[i] = pair.KeyPair
|
|
||||||
}
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyPairResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a method that attempts to interpret any KeyPair resource response as a KeyPair struct.
|
|
||||||
func (r keyPairResult) Extract() (*KeyPair, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
KeyPair *KeyPair `json:"keypair" mapstructure:"keypair"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &res)
|
|
||||||
return res.KeyPair, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
type GetResult struct {
|
|
||||||
keyPairResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
|
|
||||||
// the call succeeded or failed.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
25
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/urls.go
generated
vendored
25
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs/urls.go
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
package keypairs
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const resourcePath = "os-keypairs"
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(resourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, name string) string {
|
|
||||||
return c.ServiceURL(resourcePath, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, name string) string {
|
|
||||||
return getURL(c, name)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package schedulerhints enables instances to provide the OpenStack scheduler
|
|
||||||
// hints about where they should be placed in the cloud.
|
|
||||||
package schedulerhints
|
|
|
@ -1,134 +0,0 @@
|
||||||
package schedulerhints
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SchedulerHints represents a set of scheduling hints that are passed to the
|
|
||||||
// OpenStack scheduler
|
|
||||||
type SchedulerHints struct {
|
|
||||||
// Group specifies a Server Group to place the instance in.
|
|
||||||
Group string
|
|
||||||
|
|
||||||
// DifferentHost will place the instance on a compute node that does not
|
|
||||||
// host the given instances.
|
|
||||||
DifferentHost []string
|
|
||||||
|
|
||||||
// SameHost will place the instance on a compute node that hosts the given
|
|
||||||
// instances.
|
|
||||||
SameHost []string
|
|
||||||
|
|
||||||
// Query is a conditional statement that results in compute nodes able to
|
|
||||||
// host the instance.
|
|
||||||
Query []interface{}
|
|
||||||
|
|
||||||
// TargetCell specifies a cell name where the instance will be placed.
|
|
||||||
TargetCell string
|
|
||||||
|
|
||||||
// BuildNearHostIP specifies a subnet of compute nodes to host the instance.
|
|
||||||
BuildNearHostIP string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SchedulerHintsBuilder builds the scheduler hints into a serializable format.
|
|
||||||
type SchedulerHintsBuilder interface {
|
|
||||||
ToServerSchedulerHintsMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerSchedulerHintsMap builds the scheduler hints into a serializable format.
|
|
||||||
func (opts SchedulerHints) ToServerSchedulerHintsMap() (map[string]interface{}, error) {
|
|
||||||
sh := make(map[string]interface{})
|
|
||||||
|
|
||||||
uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$")
|
|
||||||
|
|
||||||
if opts.Group != "" {
|
|
||||||
if !uuidRegex.MatchString(opts.Group) {
|
|
||||||
return nil, fmt.Errorf("Group must be a UUID")
|
|
||||||
}
|
|
||||||
sh["group"] = opts.Group
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.DifferentHost) > 0 {
|
|
||||||
for _, diffHost := range opts.DifferentHost {
|
|
||||||
if !uuidRegex.MatchString(diffHost) {
|
|
||||||
return nil, fmt.Errorf("The hosts in DifferentHost must be in UUID format.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sh["different_host"] = opts.DifferentHost
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.SameHost) > 0 {
|
|
||||||
for _, sameHost := range opts.SameHost {
|
|
||||||
if !uuidRegex.MatchString(sameHost) {
|
|
||||||
return nil, fmt.Errorf("The hosts in SameHost must be in UUID format.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sh["same_host"] = opts.SameHost
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Query can be something simple like:
|
|
||||||
[">=", "$free_ram_mb", 1024]
|
|
||||||
|
|
||||||
Or more complex like:
|
|
||||||
['and',
|
|
||||||
['>=', '$free_ram_mb', 1024],
|
|
||||||
['>=', '$free_disk_mb', 200 * 1024]
|
|
||||||
]
|
|
||||||
|
|
||||||
Because of the possible complexity, just make sure the length is a minimum of 3.
|
|
||||||
*/
|
|
||||||
if len(opts.Query) > 0 {
|
|
||||||
if len(opts.Query) < 3 {
|
|
||||||
return nil, fmt.Errorf("Query must be a conditional statement in the format of [op,variable,value]")
|
|
||||||
}
|
|
||||||
sh["query"] = opts.Query
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.TargetCell != "" {
|
|
||||||
sh["target_cell"] = opts.TargetCell
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.BuildNearHostIP != "" {
|
|
||||||
if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
|
|
||||||
return nil, fmt.Errorf("BuildNearHostIP must be a valid subnet in the form 192.168.1.1/24")
|
|
||||||
}
|
|
||||||
ipParts := strings.Split(opts.BuildNearHostIP, "/")
|
|
||||||
sh["build_near_host_ip"] = ipParts[0]
|
|
||||||
sh["cidr"] = "/" + ipParts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return sh, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsExt adds a SchedulerHints option to the base CreateOpts.
|
|
||||||
type CreateOptsExt struct {
|
|
||||||
servers.CreateOptsBuilder
|
|
||||||
|
|
||||||
// SchedulerHints provides a set of hints to the scheduler.
|
|
||||||
SchedulerHints SchedulerHintsBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerCreateMap adds the SchedulerHints option to the base server creation options.
|
|
||||||
func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
|
|
||||||
base, err := opts.CreateOptsBuilder.ToServerCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
schedulerHints, err := opts.SchedulerHints.ToServerSchedulerHintsMap()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(schedulerHints) == 0 {
|
|
||||||
return base, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
base["os:scheduler_hints"] = schedulerHints
|
|
||||||
|
|
||||||
return base, nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
package secgroups
|
|
|
@ -1,305 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package secgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
fake "github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
const rootPath = "/os-security-groups"
|
|
||||||
|
|
||||||
const listGroupsJSON = `
|
|
||||||
{
|
|
||||||
"security_groups": [
|
|
||||||
{
|
|
||||||
"description": "default",
|
|
||||||
"id": "{groupID}",
|
|
||||||
"name": "default",
|
|
||||||
"rules": [],
|
|
||||||
"tenant_id": "openstack"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
func mockListGroupsResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, listGroupsJSON)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockListGroupsByServerResponse(t *testing.T, serverID string) {
|
|
||||||
url := fmt.Sprintf("/servers/%s%s", serverID, rootPath)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, listGroupsJSON)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockCreateGroupResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"name": "test",
|
|
||||||
"description": "something"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"description": "something",
|
|
||||||
"id": "{groupID}",
|
|
||||||
"name": "test",
|
|
||||||
"rules": [],
|
|
||||||
"tenant_id": "openstack"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockUpdateGroupResponse(t *testing.T, groupID string) {
|
|
||||||
url := fmt.Sprintf("%s/%s", rootPath, groupID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "PUT")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"name": "new_name",
|
|
||||||
"description": "new_desc"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"description": "something",
|
|
||||||
"id": "{groupID}",
|
|
||||||
"name": "new_name",
|
|
||||||
"rules": [],
|
|
||||||
"tenant_id": "openstack"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockGetGroupsResponse(t *testing.T, groupID string) {
|
|
||||||
url := fmt.Sprintf("%s/%s", rootPath, groupID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"description": "default",
|
|
||||||
"id": "{groupID}",
|
|
||||||
"name": "default",
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"from_port": 80,
|
|
||||||
"group": {
|
|
||||||
"tenant_id": "openstack",
|
|
||||||
"name": "default"
|
|
||||||
},
|
|
||||||
"ip_protocol": "TCP",
|
|
||||||
"to_port": 85,
|
|
||||||
"parent_group_id": "{groupID}",
|
|
||||||
"ip_range": {
|
|
||||||
"cidr": "0.0.0.0"
|
|
||||||
},
|
|
||||||
"id": "{ruleID}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tenant_id": "openstack"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockGetNumericIDGroupResponse(t *testing.T, groupID int) {
|
|
||||||
url := fmt.Sprintf("%s/%d", rootPath, groupID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group": {
|
|
||||||
"id": 12345
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockDeleteGroupResponse(t *testing.T, groupID string) {
|
|
||||||
url := fmt.Sprintf("%s/%s", rootPath, groupID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockAddRuleResponse(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"security_group_rule": {
|
|
||||||
"from_port": 22,
|
|
||||||
"ip_protocol": "TCP",
|
|
||||||
"to_port": 22,
|
|
||||||
"parent_group_id": "{groupID}",
|
|
||||||
"cidr": "0.0.0.0/0"
|
|
||||||
}
|
|
||||||
} `)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group_rule": {
|
|
||||||
"from_port": 22,
|
|
||||||
"group": {},
|
|
||||||
"ip_protocol": "TCP",
|
|
||||||
"to_port": 22,
|
|
||||||
"parent_group_id": "{groupID}",
|
|
||||||
"ip_range": {
|
|
||||||
"cidr": "0.0.0.0/0"
|
|
||||||
},
|
|
||||||
"id": "{ruleID}"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockAddRuleResponseICMPZero(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"security_group_rule": {
|
|
||||||
"from_port": 0,
|
|
||||||
"ip_protocol": "ICMP",
|
|
||||||
"to_port": 0,
|
|
||||||
"parent_group_id": "{groupID}",
|
|
||||||
"cidr": "0.0.0.0/0"
|
|
||||||
}
|
|
||||||
} `)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, `
|
|
||||||
{
|
|
||||||
"security_group_rule": {
|
|
||||||
"from_port": 0,
|
|
||||||
"group": {},
|
|
||||||
"ip_protocol": "ICMP",
|
|
||||||
"to_port": 0,
|
|
||||||
"parent_group_id": "{groupID}",
|
|
||||||
"ip_range": {
|
|
||||||
"cidr": "0.0.0.0/0"
|
|
||||||
},
|
|
||||||
"id": "{ruleID}"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockDeleteRuleResponse(t *testing.T, ruleID string) {
|
|
||||||
url := fmt.Sprintf("/os-security-group-rules/%s", ruleID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockAddServerToGroupResponse(t *testing.T, serverID string) {
|
|
||||||
url := fmt.Sprintf("/servers/%s/action", serverID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"addSecurityGroup": {
|
|
||||||
"name": "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
fmt.Fprintf(w, `{}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockRemoveServerFromGroupResponse(t *testing.T, serverID string) {
|
|
||||||
url := fmt.Sprintf("/servers/%s/action", serverID)
|
|
||||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
|
||||||
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"removeSecurityGroup": {
|
|
||||||
"name": "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
fmt.Fprintf(w, `{}`)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,258 +0,0 @@
|
||||||
package secgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
func commonList(client *gophercloud.ServiceClient, url string) pagination.Pager {
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return SecurityGroupPage{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List will return a collection of all the security groups for a particular
|
|
||||||
// tenant.
|
|
||||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
|
||||||
return commonList(client, rootURL(client))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListByServer will return a collection of all the security groups which are
|
|
||||||
// associated with a particular server.
|
|
||||||
func ListByServer(client *gophercloud.ServiceClient, serverID string) pagination.Pager {
|
|
||||||
return commonList(client, listByServerURL(client, serverID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupOpts is the underlying struct responsible for creating or updating
|
|
||||||
// security groups. It therefore represents the mutable attributes of a
|
|
||||||
// security group.
|
|
||||||
type GroupOpts struct {
|
|
||||||
// Required - the name of your security group.
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Required - the description of your security group.
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts is the struct responsible for creating a security group.
|
|
||||||
type CreateOpts GroupOpts
|
|
||||||
|
|
||||||
// CreateOptsBuilder builds the create options into a serializable format.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToSecGroupCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errName = errors.New("Name is a required field")
|
|
||||||
errDesc = errors.New("Description is a required field")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToSecGroupCreateMap builds the create options into a serializable format.
|
|
||||||
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
|
|
||||||
sg := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Name == "" {
|
|
||||||
return sg, errName
|
|
||||||
}
|
|
||||||
if opts.Description == "" {
|
|
||||||
return sg, errDesc
|
|
||||||
}
|
|
||||||
|
|
||||||
sg["name"] = opts.Name
|
|
||||||
sg["description"] = opts.Description
|
|
||||||
|
|
||||||
return map[string]interface{}{"security_group": sg}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create will create a new security group.
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var result CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToSecGroupCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
result.Err = err
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result.Err = client.Post(rootURL(client), reqBody, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOpts is the struct responsible for updating an existing security group.
|
|
||||||
type UpdateOpts GroupOpts
|
|
||||||
|
|
||||||
// UpdateOptsBuilder builds the update options into a serializable format.
|
|
||||||
type UpdateOptsBuilder interface {
|
|
||||||
ToSecGroupUpdateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToSecGroupUpdateMap builds the update options into a serializable format.
|
|
||||||
func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) {
|
|
||||||
sg := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.Name == "" {
|
|
||||||
return sg, errName
|
|
||||||
}
|
|
||||||
if opts.Description == "" {
|
|
||||||
return sg, errDesc
|
|
||||||
}
|
|
||||||
|
|
||||||
sg["name"] = opts.Name
|
|
||||||
sg["description"] = opts.Description
|
|
||||||
|
|
||||||
return map[string]interface{}{"security_group": sg}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update will modify the mutable properties of a security group, notably its
|
|
||||||
// name and description.
|
|
||||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
|
|
||||||
var result UpdateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToSecGroupUpdateMap()
|
|
||||||
if err != nil {
|
|
||||||
result.Err = err
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result.Err = client.Put(resourceURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get will return details for a particular security group.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var result GetResult
|
|
||||||
_, result.Err = client.Get(resourceURL(client, id), &result.Body, nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete will permanently delete a security group from the project.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
|
|
||||||
var result gophercloud.ErrResult
|
|
||||||
_, result.Err = client.Delete(resourceURL(client, id), nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRuleOpts represents the configuration for adding a new rule to an
|
|
||||||
// existing security group.
|
|
||||||
type CreateRuleOpts struct {
|
|
||||||
// Required - the ID of the group that this rule will be added to.
|
|
||||||
ParentGroupID string `json:"parent_group_id"`
|
|
||||||
|
|
||||||
// Required - the lower bound of the port range that will be opened.
|
|
||||||
FromPort int `json:"from_port"`
|
|
||||||
|
|
||||||
// Required - the upper bound of the port range that will be opened.
|
|
||||||
ToPort int `json:"to_port"`
|
|
||||||
|
|
||||||
// Required - the protocol type that will be allowed, e.g. TCP.
|
|
||||||
IPProtocol string `json:"ip_protocol"`
|
|
||||||
|
|
||||||
// ONLY required if FromGroupID is blank. This represents the IP range that
|
|
||||||
// will be the source of network traffic to your security group. Use
|
|
||||||
// 0.0.0.0/0 to allow all IP addresses.
|
|
||||||
CIDR string `json:"cidr,omitempty"`
|
|
||||||
|
|
||||||
// ONLY required if CIDR is blank. This value represents the ID of a group
|
|
||||||
// that forwards traffic to the parent group. So, instead of accepting
|
|
||||||
// network traffic from an entire IP range, you can instead refine the
|
|
||||||
// inbound source by an existing security group.
|
|
||||||
FromGroupID string `json:"group_id,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRuleOptsBuilder builds the create rule options into a serializable format.
|
|
||||||
type CreateRuleOptsBuilder interface {
|
|
||||||
ToRuleCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToRuleCreateMap builds the create rule options into a serializable format.
|
|
||||||
func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
|
|
||||||
rule := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.ParentGroupID == "" {
|
|
||||||
return rule, errors.New("A ParentGroupID must be set")
|
|
||||||
}
|
|
||||||
if opts.FromPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
|
||||||
return rule, errors.New("A FromPort must be set")
|
|
||||||
}
|
|
||||||
if opts.ToPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
|
||||||
return rule, errors.New("A ToPort must be set")
|
|
||||||
}
|
|
||||||
if opts.IPProtocol == "" {
|
|
||||||
return rule, errors.New("A IPProtocol must be set")
|
|
||||||
}
|
|
||||||
if opts.CIDR == "" && opts.FromGroupID == "" {
|
|
||||||
return rule, errors.New("A CIDR or FromGroupID must be set")
|
|
||||||
}
|
|
||||||
|
|
||||||
rule["parent_group_id"] = opts.ParentGroupID
|
|
||||||
rule["from_port"] = opts.FromPort
|
|
||||||
rule["to_port"] = opts.ToPort
|
|
||||||
rule["ip_protocol"] = opts.IPProtocol
|
|
||||||
|
|
||||||
if opts.CIDR != "" {
|
|
||||||
rule["cidr"] = opts.CIDR
|
|
||||||
}
|
|
||||||
if opts.FromGroupID != "" {
|
|
||||||
rule["group_id"] = opts.FromGroupID
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"security_group_rule": rule}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRule will add a new rule to an existing security group (whose ID is
|
|
||||||
// specified in CreateRuleOpts). You have the option of controlling inbound
|
|
||||||
// traffic from either an IP range (CIDR) or from another security group.
|
|
||||||
func CreateRule(client *gophercloud.ServiceClient, opts CreateRuleOptsBuilder) CreateRuleResult {
|
|
||||||
var result CreateRuleResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToRuleCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
result.Err = err
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result.Err = client.Post(rootRuleURL(client), reqBody, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRule will permanently delete a rule from a security group.
|
|
||||||
func DeleteRule(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
|
|
||||||
var result gophercloud.ErrResult
|
|
||||||
_, result.Err = client.Delete(resourceRuleURL(client, id), nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func actionMap(prefix, groupName string) map[string]map[string]string {
|
|
||||||
return map[string]map[string]string{
|
|
||||||
prefix + "SecurityGroup": map[string]string{"name": groupName},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddServerToGroup will associate a server and a security group, enforcing the
|
|
||||||
// rules of the group on the server.
|
|
||||||
func AddServerToGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
|
|
||||||
var result gophercloud.ErrResult
|
|
||||||
_, result.Err = client.Post(serverActionURL(client, serverID), actionMap("add", groupName), &result.Body, nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveServerFromGroup will disassociate a server from a security group.
|
|
||||||
func RemoveServerFromGroup(client *gophercloud.ServiceClient, serverID, groupName string) gophercloud.ErrResult {
|
|
||||||
var result gophercloud.ErrResult
|
|
||||||
_, result.Err = client.Post(serverActionURL(client, serverID), actionMap("remove", groupName), &result.Body, nil)
|
|
||||||
return result
|
|
||||||
}
|
|
147
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/results.go
generated
vendored
147
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups/results.go
generated
vendored
|
@ -1,147 +0,0 @@
|
||||||
package secgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecurityGroup represents a security group.
|
|
||||||
type SecurityGroup struct {
|
|
||||||
// The unique ID of the group. If Neutron is installed, this ID will be
|
|
||||||
// represented as a string UUID; if Neutron is not installed, it will be a
|
|
||||||
// numeric ID. For the sake of consistency, we always cast it to a string.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// The human-readable name of the group, which needs to be unique.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// The human-readable description of the group.
|
|
||||||
Description string
|
|
||||||
|
|
||||||
// The rules which determine how this security group operates.
|
|
||||||
Rules []Rule
|
|
||||||
|
|
||||||
// The ID of the tenant to which this security group belongs.
|
|
||||||
TenantID string `mapstructure:"tenant_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule represents a security group rule, a policy which determines how a
|
|
||||||
// security group operates and what inbound traffic it allows in.
|
|
||||||
type Rule struct {
|
|
||||||
// The unique ID. If Neutron is installed, this ID will be
|
|
||||||
// represented as a string UUID; if Neutron is not installed, it will be a
|
|
||||||
// numeric ID. For the sake of consistency, we always cast it to a string.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// The lower bound of the port range which this security group should open up
|
|
||||||
FromPort int `mapstructure:"from_port"`
|
|
||||||
|
|
||||||
// The upper bound of the port range which this security group should open up
|
|
||||||
ToPort int `mapstructure:"to_port"`
|
|
||||||
|
|
||||||
// The IP protocol (e.g. TCP) which the security group accepts
|
|
||||||
IPProtocol string `mapstructure:"ip_protocol"`
|
|
||||||
|
|
||||||
// The CIDR IP range whose traffic can be received
|
|
||||||
IPRange IPRange `mapstructure:"ip_range"`
|
|
||||||
|
|
||||||
// The security group ID to which this rule belongs
|
|
||||||
ParentGroupID string `mapstructure:"parent_group_id"`
|
|
||||||
|
|
||||||
// Not documented.
|
|
||||||
Group Group
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPRange represents the IP range whose traffic will be accepted by the
|
|
||||||
// security group.
|
|
||||||
type IPRange struct {
|
|
||||||
CIDR string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group represents a group.
|
|
||||||
type Group struct {
|
|
||||||
TenantID string `mapstructure:"tenant_id"`
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecurityGroupPage is a single page of a SecurityGroup collection.
|
|
||||||
type SecurityGroupPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a page of Security Groups contains any results.
|
|
||||||
func (page SecurityGroupPage) IsEmpty() (bool, error) {
|
|
||||||
users, err := ExtractSecurityGroups(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return len(users) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractSecurityGroups returns a slice of SecurityGroups contained in a single page of results.
|
|
||||||
func ExtractSecurityGroups(page pagination.Page) ([]SecurityGroup, error) {
|
|
||||||
casted := page.(SecurityGroupPage).Body
|
|
||||||
var response struct {
|
|
||||||
SecurityGroups []SecurityGroup `mapstructure:"security_groups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(casted, &response)
|
|
||||||
|
|
||||||
return response.SecurityGroups, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type commonResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult represents the result of a create operation.
|
|
||||||
type CreateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult represents the result of a get operation.
|
|
||||||
type GetResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateResult represents the result of an update operation.
|
|
||||||
type UpdateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract will extract a SecurityGroup struct from most responses.
|
|
||||||
func (r commonResult) Extract() (*SecurityGroup, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
SecurityGroup SecurityGroup `mapstructure:"security_group"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(r.Body, &response)
|
|
||||||
|
|
||||||
return &response.SecurityGroup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateRuleResult represents the result when adding rules to a security group.
|
|
||||||
type CreateRuleResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract will extract a Rule struct from a CreateRuleResult.
|
|
||||||
func (r CreateRuleResult) Extract() (*Rule, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Rule Rule `mapstructure:"security_group_rule"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(r.Body, &response)
|
|
||||||
|
|
||||||
return &response.Rule, err
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package secgroups
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const (
|
|
||||||
secgrouppath = "os-security-groups"
|
|
||||||
rulepath = "os-security-group-rules"
|
|
||||||
)
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL(secgrouppath, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rootURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(secgrouppath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listByServerURL(c *gophercloud.ServiceClient, serverID string) string {
|
|
||||||
return c.ServiceURL("servers", serverID, secgrouppath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rootRuleURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(rulepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceRuleURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL(rulepath, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serverActionURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL("servers", id, "action")
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package servergroups provides the ability to manage server groups
|
|
||||||
package servergroups
|
|
|
@ -1,161 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package servergroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOutput is a sample response to a List call.
|
|
||||||
const ListOutput = `
|
|
||||||
{
|
|
||||||
"server_groups": [
|
|
||||||
{
|
|
||||||
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
|
||||||
"name": "test",
|
|
||||||
"policies": [
|
|
||||||
"anti-affinity"
|
|
||||||
],
|
|
||||||
"members": [],
|
|
||||||
"metadata": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
|
||||||
"name": "test2",
|
|
||||||
"policies": [
|
|
||||||
"affinity"
|
|
||||||
],
|
|
||||||
"members": [],
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// GetOutput is a sample response to a Get call.
|
|
||||||
const GetOutput = `
|
|
||||||
{
|
|
||||||
"server_group": {
|
|
||||||
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
|
||||||
"name": "test",
|
|
||||||
"policies": [
|
|
||||||
"anti-affinity"
|
|
||||||
],
|
|
||||||
"members": [],
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// CreateOutput is a sample response to a Post call
|
|
||||||
const CreateOutput = `
|
|
||||||
{
|
|
||||||
"server_group": {
|
|
||||||
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
|
||||||
"name": "test",
|
|
||||||
"policies": [
|
|
||||||
"anti-affinity"
|
|
||||||
],
|
|
||||||
"members": [],
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// FirstServerGroup is the first result in ListOutput.
|
|
||||||
var FirstServerGroup = ServerGroup{
|
|
||||||
ID: "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
|
||||||
Name: "test",
|
|
||||||
Policies: []string{
|
|
||||||
"anti-affinity",
|
|
||||||
},
|
|
||||||
Members: []string{},
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondServerGroup is the second result in ListOutput.
|
|
||||||
var SecondServerGroup = ServerGroup{
|
|
||||||
ID: "4d8c3732-a248-40ed-bebc-539a6ffd25c0",
|
|
||||||
Name: "test2",
|
|
||||||
Policies: []string{
|
|
||||||
"affinity",
|
|
||||||
},
|
|
||||||
Members: []string{},
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedServerGroupSlice is the slice of results that should be parsed
|
|
||||||
// from ListOutput, in the expected order.
|
|
||||||
var ExpectedServerGroupSlice = []ServerGroup{FirstServerGroup, SecondServerGroup}
|
|
||||||
|
|
||||||
// CreatedServerGroup is the parsed result from CreateOutput.
|
|
||||||
var CreatedServerGroup = ServerGroup{
|
|
||||||
ID: "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
|
||||||
Name: "test",
|
|
||||||
Policies: []string{
|
|
||||||
"anti-affinity",
|
|
||||||
},
|
|
||||||
Members: []string{},
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
|
||||||
func HandleListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, ListOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGetSuccessfully configures the test server to respond to a Get request
|
|
||||||
// for an existing server group
|
|
||||||
func HandleGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-server-groups/4d8c3732-a248-40ed-bebc-539a6ffd25c0", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, GetOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleCreateSuccessfully configures the test server to respond to a Create request
|
|
||||||
// for a new server group
|
|
||||||
func HandleCreateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-server-groups", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"server_group": {
|
|
||||||
"name": "test",
|
|
||||||
"policies": [
|
|
||||||
"anti-affinity"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, CreateOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleDeleteSuccessfully configures the test server to respond to a Delete request for a
|
|
||||||
// an existing server group
|
|
||||||
func HandleDeleteSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-server-groups/616fb98f-46ca-475e-917e-2563e5a8cd19", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package servergroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List returns a Pager that allows you to iterate over a collection of ServerGroups.
|
|
||||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
|
||||||
return pagination.NewPager(client, listURL(client), func(r pagination.PageResult) pagination.Page {
|
|
||||||
return ServerGroupsPage{pagination.SinglePageBase(r)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notably, the
|
|
||||||
// CreateOpts struct in this package does.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToServerGroupCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts specifies a Server Group allocation request
|
|
||||||
type CreateOpts struct {
|
|
||||||
// Name is the name of the server group
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Policies are the server group policies
|
|
||||||
Policies []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerGroupCreateMap constructs a request body from CreateOpts.
|
|
||||||
func (opts CreateOpts) ToServerGroupCreateMap() (map[string]interface{}, error) {
|
|
||||||
if opts.Name == "" {
|
|
||||||
return nil, errors.New("Missing field required for server group creation: Name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Policies) < 1 {
|
|
||||||
return nil, errors.New("Missing field required for server group creation: Policies")
|
|
||||||
}
|
|
||||||
|
|
||||||
serverGroup := make(map[string]interface{})
|
|
||||||
serverGroup["name"] = opts.Name
|
|
||||||
serverGroup["policies"] = opts.Policies
|
|
||||||
|
|
||||||
return map[string]interface{}{"server_group": serverGroup}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create requests the creation of a new Server Group
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToServerGroupCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns data about a previously created ServerGroup.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete requests the deletion of a previously allocated ServerGroup.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
package servergroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A ServerGroup creates a policy for instance placement in the cloud
|
|
||||||
type ServerGroup struct {
|
|
||||||
// ID is the unique ID of the Server Group.
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Name is the common name of the server group.
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
|
|
||||||
// Polices are the group policies.
|
|
||||||
Policies []string `mapstructure:"policies"`
|
|
||||||
|
|
||||||
// Members are the members of the server group.
|
|
||||||
Members []string `mapstructure:"members"`
|
|
||||||
|
|
||||||
// Metadata includes a list of all user-specified key-value pairs attached to the Server Group.
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerGroupsPage stores a single, only page of ServerGroups
|
|
||||||
// results from a List call.
|
|
||||||
type ServerGroupsPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a ServerGroupsPage is empty.
|
|
||||||
func (page ServerGroupsPage) IsEmpty() (bool, error) {
|
|
||||||
va, err := ExtractServerGroups(page)
|
|
||||||
return len(va) == 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractServerGroups interprets a page of results as a slice of
|
|
||||||
// ServerGroups.
|
|
||||||
func ExtractServerGroups(page pagination.Page) ([]ServerGroup, error) {
|
|
||||||
casted := page.(ServerGroupsPage).Body
|
|
||||||
var response struct {
|
|
||||||
ServerGroups []ServerGroup `mapstructure:"server_groups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(casted, &response)
|
|
||||||
|
|
||||||
return response.ServerGroups, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerGroupResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a method that attempts to interpret any Server Group resource
|
|
||||||
// response as a ServerGroup struct.
|
|
||||||
func (r ServerGroupResult) Extract() (*ServerGroup, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
ServerGroup *ServerGroup `json:"server_group" mapstructure:"server_group"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(r.Body, &res)
|
|
||||||
return res.ServerGroup, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
|
|
||||||
// as a ServerGroup.
|
|
||||||
type CreateResult struct {
|
|
||||||
ServerGroupResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult is the response from a Get operation. Call its Extract method to interpret it
|
|
||||||
// as a ServerGroup.
|
|
||||||
type GetResult struct {
|
|
||||||
ServerGroupResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
|
|
||||||
// the call succeeded or failed.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package servergroups
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const resourcePath = "os-server-groups"
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(resourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL(resourcePath, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return getURL(c, id)
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
/*
|
|
||||||
Package startstop provides functionality to start and stop servers that have
|
|
||||||
been provisioned by the OpenStack Compute service.
|
|
||||||
*/
|
|
||||||
package startstop
|
|
|
@ -1,29 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package startstop
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mockStartServerResponse(t *testing.T, id string) {
|
|
||||||
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{"os-start": null}`)
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockStopServerResponse(t *testing.T, id string) {
|
|
||||||
th.Mux.HandleFunc("/servers/"+id+"/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{"os-stop": null}`)
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package startstop
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id, "action")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start is the operation responsible for starting a Compute server.
|
|
||||||
func Start(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
|
|
||||||
var res gophercloud.ErrResult
|
|
||||||
reqBody := map[string]interface{}{"os-start": nil}
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop is the operation responsible for stopping a Compute server.
|
|
||||||
func Stop(client *gophercloud.ServiceClient, id string) gophercloud.ErrResult {
|
|
||||||
var res gophercloud.ErrResult
|
|
||||||
reqBody := map[string]interface{}{"os-stop": nil}
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package tenantnetworks provides the ability for tenants to see information about the networks they have access to
|
|
||||||
package tenantnetworks
|
|
|
@ -1,84 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package tenantnetworks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOutput is a sample response to a List call.
|
|
||||||
const ListOutput = `
|
|
||||||
{
|
|
||||||
"networks": [
|
|
||||||
{
|
|
||||||
"cidr": "10.0.0.0/29",
|
|
||||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf047",
|
|
||||||
"label": "mynet_0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cidr": "10.0.0.10/29",
|
|
||||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf000",
|
|
||||||
"label": "mynet_1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// GetOutput is a sample response to a Get call.
|
|
||||||
const GetOutput = `
|
|
||||||
{
|
|
||||||
"network": {
|
|
||||||
"cidr": "10.0.0.10/29",
|
|
||||||
"id": "20c8acc0-f747-4d71-a389-46d078ebf000",
|
|
||||||
"label": "mynet_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// FirstNetwork is the first result in ListOutput.
|
|
||||||
var nilTime time.Time
|
|
||||||
var FirstNetwork = Network{
|
|
||||||
CIDR: "10.0.0.0/29",
|
|
||||||
ID: "20c8acc0-f747-4d71-a389-46d078ebf047",
|
|
||||||
Name: "mynet_0",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecondNetwork is the second result in ListOutput.
|
|
||||||
var SecondNetwork = Network{
|
|
||||||
CIDR: "10.0.0.10/29",
|
|
||||||
ID: "20c8acc0-f747-4d71-a389-46d078ebf000",
|
|
||||||
Name: "mynet_1",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedNetworkSlice is the slice of results that should be parsed
|
|
||||||
// from ListOutput, in the expected order.
|
|
||||||
var ExpectedNetworkSlice = []Network{FirstNetwork, SecondNetwork}
|
|
||||||
|
|
||||||
// HandleListSuccessfully configures the test server to respond to a List request.
|
|
||||||
func HandleListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-tenant-networks", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, ListOutput)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleGetSuccessfully configures the test server to respond to a Get request
|
|
||||||
// for an existing network.
|
|
||||||
func HandleGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/os-tenant-networks/20c8acc0-f747-4d71-a389-46d078ebf000", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, GetOutput)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package tenantnetworks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List returns a Pager that allows you to iterate over a collection of Network.
|
|
||||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
|
||||||
url := listURL(client)
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return NetworkPage{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns data about a previously created Network.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package tenantnetworks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Network represents a nova-network that an instance communicates on
|
|
||||||
type Network struct {
|
|
||||||
// CIDR is the IPv4 subnet.
|
|
||||||
CIDR string `mapstructure:"cidr"`
|
|
||||||
|
|
||||||
// ID is the UUID of the network.
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Name is the common name that the network has.
|
|
||||||
Name string `mapstructure:"label"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkPage stores a single, only page of Networks
|
|
||||||
// results from a List call.
|
|
||||||
type NetworkPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a NetworkPage is empty.
|
|
||||||
func (page NetworkPage) IsEmpty() (bool, error) {
|
|
||||||
va, err := ExtractNetworks(page)
|
|
||||||
return len(va) == 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractNetworks interprets a page of results as a slice of Networks
|
|
||||||
func ExtractNetworks(page pagination.Page) ([]Network, error) {
|
|
||||||
networks := page.(NetworkPage).Body
|
|
||||||
var res struct {
|
|
||||||
Networks []Network `mapstructure:"networks"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(networks, &res)
|
|
||||||
|
|
||||||
return res.Networks, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a method that attempts to interpret any Network resource
|
|
||||||
// response as a Network struct.
|
|
||||||
func (r NetworkResult) Extract() (*Network, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
Network *Network `json:"network" mapstructure:"network"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &res)
|
|
||||||
return res.Network, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult is the response from a Get operation. Call its Extract method to interpret it
|
|
||||||
// as a Network.
|
|
||||||
type GetResult struct {
|
|
||||||
NetworkResult
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package tenantnetworks
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const resourcePath = "os-tenant-networks"
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL(resourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return resourceURL(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, id string) string {
|
|
||||||
return c.ServiceURL(resourcePath, id)
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package volumeattach provides the ability to attach and detach volumes
|
|
||||||
// to instances
|
|
||||||
package volumeattach
|
|
|
@ -1,75 +0,0 @@
|
||||||
package volumeattach
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List returns a Pager that allows you to iterate over a collection of VolumeAttachments.
|
|
||||||
func List(client *gophercloud.ServiceClient, serverId string) pagination.Pager {
|
|
||||||
return pagination.NewPager(client, listURL(client, serverId), func(r pagination.PageResult) pagination.Page {
|
|
||||||
return VolumeAttachmentsPage{pagination.SinglePageBase(r)}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call. Notable, the
|
|
||||||
// CreateOpts struct in this package does.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToVolumeAttachmentCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts specifies volume attachment creation or import parameters.
|
|
||||||
type CreateOpts struct {
|
|
||||||
// Device is the device that the volume will attach to the instance as. Omit for "auto"
|
|
||||||
Device string
|
|
||||||
|
|
||||||
// VolumeID is the ID of the volume to attach to the instance
|
|
||||||
VolumeID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToVolumeAttachmentCreateMap constructs a request body from CreateOpts.
|
|
||||||
func (opts CreateOpts) ToVolumeAttachmentCreateMap() (map[string]interface{}, error) {
|
|
||||||
if opts.VolumeID == "" {
|
|
||||||
return nil, errors.New("Missing field required for volume attachment creation: VolumeID")
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeAttachment := make(map[string]interface{})
|
|
||||||
volumeAttachment["volumeId"] = opts.VolumeID
|
|
||||||
if opts.Device != "" {
|
|
||||||
volumeAttachment["device"] = opts.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"volumeAttachment": volumeAttachment}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create requests the creation of a new volume attachment on the server
|
|
||||||
func Create(client *gophercloud.ServiceClient, serverId string, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToVolumeAttachmentCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(createURL(client, serverId), reqBody, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns public data about a previously created VolumeAttachment.
|
|
||||||
func Get(client *gophercloud.ServiceClient, serverId, aId string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, serverId, aId), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete requests the deletion of a previous stored VolumeAttachment from the server.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, serverId, aId string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, serverId, aId), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package volumeattach
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VolumeAttach controls the attachment of a volume to an instance.
|
|
||||||
type VolumeAttachment struct {
|
|
||||||
// ID is a unique id of the attachment
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Device is what device the volume is attached as
|
|
||||||
Device string `mapstructure:"device"`
|
|
||||||
|
|
||||||
// VolumeID is the ID of the attached volume
|
|
||||||
VolumeID string `mapstructure:"volumeId"`
|
|
||||||
|
|
||||||
// ServerID is the ID of the instance that has the volume attached
|
|
||||||
ServerID string `mapstructure:"serverId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeAttachmentsPage stores a single, only page of VolumeAttachments
|
|
||||||
// results from a List call.
|
|
||||||
type VolumeAttachmentsPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a VolumeAttachmentsPage is empty.
|
|
||||||
func (page VolumeAttachmentsPage) IsEmpty() (bool, error) {
|
|
||||||
va, err := ExtractVolumeAttachments(page)
|
|
||||||
return len(va) == 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractVolumeAttachments interprets a page of results as a slice of
|
|
||||||
// VolumeAttachments.
|
|
||||||
func ExtractVolumeAttachments(page pagination.Page) ([]VolumeAttachment, error) {
|
|
||||||
casted := page.(VolumeAttachmentsPage).Body
|
|
||||||
var response struct {
|
|
||||||
VolumeAttachments []VolumeAttachment `mapstructure:"volumeAttachments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.WeakDecode(casted, &response)
|
|
||||||
|
|
||||||
return response.VolumeAttachments, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type VolumeAttachmentResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a method that attempts to interpret any VolumeAttachment resource
|
|
||||||
// response as a VolumeAttachment struct.
|
|
||||||
func (r VolumeAttachmentResult) Extract() (*VolumeAttachment, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var res struct {
|
|
||||||
VolumeAttachment *VolumeAttachment `json:"volumeAttachment" mapstructure:"volumeAttachment"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &res)
|
|
||||||
return res.VolumeAttachment, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
|
|
||||||
// as a VolumeAttachment.
|
|
||||||
type CreateResult struct {
|
|
||||||
VolumeAttachmentResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult is the response from a Get operation. Call its Extract method to interpret it
|
|
||||||
// as a VolumeAttachment.
|
|
||||||
type GetResult struct {
|
|
||||||
VolumeAttachmentResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult is the response from a Delete operation. Call its Extract method to determine if
|
|
||||||
// the call succeeded or failed.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package volumeattach
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
const resourcePath = "os-volume_attachments"
|
|
||||||
|
|
||||||
func resourceURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return c.ServiceURL("servers", serverId, resourcePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return resourceURL(c, serverId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createURL(c *gophercloud.ServiceClient, serverId string) string {
|
|
||||||
return resourceURL(c, serverId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(c *gophercloud.ServiceClient, serverId, aId string) string {
|
|
||||||
return c.ServiceURL("servers", serverId, resourcePath, aId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(c *gophercloud.ServiceClient, serverId, aId string) string {
|
|
||||||
return getURL(c, serverId, aId)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
// 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
|
|
103
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
103
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/requests.go
generated
vendored
|
@ -1,103 +0,0 @@
|
||||||
package flavors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// List request.
|
|
||||||
type ListOptsBuilder interface {
|
|
||||||
ToFlavorListQuery() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
|
|
||||||
// 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 int `q:"minDisk"`
|
|
||||||
MinRAM int `q:"minRam"`
|
|
||||||
|
|
||||||
// Marker and Limit control paging.
|
|
||||||
// Marker instructs List where to start listing from.
|
|
||||||
Marker string `q:"marker"`
|
|
||||||
|
|
||||||
// Limit instructs List to refrain from sending excessively large lists of flavors.
|
|
||||||
Limit int `q:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToFlavorListQuery formats a ListOpts into a query string.
|
|
||||||
func (opts ListOpts) ToFlavorListQuery() (string, error) {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|
||||||
url := listURL(client)
|
|
||||||
if opts != nil {
|
|
||||||
query, err := opts.ToFlavorListQuery()
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += query
|
|
||||||
}
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get instructs OpenStack to provide details on a single flavor, identified by its ID.
|
|
||||||
// Use ExtractFlavor to convert its result into a Flavor.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var res GetResult
|
|
||||||
_, res.Err = client.Get(getURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromName is a convienience function that returns a flavor's ID given its name.
|
|
||||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
|
||||||
flavorCount := 0
|
|
||||||
flavorID := ""
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("A flavor name must be provided.")
|
|
||||||
}
|
|
||||||
pager := ListDetail(client, nil)
|
|
||||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
flavorList, err := ExtractFlavors(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range flavorList {
|
|
||||||
if f.Name == name {
|
|
||||||
flavorCount++
|
|
||||||
flavorID = f.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch flavorCount {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("Unable to find flavor: %s", name)
|
|
||||||
case 1:
|
|
||||||
return flavorID, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, name)
|
|
||||||
}
|
|
||||||
}
|
|
122
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
122
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
|
@ -1,122 +0,0 @@
|
||||||
package flavors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrCannotInterpret is returned by an Extract call if the response body doesn't have the expected structure.
|
|
||||||
var ErrCannotInterpet = errors.New("Unable to interpret a response body.")
|
|
||||||
|
|
||||||
// GetResult temporarily holds the response from a Get call.
|
|
||||||
type GetResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract provides access to the individual Flavor returned by the Get function.
|
|
||||||
func (gr GetResult) Extract() (*Flavor, error) {
|
|
||||||
if gr.Err != nil {
|
|
||||||
return nil, gr.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var result struct {
|
|
||||||
Flavor Flavor `mapstructure:"flavor"`
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: defaulter,
|
|
||||||
Result: &result,
|
|
||||||
}
|
|
||||||
decoder, err := mapstructure.NewDecoder(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = decoder.Decode(gr.Body)
|
|
||||||
return &result.Flavor, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flavor records 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 string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
|
||||||
Disk int `mapstructure:"disk"`
|
|
||||||
RAM int `mapstructure:"ram"`
|
|
||||||
|
|
||||||
// The Name field provides a human-readable moniker for the flavor.
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
|
|
||||||
RxTxFactor float64 `mapstructure:"rxtx_factor"`
|
|
||||||
|
|
||||||
// Swap indicates how much space is reserved for swap.
|
|
||||||
// If not provided, this field will be set to 0.
|
|
||||||
Swap int `mapstructure:"swap"`
|
|
||||||
|
|
||||||
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
|
|
||||||
VCPUs int `mapstructure:"vcpus"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlavorPage contains a single page of the response from a List call.
|
|
||||||
type FlavorPage struct {
|
|
||||||
pagination.LinkedPageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines if a page contains any results.
|
|
||||||
func (p FlavorPage) IsEmpty() (bool, error) {
|
|
||||||
flavors, err := ExtractFlavors(p)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(flavors) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
|
||||||
func (p FlavorPage) NextPageURL() (string, error) {
|
|
||||||
type resp struct {
|
|
||||||
Links []gophercloud.Link `mapstructure:"flavors_links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var r resp
|
|
||||||
err := mapstructure.Decode(p.Body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gophercloud.ExtractNextURL(r.Links)
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaulter(from, to reflect.Kind, v interface{}) (interface{}, error) {
|
|
||||||
if (from == reflect.String) && (to == reflect.Int) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
|
|
||||||
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
|
|
||||||
casted := page.(FlavorPage).Body
|
|
||||||
var container struct {
|
|
||||||
Flavors []Flavor `mapstructure:"flavors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: defaulter,
|
|
||||||
Result: &container,
|
|
||||||
}
|
|
||||||
decoder, err := mapstructure.NewDecoder(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return container.Flavors, err
|
|
||||||
}
|
|
||||||
err = decoder.Decode(casted)
|
|
||||||
if err != nil {
|
|
||||||
return container.Flavors, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return container.Flavors, nil
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package flavors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("flavors", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("flavors", "detail")
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
// 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
|
|
109
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
109
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images/requests.go
generated
vendored
|
@ -1,109 +0,0 @@
|
||||||
package images
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// List request.
|
|
||||||
type ListOptsBuilder interface {
|
|
||||||
ToImageListQuery() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOpts contain options for limiting the number of Images returned from a call to ListDetail.
|
|
||||||
type ListOpts struct {
|
|
||||||
// When the image last changed status (in date-time format).
|
|
||||||
ChangesSince string `q:"changes-since"`
|
|
||||||
// The number of Images to return.
|
|
||||||
Limit int `q:"limit"`
|
|
||||||
// UUID of the Image at which to set a marker.
|
|
||||||
Marker string `q:"marker"`
|
|
||||||
// The name of the Image.
|
|
||||||
Name string `q:"name"`
|
|
||||||
// The name of the Server (in URL format).
|
|
||||||
Server string `q:"server"`
|
|
||||||
// The current status of the Image.
|
|
||||||
Status string `q:"status"`
|
|
||||||
// The value of the type of image (e.g. BASE, SERVER, ALL)
|
|
||||||
Type string `q:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToImageListQuery formats a ListOpts into a query string.
|
|
||||||
func (opts ListOpts) ToImageListQuery() (string, error) {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListDetail enumerates the available images.
|
|
||||||
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|
||||||
url := listDetailURL(client)
|
|
||||||
if opts != nil {
|
|
||||||
query, err := opts.ToImageListQuery()
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += query
|
|
||||||
}
|
|
||||||
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get acquires additional detail about a specific image by ID.
|
|
||||||
// Use ExtractImage() to interpret the result as an openstack Image.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var result GetResult
|
|
||||||
_, result.Err = client.Get(getURL(client, id), &result.Body, nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the specified image ID.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var result DeleteResult
|
|
||||||
_, result.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromName is a convienience function that returns an image's ID given its name.
|
|
||||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
|
||||||
imageCount := 0
|
|
||||||
imageID := ""
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("An image name must be provided.")
|
|
||||||
}
|
|
||||||
pager := ListDetail(client, &ListOpts{
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
imageList, err := ExtractImages(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range imageList {
|
|
||||||
if i.Name == name {
|
|
||||||
imageCount++
|
|
||||||
imageID = i.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch imageCount {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("Unable to find image: %s", name)
|
|
||||||
case 1:
|
|
||||||
return imageID, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
|
|
||||||
}
|
|
||||||
}
|
|
97
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
97
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/images/results.go
generated
vendored
|
@ -1,97 +0,0 @@
|
||||||
package images
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetResult temporarily stores a Get response.
|
|
||||||
type GetResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult represents the result of an image.Delete operation.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract interprets a GetResult as an Image.
|
|
||||||
func (gr GetResult) Extract() (*Image, error) {
|
|
||||||
if gr.Err != nil {
|
|
||||||
return nil, gr.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var decoded struct {
|
|
||||||
Image Image `mapstructure:"image"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(gr.Body, &decoded)
|
|
||||||
return &decoded.Image, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image is used for JSON (un)marshalling.
|
|
||||||
// It provides a description of an OS image.
|
|
||||||
type Image struct {
|
|
||||||
// ID contains the image's unique identifier.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
Created string
|
|
||||||
|
|
||||||
// MinDisk and MinRAM specify the minimum resources a server must provide to be able to install the image.
|
|
||||||
MinDisk int
|
|
||||||
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
|
|
||||||
|
|
||||||
Updated string
|
|
||||||
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImagePage contains a single page of results from a List 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.
|
|
||||||
func (page ImagePage) IsEmpty() (bool, error) {
|
|
||||||
images, err := ExtractImages(page)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(images) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
|
||||||
func (page ImagePage) NextPageURL() (string, error) {
|
|
||||||
type resp struct {
|
|
||||||
Links []gophercloud.Link `mapstructure:"images_links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var r resp
|
|
||||||
err := mapstructure.Decode(page.Body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gophercloud.ExtractNextURL(r.Links)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractImages converts a page of List results into a slice of usable Image structs.
|
|
||||||
func ExtractImages(page pagination.Page) ([]Image, error) {
|
|
||||||
casted := page.(ImagePage).Body
|
|
||||||
var results struct {
|
|
||||||
Images []Image `mapstructure:"images"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(casted, &results)
|
|
||||||
return results.Images, err
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package images
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("images", "detail")
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("images", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("images", id)
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
// 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
|
|
692
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go
generated
vendored
692
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/fixtures.go
generated
vendored
|
@ -1,692 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package servers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServerListBody contains the canned body of a servers.List response.
|
|
||||||
const ServerListBody = `
|
|
||||||
{
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"updated": "2014-09-25T13:10:10Z",
|
|
||||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
|
||||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
|
||||||
"addresses": {
|
|
||||||
"private": [
|
|
||||||
{
|
|
||||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
|
|
||||||
"version": 4,
|
|
||||||
"addr": "10.0.0.32",
|
|
||||||
"OS-EXT-IPS:type": "fixed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"key_name": null,
|
|
||||||
"image": {
|
|
||||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"OS-EXT-STS:task_state": null,
|
|
||||||
"OS-EXT-STS:vm_state": "active",
|
|
||||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001e",
|
|
||||||
"OS-SRV-USG:launched_at": "2014-09-25T13:10:10.000000",
|
|
||||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
|
||||||
"flavor": {
|
|
||||||
"id": "1",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
"security_groups": [
|
|
||||||
{
|
|
||||||
"name": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"OS-SRV-USG:terminated_at": null,
|
|
||||||
"OS-EXT-AZ:availability_zone": "nova",
|
|
||||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
|
||||||
"name": "herp",
|
|
||||||
"created": "2014-09-25T13:10:02Z",
|
|
||||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
|
||||||
"OS-DCF:diskConfig": "MANUAL",
|
|
||||||
"os-extended-volumes:volumes_attached": [],
|
|
||||||
"accessIPv4": "",
|
|
||||||
"accessIPv6": "",
|
|
||||||
"progress": 0,
|
|
||||||
"OS-EXT-STS:power_state": 1,
|
|
||||||
"config_drive": "",
|
|
||||||
"metadata": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"updated": "2014-09-25T13:04:49Z",
|
|
||||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
|
||||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
|
||||||
"addresses": {
|
|
||||||
"private": [
|
|
||||||
{
|
|
||||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
|
|
||||||
"version": 4,
|
|
||||||
"addr": "10.0.0.31",
|
|
||||||
"OS-EXT-IPS:type": "fixed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"key_name": null,
|
|
||||||
"image": {
|
|
||||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"OS-EXT-STS:task_state": null,
|
|
||||||
"OS-EXT-STS:vm_state": "active",
|
|
||||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
|
|
||||||
"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
|
|
||||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
|
||||||
"flavor": {
|
|
||||||
"id": "1",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"security_groups": [
|
|
||||||
{
|
|
||||||
"name": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"OS-SRV-USG:terminated_at": null,
|
|
||||||
"OS-EXT-AZ:availability_zone": "nova",
|
|
||||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
|
||||||
"name": "derp",
|
|
||||||
"created": "2014-09-25T13:04:41Z",
|
|
||||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
|
||||||
"OS-DCF:diskConfig": "MANUAL",
|
|
||||||
"os-extended-volumes:volumes_attached": [],
|
|
||||||
"accessIPv4": "",
|
|
||||||
"accessIPv6": "",
|
|
||||||
"progress": 0,
|
|
||||||
"OS-EXT-STS:power_state": 1,
|
|
||||||
"config_drive": "",
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// SingleServerBody is the canned body of a Get request on an existing server.
|
|
||||||
const SingleServerBody = `
|
|
||||||
{
|
|
||||||
"server": {
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"updated": "2014-09-25T13:04:49Z",
|
|
||||||
"hostId": "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
|
||||||
"OS-EXT-SRV-ATTR:host": "devstack",
|
|
||||||
"addresses": {
|
|
||||||
"private": [
|
|
||||||
{
|
|
||||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
|
|
||||||
"version": 4,
|
|
||||||
"addr": "10.0.0.31",
|
|
||||||
"OS-EXT-IPS:type": "fixed"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"key_name": null,
|
|
||||||
"image": {
|
|
||||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"OS-EXT-STS:task_state": null,
|
|
||||||
"OS-EXT-STS:vm_state": "active",
|
|
||||||
"OS-EXT-SRV-ATTR:instance_name": "instance-0000001d",
|
|
||||||
"OS-SRV-USG:launched_at": "2014-09-25T13:04:49.000000",
|
|
||||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "devstack",
|
|
||||||
"flavor": {
|
|
||||||
"id": "1",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
|
||||||
"rel": "bookmark"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"id": "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"security_groups": [
|
|
||||||
{
|
|
||||||
"name": "default"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"OS-SRV-USG:terminated_at": null,
|
|
||||||
"OS-EXT-AZ:availability_zone": "nova",
|
|
||||||
"user_id": "9349aff8be7545ac9d2f1d00999a23cd",
|
|
||||||
"name": "derp",
|
|
||||||
"created": "2014-09-25T13:04:41Z",
|
|
||||||
"tenant_id": "fcad67a6189847c4aecfa3c81a05783b",
|
|
||||||
"OS-DCF:diskConfig": "MANUAL",
|
|
||||||
"os-extended-volumes:volumes_attached": [],
|
|
||||||
"accessIPv4": "",
|
|
||||||
"accessIPv6": "",
|
|
||||||
"progress": 0,
|
|
||||||
"OS-EXT-STS:power_state": 1,
|
|
||||||
"config_drive": "",
|
|
||||||
"metadata": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const ServerPasswordBody = `
|
|
||||||
{
|
|
||||||
"password": "xlozO3wLCBRWAa2yDjCCVx8vwNPypxnypmRYDa/zErlQ+EzPe1S/Gz6nfmC52mOlOSCRuUOmG7kqqgejPof6M7bOezS387zjq4LSvvwp28zUknzy4YzfFGhnHAdai3TxUJ26pfQCYrq8UTzmKF2Bq8ioSEtVVzM0A96pDh8W2i7BOz6MdoiVyiev/I1K2LsuipfxSJR7Wdke4zNXJjHHP2RfYsVbZ/k9ANu+Nz4iIH8/7Cacud/pphH7EjrY6a4RZNrjQskrhKYed0YERpotyjYk1eDtRe72GrSiXteqCM4biaQ5w3ruS+AcX//PXk3uJ5kC7d67fPXaVz4WaQRYMg=="
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ServerHerp is a Server struct that should correspond to the first result in ServerListBody.
|
|
||||||
ServerHerp = Server{
|
|
||||||
Status: "ACTIVE",
|
|
||||||
Updated: "2014-09-25T13:10:10Z",
|
|
||||||
HostID: "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
|
||||||
Addresses: map[string]interface{}{
|
|
||||||
"private": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:7c:1b:2b",
|
|
||||||
"version": float64(4),
|
|
||||||
"addr": "10.0.0.32",
|
|
||||||
"OS-EXT-IPS:type": "fixed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Links: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
"rel": "self",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Image: map[string]interface{}{
|
|
||||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"links": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Flavor: map[string]interface{}{
|
|
||||||
"id": "1",
|
|
||||||
"links": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ID: "ef079b0c-e610-4dfb-b1aa-b49f07ac48e5",
|
|
||||||
UserID: "9349aff8be7545ac9d2f1d00999a23cd",
|
|
||||||
Name: "herp",
|
|
||||||
Created: "2014-09-25T13:10:02Z",
|
|
||||||
TenantID: "fcad67a6189847c4aecfa3c81a05783b",
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
SecurityGroups: []map[string]interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerDerp is a Server struct that should correspond to the second server in ServerListBody.
|
|
||||||
ServerDerp = Server{
|
|
||||||
Status: "ACTIVE",
|
|
||||||
Updated: "2014-09-25T13:04:49Z",
|
|
||||||
HostID: "29d3c8c896a45aa4c34e52247875d7fefc3d94bbcc9f622b5d204362",
|
|
||||||
Addresses: map[string]interface{}{
|
|
||||||
"private": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:9e:89:be",
|
|
||||||
"version": float64(4),
|
|
||||||
"addr": "10.0.0.31",
|
|
||||||
"OS-EXT-IPS:type": "fixed",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Links: []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/v2/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "self",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/servers/9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Image: map[string]interface{}{
|
|
||||||
"id": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"links": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Flavor: map[string]interface{}{
|
|
||||||
"id": "1",
|
|
||||||
"links": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"href": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/flavors/1",
|
|
||||||
"rel": "bookmark",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ID: "9e5476bd-a4ec-4653-93d6-72c93aa682ba",
|
|
||||||
UserID: "9349aff8be7545ac9d2f1d00999a23cd",
|
|
||||||
Name: "derp",
|
|
||||||
Created: "2014-09-25T13:04:41Z",
|
|
||||||
TenantID: "fcad67a6189847c4aecfa3c81a05783b",
|
|
||||||
Metadata: map[string]interface{}{},
|
|
||||||
SecurityGroups: []map[string]interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// HandleServerCreationSuccessfully sets up the test server to respond to a server creation request
|
|
||||||
// with a given response.
|
|
||||||
func HandleServerCreationSuccessfully(t *testing.T, response string) {
|
|
||||||
th.Mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{
|
|
||||||
"server": {
|
|
||||||
"name": "derp",
|
|
||||||
"imageRef": "f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"flavorRef": "1"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, response)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerListSuccessfully sets up the test server to respond to a server List request.
|
|
||||||
func HandleServerListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/detail", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
r.ParseForm()
|
|
||||||
marker := r.Form.Get("marker")
|
|
||||||
switch marker {
|
|
||||||
case "":
|
|
||||||
fmt.Fprintf(w, ServerListBody)
|
|
||||||
case "9e5476bd-a4ec-4653-93d6-72c93aa682ba":
|
|
||||||
fmt.Fprintf(w, `{ "servers": [] }`)
|
|
||||||
default:
|
|
||||||
t.Fatalf("/servers/detail invoked with unexpected marker=[%s]", marker)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerDeletionSuccessfully sets up the test server to respond to a server deletion request.
|
|
||||||
func HandleServerDeletionSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/asdfasdfasdf", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerForceDeletionSuccessfully sets up the test server to respond to a server force deletion
|
|
||||||
// request.
|
|
||||||
func HandleServerForceDeletionSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{ "forceDelete": "" }`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerGetSuccessfully sets up the test server to respond to a server Get request.
|
|
||||||
func HandleServerGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, SingleServerBody)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerUpdateSuccessfully sets up the test server to respond to a server Update request.
|
|
||||||
func HandleServerUpdateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "PUT")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
th.TestHeader(t, r, "Content-Type", "application/json")
|
|
||||||
th.TestJSONRequest(t, r, `{ "server": { "name": "new-name" } }`)
|
|
||||||
|
|
||||||
fmt.Fprintf(w, SingleServerBody)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAdminPasswordChangeSuccessfully sets up the test server to respond to a server password
|
|
||||||
// change request.
|
|
||||||
func HandleAdminPasswordChangeSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{ "changePassword": { "adminPass": "new-password" } }`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRebootSuccessfully sets up the test server to respond to a reboot request with success.
|
|
||||||
func HandleRebootSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{ "reboot": { "type": "SOFT" } }`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleRebuildSuccessfully sets up the test server to respond to a rebuild request with success.
|
|
||||||
func HandleRebuildSuccessfully(t *testing.T, response string) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `
|
|
||||||
{
|
|
||||||
"rebuild": {
|
|
||||||
"name": "new-name",
|
|
||||||
"adminPass": "swordfish",
|
|
||||||
"imageRef": "http://104.130.131.164:8774/fcad67a6189847c4aecfa3c81a05783b/images/f90f6034-2570-4974-8351-6b49732ef2eb",
|
|
||||||
"accessIPv4": "1.2.3.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, response)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleServerRescueSuccessfully sets up the test server to respond to a server Rescue request.
|
|
||||||
func HandleServerRescueSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{ "rescue": { "adminPass": "1234567890" } }`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(`{ "adminPass": "1234567890" }`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadatumGetSuccessfully sets up the test server to respond to a metadatum Get request.
|
|
||||||
func HandleMetadatumGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(`{ "meta": {"foo":"bar"}}`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadatumCreateSuccessfully sets up the test server to respond to a metadatum Create request.
|
|
||||||
func HandleMetadatumCreateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "PUT")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{
|
|
||||||
"meta": {
|
|
||||||
"foo": "bar"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(`{ "meta": {"foo":"bar"}}`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadatumDeleteSuccessfully sets up the test server to respond to a metadatum Delete request.
|
|
||||||
func HandleMetadatumDeleteSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "DELETE")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadataGetSuccessfully sets up the test server to respond to a metadata Get request.
|
|
||||||
func HandleMetadataGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Write([]byte(`{ "metadata": {"foo":"bar", "this":"that"}}`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadataResetSuccessfully sets up the test server to respond to a metadata Create request.
|
|
||||||
func HandleMetadataResetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "PUT")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{
|
|
||||||
"metadata": {
|
|
||||||
"foo": "bar",
|
|
||||||
"this": "that"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(`{ "metadata": {"foo":"bar", "this":"that"}}`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleMetadataUpdateSuccessfully sets up the test server to respond to a metadata Update request.
|
|
||||||
func HandleMetadataUpdateSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestJSONRequest(t, r, `{
|
|
||||||
"metadata": {
|
|
||||||
"foo": "baz",
|
|
||||||
"this": "those"
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
w.Write([]byte(`{ "metadata": {"foo":"baz", "this":"those"}}`))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAddressesExpected represents an expected repsonse from a ListAddresses request.
|
|
||||||
var ListAddressesExpected = map[string][]Address{
|
|
||||||
"public": []Address{
|
|
||||||
Address{
|
|
||||||
Version: 4,
|
|
||||||
Address: "80.56.136.39",
|
|
||||||
},
|
|
||||||
Address{
|
|
||||||
Version: 6,
|
|
||||||
Address: "2001:4800:790e:510:be76:4eff:fe04:82a8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"private": []Address{
|
|
||||||
Address{
|
|
||||||
Version: 4,
|
|
||||||
Address: "10.880.3.154",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleAddressListSuccessfully sets up the test server to respond to a ListAddresses request.
|
|
||||||
func HandleAddressListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/asdfasdfasdf/ips", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, `{
|
|
||||||
"addresses": {
|
|
||||||
"public": [
|
|
||||||
{
|
|
||||||
"version": 4,
|
|
||||||
"addr": "50.56.176.35"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": 6,
|
|
||||||
"addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"private": [
|
|
||||||
{
|
|
||||||
"version": 4,
|
|
||||||
"addr": "10.180.3.155"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListNetworkAddressesExpected represents an expected repsonse from a ListAddressesByNetwork request.
|
|
||||||
var ListNetworkAddressesExpected = []Address{
|
|
||||||
Address{
|
|
||||||
Version: 4,
|
|
||||||
Address: "50.56.176.35",
|
|
||||||
},
|
|
||||||
Address{
|
|
||||||
Version: 6,
|
|
||||||
Address: "2001:4800:780e:510:be76:4eff:fe04:84a8",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleNetworkAddressListSuccessfully sets up the test server to respond to a ListAddressesByNetwork request.
|
|
||||||
func HandleNetworkAddressListSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/asdfasdfasdf/ips/public", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
fmt.Fprintf(w, `{
|
|
||||||
"public": [
|
|
||||||
{
|
|
||||||
"version": 4,
|
|
||||||
"addr": "50.56.176.35"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": 6,
|
|
||||||
"addr": "2001:4800:780e:510:be76:4eff:fe04:84a8"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleCreateServerImageSuccessfully sets up the test server to respond to a TestCreateServerImage request.
|
|
||||||
func HandleCreateServerImageSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/serverimage/action", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
w.Header().Add("Location", "https://0.0.0.0/images/xxxx-xxxxx-xxxxx-xxxx")
|
|
||||||
w.WriteHeader(http.StatusAccepted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlePasswordGetSuccessfully sets up the test server to respond to a password Get request.
|
|
||||||
func HandlePasswordGetSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/servers/1234asdf/os-server-password", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
|
|
||||||
fmt.Fprintf(w, ServerPasswordBody)
|
|
||||||
})
|
|
||||||
}
|
|
872
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
872
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/requests.go
generated
vendored
|
@ -1,872 +0,0 @@
|
||||||
package servers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// List request.
|
|
||||||
type ListOptsBuilder interface {
|
|
||||||
ToServerListQuery() (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListOpts allows the filtering and sorting of paginated collections through
|
|
||||||
// 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.
|
|
||||||
type ListOpts struct {
|
|
||||||
// A time/date stamp for when the server last changed status.
|
|
||||||
ChangesSince string `q:"changes-since"`
|
|
||||||
|
|
||||||
// Name of the image in URL format.
|
|
||||||
Image string `q:"image"`
|
|
||||||
|
|
||||||
// Name of the flavor in URL format.
|
|
||||||
Flavor string `q:"flavor"`
|
|
||||||
|
|
||||||
// Name of the server as a string; can be queried with regular expressions.
|
|
||||||
// Realize that ?name=bob returns both bob and bobb. If you need to match bob
|
|
||||||
// only, you can use a regular expression matching the syntax of the
|
|
||||||
// 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 string `q:"status"`
|
|
||||||
|
|
||||||
// Name of the host as a string.
|
|
||||||
Host string `q:"host"`
|
|
||||||
|
|
||||||
// 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 int `q:"limit"`
|
|
||||||
|
|
||||||
// Bool to show all tenants
|
|
||||||
AllTenants bool `q:"all_tenants"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerListQuery formats a ListOpts into a query string.
|
|
||||||
func (opts ListOpts) ToServerListQuery() (string, error) {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return q.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List makes a request against the API to list servers accessible to you.
|
|
||||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
|
||||||
url := listDetailURL(client)
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
query, err := opts.ToServerListQuery()
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += query
|
|
||||||
}
|
|
||||||
|
|
||||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return ServerPage{pagination.LinkedPageBase{PageResult: r}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPageFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
|
|
||||||
// The CreateOpts struct in this package does.
|
|
||||||
type CreateOptsBuilder interface {
|
|
||||||
ToServerCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// Required unless Port is provided.
|
|
||||||
UUID string
|
|
||||||
|
|
||||||
// Port of a neutron network to attach to the newly provisioned server.
|
|
||||||
// Required unless UUID is provided.
|
|
||||||
Port string
|
|
||||||
|
|
||||||
// FixedIP [optional] 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.
|
|
||||||
type File struct {
|
|
||||||
// Path of the file
|
|
||||||
Path string
|
|
||||||
// Contents of the file. Maximum content size is 255 bytes.
|
|
||||||
Contents []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON marshals the escaped file, base64 encoding the contents.
|
|
||||||
func (f *File) MarshalJSON() ([]byte, error) {
|
|
||||||
file := struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Contents string `json:"contents"`
|
|
||||||
}{
|
|
||||||
Path: f.Path,
|
|
||||||
Contents: base64.StdEncoding.EncodeToString(f.Contents),
|
|
||||||
}
|
|
||||||
return json.Marshal(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOpts specifies server creation parameters.
|
|
||||||
type CreateOpts struct {
|
|
||||||
// Name [required] is the name to assign to the newly launched server.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
|
|
||||||
// full URL to the flavor that describes the server's specs.
|
|
||||||
FlavorRef string
|
|
||||||
|
|
||||||
// FlavorName [optional; required if FlavorRef is not provided] is the name of
|
|
||||||
// the flavor that describes the server's specs.
|
|
||||||
FlavorName string
|
|
||||||
|
|
||||||
// SecurityGroups [optional] lists the names of the security groups to which this server should belong.
|
|
||||||
SecurityGroups []string
|
|
||||||
|
|
||||||
// UserData [optional] contains configuration information or scripts to use upon launch.
|
|
||||||
// Create will base64-encode it for you.
|
|
||||||
UserData []byte
|
|
||||||
|
|
||||||
// AvailabilityZone [optional] in which to launch the server.
|
|
||||||
AvailabilityZone string
|
|
||||||
|
|
||||||
// Networks [optional] dictates how this server will be attached to available networks.
|
|
||||||
// By default, the server will be attached to all isolated networks for the tenant.
|
|
||||||
Networks []Network
|
|
||||||
|
|
||||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
|
||||||
Metadata map[string]string
|
|
||||||
|
|
||||||
// Personality [optional] includes files to inject into the server at launch.
|
|
||||||
// Create will base64-encode file contents for you.
|
|
||||||
Personality Personality
|
|
||||||
|
|
||||||
// ConfigDrive [optional] enables metadata injection through a configuration drive.
|
|
||||||
ConfigDrive bool
|
|
||||||
|
|
||||||
// AdminPass [optional] sets the root user password. If not set, a randomly-generated
|
|
||||||
// password will be created and returned in the response.
|
|
||||||
AdminPass string
|
|
||||||
|
|
||||||
// AccessIPv4 [optional] specifies an IPv4 address for the instance.
|
|
||||||
AccessIPv4 string
|
|
||||||
|
|
||||||
// AccessIPv6 [optional] specifies an IPv6 address for the instance.
|
|
||||||
AccessIPv6 string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerCreateMap assembles a request body based on the contents of a CreateOpts.
|
|
||||||
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
|
|
||||||
server := make(map[string]interface{})
|
|
||||||
|
|
||||||
server["name"] = opts.Name
|
|
||||||
server["imageRef"] = opts.ImageRef
|
|
||||||
server["imageName"] = opts.ImageName
|
|
||||||
server["flavorRef"] = opts.FlavorRef
|
|
||||||
server["flavorName"] = opts.FlavorName
|
|
||||||
|
|
||||||
if opts.UserData != nil {
|
|
||||||
encoded := base64.StdEncoding.EncodeToString(opts.UserData)
|
|
||||||
server["user_data"] = &encoded
|
|
||||||
}
|
|
||||||
if opts.ConfigDrive {
|
|
||||||
server["config_drive"] = "true"
|
|
||||||
}
|
|
||||||
if opts.AvailabilityZone != "" {
|
|
||||||
server["availability_zone"] = opts.AvailabilityZone
|
|
||||||
}
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
server["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
if opts.AdminPass != "" {
|
|
||||||
server["adminPass"] = opts.AdminPass
|
|
||||||
}
|
|
||||||
if opts.AccessIPv4 != "" {
|
|
||||||
server["accessIPv4"] = opts.AccessIPv4
|
|
||||||
}
|
|
||||||
if opts.AccessIPv6 != "" {
|
|
||||||
server["accessIPv6"] = opts.AccessIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.SecurityGroups) > 0 {
|
|
||||||
securityGroups := make([]map[string]interface{}, len(opts.SecurityGroups))
|
|
||||||
for i, groupName := range opts.SecurityGroups {
|
|
||||||
securityGroups[i] = map[string]interface{}{"name": groupName}
|
|
||||||
}
|
|
||||||
server["security_groups"] = securityGroups
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Networks) > 0 {
|
|
||||||
networks := make([]map[string]interface{}, len(opts.Networks))
|
|
||||||
for i, net := range opts.Networks {
|
|
||||||
networks[i] = make(map[string]interface{})
|
|
||||||
if net.UUID != "" {
|
|
||||||
networks[i]["uuid"] = net.UUID
|
|
||||||
}
|
|
||||||
if net.Port != "" {
|
|
||||||
networks[i]["port"] = net.Port
|
|
||||||
}
|
|
||||||
if net.FixedIP != "" {
|
|
||||||
networks[i]["fixed_ip"] = net.FixedIP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server["networks"] = networks
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Personality) > 0 {
|
|
||||||
server["personality"] = opts.Personality
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"server": server}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create requests a server to be provisioned to the user in the current tenant.
|
|
||||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
|
||||||
var res CreateResult
|
|
||||||
|
|
||||||
reqBody, err := opts.ToServerCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// If ImageRef isn't provided, use ImageName to ascertain the image ID.
|
|
||||||
if reqBody["server"].(map[string]interface{})["imageRef"].(string) == "" {
|
|
||||||
imageName := reqBody["server"].(map[string]interface{})["imageName"].(string)
|
|
||||||
if imageName == "" {
|
|
||||||
res.Err = errors.New("One and only one of ImageRef and ImageName must be provided.")
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
imageID, err := images.IDFromName(client, imageName)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
reqBody["server"].(map[string]interface{})["imageRef"] = imageID
|
|
||||||
}
|
|
||||||
delete(reqBody["server"].(map[string]interface{}), "imageName")
|
|
||||||
|
|
||||||
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
|
|
||||||
if reqBody["server"].(map[string]interface{})["flavorRef"].(string) == "" {
|
|
||||||
flavorName := reqBody["server"].(map[string]interface{})["flavorName"].(string)
|
|
||||||
if flavorName == "" {
|
|
||||||
res.Err = errors.New("One and only one of FlavorRef and FlavorName must be provided.")
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
flavorID, err := flavors.IDFromName(client, flavorName)
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
reqBody["server"].(map[string]interface{})["flavorRef"] = flavorID
|
|
||||||
}
|
|
||||||
delete(reqBody["server"].(map[string]interface{}), "flavorName")
|
|
||||||
|
|
||||||
_, res.Err = client.Post(listURL(client), reqBody, &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete requests that a server previously provisioned be removed from your account.
|
|
||||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
|
||||||
var res DeleteResult
|
|
||||||
_, res.Err = client.Delete(deleteURL(client, id), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func ForceDelete(client *gophercloud.ServiceClient, id string) ActionResult {
|
|
||||||
var req struct {
|
|
||||||
ForceDelete string `json:"forceDelete"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var res ActionResult
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
|
|
||||||
return res
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get requests details on a single server, by ID.
|
|
||||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
|
||||||
var result GetResult
|
|
||||||
_, result.Err = client.Get(getURL(client, id), &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 203},
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
|
|
||||||
type UpdateOptsBuilder interface {
|
|
||||||
ToServerUpdateMap() map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateOpts specifies the base attributes that may be updated on an existing server.
|
|
||||||
type UpdateOpts struct {
|
|
||||||
// Name [optional] changes the displayed name of the server.
|
|
||||||
// The server host name will *not* change.
|
|
||||||
// Server names are not constrained to be unique, even within the same tenant.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
|
|
||||||
AccessIPv4 string
|
|
||||||
|
|
||||||
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
|
|
||||||
AccessIPv6 string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerUpdateMap formats an UpdateOpts structure into a request body.
|
|
||||||
func (opts UpdateOpts) ToServerUpdateMap() map[string]interface{} {
|
|
||||||
server := make(map[string]string)
|
|
||||||
if opts.Name != "" {
|
|
||||||
server["name"] = opts.Name
|
|
||||||
}
|
|
||||||
if opts.AccessIPv4 != "" {
|
|
||||||
server["accessIPv4"] = opts.AccessIPv4
|
|
||||||
}
|
|
||||||
if opts.AccessIPv6 != "" {
|
|
||||||
server["accessIPv6"] = opts.AccessIPv6
|
|
||||||
}
|
|
||||||
return map[string]interface{}{"server": server}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update requests that various attributes of the indicated server be changed.
|
|
||||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) UpdateResult {
|
|
||||||
var result UpdateResult
|
|
||||||
reqBody := opts.ToServerUpdateMap()
|
|
||||||
_, result.Err = client.Put(updateURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangeAdminPassword alters the administrator or root password for a specified server.
|
|
||||||
func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword string) ActionResult {
|
|
||||||
var req struct {
|
|
||||||
ChangePassword struct {
|
|
||||||
AdminPass string `json:"adminPass"`
|
|
||||||
} `json:"changePassword"`
|
|
||||||
}
|
|
||||||
|
|
||||||
req.ChangePassword.AdminPass = newPassword
|
|
||||||
|
|
||||||
var res ActionResult
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrArgument errors occur when an argument supplied to a package function
|
|
||||||
// fails to fall within acceptable values. For example, the Reboot() function
|
|
||||||
// expects the "how" parameter to be one of HardReboot or SoftReboot. These
|
|
||||||
// constants are (currently) strings, leading someone to wonder if they can pass
|
|
||||||
// other string values instead, perhaps in an effort to break the API of their
|
|
||||||
// provider. Reboot() returns this error in this situation.
|
|
||||||
//
|
|
||||||
// Function identifies which function was called/which function is generating
|
|
||||||
// the error.
|
|
||||||
// Argument identifies which formal argument was responsible for producing the
|
|
||||||
// error.
|
|
||||||
// Value provides the value as it was passed into the function.
|
|
||||||
type ErrArgument struct {
|
|
||||||
Function, Argument string
|
|
||||||
Value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error yields a useful diagnostic for debugging purposes.
|
|
||||||
func (e *ErrArgument) Error() string {
|
|
||||||
return fmt.Sprintf("Bad argument in call to %s, formal parameter %s, value %#v", e.Function, e.Argument, e.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrArgument) String() string {
|
|
||||||
return e.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebootMethod describes the mechanisms by which a server reboot can be requested.
|
|
||||||
type RebootMethod string
|
|
||||||
|
|
||||||
// These constants determine how a server should be rebooted.
|
|
||||||
// See the Reboot() function for further details.
|
|
||||||
const (
|
|
||||||
SoftReboot RebootMethod = "SOFT"
|
|
||||||
HardReboot RebootMethod = "HARD"
|
|
||||||
OSReboot = SoftReboot
|
|
||||||
PowerCycle = HardReboot
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reboot requests that a given server reboot.
|
|
||||||
// Two methods exist for rebooting a server:
|
|
||||||
//
|
|
||||||
// HardReboot (aka PowerCycle) restarts 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 restored or the VM instance restarted.
|
|
||||||
//
|
|
||||||
// SoftReboot (aka OSReboot) simply tells the OS to restart under its own procedures.
|
|
||||||
// E.g., in Linux, asking it to enter runlevel 6, or executing "sudo shutdown -r now", or by asking Windows to restart the machine.
|
|
||||||
func Reboot(client *gophercloud.ServiceClient, id string, how RebootMethod) ActionResult {
|
|
||||||
var res ActionResult
|
|
||||||
|
|
||||||
if (how != SoftReboot) && (how != HardReboot) {
|
|
||||||
res.Err = &ErrArgument{
|
|
||||||
Function: "Reboot",
|
|
||||||
Argument: "how",
|
|
||||||
Value: how,
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBody := struct {
|
|
||||||
C map[string]string `json:"reboot"`
|
|
||||||
}{
|
|
||||||
map[string]string{"type": string(how)},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebuildOptsBuilder is an interface that allows extensions to override the
|
|
||||||
// default behaviour of rebuild options
|
|
||||||
type RebuildOptsBuilder interface {
|
|
||||||
ToServerRebuildMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebuildOpts represents the configuration options used in a server rebuild
|
|
||||||
// operation
|
|
||||||
type RebuildOpts struct {
|
|
||||||
// Required. The ID of the image you want your server to be provisioned on
|
|
||||||
ImageID string
|
|
||||||
|
|
||||||
// Name to set the server to
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Required. The server's admin password
|
|
||||||
AdminPass string
|
|
||||||
|
|
||||||
// AccessIPv4 [optional] provides a new IPv4 address for the instance.
|
|
||||||
AccessIPv4 string
|
|
||||||
|
|
||||||
// AccessIPv6 [optional] provides a new IPv6 address for the instance.
|
|
||||||
AccessIPv6 string
|
|
||||||
|
|
||||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the server.
|
|
||||||
Metadata map[string]string
|
|
||||||
|
|
||||||
// Personality [optional] includes files to inject into the server at launch.
|
|
||||||
// Rebuild will base64-encode file contents for you.
|
|
||||||
Personality Personality
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
|
|
||||||
func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
|
|
||||||
var err error
|
|
||||||
server := make(map[string]interface{})
|
|
||||||
|
|
||||||
if opts.AdminPass == "" {
|
|
||||||
err = fmt.Errorf("AdminPass is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.ImageID == "" {
|
|
||||||
err = fmt.Errorf("ImageID is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return server, err
|
|
||||||
}
|
|
||||||
|
|
||||||
server["name"] = opts.Name
|
|
||||||
server["adminPass"] = opts.AdminPass
|
|
||||||
server["imageRef"] = opts.ImageID
|
|
||||||
|
|
||||||
if opts.AccessIPv4 != "" {
|
|
||||||
server["accessIPv4"] = opts.AccessIPv4
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.AccessIPv6 != "" {
|
|
||||||
server["accessIPv6"] = opts.AccessIPv6
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
server["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Personality) > 0 {
|
|
||||||
server["personality"] = opts.Personality
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"rebuild": server}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild will reprovision the server according to the configuration options
|
|
||||||
// provided in the RebuildOpts struct.
|
|
||||||
func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) RebuildResult {
|
|
||||||
var result RebuildResult
|
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
result.Err = fmt.Errorf("ID is required")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBody, err := opts.ToServerRebuildMap()
|
|
||||||
if err != nil {
|
|
||||||
result.Err = err
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, nil)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeOptsBuilder is an interface that allows extensions to override the default structure of
|
|
||||||
// a Resize request.
|
|
||||||
type ResizeOptsBuilder interface {
|
|
||||||
ToServerResizeMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
resize := map[string]interface{}{
|
|
||||||
"flavorRef": opts.FlavorRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"resize": resize}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) ActionResult {
|
|
||||||
var res ActionResult
|
|
||||||
reqBody, err := opts.ToServerResizeMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfirmResize confirms a previous resize operation on a server.
|
|
||||||
// See Resize() for more details.
|
|
||||||
func ConfirmResize(client *gophercloud.ServiceClient, id string) ActionResult {
|
|
||||||
var res ActionResult
|
|
||||||
|
|
||||||
reqBody := map[string]interface{}{"confirmResize": nil}
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{201, 202, 204},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevertResize cancels a previous resize operation on a server.
|
|
||||||
// See Resize() for more details.
|
|
||||||
func RevertResize(client *gophercloud.ServiceClient, id string) ActionResult {
|
|
||||||
var res ActionResult
|
|
||||||
reqBody := map[string]interface{}{"revertResize": nil}
|
|
||||||
_, res.Err = client.Post(actionURL(client, id), reqBody, nil, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// RescueOptsBuilder is an interface that allows extensions to override the
|
|
||||||
// default structure of a Rescue request.
|
|
||||||
type RescueOptsBuilder interface {
|
|
||||||
ToServerRescueMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RescueOpts represents the configuration options used to control a Rescue
|
|
||||||
// option.
|
|
||||||
type RescueOpts struct {
|
|
||||||
// AdminPass is the desired administrative password for the instance in
|
|
||||||
// RESCUE mode. If it's left blank, the server will generate a password.
|
|
||||||
AdminPass string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
|
|
||||||
// request body for the Rescue request.
|
|
||||||
func (opts RescueOpts) ToServerRescueMap() (map[string]interface{}, error) {
|
|
||||||
server := make(map[string]interface{})
|
|
||||||
if opts.AdminPass != "" {
|
|
||||||
server["adminPass"] = opts.AdminPass
|
|
||||||
}
|
|
||||||
return map[string]interface{}{"rescue": server}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rescue instructs the provider to place the server into RESCUE mode.
|
|
||||||
func Rescue(client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) RescueResult {
|
|
||||||
var result RescueResult
|
|
||||||
|
|
||||||
if id == "" {
|
|
||||||
result.Err = fmt.Errorf("ID is required")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
reqBody, err := opts.ToServerRescueMap()
|
|
||||||
if err != nil {
|
|
||||||
result.Err = err
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
_, result.Err = client.Post(actionURL(client, id), reqBody, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Reset request.
|
|
||||||
type ResetMetadataOptsBuilder interface {
|
|
||||||
ToMetadataResetMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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.
|
|
||||||
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.
|
|
||||||
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) ResetMetadataResult {
|
|
||||||
var res ResetMetadataResult
|
|
||||||
metadata, err := opts.ToMetadataResetMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
_, res.Err = client.Put(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata requests all the metadata for the given server ID.
|
|
||||||
func Metadata(client *gophercloud.ServiceClient, id string) GetMetadataResult {
|
|
||||||
var res GetMetadataResult
|
|
||||||
_, res.Err = client.Get(metadataURL(client, id), &res.Body, nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
|
|
||||||
var res UpdateMetadataResult
|
|
||||||
metadata, err := opts.ToMetadataUpdateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
_, res.Err = client.Post(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetadatumOptsBuilder allows extensions to add additional parameters to the
|
|
||||||
// Create request.
|
|
||||||
type MetadatumOptsBuilder interface {
|
|
||||||
ToMetadatumCreateMap() (map[string]interface{}, string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
|
|
||||||
if len(opts) != 1 {
|
|
||||||
return nil, "", errors.New("CreateMetadatum operation must have 1 and only 1 key-value pair.")
|
|
||||||
}
|
|
||||||
metadatum := map[string]interface{}{"meta": opts}
|
|
||||||
var key string
|
|
||||||
for k := range metadatum["meta"].(MetadatumOpts) {
|
|
||||||
key = k
|
|
||||||
}
|
|
||||||
return metadatum, key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) CreateMetadatumResult {
|
|
||||||
var res CreateMetadatumResult
|
|
||||||
metadatum, key, err := opts.ToMetadatumCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
_, res.Err = client.Put(metadatumURL(client, id, key), metadatum, &res.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200},
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadatum requests the key-value pair with the given key for the given server ID.
|
|
||||||
func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult {
|
|
||||||
var res GetMetadatumResult
|
|
||||||
_, res.Err = client.Request("GET", metadatumURL(client, id, key), gophercloud.RequestOpts{
|
|
||||||
JSONResponse: &res.Body,
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMetadatum will delete the key-value pair with the given key for the given server ID.
|
|
||||||
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult {
|
|
||||||
var res DeleteMetadatumResult
|
|
||||||
_, res.Err = client.Delete(metadatumURL(client, id, key), nil)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListAddresses makes a request against the API to list the servers IP addresses.
|
|
||||||
func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
|
||||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return AddressPage{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
return pagination.NewPager(client, listAddressesURL(client, id), createPageFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return NetworkAddressPage{pagination.SinglePageBase(r)}
|
|
||||||
}
|
|
||||||
return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), createPageFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateImageOpts struct {
|
|
||||||
// Name [required] of the image/snapshot
|
|
||||||
Name string
|
|
||||||
// Metadata [optional] contains key-value pairs (up to 255 bytes each) to attach to the created image.
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateImageOptsBuilder interface {
|
|
||||||
ToServerCreateImageMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToServerCreateImageMap formats a CreateImageOpts structure into a request body.
|
|
||||||
func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]interface{}, error) {
|
|
||||||
var err error
|
|
||||||
img := make(map[string]interface{})
|
|
||||||
if opts.Name == "" {
|
|
||||||
return nil, fmt.Errorf("Cannot create a server image without a name")
|
|
||||||
}
|
|
||||||
img["name"] = opts.Name
|
|
||||||
if opts.Metadata != nil {
|
|
||||||
img["metadata"] = opts.Metadata
|
|
||||||
}
|
|
||||||
createImage := make(map[string]interface{})
|
|
||||||
createImage["createImage"] = img
|
|
||||||
return createImage, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateImage makes a request against the nova API to schedule an image to be created of the server
|
|
||||||
func CreateImage(client *gophercloud.ServiceClient, serverId string, opts CreateImageOptsBuilder) CreateImageResult {
|
|
||||||
var res CreateImageResult
|
|
||||||
reqBody, err := opts.ToServerCreateImageMap()
|
|
||||||
if err != nil {
|
|
||||||
res.Err = err
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
response, err := client.Post(actionURL(client, serverId), reqBody, nil, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{202},
|
|
||||||
})
|
|
||||||
res.Err = err
|
|
||||||
res.Header = response.Header
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromName is a convienience function that returns a server's ID given its name.
|
|
||||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
|
||||||
serverCount := 0
|
|
||||||
serverID := ""
|
|
||||||
if name == "" {
|
|
||||||
return "", fmt.Errorf("A server name must be provided.")
|
|
||||||
}
|
|
||||||
pager := List(client, nil)
|
|
||||||
pager.EachPage(func(page pagination.Page) (bool, error) {
|
|
||||||
serverList, err := ExtractServers(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range serverList {
|
|
||||||
if s.Name == name {
|
|
||||||
serverCount++
|
|
||||||
serverID = s.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
switch serverCount {
|
|
||||||
case 0:
|
|
||||||
return "", fmt.Errorf("Unable to find server: %s", name)
|
|
||||||
case 1:
|
|
||||||
return serverID, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Found %d servers matching %s", serverCount, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPassword makes a request against the nova API to get the encrypted administrative password.
|
|
||||||
func GetPassword(client *gophercloud.ServiceClient, serverId string) GetPasswordResult {
|
|
||||||
var res GetPasswordResult
|
|
||||||
_, res.Err = client.Request("GET", passwordURL(client, serverId), gophercloud.RequestOpts{
|
|
||||||
JSONResponse: &res.Body,
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
415
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
415
vendor/github.com/rackspace/gophercloud/openstack/compute/v2/servers/results.go
generated
vendored
|
@ -1,415 +0,0 @@
|
||||||
package servers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rsa"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
type serverResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract interprets any serverResult as a Server, if possible.
|
|
||||||
func (r serverResult) Extract() (*Server, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Server Server `mapstructure:"server"`
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: toMapFromString,
|
|
||||||
Result: &response,
|
|
||||||
}
|
|
||||||
decoder, err := mapstructure.NewDecoder(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateResult temporarily contains the response from a Create call.
|
|
||||||
type CreateResult struct {
|
|
||||||
serverResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult temporarily contains the response from a Get call.
|
|
||||||
type GetResult struct {
|
|
||||||
serverResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateResult temporarily contains the response from an Update call.
|
|
||||||
type UpdateResult struct {
|
|
||||||
serverResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResult temporarily contains the response from a Delete call.
|
|
||||||
type DeleteResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebuildResult temporarily contains the response from a Rebuild call.
|
|
||||||
type RebuildResult struct {
|
|
||||||
serverResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActionResult represents the result of server action operations, like reboot
|
|
||||||
type ActionResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// RescueResult represents the result of a server rescue operation
|
|
||||||
type RescueResult struct {
|
|
||||||
ActionResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateImageResult represents the result of an image creation operation
|
|
||||||
type CreateImageResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPasswordResult represent the result of a get os-server-password operation.
|
|
||||||
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:
|
|
||||||
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
|
|
||||||
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
|
|
||||||
|
|
||||||
if r.Err != nil {
|
|
||||||
return "", r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Password string `mapstructure:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &response)
|
|
||||||
if err == nil && privateKey != nil && response.Password != "" {
|
|
||||||
return decryptPassword(response.Password, privateKey)
|
|
||||||
}
|
|
||||||
return response.Password, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
|
|
||||||
b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
|
|
||||||
|
|
||||||
n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
|
|
||||||
}
|
|
||||||
password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Failed to decrypt password: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(password), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractImageID gets the ID of the newly created server image from the header
|
|
||||||
func (res CreateImageResult) ExtractImageID() (string, error) {
|
|
||||||
if res.Err != nil {
|
|
||||||
return "", res.Err
|
|
||||||
}
|
|
||||||
// Get the image id from the header
|
|
||||||
u, err := url.ParseRequestURI(res.Header.Get("Location"))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("Failed to parse the image id: %s", err.Error())
|
|
||||||
}
|
|
||||||
imageId := path.Base(u.Path)
|
|
||||||
if imageId == "." || imageId == "/" {
|
|
||||||
return "", fmt.Errorf("Failed to parse the ID of newly created image: %s", u)
|
|
||||||
}
|
|
||||||
return imageId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract interprets any RescueResult as an AdminPass, if possible.
|
|
||||||
func (r RescueResult) Extract() (string, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return "", r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
AdminPass string `mapstructure:"adminPass"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &response)
|
|
||||||
return response.AdminPass, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
|
|
||||||
type Server struct {
|
|
||||||
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// TenantID identifies the tenant owning this server resource.
|
|
||||||
TenantID string `mapstructure:"tenant_id"`
|
|
||||||
|
|
||||||
// UserID uniquely identifies the user account owning the tenant.
|
|
||||||
UserID string `mapstructure:"user_id"`
|
|
||||||
|
|
||||||
// Name contains the human-readable name for the server.
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
|
|
||||||
Updated string
|
|
||||||
Created string
|
|
||||||
|
|
||||||
HostID string
|
|
||||||
|
|
||||||
// Status contains the current operational status of the server, such as IN_PROGRESS or ACTIVE.
|
|
||||||
Status string
|
|
||||||
|
|
||||||
// Progress ranges from 0..100.
|
|
||||||
// A request made against the server completes only once Progress reaches 100.
|
|
||||||
Progress int
|
|
||||||
|
|
||||||
// AccessIPv4 and AccessIPv6 contain the IP addresses of the server, suitable for remote access for administration.
|
|
||||||
AccessIPv4, AccessIPv6 string
|
|
||||||
|
|
||||||
// Image refers to a JSON object, which itself indicates the OS image used to deploy the server.
|
|
||||||
Image map[string]interface{}
|
|
||||||
|
|
||||||
// Flavor refers to a JSON object, which itself indicates the hardware configuration of the deployed server.
|
|
||||||
Flavor map[string]interface{}
|
|
||||||
|
|
||||||
// Addresses includes a list of all IP addresses assigned to the server, keyed by pool.
|
|
||||||
Addresses map[string]interface{}
|
|
||||||
|
|
||||||
// Metadata includes a list of all user-specified key-value pairs attached to the server.
|
|
||||||
Metadata map[string]interface{}
|
|
||||||
|
|
||||||
// Links includes HTTP references to the itself, useful for passing along to other APIs that might want a server reference.
|
|
||||||
Links []interface{}
|
|
||||||
|
|
||||||
// KeyName indicates which public key was injected into the server on launch.
|
|
||||||
KeyName string `json:"key_name" mapstructure:"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.
|
|
||||||
// Note that this is the ONLY time this field will be valid.
|
|
||||||
AdminPass string `json:"adminPass" mapstructure:"adminPass"`
|
|
||||||
|
|
||||||
// SecurityGroups includes the security groups that this instance has applied to it
|
|
||||||
SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if a page contains no Server results.
|
|
||||||
func (page ServerPage) IsEmpty() (bool, error) {
|
|
||||||
servers, err := ExtractServers(page)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(servers) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
|
||||||
func (page ServerPage) NextPageURL() (string, error) {
|
|
||||||
type resp struct {
|
|
||||||
Links []gophercloud.Link `mapstructure:"servers_links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var r resp
|
|
||||||
err := mapstructure.Decode(page.Body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gophercloud.ExtractNextURL(r.Links)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractServers interprets the results of a single page from a List() call, producing a slice of Server entities.
|
|
||||||
func ExtractServers(page pagination.Page) ([]Server, error) {
|
|
||||||
casted := page.(ServerPage).Body
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Servers []Server `mapstructure:"servers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &mapstructure.DecoderConfig{
|
|
||||||
DecodeHook: toMapFromString,
|
|
||||||
Result: &response,
|
|
||||||
}
|
|
||||||
decoder, err := mapstructure.NewDecoder(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = decoder.Decode(casted)
|
|
||||||
|
|
||||||
return response.Servers, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
|
|
||||||
type MetadataResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetadataResult temporarily contains the response from a metadata Get call.
|
|
||||||
type GetMetadataResult struct {
|
|
||||||
MetadataResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetMetadataResult temporarily contains the response from a metadata Reset call.
|
|
||||||
type ResetMetadataResult struct {
|
|
||||||
MetadataResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMetadataResult temporarily contains the response from a metadata Update call.
|
|
||||||
type UpdateMetadataResult struct {
|
|
||||||
MetadataResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
type GetMetadatumResult struct {
|
|
||||||
MetadatumResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateMetadatumResult temporarily contains the response from a metadatum Create call.
|
|
||||||
type CreateMetadatumResult struct {
|
|
||||||
MetadatumResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteMetadatumResult temporarily contains the response from a metadatum Delete call.
|
|
||||||
type DeleteMetadatumResult struct {
|
|
||||||
gophercloud.ErrResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract interprets any MetadataResult as a Metadata, if possible.
|
|
||||||
func (r MetadataResult) Extract() (map[string]string, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Metadata map[string]string `mapstructure:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &response)
|
|
||||||
return response.Metadata, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract interprets any MetadatumResult as a Metadatum, if possible.
|
|
||||||
func (r MetadatumResult) Extract() (map[string]string, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Metadatum map[string]string `mapstructure:"meta"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &response)
|
|
||||||
return response.Metadatum, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func toMapFromString(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) {
|
|
||||||
if (from == reflect.String) && (to == reflect.Map) {
|
|
||||||
return map[string]interface{}{}, nil
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address represents an IP address.
|
|
||||||
type Address struct {
|
|
||||||
Version int `mapstructure:"version"`
|
|
||||||
Address string `mapstructure:"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.
|
|
||||||
type AddressPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if an AddressPage contains no networks.
|
|
||||||
func (r AddressPage) IsEmpty() (bool, error) {
|
|
||||||
addresses, err := ExtractAddresses(r)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(addresses) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractAddresses interprets the results of a single page from a ListAddresses() call,
|
|
||||||
// producing a map of addresses.
|
|
||||||
func ExtractAddresses(page pagination.Page) (map[string][]Address, error) {
|
|
||||||
casted := page.(AddressPage).Body
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Addresses map[string][]Address `mapstructure:"addresses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(casted, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.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.
|
|
||||||
type NetworkAddressPage struct {
|
|
||||||
pagination.SinglePageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty returns true if a NetworkAddressPage contains no addresses.
|
|
||||||
func (r NetworkAddressPage) IsEmpty() (bool, error) {
|
|
||||||
addresses, err := ExtractNetworkAddresses(r)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
return len(addresses) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractNetworkAddresses interprets the results of a single page from a ListAddressesByNetwork() call,
|
|
||||||
// producing a slice of addresses.
|
|
||||||
func ExtractNetworkAddresses(page pagination.Page) ([]Address, error) {
|
|
||||||
casted := page.(NetworkAddressPage).Body
|
|
||||||
|
|
||||||
var response map[string][]Address
|
|
||||||
err := mapstructure.Decode(casted, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key string
|
|
||||||
for k := range response {
|
|
||||||
key = k
|
|
||||||
}
|
|
||||||
|
|
||||||
return response[key], err
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package servers
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func createURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("servers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func listURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return createURL(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("servers", "detail")
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(client, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return deleteURL(client, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func actionURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id, "action")
|
|
||||||
}
|
|
||||||
|
|
||||||
func metadatumURL(client *gophercloud.ServiceClient, id, key string) string {
|
|
||||||
return client.ServiceURL("servers", id, "metadata", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func metadataURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id, "metadata")
|
|
||||||
}
|
|
||||||
|
|
||||||
func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id, "ips")
|
|
||||||
}
|
|
||||||
|
|
||||||
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
|
|
||||||
return client.ServiceURL("servers", id, "ips", network)
|
|
||||||
}
|
|
||||||
|
|
||||||
func passwordURL(client *gophercloud.ServiceClient, id string) string {
|
|
||||||
return client.ServiceURL("servers", id, "os-server-password")
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package servers
|
|
||||||
|
|
||||||
import "github.com/rackspace/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.
|
|
||||||
func WaitForStatus(c *gophercloud.ServiceClient, id, status string, secs int) error {
|
|
||||||
return gophercloud.WaitFor(secs, func() (bool, error) {
|
|
||||||
current, err := Get(c, id).Extract()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if current.Status == status {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package openstack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
|
|
||||||
tokens3 "github.com/rackspace/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.
|
|
||||||
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)
|
|
||||||
for _, entry := range catalog.Entries {
|
|
||||||
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
|
||||||
for _, endpoint := range entry.Endpoints {
|
|
||||||
if opts.Region == "" || endpoint.Region == opts.Region {
|
|
||||||
endpoints = append(endpoints, endpoint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report an error if the options were ambiguous.
|
|
||||||
if len(endpoints) > 1 {
|
|
||||||
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the appropriate URL from the matching Endpoint.
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
switch opts.Availability {
|
|
||||||
case gophercloud.AvailabilityPublic:
|
|
||||||
return gophercloud.NormalizeURL(endpoint.PublicURL), nil
|
|
||||||
case gophercloud.AvailabilityInternal:
|
|
||||||
return gophercloud.NormalizeURL(endpoint.InternalURL), nil
|
|
||||||
case gophercloud.AvailabilityAdmin:
|
|
||||||
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report an error if there were no matching endpoints.
|
|
||||||
return "", gophercloud.ErrEndpointNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
var endpoints = make([]tokens3.Endpoint, 0, 1)
|
|
||||||
for _, entry := range catalog.Entries {
|
|
||||||
if (entry.Type == opts.Type) && (opts.Name == "" || entry.Name == opts.Name) {
|
|
||||||
for _, endpoint := range entry.Endpoints {
|
|
||||||
if opts.Availability != gophercloud.AvailabilityAdmin &&
|
|
||||||
opts.Availability != gophercloud.AvailabilityPublic &&
|
|
||||||
opts.Availability != gophercloud.AvailabilityInternal {
|
|
||||||
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
|
|
||||||
}
|
|
||||||
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
|
|
||||||
(opts.Region == "" || endpoint.Region == opts.Region) {
|
|
||||||
endpoints = append(endpoints, endpoint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report an error if the options were ambiguous.
|
|
||||||
if len(endpoints) > 1 {
|
|
||||||
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the URL from the matching Endpoint.
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
return gophercloud.NormalizeURL(endpoint.URL), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report an error if there were no matching endpoints.
|
|
||||||
return "", gophercloud.ErrEndpointNotFound
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
// 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
|
|
65
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/fixtures.go
generated
vendored
65
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/fixtures.go
generated
vendored
|
@ -1,65 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package tenants
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
"github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOutput provides a single page of Tenant results.
|
|
||||||
const ListOutput = `
|
|
||||||
{
|
|
||||||
"tenants": [
|
|
||||||
{
|
|
||||||
"id": "1234",
|
|
||||||
"name": "Red Team",
|
|
||||||
"description": "The team that is red",
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9876",
|
|
||||||
"name": "Blue Team",
|
|
||||||
"description": "The team that is blue",
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// RedTeam is a Tenant fixture.
|
|
||||||
var RedTeam = Tenant{
|
|
||||||
ID: "1234",
|
|
||||||
Name: "Red Team",
|
|
||||||
Description: "The team that is red",
|
|
||||||
Enabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlueTeam is a Tenant fixture.
|
|
||||||
var BlueTeam = Tenant{
|
|
||||||
ID: "9876",
|
|
||||||
Name: "Blue Team",
|
|
||||||
Description: "The team that is blue",
|
|
||||||
Enabled: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedTenantSlice is the slice of tenants expected to be returned from ListOutput.
|
|
||||||
var ExpectedTenantSlice = []Tenant{RedTeam, BlueTeam}
|
|
||||||
|
|
||||||
// HandleListTenantsSuccessfully creates an HTTP handler at `/tenants` on the test handler mux that
|
|
||||||
// responds with a list of two tenants.
|
|
||||||
func HandleListTenantsSuccessfully(t *testing.T) {
|
|
||||||
th.Mux.HandleFunc("/tenants", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, ListOutput)
|
|
||||||
})
|
|
||||||
}
|
|
33
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
33
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/requests.go
generated
vendored
|
@ -1,33 +0,0 @@
|
||||||
package tenants
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListOpts filters the Tenants that are returned by the List call.
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// List enumerates the Tenants to which the current token has access.
|
|
||||||
func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
|
|
||||||
createPage := func(r pagination.PageResult) pagination.Page {
|
|
||||||
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
|
|
||||||
}
|
|
||||||
|
|
||||||
url := listURL(client)
|
|
||||||
if opts != nil {
|
|
||||||
q, err := gophercloud.BuildQueryString(opts)
|
|
||||||
if err != nil {
|
|
||||||
return pagination.Pager{Err: err}
|
|
||||||
}
|
|
||||||
url += q.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination.NewPager(client, url, createPage)
|
|
||||||
}
|
|
62
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
62
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
|
@ -1,62 +0,0 @@
|
||||||
package tenants
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/pagination"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tenant is a grouping of users in the identity service.
|
|
||||||
type Tenant struct {
|
|
||||||
// ID is a unique identifier for this tenant.
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Name is a friendlier user-facing name for this tenant.
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
|
|
||||||
// Description is a human-readable explanation of this Tenant's purpose.
|
|
||||||
Description string `mapstructure:"description"`
|
|
||||||
|
|
||||||
// Enabled indicates whether or not a tenant is active.
|
|
||||||
Enabled bool `mapstructure:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TenantPage is a single page of Tenant results.
|
|
||||||
type TenantPage struct {
|
|
||||||
pagination.LinkedPageBase
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEmpty determines whether or not a page of Tenants contains any results.
|
|
||||||
func (page TenantPage) IsEmpty() (bool, error) {
|
|
||||||
tenants, err := ExtractTenants(page)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return len(tenants) == 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextPageURL extracts the "next" link from the tenants_links section of the result.
|
|
||||||
func (page TenantPage) NextPageURL() (string, error) {
|
|
||||||
type resp struct {
|
|
||||||
Links []gophercloud.Link `mapstructure:"tenants_links"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var r resp
|
|
||||||
err := mapstructure.Decode(page.Body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gophercloud.ExtractNextURL(r.Links)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractTenants returns a slice of Tenants contained in a single page of results.
|
|
||||||
func ExtractTenants(page pagination.Page) ([]Tenant, error) {
|
|
||||||
casted := page.(TenantPage).Body
|
|
||||||
var response struct {
|
|
||||||
Tenants []Tenant `mapstructure:"tenants"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(casted, &response)
|
|
||||||
return response.Tenants, err
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package tenants
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func listURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("tenants")
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
// 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
|
|
30
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/errors.go
generated
vendored
30
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/errors.go
generated
vendored
|
@ -1,30 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrUserIDProvided is returned if you attempt to authenticate with a UserID.
|
|
||||||
ErrUserIDProvided = unacceptedAttributeErr("UserID")
|
|
||||||
|
|
||||||
// ErrAPIKeyProvided is returned if you attempt to authenticate with an APIKey.
|
|
||||||
ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
|
|
||||||
|
|
||||||
// ErrDomainIDProvided is returned if you attempt to authenticate with a DomainID.
|
|
||||||
ErrDomainIDProvided = unacceptedAttributeErr("DomainID")
|
|
||||||
|
|
||||||
// ErrDomainNameProvided is returned if you attempt to authenticate with a DomainName.
|
|
||||||
ErrDomainNameProvided = unacceptedAttributeErr("DomainName")
|
|
||||||
|
|
||||||
// ErrUsernameRequired is returned if you attempt to authenticate without a Username.
|
|
||||||
ErrUsernameRequired = errors.New("You must supply a Username in your AuthOptions.")
|
|
||||||
|
|
||||||
// ErrPasswordRequired is returned if you don't provide a password.
|
|
||||||
ErrPasswordRequired = errors.New("Please supply a Password in your AuthOptions.")
|
|
||||||
)
|
|
||||||
|
|
||||||
func unacceptedAttributeErr(attribute string) error {
|
|
||||||
return fmt.Errorf("The base Identity V2 API does not accept authentication by %s", attribute)
|
|
||||||
}
|
|
195
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/fixtures.go
generated
vendored
195
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/fixtures.go
generated
vendored
|
@ -1,195 +0,0 @@
|
||||||
// +build fixtures
|
|
||||||
|
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
|
|
||||||
th "github.com/rackspace/gophercloud/testhelper"
|
|
||||||
thclient "github.com/rackspace/gophercloud/testhelper/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExpectedToken is the token that should be parsed from TokenCreationResponse.
|
|
||||||
var ExpectedToken = &Token{
|
|
||||||
ID: "aaaabbbbccccdddd",
|
|
||||||
ExpiresAt: time.Date(2014, time.January, 31, 15, 30, 58, 0, time.UTC),
|
|
||||||
Tenant: tenants.Tenant{
|
|
||||||
ID: "fc394f2ab2df4114bde39905f800dc57",
|
|
||||||
Name: "test",
|
|
||||||
Description: "There are many tenants. This one is yours.",
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedServiceCatalog is the service catalog that should be parsed from TokenCreationResponse.
|
|
||||||
var ExpectedServiceCatalog = &ServiceCatalog{
|
|
||||||
Entries: []CatalogEntry{
|
|
||||||
CatalogEntry{
|
|
||||||
Name: "inscrutablewalrus",
|
|
||||||
Type: "something",
|
|
||||||
Endpoints: []Endpoint{
|
|
||||||
Endpoint{
|
|
||||||
PublicURL: "http://something0:1234/v2/",
|
|
||||||
Region: "region0",
|
|
||||||
},
|
|
||||||
Endpoint{
|
|
||||||
PublicURL: "http://something1:1234/v2/",
|
|
||||||
Region: "region1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CatalogEntry{
|
|
||||||
Name: "arbitrarypenguin",
|
|
||||||
Type: "else",
|
|
||||||
Endpoints: []Endpoint{
|
|
||||||
Endpoint{
|
|
||||||
PublicURL: "http://else0:4321/v3/",
|
|
||||||
Region: "region0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpectedUser is the token that should be parsed from TokenGetResponse.
|
|
||||||
var ExpectedUser = &User{
|
|
||||||
ID: "a530fefc3d594c4ba2693a4ecd6be74e",
|
|
||||||
Name: "apiserver",
|
|
||||||
Roles: []Role{{"member"}, {"service"}},
|
|
||||||
UserName: "apiserver",
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenCreationResponse is a JSON response that contains ExpectedToken and ExpectedServiceCatalog.
|
|
||||||
const TokenCreationResponse = `
|
|
||||||
{
|
|
||||||
"access": {
|
|
||||||
"token": {
|
|
||||||
"issued_at": "2014-01-30T15:30:58.000000Z",
|
|
||||||
"expires": "2014-01-31T15:30:58Z",
|
|
||||||
"id": "aaaabbbbccccdddd",
|
|
||||||
"tenant": {
|
|
||||||
"description": "There are many tenants. This one is yours.",
|
|
||||||
"enabled": true,
|
|
||||||
"id": "fc394f2ab2df4114bde39905f800dc57",
|
|
||||||
"name": "test"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serviceCatalog": [
|
|
||||||
{
|
|
||||||
"endpoints": [
|
|
||||||
{
|
|
||||||
"publicURL": "http://something0:1234/v2/",
|
|
||||||
"region": "region0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"publicURL": "http://something1:1234/v2/",
|
|
||||||
"region": "region1"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "something",
|
|
||||||
"name": "inscrutablewalrus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"endpoints": [
|
|
||||||
{
|
|
||||||
"publicURL": "http://else0:4321/v3/",
|
|
||||||
"region": "region0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "else",
|
|
||||||
"name": "arbitrarypenguin"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// TokenGetResponse is a JSON response that contains ExpectedToken and ExpectedUser.
|
|
||||||
const TokenGetResponse = `
|
|
||||||
{
|
|
||||||
"access": {
|
|
||||||
"token": {
|
|
||||||
"issued_at": "2014-01-30T15:30:58.000000Z",
|
|
||||||
"expires": "2014-01-31T15:30:58Z",
|
|
||||||
"id": "aaaabbbbccccdddd",
|
|
||||||
"tenant": {
|
|
||||||
"description": "There are many tenants. This one is yours.",
|
|
||||||
"enabled": true,
|
|
||||||
"id": "fc394f2ab2df4114bde39905f800dc57",
|
|
||||||
"name": "test"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"serviceCatalog": [],
|
|
||||||
"user": {
|
|
||||||
"id": "a530fefc3d594c4ba2693a4ecd6be74e",
|
|
||||||
"name": "apiserver",
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"name": "member"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "service"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"roles_links": [],
|
|
||||||
"username": "apiserver"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
// HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been
|
|
||||||
// constructed properly given certain auth options, and returns the result.
|
|
||||||
func HandleTokenPost(t *testing.T, requestJSON string) {
|
|
||||||
th.Mux.HandleFunc("/tokens", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "POST")
|
|
||||||
th.TestHeader(t, r, "Content-Type", "application/json")
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
if requestJSON != "" {
|
|
||||||
th.TestJSONRequest(t, r, requestJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, TokenCreationResponse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been
|
|
||||||
// constructed properly given certain auth options, and returns the result.
|
|
||||||
func HandleTokenGet(t *testing.T, token string) {
|
|
||||||
th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
th.TestMethod(t, r, "GET")
|
|
||||||
th.TestHeader(t, r, "Accept", "application/json")
|
|
||||||
th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID)
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, TokenGetResponse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccessful ensures that a CreateResult was successful and contains the correct token and
|
|
||||||
// service catalog.
|
|
||||||
func IsSuccessful(t *testing.T, result CreateResult) {
|
|
||||||
token, err := result.ExtractToken()
|
|
||||||
th.AssertNoErr(t, err)
|
|
||||||
th.CheckDeepEquals(t, ExpectedToken, token)
|
|
||||||
|
|
||||||
serviceCatalog, err := result.ExtractServiceCatalog()
|
|
||||||
th.AssertNoErr(t, err)
|
|
||||||
th.CheckDeepEquals(t, ExpectedServiceCatalog, serviceCatalog)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIsSuccessful ensures that a GetResult was successful and contains the correct token and
|
|
||||||
// User Info.
|
|
||||||
func GetIsSuccessful(t *testing.T, result GetResult) {
|
|
||||||
token, err := result.ExtractToken()
|
|
||||||
th.AssertNoErr(t, err)
|
|
||||||
th.CheckDeepEquals(t, ExpectedToken, token)
|
|
||||||
|
|
||||||
user, err := result.ExtractUser()
|
|
||||||
th.AssertNoErr(t, err)
|
|
||||||
th.CheckDeepEquals(t, ExpectedUser, user)
|
|
||||||
}
|
|
99
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
99
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
|
@ -1,99 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
|
||||||
type AuthOptionsBuilder interface {
|
|
||||||
|
|
||||||
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
|
|
||||||
// missing or inconsistent.
|
|
||||||
ToTokenCreateMap() (map[string]interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthOptions wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
|
|
||||||
// interface.
|
|
||||||
type AuthOptions struct {
|
|
||||||
gophercloud.AuthOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapOptions embeds a root AuthOptions struct in a package-specific one.
|
|
||||||
func WrapOptions(original gophercloud.AuthOptions) AuthOptions {
|
|
||||||
return AuthOptions{AuthOptions: original}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToTokenCreateMap converts AuthOptions into nested maps that can be serialized into a JSON
|
|
||||||
// request.
|
|
||||||
func (auth AuthOptions) ToTokenCreateMap() (map[string]interface{}, error) {
|
|
||||||
// Error out if an unsupported auth option is present.
|
|
||||||
if auth.UserID != "" {
|
|
||||||
return nil, ErrUserIDProvided
|
|
||||||
}
|
|
||||||
if auth.APIKey != "" {
|
|
||||||
return nil, ErrAPIKeyProvided
|
|
||||||
}
|
|
||||||
if auth.DomainID != "" {
|
|
||||||
return nil, ErrDomainIDProvided
|
|
||||||
}
|
|
||||||
if auth.DomainName != "" {
|
|
||||||
return nil, ErrDomainNameProvided
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the request map.
|
|
||||||
authMap := make(map[string]interface{})
|
|
||||||
|
|
||||||
if auth.Username != "" {
|
|
||||||
if auth.Password != "" {
|
|
||||||
authMap["passwordCredentials"] = map[string]interface{}{
|
|
||||||
"username": auth.Username,
|
|
||||||
"password": auth.Password,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrPasswordRequired
|
|
||||||
}
|
|
||||||
} else if auth.TokenID != "" {
|
|
||||||
authMap["token"] = map[string]interface{}{
|
|
||||||
"id": auth.TokenID,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("You must provide either username/password or tenantID/token values.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.TenantID != "" {
|
|
||||||
authMap["tenantId"] = auth.TenantID
|
|
||||||
}
|
|
||||||
if auth.TenantName != "" {
|
|
||||||
authMap["tenantName"] = auth.TenantName
|
|
||||||
}
|
|
||||||
|
|
||||||
return map[string]interface{}{"auth": authMap}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) CreateResult {
|
|
||||||
request, err := auth.ToTokenCreateMap()
|
|
||||||
if err != nil {
|
|
||||||
return CreateResult{gophercloud.Result{Err: err}}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result CreateResult
|
|
||||||
_, result.Err = client.Post(CreateURL(client), request, &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 203},
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates and retrieves information for user's token.
|
|
||||||
func Get(client *gophercloud.ServiceClient, token string) GetResult {
|
|
||||||
var result GetResult
|
|
||||||
_, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
|
|
||||||
OkCodes: []int{200, 203},
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
170
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
170
vendor/github.com/rackspace/gophercloud/openstack/identity/v2/tokens/results.go
generated
vendored
|
@ -1,170 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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.
|
|
||||||
// See the AuthOptions structure for more details.
|
|
||||||
ExpiresAt time.Time
|
|
||||||
|
|
||||||
// Tenant provides information about the tenant to which this token grants access.
|
|
||||||
Tenant tenants.Tenant
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization need user info which can get from token authentication's response
|
|
||||||
type Role struct {
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
}
|
|
||||||
type User struct {
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
Name string `mapstructure:"name"`
|
|
||||||
UserName string `mapstructure:"username"`
|
|
||||||
Roles []Role `mapstructure:"roles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// 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 all cases, fields which aren't supported by the provider and service combined will assume a zero-value ("").
|
|
||||||
type Endpoint struct {
|
|
||||||
TenantID string `mapstructure:"tenantId"`
|
|
||||||
PublicURL string `mapstructure:"publicURL"`
|
|
||||||
InternalURL string `mapstructure:"internalURL"`
|
|
||||||
AdminURL string `mapstructure:"adminURL"`
|
|
||||||
Region string `mapstructure:"region"`
|
|
||||||
VersionID string `mapstructure:"versionId"`
|
|
||||||
VersionInfo string `mapstructure:"versionInfo"`
|
|
||||||
VersionList string `mapstructure:"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.
|
|
||||||
//
|
|
||||||
// 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 `mapstructure:"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 string `mapstructure:"type"`
|
|
||||||
|
|
||||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
|
||||||
// the service.
|
|
||||||
Endpoints []Endpoint `mapstructure:"endpoints"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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.
|
|
||||||
type GetResult struct {
|
|
||||||
CreateResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractToken returns the just-created Token from a CreateResult.
|
|
||||||
func (result CreateResult) ExtractToken() (*Token, error) {
|
|
||||||
if result.Err != nil {
|
|
||||||
return nil, result.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Access struct {
|
|
||||||
Token struct {
|
|
||||||
Expires string `mapstructure:"expires"`
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
Tenant tenants.Tenant `mapstructure:"tenant"`
|
|
||||||
} `mapstructure:"token"`
|
|
||||||
} `mapstructure:"access"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(result.Body, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, response.Access.Token.Expires)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Token{
|
|
||||||
ID: response.Access.Token.ID,
|
|
||||||
ExpiresAt: expiresTs,
|
|
||||||
Tenant: response.Access.Token.Tenant,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
|
|
||||||
func (result CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
|
||||||
if result.Err != nil {
|
|
||||||
return nil, result.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Access struct {
|
|
||||||
Entries []CatalogEntry `mapstructure:"serviceCatalog"`
|
|
||||||
} `mapstructure:"access"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(result.Body, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ServiceCatalog{Entries: response.Access.Entries}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createErr quickly packs an error in a CreateResult.
|
|
||||||
func createErr(err error) CreateResult {
|
|
||||||
return CreateResult{gophercloud.Result{Err: err}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractUser returns the User from a GetResult.
|
|
||||||
func (result GetResult) ExtractUser() (*User, error) {
|
|
||||||
if result.Err != nil {
|
|
||||||
return nil, result.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Access struct {
|
|
||||||
User User `mapstructure:"user"`
|
|
||||||
} `mapstructure:"access"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(result.Body, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &response.Access.User, nil
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
// CreateURL generates the URL used to create new Tokens.
|
|
||||||
func CreateURL(client *gophercloud.ServiceClient) string {
|
|
||||||
return client.ServiceURL("tokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetURL generates the URL used to Validate Tokens.
|
|
||||||
func GetURL(client *gophercloud.ServiceClient, token string) string {
|
|
||||||
return client.ServiceURL("tokens", token)
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
// 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
|
|
72
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/errors.go
generated
vendored
72
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/errors.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func unacceptedAttributeErr(attribute string) error {
|
|
||||||
return fmt.Errorf("The base Identity V3 API does not accept authentication by %s", attribute)
|
|
||||||
}
|
|
||||||
|
|
||||||
func redundantWithTokenErr(attribute string) error {
|
|
||||||
return fmt.Errorf("%s may not be provided when authenticating with a TokenID", attribute)
|
|
||||||
}
|
|
||||||
|
|
||||||
func redundantWithUserID(attribute string) error {
|
|
||||||
return fmt.Errorf("%s may not be provided when authenticating with a UserID", attribute)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
|
|
||||||
ErrAPIKeyProvided = unacceptedAttributeErr("APIKey")
|
|
||||||
|
|
||||||
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
|
|
||||||
ErrTenantIDProvided = unacceptedAttributeErr("TenantID")
|
|
||||||
|
|
||||||
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
|
|
||||||
ErrTenantNameProvided = unacceptedAttributeErr("TenantName")
|
|
||||||
|
|
||||||
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
|
|
||||||
ErrUsernameWithToken = redundantWithTokenErr("Username")
|
|
||||||
|
|
||||||
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
|
|
||||||
ErrUserIDWithToken = redundantWithTokenErr("UserID")
|
|
||||||
|
|
||||||
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
|
|
||||||
ErrDomainIDWithToken = redundantWithTokenErr("DomainID")
|
|
||||||
|
|
||||||
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
|
|
||||||
ErrDomainNameWithToken = redundantWithTokenErr("DomainName")
|
|
||||||
|
|
||||||
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
|
|
||||||
ErrUsernameOrUserID = errors.New("Exactly one of Username and UserID must be provided for password authentication")
|
|
||||||
|
|
||||||
// ErrDomainIDWithUserID indicates that a DomainID was provided, but unnecessary because a UserID is being used.
|
|
||||||
ErrDomainIDWithUserID = redundantWithUserID("DomainID")
|
|
||||||
|
|
||||||
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
|
|
||||||
ErrDomainNameWithUserID = redundantWithUserID("DomainName")
|
|
||||||
|
|
||||||
// ErrDomainIDOrDomainName indicates that a username was provided, but no domain to scope it.
|
|
||||||
// It may also indicate that both a DomainID and a DomainName were provided at once.
|
|
||||||
ErrDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName to authenticate by Username")
|
|
||||||
|
|
||||||
// ErrMissingPassword indicates that no password and no token were provided and no token is available.
|
|
||||||
ErrMissingPassword = errors.New("You must provide a password or a token to authenticate")
|
|
||||||
|
|
||||||
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
|
|
||||||
ErrScopeDomainIDOrDomainName = errors.New("You must provide exactly one of DomainID or DomainName in a Scope with ProjectName")
|
|
||||||
|
|
||||||
// ErrScopeProjectIDOrProjectName indicates that both a ProjectID and a ProjectName were provided in a Scope.
|
|
||||||
ErrScopeProjectIDOrProjectName = errors.New("You must provide at most one of ProjectID or ProjectName in a Scope")
|
|
||||||
|
|
||||||
// ErrScopeProjectIDAlone indicates that a ProjectID was provided with other constraints in a Scope.
|
|
||||||
ErrScopeProjectIDAlone = errors.New("ProjectID must be supplied alone in a Scope")
|
|
||||||
|
|
||||||
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
|
|
||||||
ErrScopeDomainName = errors.New("DomainName must be supplied with a ProjectName or ProjectID in a Scope.")
|
|
||||||
|
|
||||||
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
|
|
||||||
ErrScopeEmpty = errors.New("You must provide either a Project or Domain in a Scope")
|
|
||||||
)
|
|
278
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
278
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
|
@ -1,278 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scope allows a created token to be limited to a specific domain or project.
|
|
||||||
type Scope struct {
|
|
||||||
ProjectID string
|
|
||||||
ProjectName string
|
|
||||||
DomainID string
|
|
||||||
DomainName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
|
|
||||||
return map[string]string{
|
|
||||||
"X-Subject-Token": subjectToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
|
|
||||||
func Create(c *gophercloud.ServiceClient, options gophercloud.AuthOptions, scope *Scope) CreateResult {
|
|
||||||
type domainReq struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type projectReq struct {
|
|
||||||
Domain *domainReq `json:"domain,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type userReq struct {
|
|
||||||
ID *string `json:"id,omitempty"`
|
|
||||||
Name *string `json:"name,omitempty"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Domain *domainReq `json:"domain,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type passwordReq struct {
|
|
||||||
User userReq `json:"user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type tokenReq struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type identityReq struct {
|
|
||||||
Methods []string `json:"methods"`
|
|
||||||
Password *passwordReq `json:"password,omitempty"`
|
|
||||||
Token *tokenReq `json:"token,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type scopeReq struct {
|
|
||||||
Domain *domainReq `json:"domain,omitempty"`
|
|
||||||
Project *projectReq `json:"project,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type authReq struct {
|
|
||||||
Identity identityReq `json:"identity"`
|
|
||||||
Scope *scopeReq `json:"scope,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type request struct {
|
|
||||||
Auth authReq `json:"auth"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate the request structure based on the provided arguments. Create and return an error
|
|
||||||
// if insufficient or incompatible information is present.
|
|
||||||
var req request
|
|
||||||
|
|
||||||
// Test first for unrecognized arguments.
|
|
||||||
if options.APIKey != "" {
|
|
||||||
return createErr(ErrAPIKeyProvided)
|
|
||||||
}
|
|
||||||
if options.TenantID != "" {
|
|
||||||
return createErr(ErrTenantIDProvided)
|
|
||||||
}
|
|
||||||
if options.TenantName != "" {
|
|
||||||
return createErr(ErrTenantNameProvided)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Password == "" {
|
|
||||||
if options.TokenID != "" {
|
|
||||||
c.TokenID = options.TokenID
|
|
||||||
}
|
|
||||||
if c.TokenID != "" {
|
|
||||||
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
|
||||||
// parameters.
|
|
||||||
if options.Username != "" {
|
|
||||||
return createErr(ErrUsernameWithToken)
|
|
||||||
}
|
|
||||||
if options.UserID != "" {
|
|
||||||
return createErr(ErrUserIDWithToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the request for Token authentication.
|
|
||||||
req.Auth.Identity.Methods = []string{"token"}
|
|
||||||
req.Auth.Identity.Token = &tokenReq{
|
|
||||||
ID: c.TokenID,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no password or token ID are available, authentication can't continue.
|
|
||||||
return createErr(ErrMissingPassword)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Password authentication.
|
|
||||||
req.Auth.Identity.Methods = []string{"password"}
|
|
||||||
|
|
||||||
// At least one of Username and UserID must be specified.
|
|
||||||
if options.Username == "" && options.UserID == "" {
|
|
||||||
return createErr(ErrUsernameOrUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Username != "" {
|
|
||||||
// If Username is provided, UserID may not be provided.
|
|
||||||
if options.UserID != "" {
|
|
||||||
return createErr(ErrUsernameOrUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Either DomainID or DomainName must also be specified.
|
|
||||||
if options.DomainID == "" && options.DomainName == "" {
|
|
||||||
return createErr(ErrDomainIDOrDomainName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.DomainID != "" {
|
|
||||||
if options.DomainName != "" {
|
|
||||||
return createErr(ErrDomainIDOrDomainName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the request for Username and Password authentication with a DomainID.
|
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
|
||||||
User: userReq{
|
|
||||||
Name: &options.Username,
|
|
||||||
Password: options.Password,
|
|
||||||
Domain: &domainReq{ID: &options.DomainID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.DomainName != "" {
|
|
||||||
// Configure the request for Username and Password authentication with a DomainName.
|
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
|
||||||
User: userReq{
|
|
||||||
Name: &options.Username,
|
|
||||||
Password: options.Password,
|
|
||||||
Domain: &domainReq{Name: &options.DomainName},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.UserID != "" {
|
|
||||||
// If UserID is specified, neither DomainID nor DomainName may be.
|
|
||||||
if options.DomainID != "" {
|
|
||||||
return createErr(ErrDomainIDWithUserID)
|
|
||||||
}
|
|
||||||
if options.DomainName != "" {
|
|
||||||
return createErr(ErrDomainNameWithUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the request for UserID and Password authentication.
|
|
||||||
req.Auth.Identity.Password = &passwordReq{
|
|
||||||
User: userReq{ID: &options.UserID, Password: options.Password},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a "scope" element if a Scope has been provided.
|
|
||||||
if scope != nil {
|
|
||||||
if scope.ProjectName != "" {
|
|
||||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
|
||||||
// ProjectID may not be supplied.
|
|
||||||
if scope.DomainID == "" && scope.DomainName == "" {
|
|
||||||
return createErr(ErrScopeDomainIDOrDomainName)
|
|
||||||
}
|
|
||||||
if scope.ProjectID != "" {
|
|
||||||
return createErr(ErrScopeProjectIDOrProjectName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if scope.DomainID != "" {
|
|
||||||
// ProjectName + DomainID
|
|
||||||
req.Auth.Scope = &scopeReq{
|
|
||||||
Project: &projectReq{
|
|
||||||
Name: &scope.ProjectName,
|
|
||||||
Domain: &domainReq{ID: &scope.DomainID},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if scope.DomainName != "" {
|
|
||||||
// ProjectName + DomainName
|
|
||||||
req.Auth.Scope = &scopeReq{
|
|
||||||
Project: &projectReq{
|
|
||||||
Name: &scope.ProjectName,
|
|
||||||
Domain: &domainReq{Name: &scope.DomainName},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if scope.ProjectID != "" {
|
|
||||||
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
|
|
||||||
if scope.DomainID != "" {
|
|
||||||
return createErr(ErrScopeProjectIDAlone)
|
|
||||||
}
|
|
||||||
if scope.DomainName != "" {
|
|
||||||
return createErr(ErrScopeProjectIDAlone)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProjectID
|
|
||||||
req.Auth.Scope = &scopeReq{
|
|
||||||
Project: &projectReq{ID: &scope.ProjectID},
|
|
||||||
}
|
|
||||||
} else if scope.DomainID != "" {
|
|
||||||
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
|
||||||
if scope.DomainName != "" {
|
|
||||||
return createErr(ErrScopeDomainIDOrDomainName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainID
|
|
||||||
req.Auth.Scope = &scopeReq{
|
|
||||||
Domain: &domainReq{ID: &scope.DomainID},
|
|
||||||
}
|
|
||||||
} else if scope.DomainName != "" {
|
|
||||||
return createErr(ErrScopeDomainName)
|
|
||||||
} else {
|
|
||||||
return createErr(ErrScopeEmpty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var result CreateResult
|
|
||||||
var response *http.Response
|
|
||||||
response, result.Err = c.Post(tokenURL(c), req, &result.Body, nil)
|
|
||||||
if result.Err != nil {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result.Header = response.Header
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get validates and retrieves information about another token.
|
|
||||||
func Get(c *gophercloud.ServiceClient, token string) GetResult {
|
|
||||||
var result GetResult
|
|
||||||
var response *http.Response
|
|
||||||
response, result.Err = c.Get(tokenURL(c), &result.Body, &gophercloud.RequestOpts{
|
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
|
||||||
OkCodes: []int{200, 203},
|
|
||||||
})
|
|
||||||
if result.Err != nil {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
result.Header = response.Header
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate determines if a specified token is valid or not.
|
|
||||||
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
|
||||||
response, err := c.Request("HEAD", tokenURL(c), gophercloud.RequestOpts{
|
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
|
||||||
OkCodes: []int{204, 404},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.StatusCode == 204, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoke immediately makes specified token invalid.
|
|
||||||
func Revoke(c *gophercloud.ServiceClient, token string) RevokeResult {
|
|
||||||
var res RevokeResult
|
|
||||||
_, res.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
|
||||||
MoreHeaders: subjectTokenHeaders(c, token),
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
139
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
139
vendor/github.com/rackspace/gophercloud/openstack/identity/v3/tokens/results.go
generated
vendored
|
@ -1,139 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
"github.com/rackspace/gophercloud"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Endpoint represents a single API endpoint offered by a service.
|
|
||||||
// It matches either a public, internal or admin URL.
|
|
||||||
// If supported, it contains a region specifier, again if provided.
|
|
||||||
// The significance of the Region field will depend upon your provider.
|
|
||||||
type Endpoint struct {
|
|
||||||
ID string `mapstructure:"id"`
|
|
||||||
Region string `mapstructure:"region"`
|
|
||||||
Interface string `mapstructure:"interface"`
|
|
||||||
URL string `mapstructure:"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).
|
|
||||||
//
|
|
||||||
// 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 `mapstructure:"id"`
|
|
||||||
|
|
||||||
// Name will contain the provider-specified name for the service.
|
|
||||||
Name string `mapstructure:"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 string `mapstructure:"type"`
|
|
||||||
|
|
||||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
|
||||||
// the service.
|
|
||||||
Endpoints []Endpoint `mapstructure:"endpoints"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
|
|
||||||
type ServiceCatalog struct {
|
|
||||||
Entries []CatalogEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
// commonResult is the deferred result of a Create or a Get call.
|
|
||||||
type commonResult struct {
|
|
||||||
gophercloud.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract is a shortcut for ExtractToken.
|
|
||||||
// This function is deprecated and still present for backward compatibility.
|
|
||||||
func (r commonResult) Extract() (*Token, error) {
|
|
||||||
return r.ExtractToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractToken interprets a commonResult as a Token.
|
|
||||||
func (r commonResult) ExtractToken() (*Token, error) {
|
|
||||||
if r.Err != nil {
|
|
||||||
return nil, r.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Token struct {
|
|
||||||
ExpiresAt string `mapstructure:"expires_at"`
|
|
||||||
} `mapstructure:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var token Token
|
|
||||||
|
|
||||||
// Parse the token itself from the stored headers.
|
|
||||||
token.ID = r.Header.Get("X-Subject-Token")
|
|
||||||
|
|
||||||
err := mapstructure.Decode(r.Body, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to parse the timestamp.
|
|
||||||
token.ExpiresAt, err = time.Parse(gophercloud.RFC3339Milli, response.Token.ExpiresAt)
|
|
||||||
|
|
||||||
return &token, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractServiceCatalog returns the ServiceCatalog that was generated along with the user's Token.
|
|
||||||
func (result CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
|
||||||
if result.Err != nil {
|
|
||||||
return nil, result.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
var response struct {
|
|
||||||
Token struct {
|
|
||||||
Entries []CatalogEntry `mapstructure:"catalog"`
|
|
||||||
} `mapstructure:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := mapstructure.Decode(result.Body, &response)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ServiceCatalog{Entries: response.Token.Entries}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
type CreateResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// createErr quickly creates a CreateResult that reports an error.
|
|
||||||
func createErr(err error) CreateResult {
|
|
||||||
return CreateResult{
|
|
||||||
commonResult: commonResult{Result: gophercloud.Result{Err: err}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetResult is the deferred response from a Get call.
|
|
||||||
type GetResult struct {
|
|
||||||
commonResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeResult is the deferred response from a Revoke call.
|
|
||||||
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.
|
|
||||||
type Token struct {
|
|
||||||
// ID is the issued token.
|
|
||||||
ID string
|
|
||||||
|
|
||||||
// ExpiresAt is the timestamp at which this token will no longer be accepted.
|
|
||||||
ExpiresAt time.Time
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package tokens
|
|
||||||
|
|
||||||
import "github.com/rackspace/gophercloud"
|
|
||||||
|
|
||||||
func tokenURL(c *gophercloud.ServiceClient) string {
|
|
||||||
return c.ServiceURL("auth", "tokens")
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue