Merge pull request #4283 from mitchellh/openstackhacks
builder/openstack: Add image visibility & members
This commit is contained in:
commit
88841cb0e4
|
@ -6,9 +6,9 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack"
|
||||
)
|
||||
|
||||
// AccessConfig is for common configuration related to openstack access
|
||||
|
@ -16,7 +16,6 @@ type AccessConfig struct {
|
|||
Username string `mapstructure:"username"`
|
||||
UserID string `mapstructure:"user_id"`
|
||||
Password string `mapstructure:"password"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
IdentityEndpoint string `mapstructure:"identity_endpoint"`
|
||||
TenantID string `mapstructure:"tenant_id"`
|
||||
TenantName string `mapstructure:"tenant_name"`
|
||||
|
@ -42,9 +41,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
}
|
||||
|
||||
// Legacy RackSpace stuff. We're keeping this around to keep things BC.
|
||||
if c.APIKey == "" {
|
||||
c.APIKey = os.Getenv("SDK_API_KEY")
|
||||
}
|
||||
if c.Password == "" {
|
||||
c.Password = os.Getenv("SDK_PASSWORD")
|
||||
}
|
||||
|
@ -71,7 +67,6 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
{&c.Username, &ao.Username},
|
||||
{&c.UserID, &ao.UserID},
|
||||
{&c.Password, &ao.Password},
|
||||
{&c.APIKey, &ao.APIKey},
|
||||
{&c.IdentityEndpoint, &ao.IdentityEndpoint},
|
||||
{&c.TenantID, &ao.TenantID},
|
||||
{&c.TenantName, &ao.TenantName},
|
||||
|
@ -115,6 +110,13 @@ func (c *AccessConfig) computeV2Client() (*gophercloud.ServiceClient, error) {
|
|||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) imageV2Client() (*gophercloud.ServiceClient, error) {
|
||||
return openstack.NewImageServiceV2(c.osClient, gophercloud.EndpointOpts{
|
||||
Region: c.Region,
|
||||
Availability: c.getEndpointType(),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) getEndpointType() gophercloud.Availability {
|
||||
if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
|
||||
return gophercloud.AvailabilityInternal
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
)
|
||||
|
||||
// Artifact is an artifact implementation that contains built images.
|
||||
|
|
|
@ -114,6 +114,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
&common.StepProvision{},
|
||||
&StepStopServer{},
|
||||
&stepCreateImage{},
|
||||
&stepUpdateImageVisibility{},
|
||||
&stepAddImageMembers{},
|
||||
}
|
||||
|
||||
// Run!
|
||||
|
|
|
@ -2,14 +2,18 @@ package openstack
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
imageservice "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
"github.com/mitchellh/packer/template/interpolate"
|
||||
)
|
||||
|
||||
// ImageConfig is for common configuration related to creating Images.
|
||||
type ImageConfig struct {
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageMetadata map[string]string `mapstructure:"metadata"`
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageMetadata map[string]string `mapstructure:"metadata"`
|
||||
ImageVisibility imageservice.ImageVisibility `mapstructure:"image_visibility"`
|
||||
ImageMembers []string `mapstructure:"image_members"`
|
||||
}
|
||||
|
||||
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
|
@ -29,6 +33,23 @@ func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
c.ImageMetadata["image_type"] = "image"
|
||||
}
|
||||
|
||||
// ImageVisibility values
|
||||
// https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design
|
||||
if c.ImageVisibility != "" {
|
||||
validVals := []imageservice.ImageVisibility{"public", "private", "shared", "community"}
|
||||
valid := false
|
||||
for _, val := range validVals {
|
||||
if strings.EqualFold(string(c.ImageVisibility), string(val)) {
|
||||
valid = true
|
||||
c.ImageVisibility = val
|
||||
break
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
errs = append(errs, fmt.Errorf("Unknown visibility value %s", c.ImageVisibility))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
// StateRefreshFunc is a function type used for StateChangeConf that is
|
||||
|
@ -38,14 +38,12 @@ func ServerStateRefreshFunc(
|
|||
return func() (interface{}, string, int, error) {
|
||||
serverNew, err := servers.Get(client, s.ID).Extract()
|
||||
if err != nil {
|
||||
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||
if ok && errCode.Actual == 404 {
|
||||
if _, ok := err.(gophercloud.ErrDefault404); ok {
|
||||
log.Printf("[INFO] 404 on ServerStateRefresh, returning DELETED")
|
||||
return nil, "DELETED", 0, nil
|
||||
} else {
|
||||
log.Printf("[ERROR] Error on ServerStateRefresh: %s", err)
|
||||
return nil, "", 0, err
|
||||
}
|
||||
log.Printf("[ERROR] Error on ServerStateRefresh: %s", err)
|
||||
return nil, "", 0, err
|
||||
}
|
||||
|
||||
return serverNew, serverNew.Status, serverNew.Progress, nil
|
||||
|
|
|
@ -6,11 +6,11 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
packerssh "github.com/mitchellh/packer/communicator/ssh"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
@ -31,7 +31,7 @@ func CommHost(
|
|||
}
|
||||
|
||||
// If we have a floating IP, use that
|
||||
ip := state.Get("access_ip").(*floatingip.FloatingIP)
|
||||
ip := state.Get("access_ip").(*floatingips.FloatingIP)
|
||||
if ip != nil && ip.IP != "" {
|
||||
log.Printf("[DEBUG] Using floating IP %s to connect", ip.IP)
|
||||
return ip.IP, nil
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/members"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepAddImageMembers struct{}
|
||||
|
||||
func (s *stepAddImageMembers) Run(state multistep.StateBag) multistep.StepAction {
|
||||
imageId := state.Get("image").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
if len(config.ImageMembers) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
imageClient, err := config.imageV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing image service client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
for _, member := range config.ImageMembers {
|
||||
ui.Say(fmt.Sprintf("Adding member '%s' to image %s", member, imageId))
|
||||
r := members.Create(imageClient, imageId, member)
|
||||
if _, err = r.Extract(); err != nil {
|
||||
err = fmt.Errorf("Error adding member to image: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepAddImageMembers) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
|
@ -3,10 +3,10 @@ package openstack
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type StepAllocateIp struct {
|
||||
|
@ -27,7 +27,7 @@ func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
|||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var instanceIp floatingip.FloatingIP
|
||||
var instanceIp floatingips.FloatingIP
|
||||
|
||||
// This is here in case we error out before putting instanceIp into the
|
||||
// statebag below, because it is requested by Cleanup()
|
||||
|
@ -38,7 +38,7 @@ func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
|||
} else if s.FloatingIpPool != "" {
|
||||
ui.Say(fmt.Sprintf("Creating floating IP..."))
|
||||
ui.Message(fmt.Sprintf("Pool: %s", s.FloatingIpPool))
|
||||
newIp, err := floatingip.Create(client, floatingip.CreateOpts{
|
||||
newIp, err := floatingips.Create(client, floatingips.CreateOpts{
|
||||
Pool: s.FloatingIpPool,
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
|
@ -55,7 +55,9 @@ func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
|||
if instanceIp.IP != "" {
|
||||
ui.Say(fmt.Sprintf("Associating floating IP with server..."))
|
||||
ui.Message(fmt.Sprintf("IP: %s", instanceIp.IP))
|
||||
err := floatingip.Associate(client, server.ID, instanceIp.IP).ExtractErr()
|
||||
err := floatingips.AssociateInstance(client, server.ID, floatingips.AssociateOpts{
|
||||
FloatingIP: instanceIp.IP,
|
||||
}).ExtractErr()
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
"Error associating floating IP %s with instance: %s",
|
||||
|
@ -76,7 +78,7 @@ func (s *StepAllocateIp) Run(state multistep.StateBag) multistep.StepAction {
|
|||
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instanceIp := state.Get("access_ip").(*floatingip.FloatingIP)
|
||||
instanceIp := state.Get("access_ip").(*floatingips.FloatingIP)
|
||||
|
||||
// We need the v2 compute client
|
||||
client, err := config.computeV2Client()
|
||||
|
@ -87,7 +89,7 @@ func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
|
|||
}
|
||||
|
||||
if s.FloatingIpPool != "" && instanceIp.ID != "" {
|
||||
if err := floatingip.Delete(client, instanceIp.ID).ExtractErr(); err != nil {
|
||||
if err := floatingips.Delete(client, instanceIp.ID).ExtractErr(); err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error deleting temporary floating IP %s", instanceIp.IP))
|
||||
return
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type stepCreateImage struct{}
|
||||
|
@ -68,7 +68,7 @@ func WaitForImage(client *gophercloud.ServiceClient, imageId string) error {
|
|||
for {
|
||||
image, err := images.Get(client, imageId).Extract()
|
||||
if err != nil {
|
||||
errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError)
|
||||
errCode, ok := err.(*gophercloud.ErrUnexpectedResponseCode)
|
||||
if ok && (errCode.Actual == 500 || errCode.Actual == 404) {
|
||||
numErrors++
|
||||
if numErrors >= maxNumErrors {
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/helper/communicator"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/common/uuid"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// StepLoadExtensions gets the FlavorRef from a Flavor. It first assumes
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
|
||||
)
|
||||
|
||||
// StepLoadFlavor gets the FlavorRef from a Flavor. It first assumes
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type StepRunSourceServer struct {
|
||||
|
@ -63,7 +63,7 @@ func (s *StepRunSourceServer) Run(state multistep.StateBag) multistep.StepAction
|
|||
Networks: networks,
|
||||
AvailabilityZone: s.AvailabilityZone,
|
||||
UserData: userData,
|
||||
ConfigDrive: s.ConfigDrive,
|
||||
ConfigDrive: &s.ConfigDrive,
|
||||
}
|
||||
|
||||
var serverOptsExt servers.CreateOptsBuilder
|
||||
|
|
|
@ -3,10 +3,10 @@ package openstack
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type StepStopServer struct{}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
imageservice "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
)
|
||||
|
||||
type stepUpdateImageVisibility struct{}
|
||||
|
||||
func (s *stepUpdateImageVisibility) Run(state multistep.StateBag) multistep.StepAction {
|
||||
imageId := state.Get("image").(string)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
config := state.Get("config").(Config)
|
||||
|
||||
if config.ImageVisibility == "" {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
imageClient, err := config.imageV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing image service client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Updating image visibility to %s", config.ImageVisibility))
|
||||
r := imageservice.Update(
|
||||
imageClient,
|
||||
imageId,
|
||||
imageservice.UpdateOpts{
|
||||
imageservice.UpdateVisibility{
|
||||
Visibility: config.ImageVisibility,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if _, err = r.Extract(); err != nil {
|
||||
err = fmt.Errorf("Error updating image visibility: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepUpdateImageVisibility) Cleanup(multistep.StateBag) {
|
||||
// No cleanup...
|
||||
}
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/mitchellh/multistep"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
type StepWaitForRackConnect struct {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# Compute
|
||||
|
||||
## Floating IPs
|
||||
|
||||
* `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingip` is now `github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips`
|
||||
* `floatingips.Associate` and `floatingips.Disassociate` have been removed.
|
||||
* `floatingips.DisassociateOpts` is now required to disassociate a Floating IP.
|
||||
|
||||
## Security Groups
|
||||
|
||||
* `secgroups.AddServerToGroup` is now `secgroups.AddServer`.
|
||||
* `secgroups.RemoveServerFromGroup` is now `secgroups.RemoveServer`.
|
||||
|
||||
## Servers
|
||||
|
||||
* `servers.Reboot` now requires a `servers.RebootOpts` struct:
|
||||
|
||||
```golang
|
||||
rebootOpts := &servers.RebootOpts{
|
||||
Type: servers.SoftReboot,
|
||||
}
|
||||
res := servers.Reboot(client, server.ID, rebootOpts)
|
||||
```
|
||||
|
||||
# Identity
|
||||
|
||||
## V3
|
||||
|
||||
### Tokens
|
||||
|
||||
* `Token.ExpiresAt` is now of type `gophercloud.JSONRFC3339Milli` instead of
|
||||
`time.Time`
|
|
@ -1,17 +1,12 @@
|
|||
# Gophercloud: an OpenStack SDK for Go
|
||||
[![Build Status](https://travis-ci.org/rackspace/gophercloud.svg?branch=master)](https://travis-ci.org/rackspace/gophercloud)
|
||||
[![Build Status](https://travis-ci.org/gophercloud/gophercloud.svg?branch=master)](https://travis-ci.org/gophercloud/gophercloud)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/gophercloud/gophercloud/badge.svg?branch=master)](https://coveralls.io/github/gophercloud/gophercloud?branch=master)
|
||||
|
||||
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.
|
||||
Gophercloud is an OpenStack Go SDK.
|
||||
|
||||
## Useful links
|
||||
|
||||
* [Gophercloud homepage](http://gophercloud.io)
|
||||
* [Reference documentation](http://godoc.org/github.com/rackspace/gophercloud)
|
||||
* [Getting started guides](http://gophercloud.io/docs)
|
||||
* [Reference documentation](http://godoc.org/github.com/gophercloud/gophercloud)
|
||||
* [Effective Go](https://golang.org/doc/effective_go.html)
|
||||
|
||||
## How to install
|
||||
|
@ -30,9 +25,9 @@ your projects, such as [godep](https://github.com/tools/godep). Once this is set
|
|||
Gophercloud as a dependency like so:
|
||||
|
||||
```bash
|
||||
go get github.com/rackspace/gophercloud
|
||||
go get github.com/gophercloud/gophercloud
|
||||
|
||||
# Edit your code to import relevant packages from "github.com/rackspace/gophercloud"
|
||||
# Edit your code to import relevant packages from "github.com/gophercloud/gophercloud"
|
||||
|
||||
godep save ./...
|
||||
```
|
||||
|
@ -54,7 +49,6 @@ 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
|
||||
|
@ -73,9 +67,9 @@ 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"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||
)
|
||||
|
||||
// Option 1: Pass in the values yourself
|
||||
|
@ -83,7 +77,6 @@ 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
|
||||
|
@ -119,7 +112,7 @@ 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"
|
||||
import "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
|
||||
server, err := servers.Create(client, servers.CreateOpts{
|
||||
Name: "My new server!",
|
||||
|
@ -128,33 +121,19 @@ server, err := servers.Create(client, servers.CreateOpts{
|
|||
}).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).
|
||||
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/gophercloud/gophercloud) struct).
|
||||
|
||||
### Next steps
|
||||
## Backwards-Compatibility Guarantees
|
||||
|
||||
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)
|
||||
None. Vendor it and write tests covering the parts you use.
|
||||
|
||||
## 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!
|
||||
See the [contributing guide](./.github/CONTRIBUTING.md).
|
||||
|
||||
## Help and feedback
|
||||
|
||||
If you're struggling with something or have spotted a potential bug, feel free
|
||||
to submit an issue to our [bug tracker](/issues) or [contact us directly](https://developer.rackspace.com/support/).
|
||||
to submit an issue to our [bug tracker](/issues).
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
## On Pull Requests
|
||||
|
||||
- Before you start a PR there needs to be a Github issue and a discussion about it
|
||||
on that issue with a core contributor, even if it's just a 'SGTM'.
|
||||
|
||||
- A PR's description must reference the issue it closes with a `For <ISSUE NUMBER>` (e.g. For #293).
|
||||
|
||||
- A PR's description must contain link(s) to the line(s) in the OpenStack
|
||||
source code (on Github) that prove(s) the PR code to be valid. Links to documentation
|
||||
are not good enough. The link(s) should be to a non-`master` branch. For example,
|
||||
a pull request implementing the creation of a Neutron v2 subnet might put the
|
||||
following link in the description:
|
||||
|
||||
https://github.com/openstack/neutron/blob/stable/mitaka/neutron/api/v2/attributes.py#L749
|
||||
|
||||
From that link, a reviewer (or user) can verify the fields in the request/response
|
||||
objects in the PR.
|
||||
|
||||
- A PR that is in-progress should have `[wip]` in front of the PR's title. When
|
||||
ready for review, remove the `[wip]` and ping a core contributor with an `@`.
|
||||
|
||||
- A PR should be small. Even if you intend on implementing an entire
|
||||
service, a PR should only be one route of that service
|
||||
(e.g. create server or get server, but not both).
|
||||
|
||||
- Unless explicitly asked, do not squash commits in the middle of a review; only
|
||||
append. It makes it difficult for the reviewer to see what's changed from one
|
||||
review to the next.
|
||||
|
||||
## On Code
|
||||
|
||||
- In re design: follow as closely as is reasonable the code already in the library.
|
||||
Most operations (e.g. create, delete) admit the same design.
|
||||
|
||||
- Unit tests and acceptance (integration) tests must be written to cover each PR.
|
||||
Tests for operations with several options (e.g. list, create) should include all
|
||||
the options in the tests. This will allow users to verify an operation on their
|
||||
own infrastructure and see an example of usage.
|
||||
|
||||
- If in doubt, ask in-line on the PR.
|
||||
|
||||
### File Structure
|
||||
|
||||
- The following should be used in most cases:
|
||||
|
||||
- `requests.go`: contains all the functions that make HTTP requests and the
|
||||
types associated with the HTTP request (parameters for URL, body, etc)
|
||||
- `results.go`: contains all the response objects and their methods
|
||||
- `urls.go`: contains the endpoints to which the requests are made
|
||||
|
||||
### Naming
|
||||
|
||||
- For methods on a type in `response.go`, the receiver should be named `r` and the
|
||||
variable into which it will be unmarshalled `s`.
|
||||
|
||||
- Functions in `requests.go`, with the exception of functions that return a
|
||||
`pagination.Pager`, should be named returns of the name `r`.
|
||||
|
||||
- Functions in `requests.go` that accept request bodies should accept as their
|
||||
last parameter an `interface` named `<Action>OptsBuilder` (eg `CreateOptsBuilder`).
|
||||
This `interface` should have at the least a method named `To<Resource><Action>Map`
|
||||
(eg `ToPortCreateMap`).
|
||||
|
||||
- Functions in `requests.go` that accept query strings should accept as their
|
||||
last parameter an `interface` named `<Action>OptsBuilder` (eg `ListOptsBuilder`).
|
||||
This `interface` should have at the least a method named `To<Resource><Action>Query`
|
||||
(eg `ToServerListQuery`).
|
|
@ -0,0 +1,331 @@
|
|||
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 `json:"-"`
|
||||
|
||||
// Username is required if using Identity V2 API. Consult with your provider's
|
||||
// control panel to discover your account's username. In Identity V3, either
|
||||
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||
Username string `json:"username,omitempty"`
|
||||
UserID string `json:"id,omitempty"`
|
||||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// At most one of DomainID and DomainName must be provided if using Username
|
||||
// with Identity V3. Otherwise, either are optional.
|
||||
DomainID string `json:"id,omitempty"`
|
||||
DomainName string `json:"name,omitempty"`
|
||||
|
||||
// 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 string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to
|
||||
// cache your credentials in memory, and to allow Gophercloud to attempt to
|
||||
// re-authenticate automatically if/when your token expires. If you set it to
|
||||
// false, it will not cache these settings, but re-authentication will not be
|
||||
// possible. This setting defaults to false.
|
||||
//
|
||||
// NOTE: The reauth function will try to re-authenticate endlessly if left unchecked.
|
||||
// The way to limit the number of attempts is to provide a custom HTTP client to the provider client
|
||||
// and provide a transport that implements the RoundTripper interface and stores the number of failed retries.
|
||||
// For an example of this, see here: https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
|
||||
AllowReauth bool `json:"-"`
|
||||
|
||||
// TokenID allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenID string `json:"-"`
|
||||
}
|
||||
|
||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v2 tokens package
|
||||
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||
// Populate the request map.
|
||||
authMap := make(map[string]interface{})
|
||||
|
||||
if opts.Username != "" {
|
||||
if opts.Password != "" {
|
||||
authMap["passwordCredentials"] = map[string]interface{}{
|
||||
"username": opts.Username,
|
||||
"password": opts.Password,
|
||||
}
|
||||
} else {
|
||||
return nil, ErrMissingInput{Argument: "Password"}
|
||||
}
|
||||
} else if opts.TokenID != "" {
|
||||
authMap["token"] = map[string]interface{}{
|
||||
"id": opts.TokenID,
|
||||
}
|
||||
} else {
|
||||
return nil, ErrMissingInput{Argument: "Username"}
|
||||
}
|
||||
|
||||
if opts.TenantID != "" {
|
||||
authMap["tenantId"] = opts.TenantID
|
||||
}
|
||||
if opts.TenantName != "" {
|
||||
authMap["tenantName"] = opts.TenantName
|
||||
}
|
||||
|
||||
return map[string]interface{}{"auth": authMap}, nil
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||
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 authReq struct {
|
||||
Identity identityReq `json:"identity"`
|
||||
}
|
||||
|
||||
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 opts.TenantID != "" {
|
||||
return nil, ErrTenantIDProvided{}
|
||||
}
|
||||
if opts.TenantName != "" {
|
||||
return nil, ErrTenantNameProvided{}
|
||||
}
|
||||
|
||||
if opts.Password == "" {
|
||||
if opts.TokenID != "" {
|
||||
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
|
||||
// parameters.
|
||||
if opts.Username != "" {
|
||||
return nil, ErrUsernameWithToken{}
|
||||
}
|
||||
if opts.UserID != "" {
|
||||
return nil, ErrUserIDWithToken{}
|
||||
}
|
||||
if opts.DomainID != "" {
|
||||
return nil, ErrDomainIDWithToken{}
|
||||
}
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainNameWithToken{}
|
||||
}
|
||||
|
||||
// Configure the request for Token authentication.
|
||||
req.Auth.Identity.Methods = []string{"token"}
|
||||
req.Auth.Identity.Token = &tokenReq{
|
||||
ID: opts.TokenID,
|
||||
}
|
||||
} else {
|
||||
// If no password or token ID are available, authentication can't continue.
|
||||
return nil, ErrMissingPassword{}
|
||||
}
|
||||
} else {
|
||||
// Password authentication.
|
||||
req.Auth.Identity.Methods = []string{"password"}
|
||||
|
||||
// At least one of Username and UserID must be specified.
|
||||
if opts.Username == "" && opts.UserID == "" {
|
||||
return nil, ErrUsernameOrUserID{}
|
||||
}
|
||||
|
||||
if opts.Username != "" {
|
||||
// If Username is provided, UserID may not be provided.
|
||||
if opts.UserID != "" {
|
||||
return nil, ErrUsernameOrUserID{}
|
||||
}
|
||||
|
||||
// Either DomainID or DomainName must also be specified.
|
||||
if opts.DomainID == "" && opts.DomainName == "" {
|
||||
return nil, ErrDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
if opts.DomainID != "" {
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
// Configure the request for Username and Password authentication with a DomainID.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: opts.Password,
|
||||
Domain: &domainReq{ID: &opts.DomainID},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if opts.DomainName != "" {
|
||||
// Configure the request for Username and Password authentication with a DomainName.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{
|
||||
Name: &opts.Username,
|
||||
Password: opts.Password,
|
||||
Domain: &domainReq{Name: &opts.DomainName},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts.UserID != "" {
|
||||
// If UserID is specified, neither DomainID nor DomainName may be.
|
||||
if opts.DomainID != "" {
|
||||
return nil, ErrDomainIDWithUserID{}
|
||||
}
|
||||
if opts.DomainName != "" {
|
||||
return nil, ErrDomainNameWithUserID{}
|
||||
}
|
||||
|
||||
// Configure the request for UserID and Password authentication.
|
||||
req.Auth.Identity.Password = &passwordReq{
|
||||
User: userReq{ID: &opts.UserID, Password: opts.Password},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b, err := BuildRequestBody(req, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(scope) != 0 {
|
||||
b["auth"].(map[string]interface{})["scope"] = scope
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
|
||||
var scope struct {
|
||||
ProjectID string
|
||||
ProjectName string
|
||||
DomainID string
|
||||
DomainName string
|
||||
}
|
||||
|
||||
if opts.TenantID != "" {
|
||||
scope.ProjectID = opts.TenantID
|
||||
opts.TenantID = ""
|
||||
opts.TenantName = ""
|
||||
} else {
|
||||
if opts.TenantName != "" {
|
||||
scope.ProjectName = opts.TenantName
|
||||
scope.DomainID = opts.DomainID
|
||||
scope.DomainName = opts.DomainName
|
||||
}
|
||||
opts.TenantName = ""
|
||||
}
|
||||
|
||||
if scope.ProjectName != "" {
|
||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||
// ProjectID may not be supplied.
|
||||
if scope.DomainID == "" && scope.DomainName == "" {
|
||||
return nil, ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
if scope.ProjectID != "" {
|
||||
return nil, ErrScopeProjectIDOrProjectName{}
|
||||
}
|
||||
|
||||
if scope.DomainID != "" {
|
||||
// ProjectName + DomainID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &scope.ProjectName,
|
||||
"domain": map[string]interface{}{"id": &scope.DomainID},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if scope.DomainName != "" {
|
||||
// ProjectName + DomainName
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &scope.ProjectName,
|
||||
"domain": map[string]interface{}{"name": &scope.DomainName},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
} else if scope.ProjectID != "" {
|
||||
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
|
||||
if scope.DomainID != "" {
|
||||
return nil, ErrScopeProjectIDAlone{}
|
||||
}
|
||||
if scope.DomainName != "" {
|
||||
return nil, ErrScopeProjectIDAlone{}
|
||||
}
|
||||
|
||||
// ProjectID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"id": &scope.ProjectID,
|
||||
},
|
||||
}, nil
|
||||
} else if scope.DomainID != "" {
|
||||
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
||||
if scope.DomainName != "" {
|
||||
return nil, ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
// DomainID
|
||||
return map[string]interface{}{
|
||||
"domain": map[string]interface{}{
|
||||
"id": &scope.DomainID,
|
||||
},
|
||||
}, nil
|
||||
} else if scope.DomainName != "" {
|
||||
return nil, ErrScopeDomainName{}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (opts AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
|
@ -1,21 +1,5 @@
|
|||
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
|
|
@ -0,0 +1,408 @@
|
|||
package gophercloud
|
||||
|
||||
import "fmt"
|
||||
|
||||
// BaseError is an error type that all other error types embed.
|
||||
type BaseError struct {
|
||||
DefaultErrString string
|
||||
Info string
|
||||
}
|
||||
|
||||
func (e BaseError) Error() string {
|
||||
e.DefaultErrString = "An error occurred while executing a Gophercloud request."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
func (e BaseError) choseErrString() string {
|
||||
if e.Info != "" {
|
||||
return e.Info
|
||||
}
|
||||
return e.DefaultErrString
|
||||
}
|
||||
|
||||
// ErrMissingInput is the error when input is required in a particular
|
||||
// situation but not provided by the user
|
||||
type ErrMissingInput struct {
|
||||
BaseError
|
||||
Argument string
|
||||
}
|
||||
|
||||
func (e ErrMissingInput) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Missing input for argument [%s]", e.Argument)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrInvalidInput is an error type used for most non-HTTP Gophercloud errors.
|
||||
type ErrInvalidInput struct {
|
||||
ErrMissingInput
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e ErrInvalidInput) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Invalid input provided for argument [%s]: [%+v]", e.Argument, e.Value)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnexpectedResponseCode is returned by the Request method when a response code other than
|
||||
// those listed in OkCodes is encountered.
|
||||
type ErrUnexpectedResponseCode struct {
|
||||
BaseError
|
||||
URL string
|
||||
Method string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (e ErrUnexpectedResponseCode) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf(
|
||||
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
|
||||
e.Expected, e.Method, e.URL, e.Actual, e.Body,
|
||||
)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrDefault400 is the default error type returned on a 400 HTTP response code.
|
||||
type ErrDefault400 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault401 is the default error type returned on a 401 HTTP response code.
|
||||
type ErrDefault401 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault404 is the default error type returned on a 404 HTTP response code.
|
||||
type ErrDefault404 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault405 is the default error type returned on a 405 HTTP response code.
|
||||
type ErrDefault405 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault408 is the default error type returned on a 408 HTTP response code.
|
||||
type ErrDefault408 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault429 is the default error type returned on a 429 HTTP response code.
|
||||
type ErrDefault429 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault500 is the default error type returned on a 500 HTTP response code.
|
||||
type ErrDefault500 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
// ErrDefault503 is the default error type returned on a 503 HTTP response code.
|
||||
type ErrDefault503 struct {
|
||||
ErrUnexpectedResponseCode
|
||||
}
|
||||
|
||||
func (e ErrDefault400) Error() string {
|
||||
return "Invalid request due to incorrect syntax or missing required parameters."
|
||||
}
|
||||
func (e ErrDefault401) Error() string {
|
||||
return "Authentication failed"
|
||||
}
|
||||
func (e ErrDefault404) Error() string {
|
||||
return "Resource not found"
|
||||
}
|
||||
func (e ErrDefault405) Error() string {
|
||||
return "Method not allowed"
|
||||
}
|
||||
func (e ErrDefault408) Error() string {
|
||||
return "The server timed out waiting for the request"
|
||||
}
|
||||
func (e ErrDefault429) Error() string {
|
||||
return "Too many requests have been sent in a given amount of time. Pause" +
|
||||
" requests, wait up to one minute, and try again."
|
||||
}
|
||||
func (e ErrDefault500) Error() string {
|
||||
return "Internal Server Error"
|
||||
}
|
||||
func (e ErrDefault503) Error() string {
|
||||
return "The service is currently unable to handle the request due to a temporary" +
|
||||
" overloading or maintenance. This is a temporary condition. Try again later."
|
||||
}
|
||||
|
||||
// Err400er is the interface resource error types implement to override the error message
|
||||
// from a 400 error.
|
||||
type Err400er interface {
|
||||
Error400(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err401er is the interface resource error types implement to override the error message
|
||||
// from a 401 error.
|
||||
type Err401er interface {
|
||||
Error401(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err404er is the interface resource error types implement to override the error message
|
||||
// from a 404 error.
|
||||
type Err404er interface {
|
||||
Error404(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err405er is the interface resource error types implement to override the error message
|
||||
// from a 405 error.
|
||||
type Err405er interface {
|
||||
Error405(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err408er is the interface resource error types implement to override the error message
|
||||
// from a 408 error.
|
||||
type Err408er interface {
|
||||
Error408(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err429er is the interface resource error types implement to override the error message
|
||||
// from a 429 error.
|
||||
type Err429er interface {
|
||||
Error429(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err500er is the interface resource error types implement to override the error message
|
||||
// from a 500 error.
|
||||
type Err500er interface {
|
||||
Error500(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// Err503er is the interface resource error types implement to override the error message
|
||||
// from a 503 error.
|
||||
type Err503er interface {
|
||||
Error503(ErrUnexpectedResponseCode) error
|
||||
}
|
||||
|
||||
// ErrTimeOut is the error type returned when an operations times out.
|
||||
type ErrTimeOut struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrTimeOut) Error() string {
|
||||
e.DefaultErrString = "A time out occurred"
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnableToReauthenticate is the error type returned when reauthentication fails.
|
||||
type ErrUnableToReauthenticate struct {
|
||||
BaseError
|
||||
ErrOriginal error
|
||||
}
|
||||
|
||||
func (e ErrUnableToReauthenticate) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Unable to re-authenticate: %s", e.ErrOriginal)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrErrorAfterReauthentication is the error type returned when reauthentication
|
||||
// succeeds, but an error occurs afterword (usually an HTTP error).
|
||||
type ErrErrorAfterReauthentication struct {
|
||||
BaseError
|
||||
ErrOriginal error
|
||||
}
|
||||
|
||||
func (e ErrErrorAfterReauthentication) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Successfully re-authenticated, but got error executing request: %s", e.ErrOriginal)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// 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.
|
||||
type ErrServiceNotFound struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrServiceNotFound) Error() string {
|
||||
e.DefaultErrString = "No suitable service could be found in the service catalog."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// 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.
|
||||
type ErrEndpointNotFound struct {
|
||||
BaseError
|
||||
}
|
||||
|
||||
func (e ErrEndpointNotFound) Error() string {
|
||||
e.DefaultErrString = "No suitable endpoint could be found in the service catalog."
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrResourceNotFound is the error when trying to retrieve a resource's
|
||||
// ID by name and the resource doesn't exist.
|
||||
type ErrResourceNotFound struct {
|
||||
BaseError
|
||||
Name string
|
||||
ResourceType string
|
||||
}
|
||||
|
||||
func (e ErrResourceNotFound) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Unable to find %s with name %s", e.ResourceType, e.Name)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrMultipleResourcesFound is the error when trying to retrieve a resource's
|
||||
// ID by name and multiple resources have the user-provided name.
|
||||
type ErrMultipleResourcesFound struct {
|
||||
BaseError
|
||||
Name string
|
||||
Count int
|
||||
ResourceType string
|
||||
}
|
||||
|
||||
func (e ErrMultipleResourcesFound) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Found %d %ss matching %s", e.Count, e.ResourceType, e.Name)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
// ErrUnexpectedType is the error when an unexpected type is encountered
|
||||
type ErrUnexpectedType struct {
|
||||
BaseError
|
||||
Expected string
|
||||
Actual string
|
||||
}
|
||||
|
||||
func (e ErrUnexpectedType) Error() string {
|
||||
e.DefaultErrString = fmt.Sprintf("Expected %s but got %s", e.Expected, e.Actual)
|
||||
return e.choseErrString()
|
||||
}
|
||||
|
||||
func unacceptedAttributeErr(attribute string) string {
|
||||
return fmt.Sprintf("The base Identity V3 API does not accept authentication by %s", attribute)
|
||||
}
|
||||
|
||||
func redundantWithTokenErr(attribute string) string {
|
||||
return fmt.Sprintf("%s may not be provided when authenticating with a TokenID", attribute)
|
||||
}
|
||||
|
||||
func redundantWithUserID(attribute string) string {
|
||||
return fmt.Sprintf("%s may not be provided when authenticating with a UserID", attribute)
|
||||
}
|
||||
|
||||
// ErrAPIKeyProvided indicates that an APIKey was provided but can't be used.
|
||||
type ErrAPIKeyProvided struct{ BaseError }
|
||||
|
||||
func (e ErrAPIKeyProvided) Error() string {
|
||||
return unacceptedAttributeErr("APIKey")
|
||||
}
|
||||
|
||||
// ErrTenantIDProvided indicates that a TenantID was provided but can't be used.
|
||||
type ErrTenantIDProvided struct{ BaseError }
|
||||
|
||||
func (e ErrTenantIDProvided) Error() string {
|
||||
return unacceptedAttributeErr("TenantID")
|
||||
}
|
||||
|
||||
// ErrTenantNameProvided indicates that a TenantName was provided but can't be used.
|
||||
type ErrTenantNameProvided struct{ BaseError }
|
||||
|
||||
func (e ErrTenantNameProvided) Error() string {
|
||||
return unacceptedAttributeErr("TenantName")
|
||||
}
|
||||
|
||||
// ErrUsernameWithToken indicates that a Username was provided, but token authentication is being used instead.
|
||||
type ErrUsernameWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrUsernameWithToken) Error() string {
|
||||
return redundantWithTokenErr("Username")
|
||||
}
|
||||
|
||||
// ErrUserIDWithToken indicates that a UserID was provided, but token authentication is being used instead.
|
||||
type ErrUserIDWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrUserIDWithToken) Error() string {
|
||||
return redundantWithTokenErr("UserID")
|
||||
}
|
||||
|
||||
// ErrDomainIDWithToken indicates that a DomainID was provided, but token authentication is being used instead.
|
||||
type ErrDomainIDWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDWithToken) Error() string {
|
||||
return redundantWithTokenErr("DomainID")
|
||||
}
|
||||
|
||||
// ErrDomainNameWithToken indicates that a DomainName was provided, but token authentication is being used instead.s
|
||||
type ErrDomainNameWithToken struct{ BaseError }
|
||||
|
||||
func (e ErrDomainNameWithToken) Error() string {
|
||||
return redundantWithTokenErr("DomainName")
|
||||
}
|
||||
|
||||
// ErrUsernameOrUserID indicates that neither username nor userID are specified, or both are at once.
|
||||
type ErrUsernameOrUserID struct{ BaseError }
|
||||
|
||||
func (e ErrUsernameOrUserID) Error() string {
|
||||
return "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.
|
||||
type ErrDomainIDWithUserID struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDWithUserID) Error() string {
|
||||
return redundantWithUserID("DomainID")
|
||||
}
|
||||
|
||||
// ErrDomainNameWithUserID indicates that a DomainName was provided, but unnecessary because a UserID is being used.
|
||||
type ErrDomainNameWithUserID struct{ BaseError }
|
||||
|
||||
func (e ErrDomainNameWithUserID) Error() string {
|
||||
return 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.
|
||||
type ErrDomainIDOrDomainName struct{ BaseError }
|
||||
|
||||
func (e ErrDomainIDOrDomainName) Error() string {
|
||||
return "You must provide exactly one of DomainID or DomainName to authenticate by Username"
|
||||
}
|
||||
|
||||
// ErrMissingPassword indicates that no password was provided and no token is available.
|
||||
type ErrMissingPassword struct{ BaseError }
|
||||
|
||||
func (e ErrMissingPassword) Error() string {
|
||||
return "You must provide a password to authenticate"
|
||||
}
|
||||
|
||||
// ErrScopeDomainIDOrDomainName indicates that a domain ID or Name was required in a Scope, but not present.
|
||||
type ErrScopeDomainIDOrDomainName struct{ BaseError }
|
||||
|
||||
func (e ErrScopeDomainIDOrDomainName) Error() string {
|
||||
return "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.
|
||||
type ErrScopeProjectIDOrProjectName struct{ BaseError }
|
||||
|
||||
func (e ErrScopeProjectIDOrProjectName) Error() string {
|
||||
return "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.
|
||||
type ErrScopeProjectIDAlone struct{ BaseError }
|
||||
|
||||
func (e ErrScopeProjectIDAlone) Error() string {
|
||||
return "ProjectID must be supplied alone in a Scope"
|
||||
}
|
||||
|
||||
// ErrScopeDomainName indicates that a DomainName was provided alone in a Scope.
|
||||
type ErrScopeDomainName struct{ BaseError }
|
||||
|
||||
func (e ErrScopeDomainName) Error() string {
|
||||
return "DomainName must be supplied with a ProjectName or ProjectID in a Scope"
|
||||
}
|
||||
|
||||
// ErrScopeEmpty indicates that no credentials were provided in a Scope.
|
||||
type ErrScopeEmpty struct{ BaseError }
|
||||
|
||||
func (e ErrScopeEmpty) Error() string {
|
||||
return "You must provide either a Project or Domain in a Scope"
|
||||
}
|
|
@ -1,23 +1,14 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/gophercloud/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 needs to be set.")
|
||||
ErrNoPassword = fmt.Errorf("Environment variable OS_PASSWORD needs to be set.")
|
||||
)
|
||||
|
||||
// AuthOptions fills out an identity.AuthOptions structure with the settings found on the various OpenStack
|
||||
// AuthOptionsFromEnv fills out an identity.AuthOptions structure with the settings found on the various OpenStack
|
||||
// OS_* environment variables. The following variables provide sources of truth: OS_AUTH_URL, OS_USERNAME,
|
||||
// OS_PASSWORD, OS_TENANT_ID, and OS_TENANT_NAME. Of these, OS_USERNAME, OS_PASSWORD, and OS_AUTH_URL must
|
||||
// have settings, or an error will result. OS_TENANT_ID and OS_TENANT_NAME are optional.
|
||||
|
@ -32,15 +23,18 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
|
|||
domainName := os.Getenv("OS_DOMAIN_NAME")
|
||||
|
||||
if authURL == "" {
|
||||
return nilOptions, ErrNoAuthURL
|
||||
err := gophercloud.ErrMissingInput{Argument: "authURL"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
if username == "" && userID == "" {
|
||||
return nilOptions, ErrNoUsername
|
||||
err := gophercloud.ErrMissingInput{Argument: "username"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return nilOptions, ErrNoPassword
|
||||
err := gophercloud.ErrMissingInput{Argument: "password"}
|
||||
return nilOptions, err
|
||||
}
|
||||
|
||||
ao := gophercloud.AuthOptions{
|
|
@ -3,12 +3,12 @@ package openstack
|
|||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"reflect"
|
||||
|
||||
"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"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
"github.com/gophercloud/gophercloud/openstack/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -76,9 +76,9 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
|
|||
|
||||
switch chosen.ID {
|
||||
case v20:
|
||||
return v2auth(client, endpoint, options)
|
||||
return v2auth(client, endpoint, options, gophercloud.EndpointOpts{})
|
||||
case v30:
|
||||
return v3auth(client, endpoint, options)
|
||||
return v3auth(client, endpoint, &options, gophercloud.EndpointOpts{})
|
||||
default:
|
||||
// The switch statement must be out of date from the versions list.
|
||||
return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
|
||||
|
@ -86,17 +86,31 @@ func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOp
|
|||
}
|
||||
|
||||
// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
|
||||
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||
return v2auth(client, "", options)
|
||||
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||
return v2auth(client, "", options, eo)
|
||||
}
|
||||
|
||||
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
|
||||
v2Client := NewIdentityV2(client)
|
||||
func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions, eo gophercloud.EndpointOpts) error {
|
||||
v2Client, err := NewIdentityV2(client, eo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if endpoint != "" {
|
||||
v2Client.Endpoint = endpoint
|
||||
}
|
||||
|
||||
result := tokens2.Create(v2Client, tokens2.AuthOptions{AuthOptions: options})
|
||||
v2Opts := tokens2.AuthOptions{
|
||||
IdentityEndpoint: options.IdentityEndpoint,
|
||||
Username: options.Username,
|
||||
Password: options.Password,
|
||||
TenantID: options.TenantID,
|
||||
TenantName: options.TenantName,
|
||||
AllowReauth: options.AllowReauth,
|
||||
TokenID: options.TokenID,
|
||||
}
|
||||
|
||||
result := tokens2.Create(v2Client, v2Opts)
|
||||
|
||||
token, err := result.ExtractToken()
|
||||
if err != nil {
|
||||
|
@ -111,7 +125,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
|||
if options.AllowReauth {
|
||||
client.ReauthFunc = func() error {
|
||||
client.TokenID = ""
|
||||
return v2auth(client, endpoint, options)
|
||||
return v2auth(client, endpoint, options, eo)
|
||||
}
|
||||
}
|
||||
client.TokenID = token.ID
|
||||
|
@ -123,40 +137,22 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
|||
}
|
||||
|
||||
// AuthenticateV3 explicitly authenticates against the identity v3 service.
|
||||
func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||
return v3auth(client, "", options)
|
||||
func AuthenticateV3(client *gophercloud.ProviderClient, options tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||
return v3auth(client, "", options, eo)
|
||||
}
|
||||
|
||||
func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
|
||||
func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.AuthOptionsBuilder, eo gophercloud.EndpointOpts) error {
|
||||
// Override the generated service endpoint with the one returned by the version endpoint.
|
||||
v3Client := NewIdentityV3(client)
|
||||
v3Client, err := NewIdentityV3(client, eo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
result := tokens3.Create(v3Client, opts)
|
||||
|
||||
token, err := result.ExtractToken()
|
||||
if err != nil {
|
||||
|
@ -170,10 +166,10 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
|||
|
||||
client.TokenID = token.ID
|
||||
|
||||
if options.AllowReauth {
|
||||
if opts.CanReauth() {
|
||||
client.ReauthFunc = func() error {
|
||||
client.TokenID = ""
|
||||
return v3auth(client, endpoint, options)
|
||||
return v3auth(client, endpoint, opts, eo)
|
||||
}
|
||||
}
|
||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||
|
@ -184,57 +180,39 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
|||
}
|
||||
|
||||
// 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/"
|
||||
func NewIdentityV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
endpoint := client.IdentityBase + "v2.0/"
|
||||
var err error
|
||||
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||
eo.ApplyDefaults("identity")
|
||||
endpoint, err = client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &gophercloud.ServiceClient{
|
||||
ProviderClient: client,
|
||||
Endpoint: v2Endpoint,
|
||||
}
|
||||
Endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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/"
|
||||
func NewIdentityV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
endpoint := client.IdentityBase + "v3/"
|
||||
var err error
|
||||
if !reflect.DeepEqual(eo, gophercloud.EndpointOpts{}) {
|
||||
eo.ApplyDefaults("identity")
|
||||
endpoint, err = client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
|
||||
|
@ -281,6 +259,26 @@ func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoi
|
|||
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
|
||||
}
|
||||
|
||||
// NewSharedFileSystemV2 creates a ServiceClient that may be used to access the v2 shared file system service.
|
||||
func NewSharedFileSystemV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("sharev2")
|
||||
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) {
|
||||
|
@ -311,3 +309,15 @@ func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*
|
|||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
||||
// NewImageServiceV2 creates a ServiceClient that may be used to access the v2 image service.
|
||||
func NewImageServiceV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("image")
|
||||
url, err := client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client,
|
||||
Endpoint: url,
|
||||
ResourceBase: url + "v2/"}, nil
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Get retrieves information for a specific extension using its alias.
|
||||
func Get(c *gophercloud.ServiceClient, alias string) GetResult {
|
||||
var res GetResult
|
||||
_, res.Err = c.Get(ExtensionURL(c, alias), &res.Body, nil)
|
||||
return res
|
||||
func Get(c *gophercloud.ServiceClient, alias string) (r GetResult) {
|
||||
_, r.Err = c.Get(ExtensionURL(c, alias), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// List returns a Pager which allows you to iterate over the full collection of extensions.
|
|
@ -1,9 +1,8 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult temporarily stores the result of a Get call.
|
||||
|
@ -14,27 +13,21 @@ type GetResult struct {
|
|||
|
||||
// Extract interprets a GetResult as an Extension.
|
||||
func (r GetResult) Extract() (*Extension, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
var s struct {
|
||||
Extension *Extension `json:"extension"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &res)
|
||||
|
||||
return res.Extension, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Extension, err
|
||||
}
|
||||
|
||||
// Extension is a struct that represents an OpenStack extension.
|
||||
type Extension struct {
|
||||
Updated string `json:"updated" mapstructure:"updated"`
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
Links []interface{} `json:"links" mapstructure:"links"`
|
||||
Namespace string `json:"namespace" mapstructure:"namespace"`
|
||||
Alias string `json:"alias" mapstructure:"alias"`
|
||||
Description string `json:"description" mapstructure:"description"`
|
||||
Updated string `json:"updated"`
|
||||
Name string `json:"name"`
|
||||
Links []interface{} `json:"links"`
|
||||
Namespace string `json:"namespace"`
|
||||
Alias string `json:"alias"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// ExtensionPage is the page returned by a pager when traversing over a collection of extensions.
|
||||
|
@ -45,21 +38,16 @@ type ExtensionPage struct {
|
|||
// IsEmpty checks whether an ExtensionPage struct is empty.
|
||||
func (r ExtensionPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractExtensions(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
return len(is) == 0, err
|
||||
}
|
||||
|
||||
// ExtractExtensions accepts a Page struct, specifically an ExtensionPage struct, and extracts the
|
||||
// elements into a slice of Extension structs.
|
||||
// In other words, a generic collection is mapped into a relevant slice.
|
||||
func ExtractExtensions(page pagination.Page) ([]Extension, error) {
|
||||
var resp struct {
|
||||
Extensions []Extension `mapstructure:"extensions"`
|
||||
func ExtractExtensions(r pagination.Page) ([]Extension, error) {
|
||||
var s struct {
|
||||
Extensions []Extension `json:"extensions"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(ExtensionPage).Body, &resp)
|
||||
|
||||
return resp.Extensions, err
|
||||
err := (r.(ExtensionPage)).ExtractInto(&s)
|
||||
return s.Extensions, err
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package extensions
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// ExtensionURL generates the URL for an extension resource by name.
|
||||
func ExtensionURL(c *gophercloud.ServiceClient, name string) string {
|
|
@ -1,9 +1,9 @@
|
|||
package extensions
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
common "github.com/rackspace/gophercloud/openstack/common/extensions"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
common "github.com/gophercloud/gophercloud/openstack/common/extensions"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ExtractExtensions interprets a Page as a slice of Extensions.
|
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go
generated
vendored
Normal file
3
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
// Package floatingips provides the ability to manage floating ips through
|
||||
// nova-network
|
||||
package floatingips
|
112
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go
generated
vendored
Normal file
112
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,112 @@
|
|||
package floatingips
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/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 FloatingIPPage{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 `json:"pool" required:"true"`
|
||||
}
|
||||
|
||||
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
|
||||
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "")
|
||||
}
|
||||
|
||||
// Create requests the creation of a new floating IP
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
b, err := opts.ToFloatingIPCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get returns data about a previously created FloatingIP.
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete requests the deletion of a previous allocated FloatingIP.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// AssociateOptsBuilder is the interface types must satfisfy to be used as
|
||||
// Associate options
|
||||
type AssociateOptsBuilder interface {
|
||||
ToFloatingIPAssociateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// AssociateOpts specifies the required information to associate a floating IP with an instance
|
||||
type AssociateOpts struct {
|
||||
// FloatingIP is the floating IP to associate with an instance
|
||||
FloatingIP string `json:"address" required:"true"`
|
||||
// FixedIP is an optional fixed IP address of the server
|
||||
FixedIP string `json:"fixed_address,omitempty"`
|
||||
}
|
||||
|
||||
// ToFloatingIPAssociateMap constructs a request body from AssociateOpts.
|
||||
func (opts AssociateOpts) ToFloatingIPAssociateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "addFloatingIp")
|
||||
}
|
||||
|
||||
// AssociateInstance pairs an allocated floating IP with an instance.
|
||||
func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts AssociateOptsBuilder) (r AssociateResult) {
|
||||
b, err := opts.ToFloatingIPAssociateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(associateURL(client, serverID), b, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// DisassociateOptsBuilder is the interface types must satfisfy to be used as
|
||||
// Disassociate options
|
||||
type DisassociateOptsBuilder interface {
|
||||
ToFloatingIPDisassociateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DisassociateOpts specifies the required information to disassociate a floating IP with an instance
|
||||
type DisassociateOpts struct {
|
||||
FloatingIP string `json:"address" required:"true"`
|
||||
}
|
||||
|
||||
// ToFloatingIPDisassociateMap constructs a request body from AssociateOpts.
|
||||
func (opts DisassociateOpts) ToFloatingIPDisassociateMap() (map[string]interface{}, error) {
|
||||
return gophercloud.BuildRequestBody(opts, "removeFloatingIp")
|
||||
}
|
||||
|
||||
// DisassociateInstance decouples an allocated floating IP from an instance
|
||||
func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, opts DisassociateOptsBuilder) (r DisassociateResult) {
|
||||
b, err := opts.ToFloatingIPDisassociateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil)
|
||||
return
|
||||
}
|
|
@ -1,54 +1,51 @@
|
|||
package floatingip
|
||||
package floatingips
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/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"`
|
||||
ID string `json:"id"`
|
||||
|
||||
// FixedIP is the IP of the instance related to the Floating IP
|
||||
FixedIP string `mapstructure:"fixed_ip,omitempty"`
|
||||
FixedIP string `json:"fixed_ip,omitempty"`
|
||||
|
||||
// InstanceID is the ID of the instance that is using the Floating IP
|
||||
InstanceID string `mapstructure:"instance_id"`
|
||||
InstanceID string `json:"instance_id"`
|
||||
|
||||
// IP is the actual Floating IP
|
||||
IP string `mapstructure:"ip"`
|
||||
IP string `json:"ip"`
|
||||
|
||||
// Pool is the pool of floating IPs that this floating IP belongs to
|
||||
Pool string `mapstructure:"pool"`
|
||||
Pool string `json:"pool"`
|
||||
}
|
||||
|
||||
// FloatingIPsPage stores a single, only page of FloatingIPs
|
||||
// FloatingIPPage stores a single, only page of FloatingIPs
|
||||
// results from a List call.
|
||||
type FloatingIPsPage struct {
|
||||
type FloatingIPPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a FloatingIPsPage is empty.
|
||||
func (page FloatingIPsPage) IsEmpty() (bool, error) {
|
||||
func (page FloatingIPPage) 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"`
|
||||
func ExtractFloatingIPs(r pagination.Page) ([]FloatingIP, error) {
|
||||
var s struct {
|
||||
FloatingIPs []FloatingIP `json:"floating_ips"`
|
||||
}
|
||||
|
||||
err := mapstructure.WeakDecode(casted, &response)
|
||||
|
||||
return response.FloatingIPs, err
|
||||
err := (r.(FloatingIPPage)).ExtractInto(&s)
|
||||
return s.FloatingIPs, err
|
||||
}
|
||||
|
||||
// FloatingIPResult is the raw result from a FloatingIP request.
|
||||
type FloatingIPResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
@ -56,16 +53,11 @@ type FloatingIPResult struct {
|
|||
// 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 s struct {
|
||||
FloatingIP *FloatingIP `json:"floating_ip"`
|
||||
}
|
||||
|
||||
var res struct {
|
||||
FloatingIP *FloatingIP `json:"floating_ip" mapstructure:"floating_ip"`
|
||||
}
|
||||
|
||||
err := mapstructure.WeakDecode(r.Body, &res)
|
||||
return res.FloatingIP, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.FloatingIP, err
|
||||
}
|
||||
|
||||
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
|
|
@ -1,6 +1,6 @@
|
|||
package floatingip
|
||||
package floatingips
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
const resourcePath = "os-floating-ips"
|
||||
|
||||
|
@ -24,14 +24,14 @@ 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 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 associateURL(c *gophercloud.ServiceClient, serverID string) string {
|
||||
return serverURL(c, serverID)
|
||||
}
|
||||
|
||||
func disassociateURL(c *gophercloud.ServiceClient, serverId string) string {
|
||||
return serverURL(c, serverId)
|
||||
func disassociateURL(c *gophercloud.ServiceClient, serverID string) string {
|
||||
return serverURL(c, serverID)
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
package keypairs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsExt adds a KeyPair option to the base CreateOpts.
|
||||
|
@ -47,56 +45,40 @@ type CreateOptsBuilder interface {
|
|||
|
||||
// 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
|
||||
|
||||
// Name is a friendly name to refer to this KeyPair in other services.
|
||||
Name string `json:"name" required:"true"`
|
||||
// PublicKey [optional] is a pregenerated OpenSSH-formatted public key. If provided, this key
|
||||
// will be imported and no new key will be created.
|
||||
PublicKey string
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}
|
||||
|
||||
// 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
|
||||
return gophercloud.BuildRequestBody(opts, "keypair")
|
||||
}
|
||||
|
||||
// Create requests the creation of a new keypair on the server, or to import a pre-existing
|
||||
// keypair.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToKeyPairCreateMap()
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
b, err := opts.ToKeyPairCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
return res
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
func Get(client *gophercloud.ServiceClient, name string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, name), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, name), nil)
|
||||
return
|
||||
}
|
|
@ -1,31 +1,30 @@
|
|||
package keypairs
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/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"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Fingerprint is a short sequence of bytes that can be used to authenticate or validate a longer
|
||||
// public key.
|
||||
Fingerprint string `mapstructure:"fingerprint"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
|
||||
// PublicKey is the public key from this pair, in OpenSSH format. "ssh-rsa AAAAB3Nz..."
|
||||
PublicKey string `mapstructure:"public_key"`
|
||||
PublicKey string `json:"public_key"`
|
||||
|
||||
// PrivateKey is the private key from this pair, in PEM format.
|
||||
// "-----BEGIN RSA PRIVATE KEY-----\nMIICXA..." It is only present if this keypair was just
|
||||
// returned from a Create call
|
||||
PrivateKey string `mapstructure:"private_key"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
|
||||
// UserID is the user who owns this keypair.
|
||||
UserID string `mapstructure:"user_id"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// KeyPairPage stores a single, only page of KeyPair results from a List call.
|
||||
|
@ -40,18 +39,16 @@ func (page KeyPairPage) IsEmpty() (bool, error) {
|
|||
}
|
||||
|
||||
// ExtractKeyPairs interprets a page of results as a slice of KeyPairs.
|
||||
func ExtractKeyPairs(page pagination.Page) ([]KeyPair, error) {
|
||||
func ExtractKeyPairs(r pagination.Page) ([]KeyPair, error) {
|
||||
type pair struct {
|
||||
KeyPair KeyPair `mapstructure:"keypair"`
|
||||
KeyPair KeyPair `json:"keypair"`
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
KeyPairs []pair `mapstructure:"keypairs"`
|
||||
var s struct {
|
||||
KeyPairs []pair `json:"keypairs"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(KeyPairPage).Body, &resp)
|
||||
results := make([]KeyPair, len(resp.KeyPairs))
|
||||
for i, pair := range resp.KeyPairs {
|
||||
err := (r.(KeyPairPage)).ExtractInto(&s)
|
||||
results := make([]KeyPair, len(s.KeyPairs))
|
||||
for i, pair := range s.KeyPairs {
|
||||
results[i] = pair.KeyPair
|
||||
}
|
||||
return results, err
|
||||
|
@ -63,16 +60,11 @@ type keyPairResult struct {
|
|||
|
||||
// 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 s struct {
|
||||
KeyPair *KeyPair `json:"keypair"`
|
||||
}
|
||||
|
||||
var res struct {
|
||||
KeyPair *KeyPair `json:"keypair" mapstructure:"keypair"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &res)
|
||||
return res.KeyPair, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.KeyPair, err
|
||||
}
|
||||
|
||||
// CreateResult is the response from a Create operation. Call its Extract method to interpret it
|
|
@ -1,6 +1,6 @@
|
|||
package keypairs
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
const resourcePath = "os-keypairs"
|
||||
|
19
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go
generated
vendored
Normal file
19
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package startstop
|
||||
|
||||
import "github.com/gophercloud/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) (r gophercloud.ErrResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-start": nil}, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Stop is the operation responsible for stopping a Compute server.
|
||||
func Stop(client *gophercloud.ServiceClient, id string) (r gophercloud.ErrResult) {
|
||||
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"os-stop": nil}, nil, nil)
|
||||
return
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
|
@ -36,10 +34,7 @@ type ListOpts struct {
|
|||
// 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
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// ListDetail instructs OpenStack to provide a list of flavors.
|
||||
|
@ -54,50 +49,52 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat
|
|||
}
|
||||
url += query
|
||||
}
|
||||
createPage := func(r pagination.PageResult) pagination.Page {
|
||||
return pagination.NewPager(client, url, 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
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// IDFromName is a convienience function that returns a flavor's ID given its name.
|
||||
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
|
||||
flavorCount := 0
|
||||
flavorID := ""
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("A flavor name must be provided.")
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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
|
||||
})
|
||||
all, err := ExtractFlavors(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch flavorCount {
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find flavor: %s", name)
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return flavorID, nil
|
||||
return id, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, name)
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "flavor"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
114
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
Normal file
114
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors/results.go
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// 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 (r GetResult) Extract() (*Flavor, error) {
|
||||
var s struct {
|
||||
Flavor *Flavor `json:"flavor"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
return s.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 `json:"id"`
|
||||
// The Disk and RA< fields provide a measure of storage space offered by the flavor, in GB and MB, respectively.
|
||||
Disk int `json:"disk"`
|
||||
RAM int `json:"ram"`
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
Name string `json:"name"`
|
||||
RxTxFactor float64 `json:"rxtx_factor"`
|
||||
// Swap indicates how much space is reserved for swap.
|
||||
// If not provided, this field will be set to 0.
|
||||
Swap int `json:"swap"`
|
||||
// VCPUs indicates how many (virtual) CPUs are available for this flavor.
|
||||
VCPUs int `json:"vcpus"`
|
||||
}
|
||||
|
||||
func (f *Flavor) UnmarshalJSON(b []byte) error {
|
||||
var flavor struct {
|
||||
ID string `json:"id"`
|
||||
Disk int `json:"disk"`
|
||||
RAM int `json:"ram"`
|
||||
Name string `json:"name"`
|
||||
RxTxFactor float64 `json:"rxtx_factor"`
|
||||
Swap interface{} `json:"swap"`
|
||||
VCPUs int `json:"vcpus"`
|
||||
}
|
||||
err := json.Unmarshal(b, &flavor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.ID = flavor.ID
|
||||
f.Disk = flavor.Disk
|
||||
f.RAM = flavor.RAM
|
||||
f.Name = flavor.Name
|
||||
f.RxTxFactor = flavor.RxTxFactor
|
||||
f.VCPUs = flavor.VCPUs
|
||||
|
||||
switch t := flavor.Swap.(type) {
|
||||
case float64:
|
||||
f.Swap = int(t)
|
||||
case string:
|
||||
switch t {
|
||||
case "":
|
||||
f.Swap = 0
|
||||
default:
|
||||
swap, err := strconv.ParseFloat(t, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Swap = int(swap)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 (page FlavorPage) IsEmpty() (bool, error) {
|
||||
flavors, err := ExtractFlavors(page)
|
||||
return len(flavors) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
||||
func (page FlavorPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"flavors_links"`
|
||||
}
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
|
||||
func ExtractFlavors(r pagination.Page) ([]Flavor, error) {
|
||||
var s struct {
|
||||
Flavors []Flavor `json:"flavors"`
|
||||
}
|
||||
err := (r.(FlavorPage)).ExtractInto(&s)
|
||||
return s.Flavors, err
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
|
@ -1,10 +1,8 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
|
@ -34,10 +32,7 @@ type ListOpts struct {
|
|||
// 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
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// ListDetail enumerates the available images.
|
||||
|
@ -50,60 +45,58 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat
|
|||
}
|
||||
url += query
|
||||
}
|
||||
|
||||
createPage := func(r pagination.PageResult) pagination.Page {
|
||||
return pagination.NewPager(client, url, 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
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 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.")
|
||||
count := 0
|
||||
id := ""
|
||||
allPages, err := ListDetail(client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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
|
||||
})
|
||||
all, err := ExtractImages(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch imageCount {
|
||||
for _, f := range all {
|
||||
if f.Name == name {
|
||||
count++
|
||||
id = f.ID
|
||||
}
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
return "", fmt.Errorf("Unable to find image: %s", name)
|
||||
err := &gophercloud.ErrResourceNotFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
return "", err
|
||||
case 1:
|
||||
return imageID, nil
|
||||
return id, nil
|
||||
default:
|
||||
return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
|
||||
err := &gophercloud.ErrMultipleResourcesFound{}
|
||||
err.ResourceType = "image"
|
||||
err.Name = name
|
||||
err.Count = count
|
||||
return "", err
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult temporarily stores a Get response.
|
||||
|
@ -17,17 +16,12 @@ type DeleteResult struct {
|
|||
}
|
||||
|
||||
// Extract interprets a GetResult as an Image.
|
||||
func (gr GetResult) Extract() (*Image, error) {
|
||||
if gr.Err != nil {
|
||||
return nil, gr.Err
|
||||
func (r GetResult) Extract() (*Image, error) {
|
||||
var s struct {
|
||||
Image *Image `json:"image"`
|
||||
}
|
||||
|
||||
var decoded struct {
|
||||
Image Image `mapstructure:"image"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(gr.Body, &decoded)
|
||||
return &decoded.Image, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Image, err
|
||||
}
|
||||
|
||||
// Image is used for JSON (un)marshalling.
|
||||
|
@ -51,6 +45,8 @@ type Image struct {
|
|||
Status string
|
||||
|
||||
Updated string
|
||||
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// ImagePage contains a single page of results from a List operation.
|
||||
|
@ -62,34 +58,26 @@ type ImagePage struct {
|
|||
// 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
|
||||
return len(images) == 0, err
|
||||
}
|
||||
|
||||
// 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 s struct {
|
||||
Links []gophercloud.Link `json:"images_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
return gophercloud.ExtractNextURL(s.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"`
|
||||
func ExtractImages(r pagination.Page) ([]Image, error) {
|
||||
var s struct {
|
||||
Images []Image `json:"images"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &results)
|
||||
return results.Images, err
|
||||
err := (r.(ImagePage)).ExtractInto(&s)
|
||||
return s.Images, err
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package images
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func listDetailURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("images", "detail")
|
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
71
vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// ErrNeitherImageIDNorImageNameProvided is the error when neither the image
|
||||
// ID nor the image name is provided for a server operation
|
||||
type ErrNeitherImageIDNorImageNameProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNeitherImageIDNorImageNameProvided) Error() string {
|
||||
return "One and only one of the image ID and the image name must be provided."
|
||||
}
|
||||
|
||||
// ErrNeitherFlavorIDNorFlavorNameProvided is the error when neither the flavor
|
||||
// ID nor the flavor name is provided for a server operation
|
||||
type ErrNeitherFlavorIDNorFlavorNameProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNeitherFlavorIDNorFlavorNameProvided) Error() string {
|
||||
return "One and only one of the flavor ID and the flavor name must be provided."
|
||||
}
|
||||
|
||||
type ErrNoClientProvidedForIDByName struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
func (e ErrNoClientProvidedForIDByName) Error() string {
|
||||
return "A service client must be provided to find a resource ID by name."
|
||||
}
|
||||
|
||||
// ErrInvalidHowParameterProvided is the error when an unknown value is given
|
||||
// for the `how` argument
|
||||
type ErrInvalidHowParameterProvided struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
// ErrNoAdminPassProvided is the error when an administrative password isn't
|
||||
// provided for a server operation
|
||||
type ErrNoAdminPassProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrNoImageIDProvided is the error when an image ID isn't provided for a server
|
||||
// operation
|
||||
type ErrNoImageIDProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrNoIDProvided is the error when a server ID isn't provided for a server
|
||||
// operation
|
||||
type ErrNoIDProvided struct{ gophercloud.ErrMissingInput }
|
||||
|
||||
// ErrServer is a generic error type for servers HTTP operations.
|
||||
type ErrServer struct {
|
||||
gophercloud.ErrUnexpectedResponseCode
|
||||
ID string
|
||||
}
|
||||
|
||||
func (se ErrServer) Error() string {
|
||||
return fmt.Sprintf("Error while executing HTTP request for server [%s]", se.ID)
|
||||
}
|
||||
|
||||
// Error404 overrides the generic 404 error message.
|
||||
func (se ErrServer) Error404(e gophercloud.ErrUnexpectedResponseCode) error {
|
||||
se.ErrUnexpectedResponseCode = e
|
||||
return &ErrServerNotFound{se}
|
||||
}
|
||||
|
||||
// ErrServerNotFound is the error when a 404 is received during server HTTP
|
||||
// operations.
|
||||
type ErrServerNotFound struct {
|
||||
ErrServer
|
||||
}
|
||||
|
||||
func (e ErrServerNotFound) Error() string {
|
||||
return fmt.Sprintf("I couldn't find server [%s]", e.ID)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -3,14 +3,13 @@ package servers
|
|||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
type serverResult struct {
|
||||
|
@ -19,29 +18,11 @@ type serverResult struct {
|
|||
|
||||
// Extract interprets any serverResult as a Server, if possible.
|
||||
func (r serverResult) Extract() (*Server, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
var s struct {
|
||||
Server *Server `json:"server"`
|
||||
}
|
||||
|
||||
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
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Server, err
|
||||
}
|
||||
|
||||
// CreateResult temporarily contains the response from a Create call.
|
||||
|
@ -94,20 +75,14 @@ type GetPasswordResult struct {
|
|||
// 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 s struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Password string `mapstructure:"password"`
|
||||
err := r.ExtractInto(&s)
|
||||
if err == nil && privateKey != nil && s.Password != "" {
|
||||
return decryptPassword(s.Password, privateKey)
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
if err == nil && privateKey != nil && response.Password != "" {
|
||||
return decryptPassword(response.Password, privateKey)
|
||||
}
|
||||
return response.Password, err
|
||||
return s.Password, err
|
||||
}
|
||||
|
||||
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
|
||||
|
@ -133,83 +108,88 @@ func (res CreateImageResult) ExtractImageID() (string, error) {
|
|||
// 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())
|
||||
return "", err
|
||||
}
|
||||
imageId := path.Base(u.Path)
|
||||
if imageId == "." || imageId == "/" {
|
||||
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
|
||||
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 s struct {
|
||||
AdminPass string `json:"adminPass"`
|
||||
}
|
||||
|
||||
var response struct {
|
||||
AdminPass string `mapstructure:"adminPass"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
return response.AdminPass, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.AdminPass, err
|
||||
}
|
||||
|
||||
// Server exposes only the standard OpenStack fields corresponding to a given server on the user's account.
|
||||
type Server struct {
|
||||
// ID uniquely identifies this server amongst all other servers, including those not accessible to the current tenant.
|
||||
ID string
|
||||
|
||||
ID string `json:"id"`
|
||||
// TenantID identifies the tenant owning this server resource.
|
||||
TenantID string `mapstructure:"tenant_id"`
|
||||
|
||||
TenantID string `json:"tenant_id"`
|
||||
// UserID uniquely identifies the user account owning the tenant.
|
||||
UserID string `mapstructure:"user_id"`
|
||||
|
||||
UserID string `json:"user_id"`
|
||||
// Name contains the human-readable name for the server.
|
||||
Name string
|
||||
|
||||
Name string `json:"name"`
|
||||
// Updated and Created contain ISO-8601 timestamps of when the state of the server last changed, and when it was created.
|
||||
Updated string
|
||||
Created string
|
||||
|
||||
HostID 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{}
|
||||
|
||||
Metadata map[string]string
|
||||
// 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"`
|
||||
|
||||
KeyName string `json:"key_name"`
|
||||
// AdminPass will generally be empty (""). However, it will contain the administrative password chosen when provisioning a new server without a set AdminPass setting in the first place.
|
||||
// Note that this is the ONLY time this field will be valid.
|
||||
AdminPass string `json:"adminPass" mapstructure:"adminPass"`
|
||||
|
||||
AdminPass string `json:"adminPass"`
|
||||
// SecurityGroups includes the security groups that this instance has applied to it
|
||||
SecurityGroups []map[string]interface{} `json:"security_groups" mapstructure:"security_groups"`
|
||||
SecurityGroups []map[string]interface{} `json:"security_groups"`
|
||||
}
|
||||
|
||||
func (s *Server) UnmarshalJSON(b []byte) error {
|
||||
type tmp Server
|
||||
var server *struct {
|
||||
tmp
|
||||
Image interface{}
|
||||
}
|
||||
err := json.Unmarshal(b, &server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = Server(server.tmp)
|
||||
|
||||
switch t := server.Image.(type) {
|
||||
case map[string]interface{}:
|
||||
s.Image = t
|
||||
case string:
|
||||
switch t {
|
||||
case "":
|
||||
s.Image = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerPage abstracts the raw results of making a List() request against the API.
|
||||
|
@ -222,47 +202,28 @@ type ServerPage struct {
|
|||
// 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
|
||||
return len(servers) == 0, err
|
||||
}
|
||||
|
||||
// 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 s struct {
|
||||
Links []gophercloud.Link `json:"servers_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
err := page.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
return gophercloud.ExtractNextURL(s.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"`
|
||||
func ExtractServers(r pagination.Page) ([]Server, error) {
|
||||
var s struct {
|
||||
Servers []Server `json:"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
|
||||
err := (r.(ServerPage)).ExtractInto(&s)
|
||||
return s.Servers, err
|
||||
}
|
||||
|
||||
// MetadataResult contains the result of a call for (potentially) multiple key-value pairs.
|
||||
|
@ -307,43 +268,26 @@ type DeleteMetadatumResult struct {
|
|||
|
||||
// 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 s struct {
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Metadata map[string]string `mapstructure:"metadata"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
return response.Metadata, err
|
||||
err := r.ExtractInto(&s)
|
||||
return s.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 s struct {
|
||||
Metadatum map[string]string `json:"meta"`
|
||||
}
|
||||
|
||||
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
|
||||
err := r.ExtractInto(&s)
|
||||
return s.Metadatum, err
|
||||
}
|
||||
|
||||
// Address represents an IP address.
|
||||
type Address struct {
|
||||
Version int `mapstructure:"version"`
|
||||
Address string `mapstructure:"addr"`
|
||||
Version int `json:"version"`
|
||||
Address string `json:"addr"`
|
||||
}
|
||||
|
||||
// AddressPage abstracts the raw results of making a ListAddresses() request against the API.
|
||||
|
@ -356,27 +300,17 @@ type AddressPage struct {
|
|||
// 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
|
||||
return len(addresses) == 0, err
|
||||
}
|
||||
|
||||
// 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"`
|
||||
func ExtractAddresses(r pagination.Page) (map[string][]Address, error) {
|
||||
var s struct {
|
||||
Addresses map[string][]Address `json:"addresses"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.Addresses, err
|
||||
err := (r.(AddressPage)).ExtractInto(&s)
|
||||
return s.Addresses, err
|
||||
}
|
||||
|
||||
// NetworkAddressPage abstracts the raw results of making a ListAddressesByNetwork() request against the API.
|
||||
|
@ -389,27 +323,22 @@ type NetworkAddressPage struct {
|
|||
// 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
|
||||
return len(addresses) == 0, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
func ExtractNetworkAddresses(r pagination.Page) ([]Address, error) {
|
||||
var s map[string][]Address
|
||||
err := (r.(NetworkAddressPage)).ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var key string
|
||||
for k := range response {
|
||||
for k := range s {
|
||||
key = k
|
||||
}
|
||||
|
||||
return response[key], err
|
||||
return s[key], err
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package servers
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func createURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("servers")
|
|
@ -1,6 +1,6 @@
|
|||
package servers
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// WaitForStatus will continually poll a server until it successfully transitions to a specified
|
||||
// status. It will do this for at most the number of seconds specified.
|
|
@ -1,11 +1,9 @@
|
|||
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"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
)
|
||||
|
||||
// V2EndpointURL discovers the endpoint URL for a specific service from a ServiceCatalog acquired
|
||||
|
@ -29,7 +27,9 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
|
||||
// Report an error if the options were ambiguous.
|
||||
if len(endpoints) > 1 {
|
||||
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
|
||||
err := &ErrMultipleMatchingEndpointsV2{}
|
||||
err.Endpoints = endpoints
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract the appropriate URL from the matching Endpoint.
|
||||
|
@ -42,12 +42,16 @@ func V2EndpointURL(catalog *tokens2.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
case gophercloud.AvailabilityAdmin:
|
||||
return gophercloud.NormalizeURL(endpoint.AdminURL), nil
|
||||
default:
|
||||
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
|
||||
err := &ErrInvalidAvailabilityProvided{}
|
||||
err.Argument = "Availability"
|
||||
err.Value = opts.Availability
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Report an error if there were no matching endpoints.
|
||||
return "", gophercloud.ErrEndpointNotFound
|
||||
err := &gophercloud.ErrEndpointNotFound{}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// V3EndpointURL discovers the endpoint URL for a specific service from a Catalog acquired
|
||||
|
@ -66,7 +70,10 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
if opts.Availability != gophercloud.AvailabilityAdmin &&
|
||||
opts.Availability != gophercloud.AvailabilityPublic &&
|
||||
opts.Availability != gophercloud.AvailabilityInternal {
|
||||
return "", fmt.Errorf("Unexpected availability in endpoint query: %s", opts.Availability)
|
||||
err := &ErrInvalidAvailabilityProvided{}
|
||||
err.Argument = "Availability"
|
||||
err.Value = opts.Availability
|
||||
return "", err
|
||||
}
|
||||
if (opts.Availability == gophercloud.Availability(endpoint.Interface)) &&
|
||||
(opts.Region == "" || endpoint.Region == opts.Region) {
|
||||
|
@ -78,7 +85,7 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
|
||||
// Report an error if the options were ambiguous.
|
||||
if len(endpoints) > 1 {
|
||||
return "", fmt.Errorf("Discovered %d matching endpoints: %#v", len(endpoints), endpoints)
|
||||
return "", ErrMultipleMatchingEndpointsV3{Endpoints: endpoints}
|
||||
}
|
||||
|
||||
// Extract the URL from the matching Endpoint.
|
||||
|
@ -87,5 +94,6 @@ func V3EndpointURL(catalog *tokens3.ServiceCatalog, opts gophercloud.EndpointOpt
|
|||
}
|
||||
|
||||
// Report an error if there were no matching endpoints.
|
||||
return "", gophercloud.ErrEndpointNotFound
|
||||
err := &gophercloud.ErrEndpointNotFound{}
|
||||
return "", err
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
|
||||
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
|
||||
)
|
||||
|
||||
// ErrEndpointNotFound is the error when no suitable endpoint can be found
|
||||
// in the user's catalog
|
||||
type ErrEndpointNotFound struct{ gophercloud.BaseError }
|
||||
|
||||
func (e ErrEndpointNotFound) Error() string {
|
||||
return "No suitable endpoint could be found in the service catalog."
|
||||
}
|
||||
|
||||
// ErrInvalidAvailabilityProvided is the error when an invalid endpoint
|
||||
// availability is provided
|
||||
type ErrInvalidAvailabilityProvided struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrInvalidAvailabilityProvided) Error() string {
|
||||
return fmt.Sprintf("Unexpected availability in endpoint query: %s", e.Value)
|
||||
}
|
||||
|
||||
// ErrMultipleMatchingEndpointsV2 is the error when more than one endpoint
|
||||
// for the given options is found in the v2 catalog
|
||||
type ErrMultipleMatchingEndpointsV2 struct {
|
||||
gophercloud.BaseError
|
||||
Endpoints []tokens2.Endpoint
|
||||
}
|
||||
|
||||
func (e ErrMultipleMatchingEndpointsV2) Error() string {
|
||||
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||
}
|
||||
|
||||
// ErrMultipleMatchingEndpointsV3 is the error when more than one endpoint
|
||||
// for the given options is found in the v3 catalog
|
||||
type ErrMultipleMatchingEndpointsV3 struct {
|
||||
gophercloud.BaseError
|
||||
Endpoints []tokens3.Endpoint
|
||||
}
|
||||
|
||||
func (e ErrMultipleMatchingEndpointsV3) Error() string {
|
||||
return fmt.Sprintf("Discovered %d matching endpoints: %#v", len(e.Endpoints), e.Endpoints)
|
||||
}
|
||||
|
||||
// ErrNoAuthURL is the error when the OS_AUTH_URL environment variable is not
|
||||
// found
|
||||
type ErrNoAuthURL struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoAuthURL) Error() string {
|
||||
return "Environment variable OS_AUTH_URL needs to be set."
|
||||
}
|
||||
|
||||
// ErrNoUsername is the error when the OS_USERNAME environment variable is not
|
||||
// found
|
||||
type ErrNoUsername struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoUsername) Error() string {
|
||||
return "Environment variable OS_USERNAME needs to be set."
|
||||
}
|
||||
|
||||
// ErrNoPassword is the error when the OS_PASSWORD environment variable is not
|
||||
// found
|
||||
type ErrNoPassword struct{ gophercloud.ErrInvalidInput }
|
||||
|
||||
func (e ErrNoPassword) Error() string {
|
||||
return "Environment variable OS_PASSWORD needs to be set."
|
||||
}
|
|
@ -1,25 +1,20 @@
|
|||
package tenants
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/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)
|
||||
|
@ -28,6 +23,7 @@ func List(client *gophercloud.ServiceClient, opts *ListOpts) pagination.Pager {
|
|||
}
|
||||
url += q.String()
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, url, createPage)
|
||||
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
|
||||
return TenantPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
53
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tenants/results.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package tenants
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/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 `json:"id"`
|
||||
|
||||
// Name is a friendlier user-facing name for this tenant.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Description is a human-readable explanation of this Tenant's purpose.
|
||||
Description string `json:"description"`
|
||||
|
||||
// Enabled indicates whether or not a tenant is active.
|
||||
Enabled bool `json:"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 (r TenantPage) IsEmpty() (bool, error) {
|
||||
tenants, err := ExtractTenants(r)
|
||||
return len(tenants) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL extracts the "next" link from the tenants_links section of the result.
|
||||
func (r TenantPage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Links []gophercloud.Link `json:"tenants_links"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gophercloud.ExtractNextURL(s.Links)
|
||||
}
|
||||
|
||||
// ExtractTenants returns a slice of Tenants contained in a single page of results.
|
||||
func ExtractTenants(r pagination.Page) ([]Tenant, error) {
|
||||
var s struct {
|
||||
Tenants []Tenant `json:"tenants"`
|
||||
}
|
||||
err := (r.(TenantPage)).ExtractInto(&s)
|
||||
return s.Tenants, err
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package tenants
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func listURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("tenants")
|
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
99
vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
type PasswordCredentialsV2 struct {
|
||||
Username string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
}
|
||||
|
||||
type TokenCredentialsV2 struct {
|
||||
ID string `json:"id,omitempty" required:"true"`
|
||||
}
|
||||
|
||||
// AuthOptionsV2 wraps a gophercloud AuthOptions in order to adhere to the AuthOptionsBuilder
|
||||
// interface.
|
||||
type AuthOptionsV2 struct {
|
||||
PasswordCredentials *PasswordCredentialsV2 `json:"passwordCredentials,omitempty" xor:"TokenCredentials"`
|
||||
|
||||
// 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 string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
|
||||
// TokenCredentials allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenCredentials *TokenCredentialsV2 `json:"token,omitempty" xor:"PasswordCredentials"`
|
||||
}
|
||||
|
||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||
type AuthOptionsBuilder interface {
|
||||
// ToTokenCreateMap assembles the Create request body, returning an error if parameters are
|
||||
// missing or inconsistent.
|
||||
ToTokenV2CreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// AuthOptions are the valid options for Openstack Identity v2 authentication.
|
||||
// For field descriptions, see gophercloud.AuthOptions.
|
||||
type AuthOptions struct {
|
||||
IdentityEndpoint string `json:"-"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
TenantID string `json:"tenantId,omitempty"`
|
||||
TenantName string `json:"tenantName,omitempty"`
|
||||
AllowReauth bool `json:"-"`
|
||||
TokenID string
|
||||
}
|
||||
|
||||
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
|
||||
// interface in the v2 tokens package
|
||||
func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
|
||||
v2Opts := AuthOptionsV2{
|
||||
TenantID: opts.TenantID,
|
||||
TenantName: opts.TenantName,
|
||||
}
|
||||
|
||||
if opts.Password != "" {
|
||||
v2Opts.PasswordCredentials = &PasswordCredentialsV2{
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
}
|
||||
} else {
|
||||
v2Opts.TokenCredentials = &TokenCredentialsV2{
|
||||
ID: opts.TokenID,
|
||||
}
|
||||
}
|
||||
|
||||
b, err := gophercloud.BuildRequestBody(v2Opts, "auth")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, 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) (r CreateResult) {
|
||||
b, err := auth.ToTokenV2CreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Get validates and retrieves information for user's token.
|
||||
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
return
|
||||
}
|
|
@ -3,9 +3,8 @@ package tokens
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
|
||||
)
|
||||
|
||||
// Token provides only the most basic information related to an authentication token.
|
||||
|
@ -25,15 +24,17 @@ type Token struct {
|
|||
Tenant tenants.Tenant
|
||||
}
|
||||
|
||||
// Authorization need user info which can get from token authentication's response
|
||||
// Role is a role for a user.
|
||||
type Role struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// User is an OpenStack user.
|
||||
type User struct {
|
||||
ID string `mapstructure:"id"`
|
||||
Name string `mapstructure:"name"`
|
||||
UserName string `mapstructure:"username"`
|
||||
Roles []Role `mapstructure:"roles"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
UserName string `json:"username"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
// Endpoint represents a single API endpoint offered by a service.
|
||||
|
@ -45,14 +46,14 @@ type User struct {
|
|||
//
|
||||
// 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"`
|
||||
TenantID string `json:"tenantId"`
|
||||
PublicURL string `json:"publicURL"`
|
||||
InternalURL string `json:"internalURL"`
|
||||
AdminURL string `json:"adminURL"`
|
||||
Region string `json:"region"`
|
||||
VersionID string `json:"versionId"`
|
||||
VersionInfo string `json:"versionInfo"`
|
||||
VersionList string `json:"versionList"`
|
||||
}
|
||||
|
||||
// CatalogEntry provides a type-safe interface to an Identity API V2 service catalog listing.
|
||||
|
@ -63,15 +64,15 @@ type Endpoint struct {
|
|||
// 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"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// Type will contain a type string if OpenStack defines a type for the service.
|
||||
// Otherwise, for provider-specific services, the provider may assign their own type strings.
|
||||
Type string `mapstructure:"type"`
|
||||
Type string `json:"type"`
|
||||
|
||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
||||
// the service.
|
||||
Endpoints []Endpoint `mapstructure:"endpoints"`
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
|
||||
|
@ -92,56 +93,43 @@ type GetResult struct {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
func (r CreateResult) ExtractToken() (*Token, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
Token struct {
|
||||
Expires string `mapstructure:"expires"`
|
||||
ID string `mapstructure:"id"`
|
||||
Tenant tenants.Tenant `mapstructure:"tenant"`
|
||||
} `mapstructure:"token"`
|
||||
} `mapstructure:"access"`
|
||||
Expires string `json:"expires"`
|
||||
ID string `json:"id"`
|
||||
Tenant tenants.Tenant `json:"tenant"`
|
||||
} `json:"token"`
|
||||
} `json:"access"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(result.Body, &response)
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, response.Access.Token.Expires)
|
||||
expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
ID: response.Access.Token.ID,
|
||||
ID: s.Access.Token.ID,
|
||||
ExpiresAt: expiresTs,
|
||||
Tenant: response.Access.Token.Tenant,
|
||||
Tenant: s.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 {
|
||||
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
Entries []CatalogEntry `mapstructure:"serviceCatalog"`
|
||||
} `mapstructure:"access"`
|
||||
Entries []CatalogEntry `json:"serviceCatalog"`
|
||||
} `json:"access"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(result.Body, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ServiceCatalog{Entries: response.Access.Entries}, nil
|
||||
err := r.ExtractInto(&s)
|
||||
return &ServiceCatalog{Entries: s.Access.Entries}, err
|
||||
}
|
||||
|
||||
// createErr quickly packs an error in a CreateResult.
|
||||
|
@ -150,21 +138,12 @@ func createErr(err error) CreateResult {
|
|||
}
|
||||
|
||||
// ExtractUser returns the User from a GetResult.
|
||||
func (result GetResult) ExtractUser() (*User, error) {
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
func (r GetResult) ExtractUser() (*User, error) {
|
||||
var s struct {
|
||||
Access struct {
|
||||
User User `mapstructure:"user"`
|
||||
} `mapstructure:"access"`
|
||||
User User `json:"user"`
|
||||
} `json:"access"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(result.Body, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Access.User, nil
|
||||
err := r.ExtractInto(&s)
|
||||
return &s.Access.User, err
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// CreateURL generates the URL used to create new Tokens.
|
||||
func CreateURL(client *gophercloud.ServiceClient) string {
|
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
200
vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
// Scope allows a created token to be limited to a specific domain or project.
|
||||
type Scope struct {
|
||||
ProjectID string `json:"scope.project.id,omitempty" not:"ProjectName,DomainID,DomainName"`
|
||||
ProjectName string `json:"scope.project.name,omitempty"`
|
||||
DomainID string `json:"scope.project.id,omitempty" not:"ProjectName,ProjectID,DomainName"`
|
||||
DomainName string `json:"scope.project.id,omitempty"`
|
||||
}
|
||||
|
||||
// AuthOptionsBuilder describes any argument that may be passed to the Create call.
|
||||
type AuthOptionsBuilder interface {
|
||||
// ToTokenV3CreateMap assembles the Create request body, returning an error if parameters are
|
||||
// missing or inconsistent.
|
||||
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
|
||||
ToTokenV3ScopeMap() (map[string]interface{}, error)
|
||||
CanReauth() bool
|
||||
}
|
||||
|
||||
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 `json:"-"`
|
||||
|
||||
// Username is required if using Identity V2 API. Consult with your provider's
|
||||
// control panel to discover your account's username. In Identity V3, either
|
||||
// UserID or a combination of Username and DomainID or DomainName are needed.
|
||||
Username string `json:"username,omitempty"`
|
||||
UserID string `json:"id,omitempty"`
|
||||
|
||||
Password string `json:"password,omitempty"`
|
||||
|
||||
// At most one of DomainID and DomainName must be provided if using Username
|
||||
// with Identity V3. Otherwise, either are optional.
|
||||
DomainID string `json:"id,omitempty"`
|
||||
DomainName string `json:"name,omitempty"`
|
||||
|
||||
// AllowReauth should be set to true if you grant permission for Gophercloud to
|
||||
// cache your credentials in memory, and to allow Gophercloud to attempt to
|
||||
// re-authenticate automatically if/when your token expires. If you set it to
|
||||
// false, it will not cache these settings, but re-authentication will not be
|
||||
// possible. This setting defaults to false.
|
||||
AllowReauth bool `json:"-"`
|
||||
|
||||
// TokenID allows users to authenticate (possibly as another user) with an
|
||||
// authentication token ID.
|
||||
TokenID string `json:"-"`
|
||||
|
||||
Scope Scope `json:"-"`
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
|
||||
gophercloudAuthOpts := gophercloud.AuthOptions{
|
||||
Username: opts.Username,
|
||||
UserID: opts.UserID,
|
||||
Password: opts.Password,
|
||||
DomainID: opts.DomainID,
|
||||
DomainName: opts.DomainName,
|
||||
AllowReauth: opts.AllowReauth,
|
||||
TokenID: opts.TokenID,
|
||||
}
|
||||
|
||||
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
|
||||
if opts.Scope.ProjectName != "" {
|
||||
// ProjectName provided: either DomainID or DomainName must also be supplied.
|
||||
// ProjectID may not be supplied.
|
||||
if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
|
||||
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
if opts.Scope.ProjectID != "" {
|
||||
return nil, gophercloud.ErrScopeProjectIDOrProjectName{}
|
||||
}
|
||||
|
||||
if opts.Scope.DomainID != "" {
|
||||
// ProjectName + DomainID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &opts.Scope.ProjectName,
|
||||
"domain": map[string]interface{}{"id": &opts.Scope.DomainID},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if opts.Scope.DomainName != "" {
|
||||
// ProjectName + DomainName
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"name": &opts.Scope.ProjectName,
|
||||
"domain": map[string]interface{}{"name": &opts.Scope.DomainName},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
} else if opts.Scope.ProjectID != "" {
|
||||
// ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
|
||||
if opts.Scope.DomainID != "" {
|
||||
return nil, gophercloud.ErrScopeProjectIDAlone{}
|
||||
}
|
||||
if opts.Scope.DomainName != "" {
|
||||
return nil, gophercloud.ErrScopeProjectIDAlone{}
|
||||
}
|
||||
|
||||
// ProjectID
|
||||
return map[string]interface{}{
|
||||
"project": map[string]interface{}{
|
||||
"id": &opts.Scope.ProjectID,
|
||||
},
|
||||
}, nil
|
||||
} else if opts.Scope.DomainID != "" {
|
||||
// DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
|
||||
if opts.Scope.DomainName != "" {
|
||||
return nil, gophercloud.ErrScopeDomainIDOrDomainName{}
|
||||
}
|
||||
|
||||
// DomainID
|
||||
return map[string]interface{}{
|
||||
"domain": map[string]interface{}{
|
||||
"id": &opts.Scope.DomainID,
|
||||
},
|
||||
}, nil
|
||||
} else if opts.Scope.DomainName != "" {
|
||||
return nil, gophercloud.ErrScopeDomainName{}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (opts *AuthOptions) CanReauth() bool {
|
||||
return opts.AllowReauth
|
||||
}
|
||||
|
||||
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, opts AuthOptionsBuilder) (r CreateResult) {
|
||||
scope, err := opts.ToTokenV3ScopeMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
b, err := opts.ToTokenV3CreateMap(scope)
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: map[string]string{"X-Auth-Token": ""},
|
||||
})
|
||||
r.Err = err
|
||||
if resp != nil {
|
||||
r.Header = resp.Header
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get validates and retrieves information about another token.
|
||||
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
|
||||
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
if resp != nil {
|
||||
r.Err = err
|
||||
r.Header = resp.Header
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate determines if a specified token is valid or not.
|
||||
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
|
||||
resp, err := c.Request("HEAD", tokenURL(c), &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
OkCodes: []int{204, 404},
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return resp.StatusCode == 204, nil
|
||||
}
|
||||
|
||||
// Revoke immediately makes specified token invalid.
|
||||
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
|
||||
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
|
||||
MoreHeaders: subjectTokenHeaders(c, token),
|
||||
})
|
||||
return
|
||||
}
|
|
@ -1,21 +1,17 @@
|
|||
package tokens
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
import "errors"
|
||||
import "github.com/gophercloud/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"`
|
||||
ID string `json:"id"`
|
||||
Region string `json:"region"`
|
||||
Interface string `json:"interface"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// CatalogEntry provides a type-safe interface to an Identity API V3 service catalog listing.
|
||||
|
@ -25,20 +21,16 @@ type Endpoint struct {
|
|||
// 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"`
|
||||
|
||||
ID string `json:"id"`
|
||||
// Name will contain the provider-specified name for the service.
|
||||
Name string `mapstructure:"name"`
|
||||
|
||||
Name string `json:"name"`
|
||||
// Type will contain a type string if OpenStack defines a type for the service.
|
||||
// Otherwise, for provider-specific services, the provider may assign their own type strings.
|
||||
Type string `mapstructure:"type"`
|
||||
|
||||
Type string `json:"type"`
|
||||
// Endpoints will let the caller iterate over all the different endpoints that may exist for
|
||||
// the service.
|
||||
Endpoints []Endpoint `mapstructure:"endpoints"`
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
// ServiceCatalog provides a view into the service catalog from a previous, successful authentication.
|
||||
|
@ -59,50 +51,34 @@ func (r commonResult) Extract() (*Token, error) {
|
|||
|
||||
// ExtractToken interprets a commonResult as a Token.
|
||||
func (r commonResult) ExtractToken() (*Token, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
var s struct {
|
||||
Token *Token `json:"token"`
|
||||
}
|
||||
|
||||
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)
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attempt to parse the timestamp.
|
||||
token.ExpiresAt, err = time.Parse(gophercloud.RFC3339Milli, response.Token.ExpiresAt)
|
||||
if s.Token == nil {
|
||||
return nil, errors.New("'token' missing in JSON response")
|
||||
}
|
||||
|
||||
return &token, err
|
||||
// Parse the token itself from the stored headers.
|
||||
s.Token.ID = r.Header.Get("X-Subject-Token")
|
||||
|
||||
return s.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 {
|
||||
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||
var s struct {
|
||||
Token struct {
|
||||
Entries []CatalogEntry `mapstructure:"catalog"`
|
||||
} `mapstructure:"token"`
|
||||
Entries []CatalogEntry `json:"catalog"`
|
||||
} `json:"token"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(result.Body, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ServiceCatalog{Entries: response.Token.Entries}, nil
|
||||
err := r.ExtractInto(&s)
|
||||
return &ServiceCatalog{Entries: s.Token.Entries}, err
|
||||
}
|
||||
|
||||
// CreateResult defers the interpretation of a created token.
|
||||
|
@ -132,8 +108,7 @@ type RevokeResult struct {
|
|||
// Each Token is valid for a set length of time.
|
||||
type Token struct {
|
||||
// ID is the issued token.
|
||||
ID string
|
||||
|
||||
ID string `json:"id"`
|
||||
// ExpiresAt is the timestamp at which this token will no longer be accepted.
|
||||
ExpiresAt time.Time
|
||||
ExpiresAt gophercloud.JSONRFC3339Milli `json:"expires_at"`
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package tokens
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func tokenURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("auth", "tokens")
|
238
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
Normal file
238
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,238 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ListOptsBuilder allows extensions to add additional parameters to the
|
||||
// List request.
|
||||
type ListOptsBuilder interface {
|
||||
ToImageListQuery() (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.
|
||||
//http://developer.openstack.org/api-ref-image-v2.html
|
||||
type ListOpts struct {
|
||||
// Integer value for the limit of values to return.
|
||||
Limit int `q:"limit"`
|
||||
|
||||
// UUID of the server at which you want to set a marker.
|
||||
Marker string `q:"marker"`
|
||||
|
||||
Name string `q:"name"`
|
||||
Visibility ImageVisibility `q:"visibility"`
|
||||
MemberStatus ImageMemberStatus `q:"member_status"`
|
||||
Owner string `q:"owner"`
|
||||
Status ImageStatus `q:"status"`
|
||||
SizeMin int64 `q:"size_min"`
|
||||
SizeMax int64 `q:"size_max"`
|
||||
SortKey string `q:"sort_key"`
|
||||
SortDir string `q:"sort_dir"`
|
||||
Tag string `q:"tag"`
|
||||
}
|
||||
|
||||
// ToImageListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToImageListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
return q.String(), err
|
||||
}
|
||||
|
||||
// List implements image list request
|
||||
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := listURL(c)
|
||||
if opts != nil {
|
||||
query, err := opts.ToImageListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
|
||||
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOptsBuilder describes struct types that can be accepted by the Create call.
|
||||
// The CreateOpts struct in this package does.
|
||||
type CreateOptsBuilder interface {
|
||||
// Returns value that can be passed to json.Marshal
|
||||
ToImageCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateOpts implements CreateOptsBuilder
|
||||
type CreateOpts struct {
|
||||
// Name is the name of the new image.
|
||||
Name string `json:"name" required:"true"`
|
||||
|
||||
// Id is the the image ID.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// Visibility defines who can see/use the image.
|
||||
Visibility *ImageVisibility `json:"visibility,omitempty"`
|
||||
|
||||
// Tags is a set of image tags.
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
|
||||
// ContainerFormat is the format of the
|
||||
// container. Valid values are ami, ari, aki, bare, and ovf.
|
||||
ContainerFormat string `json:"container_format,omitempty"`
|
||||
|
||||
// DiskFormat is the format of the disk. If set,
|
||||
// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
|
||||
// and iso.
|
||||
DiskFormat string `json:"disk_format,omitempty"`
|
||||
|
||||
// MinDisk is the amount of disk space in
|
||||
// GB that is required to boot the image.
|
||||
MinDisk int `json:"min_disk,omitempty"`
|
||||
|
||||
// MinRAM is the amount of RAM in MB that
|
||||
// is required to boot the image.
|
||||
MinRAM int `json:"min_ram,omitempty"`
|
||||
|
||||
// protected is whether the image is not deletable.
|
||||
Protected *bool `json:"protected,omitempty"`
|
||||
|
||||
// properties is a set of properties, if any, that
|
||||
// are associated with the image.
|
||||
Properties map[string]string `json:"-,omitempty"`
|
||||
}
|
||||
|
||||
// ToImageCreateMap assembles a request body based on the contents of
|
||||
// a CreateOpts.
|
||||
func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
|
||||
b, err := gophercloud.BuildRequestBody(opts, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.Properties != nil {
|
||||
for k, v := range opts.Properties {
|
||||
b[k] = v
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Create implements create image request
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
|
||||
b, err := opts.ToImageCreateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return r
|
||||
}
|
||||
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete implements image delete request
|
||||
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteURL(client, id), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get implements image get request
|
||||
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
|
||||
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Update implements image updated request
|
||||
func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||
b, err := opts.ToImageUpdateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return r
|
||||
}
|
||||
_, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateOptsBuilder implements UpdateOptsBuilder
|
||||
type UpdateOptsBuilder interface {
|
||||
// returns value implementing json.Marshaler which when marshaled matches the patch schema:
|
||||
// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
|
||||
ToImageUpdateMap() ([]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateOpts implements UpdateOpts
|
||||
type UpdateOpts []Patch
|
||||
|
||||
// ToImageUpdateMap builder
|
||||
func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) {
|
||||
m := make([]interface{}, len(opts))
|
||||
for i, patch := range opts {
|
||||
patchJSON := patch.ToImagePatchMap()
|
||||
m[i] = patchJSON
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Patch represents a single update to an existing image. Multiple updates to an image can be
|
||||
// submitted at the same time.
|
||||
type Patch interface {
|
||||
ToImagePatchMap() map[string]interface{}
|
||||
}
|
||||
|
||||
// UpdateVisibility updated visibility
|
||||
type UpdateVisibility struct {
|
||||
Visibility ImageVisibility
|
||||
}
|
||||
|
||||
// ToImagePatchMap builder
|
||||
func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"op": "replace",
|
||||
"path": "/visibility",
|
||||
"value": u.Visibility,
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceImageName implements Patch
|
||||
type ReplaceImageName struct {
|
||||
NewName string
|
||||
}
|
||||
|
||||
// ToImagePatchMap builder
|
||||
func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"op": "replace",
|
||||
"path": "/name",
|
||||
"value": r.NewName,
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceImageChecksum implements Patch
|
||||
type ReplaceImageChecksum struct {
|
||||
Checksum string
|
||||
}
|
||||
|
||||
// ReplaceImageChecksum builder
|
||||
func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"op": "replace",
|
||||
"path": "/checksum",
|
||||
"value": rc.Checksum,
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceImageTags implements Patch
|
||||
type ReplaceImageTags struct {
|
||||
NewTags []string
|
||||
}
|
||||
|
||||
// ToImagePatchMap builder
|
||||
func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"op": "replace",
|
||||
"path": "/tags",
|
||||
"value": r.NewTags,
|
||||
}
|
||||
}
|
176
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
Normal file
176
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/results.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Image model
|
||||
// Does not include the literal image data; just metadata.
|
||||
// returned by listing images, and by fetching a specific image.
|
||||
type Image struct {
|
||||
// ID is the image UUID
|
||||
ID string `json:"id"`
|
||||
|
||||
// Name is the human-readable display name for the image.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Status is the image status. It can be "queued" or "active"
|
||||
// See imageservice/v2/images/type.go
|
||||
Status ImageStatus `json:"status"`
|
||||
|
||||
// Tags is a list of image tags. Tags are arbitrarily defined strings
|
||||
// attached to an image.
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
// ContainerFormat is the format of the container.
|
||||
// Valid values are ami, ari, aki, bare, and ovf.
|
||||
ContainerFormat string `json:"container_format"`
|
||||
|
||||
// DiskFormat is the format of the disk.
|
||||
// If set, valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi, and iso.
|
||||
DiskFormat string `json:"disk_format"`
|
||||
|
||||
// MinDiskGigabytes is the amount of disk space in GB that is required to boot the image.
|
||||
MinDiskGigabytes int `json:"min_disk"`
|
||||
|
||||
// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to boot the image.
|
||||
MinRAMMegabytes int `json:"min_ram"`
|
||||
|
||||
// Owner is the tenant the image belongs to.
|
||||
Owner string `json:"owner"`
|
||||
|
||||
// Protected is whether the image is deletable or not.
|
||||
Protected bool `json:"protected"`
|
||||
|
||||
// Visibility defines who can see/use the image.
|
||||
Visibility ImageVisibility `json:"visibility"`
|
||||
|
||||
// Checksum is the checksum of the data that's associated with the image
|
||||
Checksum string `json:"checksum"`
|
||||
|
||||
// SizeBytes is the size of the data that's associated with the image.
|
||||
SizeBytes int64 `json:"size"`
|
||||
|
||||
// Metadata is a set of metadata associated with the image.
|
||||
// Image metadata allow for meaningfully define the image properties
|
||||
// and tags. See http://docs.openstack.org/developer/glance/metadefs-concepts.html.
|
||||
Metadata map[string]string `json:"metadata"`
|
||||
|
||||
// Properties is a set of key-value pairs, if any, that are associated with the image.
|
||||
Properties map[string]string `json:"properties"`
|
||||
|
||||
// CreatedAt is the date when the image has been created.
|
||||
CreatedAt time.Time `json:"-"`
|
||||
|
||||
// UpdatedAt is the date when the last change has been made to the image or it's properties.
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
|
||||
// File is the trailing path after the glance endpoint that represent the location
|
||||
// of the image or the path to retrieve it.
|
||||
File string `json:"file"`
|
||||
|
||||
// Schema is the path to the JSON-schema that represent the image or image entity.
|
||||
Schema string `json:"schema"`
|
||||
}
|
||||
|
||||
func (s *Image) UnmarshalJSON(b []byte) error {
|
||||
type tmp Image
|
||||
var p *struct {
|
||||
tmp
|
||||
SizeBytes interface{} `json:"size"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = Image(p.tmp)
|
||||
|
||||
switch t := p.SizeBytes.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case float32:
|
||||
s.SizeBytes = int64(t)
|
||||
case float64:
|
||||
s.SizeBytes = int64(t)
|
||||
default:
|
||||
return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t)
|
||||
}
|
||||
|
||||
s.CreatedAt, err = time.Parse(time.RFC3339, p.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.UpdatedAt, err = time.Parse(time.RFC3339, p.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract interprets any commonResult as an Image.
|
||||
func (r commonResult) Extract() (*Image, error) {
|
||||
var s *Image
|
||||
err := r.ExtractInto(&s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a Create operation
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// UpdateResult represents the result of an Update operation
|
||||
type UpdateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
//DeleteResult model
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// ImagePage represents page
|
||||
type ImagePage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a page contains no Images results.
|
||||
func (r ImagePage) IsEmpty() (bool, error) {
|
||||
images, err := ExtractImages(r)
|
||||
return len(images) == 0, err
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
||||
func (r ImagePage) NextPageURL() (string, error) {
|
||||
var s struct {
|
||||
Next string `json:"next"`
|
||||
}
|
||||
err := r.ExtractInto(&s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return nextPageURL(r.URL.String(), s.Next), nil
|
||||
}
|
||||
|
||||
// ExtractImages interprets the results of a single page from a List() call, producing a slice of Image entities.
|
||||
func ExtractImages(r pagination.Page) ([]Image, error) {
|
||||
var s struct {
|
||||
Images []Image `json:"images"`
|
||||
}
|
||||
err := (r.(ImagePage)).ExtractInto(&s)
|
||||
return s.Images, err
|
||||
}
|
75
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
Normal file
75
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/types.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
package images
|
||||
|
||||
// ImageStatus image statuses
|
||||
// http://docs.openstack.org/developer/glance/statuses.html
|
||||
type ImageStatus string
|
||||
|
||||
const (
|
||||
// ImageStatusQueued is a status for an image which identifier has
|
||||
// been reserved for an image in the image registry.
|
||||
ImageStatusQueued ImageStatus = "queued"
|
||||
|
||||
// ImageStatusSaving denotes that an image’s raw data is currently being uploaded to Glance
|
||||
ImageStatusSaving ImageStatus = "saving"
|
||||
|
||||
// ImageStatusActive denotes an image that is fully available in Glance.
|
||||
ImageStatusActive ImageStatus = "active"
|
||||
|
||||
// ImageStatusKilled denotes that an error occurred during the uploading
|
||||
// of an image’s data, and that the image is not readable.
|
||||
ImageStatusKilled ImageStatus = "killed"
|
||||
|
||||
// ImageStatusDeleted is used for an image that is no longer available to use.
|
||||
// The image information is retained in the image registry.
|
||||
ImageStatusDeleted ImageStatus = "deleted"
|
||||
|
||||
// ImageStatusPendingDelete is similar to Delete, but the image is not yet deleted.
|
||||
ImageStatusPendingDelete ImageStatus = "pending_delete"
|
||||
|
||||
// ImageStatusDeactivated denotes that access to image data is not allowed to any non-admin user.
|
||||
ImageStatusDeactivated ImageStatus = "deactivated"
|
||||
)
|
||||
|
||||
// ImageVisibility denotes an image that is fully available in Glance.
|
||||
// This occurs when the image data is uploaded, or the image size
|
||||
// is explicitly set to zero on creation.
|
||||
// According to design
|
||||
// https://wiki.openstack.org/wiki/Glance-v2-community-image-visibility-design
|
||||
type ImageVisibility string
|
||||
|
||||
const (
|
||||
// ImageVisibilityPublic all users
|
||||
ImageVisibilityPublic ImageVisibility = "public"
|
||||
|
||||
// ImageVisibilityPrivate users with tenantId == tenantId(owner)
|
||||
ImageVisibilityPrivate ImageVisibility = "private"
|
||||
|
||||
// ImageVisibilityShared images are visible to:
|
||||
// - users with tenantId == tenantId(owner)
|
||||
// - users with tenantId in the member-list of the image
|
||||
// - users with tenantId in the member-list with member_status == 'accepted'
|
||||
ImageVisibilityShared ImageVisibility = "shared"
|
||||
|
||||
// ImageVisibilityCommunity images:
|
||||
// - all users can see and boot it
|
||||
// - users with tenantId in the member-list of the image with member_status == 'accepted'
|
||||
// have this image in their default image-list
|
||||
ImageVisibilityCommunity ImageVisibility = "community"
|
||||
)
|
||||
|
||||
// MemberStatus is a status for adding a new member (tenant) to an image member list.
|
||||
type ImageMemberStatus string
|
||||
|
||||
const (
|
||||
// ImageMemberStatusAccepted is the status for an accepted image member.
|
||||
ImageMemberStatusAccepted ImageMemberStatus = "accepted"
|
||||
|
||||
// ImageMemberStatusPending shows that the member addition is pending
|
||||
ImageMemberStatusPending ImageMemberStatus = "pending"
|
||||
|
||||
// ImageMemberStatusAccepted is the status for a rejected image member
|
||||
ImageMemberStatusRejected ImageMemberStatus = "rejected"
|
||||
|
||||
// ImageMemberStatusAll
|
||||
ImageMemberStatusAll ImageMemberStatus = "all"
|
||||
)
|
44
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go
generated
vendored
Normal file
44
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// `listURL` is a pure function. `listURL(c)` is a URL for which a GET
|
||||
// request will respond with a list of images in the service `c`.
|
||||
func listURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("images")
|
||||
}
|
||||
|
||||
func createURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("images")
|
||||
}
|
||||
|
||||
// `imageURL(c,i)` is the URL for the image identified by ID `i` in
|
||||
// the service `c`.
|
||||
func imageURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return c.ServiceURL("images", imageID)
|
||||
}
|
||||
|
||||
// `getURL(c,i)` is a URL for which a GET request will respond with
|
||||
// information about the image identified by ID `i` in the service
|
||||
// `c`.
|
||||
func getURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return imageURL(c, imageID)
|
||||
}
|
||||
|
||||
func updateURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return imageURL(c, imageID)
|
||||
}
|
||||
|
||||
func deleteURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return imageURL(c, imageID)
|
||||
}
|
||||
|
||||
// builds next page full url based on current url
|
||||
func nextPageURL(currentURL string, next string) string {
|
||||
base := currentURL[:strings.Index(currentURL, "/images")]
|
||||
return base + next
|
||||
}
|
77
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go
generated
vendored
Normal file
77
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/requests.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
|||
package members
|
||||
|
||||
import (
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Create member for specific image
|
||||
//
|
||||
// Preconditions
|
||||
// The specified images must exist.
|
||||
// You can only add a new member to an image which 'visibility' attribute is private.
|
||||
// You must be the owner of the specified image.
|
||||
// Synchronous Postconditions
|
||||
// With correct permissions, you can see the member status of the image as pending through API calls.
|
||||
//
|
||||
// More details here: http://developer.openstack.org/api-ref-image-v2.html#createImageMember-v2
|
||||
func Create(client *gophercloud.ServiceClient, id string, member string) (r CreateResult) {
|
||||
b := map[string]interface{}{"member": member}
|
||||
_, r.Err = client.Post(createMemberURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 409, 403},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// List members returns list of members for specifed image id
|
||||
// More details: http://developer.openstack.org/api-ref-image-v2.html#listImageMembers-v2
|
||||
func List(client *gophercloud.ServiceClient, id string) pagination.Pager {
|
||||
return pagination.NewPager(client, listMembersURL(client, id), func(r pagination.PageResult) pagination.Page {
|
||||
return MemberPage{pagination.SinglePageBase(r)}
|
||||
})
|
||||
}
|
||||
|
||||
// Get image member details.
|
||||
// More details: http://developer.openstack.org/api-ref-image-v2.html#getImageMember-v2
|
||||
func Get(client *gophercloud.ServiceClient, imageID string, memberID string) (r DetailsResult) {
|
||||
_, r.Err = client.Get(getMemberURL(client, imageID, memberID), &r.Body, &gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete membership for given image.
|
||||
// Callee should be image owner
|
||||
// More details: http://developer.openstack.org/api-ref-image-v2.html#deleteImageMember-v2
|
||||
func Delete(client *gophercloud.ServiceClient, imageID string, memberID string) (r DeleteResult) {
|
||||
_, r.Err = client.Delete(deleteMemberURL(client, imageID, memberID), &gophercloud.RequestOpts{OkCodes: []int{204, 403}})
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateOptsBuilder allows extensions to add additional attributes to the Update request.
|
||||
type UpdateOptsBuilder interface {
|
||||
ToImageMemberUpdateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateOpts implements UpdateOptsBuilder
|
||||
type UpdateOpts struct {
|
||||
Status string
|
||||
}
|
||||
|
||||
// ToMemberUpdateMap formats an UpdateOpts structure into a request body.
|
||||
func (opts UpdateOpts) ToImageMemberUpdateMap() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"status": opts.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Update function updates member
|
||||
// More details: http://developer.openstack.org/api-ref-image-v2.html#updateImageMember-v2
|
||||
func Update(client *gophercloud.ServiceClient, imageID string, memberID string, opts UpdateOptsBuilder) (r UpdateResult) {
|
||||
b, err := opts.ToImageMemberUpdateMap()
|
||||
if err != nil {
|
||||
r.Err = err
|
||||
return
|
||||
}
|
||||
_, r.Err = client.Put(updateMemberURL(client, imageID, memberID), b, &r.Body,
|
||||
&gophercloud.RequestOpts{OkCodes: []int{200}})
|
||||
return
|
||||
}
|
91
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go
generated
vendored
Normal file
91
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/results.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
package members
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Member model
|
||||
type Member struct {
|
||||
CreatedAt time.Time `json:"-"`
|
||||
ImageID string `json:"image_id"`
|
||||
MemberID string `json:"member_id"`
|
||||
Schema string `json:"schema"`
|
||||
Status string `json:"status"`
|
||||
UpdatedAt time.Time `json:"-"`
|
||||
}
|
||||
|
||||
func (s *Member) UnmarshalJSON(b []byte) error {
|
||||
type tmp Member
|
||||
var p *struct {
|
||||
tmp
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
err := json.Unmarshal(b, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = Member(p.tmp)
|
||||
s.CreatedAt, err = time.Parse(time.RFC3339, p.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.UpdatedAt, err = time.Parse(time.RFC3339, p.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract Member model from request if possible
|
||||
func (r commonResult) Extract() (*Member, error) {
|
||||
var s *Member
|
||||
err := r.ExtractInto(&s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
// MemberPage is a single page of Members results.
|
||||
type MemberPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// ExtractMembers returns a slice of Members contained in a single page of results.
|
||||
func ExtractMembers(r pagination.Page) ([]Member, error) {
|
||||
var s struct {
|
||||
Members []Member `json:"members"`
|
||||
}
|
||||
err := r.(MemberPage).ExtractInto(&s)
|
||||
return s.Members, err
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a page of Members contains any results.
|
||||
func (r MemberPage) IsEmpty() (bool, error) {
|
||||
members, err := ExtractMembers(r)
|
||||
return len(members) == 0, err
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// CreateResult result model
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// DetailsResult model
|
||||
type DetailsResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// UpdateResult model
|
||||
type UpdateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// DeleteResult model
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
31
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/urls.go
generated
vendored
Normal file
31
vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/members/urls.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
package members
|
||||
|
||||
import "github.com/gophercloud/gophercloud"
|
||||
|
||||
func imageMembersURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return c.ServiceURL("images", imageID, "members")
|
||||
}
|
||||
|
||||
func listMembersURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return imageMembersURL(c, imageID)
|
||||
}
|
||||
|
||||
func createMemberURL(c *gophercloud.ServiceClient, imageID string) string {
|
||||
return imageMembersURL(c, imageID)
|
||||
}
|
||||
|
||||
func imageMemberURL(c *gophercloud.ServiceClient, imageID string, memberID string) string {
|
||||
return c.ServiceURL("images", imageID, "members", memberID)
|
||||
}
|
||||
|
||||
func getMemberURL(c *gophercloud.ServiceClient, imageID string, memberID string) string {
|
||||
return imageMemberURL(c, imageID, memberID)
|
||||
}
|
||||
|
||||
func updateMemberURL(c *gophercloud.ServiceClient, imageID string, memberID string) string {
|
||||
return imageMemberURL(c, imageID, memberID)
|
||||
}
|
||||
|
||||
func deleteMemberURL(c *gophercloud.ServiceClient, imageID string, memberID string) string {
|
||||
return imageMemberURL(c, imageID, memberID)
|
||||
}
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// Version is a supported API version, corresponding to a vN package within the appropriate service.
|
||||
|
@ -59,7 +59,7 @@ func ChooseVersion(client *gophercloud.ProviderClient, recognized []*Version) (*
|
|||
}
|
||||
|
||||
var resp response
|
||||
_, err := client.Request("GET", client.IdentityBase, gophercloud.RequestOpts{
|
||||
_, err := client.Request("GET", client.IdentityBase, &gophercloud.RequestOpts{
|
||||
JSONResponse: &resp,
|
||||
OkCodes: []int{200, 300},
|
||||
})
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// PageResult stores the HTTP response that returned the current page of results.
|
||||
|
@ -53,7 +53,7 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
|
|||
|
||||
// Request performs an HTTP request and extracts the http.Response from the result.
|
||||
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
|
||||
return client.Request("GET", url, gophercloud.RequestOpts{
|
||||
return client.Get(url, nil, &gophercloud.RequestOpts{
|
||||
MoreHeaders: headers,
|
||||
OkCodes: []int{200, 204},
|
||||
})
|
|
@ -1,6 +1,11 @@
|
|||
package pagination
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// LinkedPageBase may be embedded to implement a page that provides navigational "Next" and "Previous" links within its result.
|
||||
type LinkedPageBase struct {
|
||||
|
@ -28,7 +33,10 @@ func (current LinkedPageBase) NextPageURL() (string, error) {
|
|||
|
||||
submap, ok := current.Body.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Expected an object, but was %#v", current.Body)
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
|
@ -42,7 +50,10 @@ func (current LinkedPageBase) NextPageURL() (string, error) {
|
|||
if len(path) > 0 {
|
||||
submap, ok = value.(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Expected an object, but was %#v", value)
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if value == nil {
|
||||
|
@ -52,7 +63,10 @@ func (current LinkedPageBase) NextPageURL() (string, error) {
|
|||
|
||||
url, ok := value.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Expected a string, but was %#v", value)
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "string"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value))
|
||||
return "", err
|
||||
}
|
||||
|
||||
return url, nil
|
||||
|
@ -60,6 +74,17 @@ func (current LinkedPageBase) NextPageURL() (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current LinkedPageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current LinkedPageBase) GetBody() interface{} {
|
|
@ -1,5 +1,12 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// MarkerPage is a stricter Page interface that describes additional functionality required for use with NewMarkerPager.
|
||||
// For convenience, embed the MarkedPageBase struct.
|
||||
type MarkerPage interface {
|
||||
|
@ -33,6 +40,17 @@ func (current MarkerPageBase) NextPageURL() (string, error) {
|
|||
return currentURL.String(), nil
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current MarkerPageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the linked page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current MarkerPageBase) GetBody() interface{} {
|
|
@ -7,7 +7,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -138,6 +138,11 @@ func (p Pager) AllPages() (Page, error) {
|
|||
// that type.
|
||||
pageType := reflect.TypeOf(testPage)
|
||||
|
||||
// if it's a single page, just return the testPage (first page)
|
||||
if _, found := pageType.FieldByName("SinglePageBase"); found {
|
||||
return testPage, nil
|
||||
}
|
||||
|
||||
// Switch on the page body type. Recognized types are `map[string]interface{}`,
|
||||
// `[]byte`, and `[]interface{}`.
|
||||
switch testPage.GetBody().(type) {
|
||||
|
@ -145,7 +150,7 @@ func (p Pager) AllPages() (Page, error) {
|
|||
// key is the map key for the page body if the body type is `map[string]interface{}`.
|
||||
var key string
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err := p.EachPage(func(page Page) (bool, error) {
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().(map[string]interface{})
|
||||
for k := range b {
|
||||
// If it's a linked page, we don't want the `links`, we want the other one.
|
||||
|
@ -153,7 +158,14 @@ func (p Pager) AllPages() (Page, error) {
|
|||
key = k
|
||||
}
|
||||
}
|
||||
pagesSlice = append(pagesSlice, b[key].([]interface{})...)
|
||||
switch keyType := b[key].(type) {
|
||||
case map[string]interface{}:
|
||||
pagesSlice = append(pagesSlice, keyType)
|
||||
case []interface{}:
|
||||
pagesSlice = append(pagesSlice, b[key].([]interface{})...)
|
||||
default:
|
||||
return false, fmt.Errorf("Unsupported page body type: %+v", keyType)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -164,7 +176,7 @@ func (p Pager) AllPages() (Page, error) {
|
|||
body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
|
||||
case []byte:
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err := p.EachPage(func(page Page) (bool, error) {
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().([]byte)
|
||||
pagesSlice = append(pagesSlice, b)
|
||||
// seperate pages with a comma
|
||||
|
@ -188,7 +200,7 @@ func (p Pager) AllPages() (Page, error) {
|
|||
body.SetBytes(b)
|
||||
case []interface{}:
|
||||
// Iterate over the pages to concatenate the bodies.
|
||||
err := p.EachPage(func(page Page) (bool, error) {
|
||||
err = p.EachPage(func(page Page) (bool, error) {
|
||||
b := page.GetBody().([]interface{})
|
||||
pagesSlice = append(pagesSlice, b...)
|
||||
return true, nil
|
||||
|
@ -202,7 +214,10 @@ func (p Pager) AllPages() (Page, error) {
|
|||
body.Index(i).Set(reflect.ValueOf(s))
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Page body has unrecognized type.")
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "map[string]interface{}/[]byte/[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(testPage.GetBody()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Each `Extract*` function is expecting a specific type of page coming back,
|
|
@ -1,5 +1,12 @@
|
|||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
)
|
||||
|
||||
// SinglePageBase may be embedded in a Page that contains all of the results from an operation at once.
|
||||
type SinglePageBase PageResult
|
||||
|
||||
|
@ -8,6 +15,17 @@ func (current SinglePageBase) NextPageURL() (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// IsEmpty satisifies the IsEmpty method of the Page interface
|
||||
func (current SinglePageBase) IsEmpty() (bool, error) {
|
||||
if b, ok := current.Body.([]interface{}); ok {
|
||||
return len(b) == 0, nil
|
||||
}
|
||||
err := gophercloud.ErrUnexpectedType{}
|
||||
err.Expected = "[]interface{}"
|
||||
err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body))
|
||||
return true, err
|
||||
}
|
||||
|
||||
// GetBody returns the single page's body. This method is needed to satisfy the
|
||||
// Page interface.
|
||||
func (current SinglePageBase) GetBody() interface{} {
|
|
@ -1,6 +1,7 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
|
@ -9,6 +10,146 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// BuildRequestBody builds a map[string]interface from the given `struct`. If
|
||||
// parent is not the empty string, the final map[string]interface returned will
|
||||
// encapsulate the built one
|
||||
//
|
||||
func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
|
||||
optsValue := reflect.ValueOf(opts)
|
||||
if optsValue.Kind() == reflect.Ptr {
|
||||
optsValue = optsValue.Elem()
|
||||
}
|
||||
|
||||
optsType := reflect.TypeOf(opts)
|
||||
if optsType.Kind() == reflect.Ptr {
|
||||
optsType = optsType.Elem()
|
||||
}
|
||||
|
||||
optsMap := make(map[string]interface{})
|
||||
if optsValue.Kind() == reflect.Struct {
|
||||
//fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
|
||||
for i := 0; i < optsValue.NumField(); i++ {
|
||||
v := optsValue.Field(i)
|
||||
f := optsType.Field(i)
|
||||
|
||||
if f.Name != strings.Title(f.Name) {
|
||||
//fmt.Printf("Skipping field: %s...\n", f.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("Starting on field: %s...\n", f.Name)
|
||||
|
||||
zero := isZero(v)
|
||||
//fmt.Printf("v is zero?: %v\n", zero)
|
||||
|
||||
// if the field has a required tag that's set to "true"
|
||||
if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
|
||||
//fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
|
||||
// if the field's value is zero, return a missing-argument error
|
||||
if zero {
|
||||
// if the field has a 'required' tag, it can't have a zero-value
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = f.Name
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if xorTag := f.Tag.Get("xor"); xorTag != "" {
|
||||
//fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
|
||||
xorField := optsValue.FieldByName(xorTag)
|
||||
var xorFieldIsZero bool
|
||||
if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
|
||||
xorFieldIsZero = true
|
||||
} else {
|
||||
if xorField.Kind() == reflect.Ptr {
|
||||
xorField = xorField.Elem()
|
||||
}
|
||||
xorFieldIsZero = isZero(xorField)
|
||||
}
|
||||
if !(zero != xorFieldIsZero) {
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
|
||||
err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if orTag := f.Tag.Get("or"); orTag != "" {
|
||||
//fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
|
||||
//fmt.Printf("field is zero?: %v\n", zero)
|
||||
if zero {
|
||||
orField := optsValue.FieldByName(orTag)
|
||||
var orFieldIsZero bool
|
||||
if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
|
||||
orFieldIsZero = true
|
||||
} else {
|
||||
if orField.Kind() == reflect.Ptr {
|
||||
orField = orField.Elem()
|
||||
}
|
||||
orFieldIsZero = isZero(orField)
|
||||
}
|
||||
if orFieldIsZero {
|
||||
err := ErrMissingInput{}
|
||||
err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
|
||||
err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
|
||||
if zero {
|
||||
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
|
||||
if jsonTag := f.Tag.Get("json"); jsonTag != "" {
|
||||
jsonTagPieces := strings.Split(jsonTag, ",")
|
||||
if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
|
||||
if v.CanSet() {
|
||||
if !v.IsNil() {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
}
|
||||
}
|
||||
//fmt.Printf("value after change: %+v\n", optsValue.Field(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
//fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
|
||||
_, err := BuildRequestBody(v.Interface(), f.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Printf("opts: %+v \n", opts)
|
||||
|
||||
b, err := json.Marshal(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("string(b): %s\n", string(b))
|
||||
|
||||
err = json.Unmarshal(b, &optsMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("optsMap: %+v\n", optsMap)
|
||||
|
||||
if parent != "" {
|
||||
optsMap = map[string]interface{}{parent: optsMap}
|
||||
}
|
||||
//fmt.Printf("optsMap after parent added: %+v\n", optsMap)
|
||||
return optsMap, nil
|
||||
}
|
||||
// Return an error if the underlying type of 'opts' isn't a struct.
|
||||
return nil, fmt.Errorf("Options type is not a struct.")
|
||||
}
|
||||
|
||||
// EnabledState is a convenience type, mostly used in Create and Update
|
||||
// operations. Because the zero value of a bool is FALSE, we need to use a
|
||||
// pointer instead to indicate zero-ness.
|
||||
|
@ -23,6 +164,17 @@ var (
|
|||
Disabled EnabledState = &iFalse
|
||||
)
|
||||
|
||||
// IPVersion is a type for the possible IP address versions. Valid instances
|
||||
// are IPv4 and IPv6
|
||||
type IPVersion int
|
||||
|
||||
const (
|
||||
// IPv4 is used for IP version 4 addresses
|
||||
IPv4 IPVersion = 4
|
||||
// IPv6 is used for IP version 6 addresses
|
||||
IPv6 IPVersion = 6
|
||||
)
|
||||
|
||||
// IntToPointer is a function for converting integers into integer pointers.
|
||||
// This is useful when passing in options to operations.
|
||||
func IntToPointer(i int) *int {
|
||||
|
@ -60,10 +212,27 @@ func MaybeInt(original int) *int {
|
|||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
func isUnderlyingStructZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
return isUnderlyingStructZero(v.Elem())
|
||||
default:
|
||||
return isZero(v)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
var t time.Time
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
//fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case reflect.Func, reflect.Map, reflect.Slice:
|
||||
return v.IsNil()
|
||||
case reflect.Array:
|
||||
|
@ -87,6 +256,7 @@ func isZero(v reflect.Value) bool {
|
|||
}
|
||||
// Compare other types directly:
|
||||
z := reflect.Zero(v.Type())
|
||||
//fmt.Printf("zero type for value: %+v\n\n\n", z)
|
||||
return v.Interface() == z.Interface()
|
||||
}
|
||||
|
||||
|
@ -137,7 +307,11 @@ func BuildQueryString(opts interface{}) (*url.URL, error) {
|
|||
|
||||
// if the field is set, add it to the slice of query pieces
|
||||
if !isZero(v) {
|
||||
loop:
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
v = v.Elem()
|
||||
goto loop
|
||||
case reflect.String:
|
||||
params.Add(tags[0], v.String())
|
||||
case reflect.Int:
|
|
@ -3,7 +3,6 @@ package gophercloud
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -11,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
// DefaultUserAgent is the default User-Agent string set in the request header.
|
||||
const DefaultUserAgent = "gophercloud/1.0.0"
|
||||
const DefaultUserAgent = "gophercloud/2.0.0"
|
||||
|
||||
// UserAgent represents a User-Agent header.
|
||||
type UserAgent struct {
|
||||
|
@ -68,6 +67,8 @@ type ProviderClient struct {
|
|||
// fails with a 401 HTTP response code. This a needed because there may be multiple
|
||||
// authentication functions for different Identity service versions.
|
||||
ReauthFunc func() error
|
||||
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
|
||||
|
@ -85,46 +86,30 @@ type RequestOpts struct {
|
|||
// content type of the request will default to "application/json" unless overridden by MoreHeaders.
|
||||
// It's an error to specify both a JSONBody and a RawBody.
|
||||
JSONBody interface{}
|
||||
// RawBody contains an io.ReadSeeker that will be consumed by the request directly. No content-type
|
||||
// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
|
||||
// will be set unless one is provided explicitly by MoreHeaders.
|
||||
RawBody io.ReadSeeker
|
||||
|
||||
RawBody io.Reader
|
||||
// JSONResponse, if provided, will be populated with the contents of the response body parsed as
|
||||
// JSON.
|
||||
JSONResponse interface{}
|
||||
// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
|
||||
// the response has a different code, an error will be returned.
|
||||
OkCodes []int
|
||||
|
||||
// MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
|
||||
// provided with a blank value (""), that header will be *omitted* instead: use this to suppress
|
||||
// the default Accept header or an inferred Content-Type, for example.
|
||||
MoreHeaders map[string]string
|
||||
}
|
||||
|
||||
// UnexpectedResponseCodeError is returned by the Request method when a response code other than
|
||||
// those listed in OkCodes is encountered.
|
||||
type UnexpectedResponseCodeError struct {
|
||||
URL string
|
||||
Method string
|
||||
Expected []int
|
||||
Actual int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (err *UnexpectedResponseCodeError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"Expected HTTP response code %v when accessing [%s %s], but got %d instead\n%s",
|
||||
err.Expected, err.Method, err.URL, err.Actual, err.Body,
|
||||
)
|
||||
// ErrorContext specifies the resource error type to return if an error is encountered.
|
||||
// This lets resources override default error messages based on the response status code.
|
||||
ErrorContext error
|
||||
}
|
||||
|
||||
var applicationJSON = "application/json"
|
||||
|
||||
// Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
|
||||
// header will automatically be provided.
|
||||
func (client *ProviderClient) Request(method, url string, options RequestOpts) (*http.Response, error) {
|
||||
var body io.ReadSeeker
|
||||
func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
|
||||
var body io.Reader
|
||||
var contentType *string
|
||||
|
||||
// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
|
||||
|
@ -179,32 +164,13 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
|||
|
||||
// Set connection parameter to close the connection immediately when we've got the response
|
||||
req.Close = true
|
||||
|
||||
|
||||
// Issue the request.
|
||||
resp, err := client.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
if client.ReauthFunc != nil {
|
||||
err = client.ReauthFunc()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error trying to re-authenticate: %s", err)
|
||||
}
|
||||
if options.RawBody != nil {
|
||||
options.RawBody.Seek(0, 0)
|
||||
}
|
||||
resp.Body.Close()
|
||||
resp, err = client.Request(method, url, options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Successfully re-authenticated, but got error executing request: %s", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Allow default OkCodes if none explicitly set
|
||||
if options.OkCodes == nil {
|
||||
options.OkCodes = defaultOkCodes(method)
|
||||
|
@ -218,16 +184,98 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return resp, &UnexpectedResponseCodeError{
|
||||
//pc := make([]uintptr, 1)
|
||||
//runtime.Callers(2, pc)
|
||||
//f := runtime.FuncForPC(pc[0])
|
||||
respErr := ErrUnexpectedResponseCode{
|
||||
URL: url,
|
||||
Method: method,
|
||||
Expected: options.OkCodes,
|
||||
Actual: resp.StatusCode,
|
||||
Body: body,
|
||||
}
|
||||
//respErr.Function = "gophercloud.ProviderClient.Request"
|
||||
|
||||
errType := options.ErrorContext
|
||||
switch resp.StatusCode {
|
||||
case http.StatusBadRequest:
|
||||
err = ErrDefault400{respErr}
|
||||
if error400er, ok := errType.(Err400er); ok {
|
||||
err = error400er.Error400(respErr)
|
||||
}
|
||||
case http.StatusUnauthorized:
|
||||
if client.ReauthFunc != nil {
|
||||
err = client.ReauthFunc()
|
||||
if err != nil {
|
||||
e := &ErrUnableToReauthenticate{}
|
||||
e.ErrOriginal = respErr
|
||||
return nil, e
|
||||
}
|
||||
if options.RawBody != nil {
|
||||
if seeker, ok := options.RawBody.(io.Seeker); ok {
|
||||
seeker.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
resp, err = client.Request(method, url, options)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *ErrUnexpectedResponseCode:
|
||||
e := &ErrErrorAfterReauthentication{}
|
||||
e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
|
||||
return nil, e
|
||||
default:
|
||||
e := &ErrErrorAfterReauthentication{}
|
||||
e.ErrOriginal = err
|
||||
return nil, e
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
err = ErrDefault401{respErr}
|
||||
if error401er, ok := errType.(Err401er); ok {
|
||||
err = error401er.Error401(respErr)
|
||||
}
|
||||
case http.StatusNotFound:
|
||||
err = ErrDefault404{respErr}
|
||||
if error404er, ok := errType.(Err404er); ok {
|
||||
err = error404er.Error404(respErr)
|
||||
}
|
||||
case http.StatusMethodNotAllowed:
|
||||
err = ErrDefault405{respErr}
|
||||
if error405er, ok := errType.(Err405er); ok {
|
||||
err = error405er.Error405(respErr)
|
||||
}
|
||||
case http.StatusRequestTimeout:
|
||||
err = ErrDefault408{respErr}
|
||||
if error408er, ok := errType.(Err408er); ok {
|
||||
err = error408er.Error408(respErr)
|
||||
}
|
||||
case 429:
|
||||
err = ErrDefault429{respErr}
|
||||
if error429er, ok := errType.(Err429er); ok {
|
||||
err = error429er.Error429(respErr)
|
||||
}
|
||||
case http.StatusInternalServerError:
|
||||
err = ErrDefault500{respErr}
|
||||
if error500er, ok := errType.(Err500er); ok {
|
||||
err = error500er.Error500(respErr)
|
||||
}
|
||||
case http.StatusServiceUnavailable:
|
||||
err = ErrDefault503{respErr}
|
||||
if error503er, ok := errType.(Err503er); ok {
|
||||
err = error503er.Error503(respErr)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = respErr
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Parse the response body as JSON, if requested to do so.
|
||||
|
@ -257,75 +305,3 @@ func defaultOkCodes(method string) []int {
|
|||
|
||||
return []int{}
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Get(url string, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
return client.Request("GET", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Post(url string, JSONBody interface{}, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
return client.Request("POST", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Put(url string, JSONBody interface{}, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
return client.Request("PUT", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Patch(url string, JSONBody interface{}, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
return client.Request("PATCH", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
return client.Request("DELETE", url, *opts)
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
Result is an internal type to be used by individual resource packages, but its
|
||||
methods will be available on a wide variety of user-facing embedding types.
|
||||
|
||||
It acts as a base struct that other Result types, returned from request
|
||||
functions, can embed for convenience. All Results capture basic information
|
||||
from the HTTP transaction that was performed, including the response body,
|
||||
HTTP headers, and any errors that happened.
|
||||
|
||||
Generally, each Result type will have an Extract method that can be used to
|
||||
further interpret the result's payload in a specific context. Extensions or
|
||||
providers can then provide additional extraction functions to pull out
|
||||
provider- or extension-specific information as well.
|
||||
*/
|
||||
type Result struct {
|
||||
// Body is the payload of the HTTP response from the server. In most cases,
|
||||
// this will be the deserialized JSON structure.
|
||||
Body interface{}
|
||||
|
||||
// Header contains the HTTP header structure from the original response.
|
||||
Header http.Header
|
||||
|
||||
// Err is an error that occurred during the operation. It's deferred until
|
||||
// extraction to make it easier to chain the Extract call.
|
||||
Err error
|
||||
}
|
||||
|
||||
// ExtractInto allows users to provide an object into which `Extract` will extract
|
||||
// the `Result.Body`. This would be useful for OpenStack providers that have
|
||||
// different fields in the response object than OpenStack proper.
|
||||
func (r Result) ExtractInto(to interface{}) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
if reader, ok := r.Body.(io.Reader); ok {
|
||||
if readCloser, ok := reader.(io.Closer); ok {
|
||||
defer readCloser.Close()
|
||||
}
|
||||
return json.NewDecoder(reader).Decode(to)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r Result) extractIntoPtr(to interface{}, label string) error {
|
||||
if label == "" {
|
||||
return r.ExtractInto(&to)
|
||||
}
|
||||
|
||||
var m map[string]interface{}
|
||||
err := r.ExtractInto(&m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(m[label])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &to)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
|
||||
// interface{} (to).
|
||||
//
|
||||
// NOTE: For internal use only
|
||||
//
|
||||
// `to` must be a pointer to an underlying struct type
|
||||
//
|
||||
// If provided, `label` will be filtered out of the response
|
||||
// body prior to `r` being unmarshalled into `to`.
|
||||
func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(to)
|
||||
if k := t.Kind(); k != reflect.Ptr {
|
||||
return fmt.Errorf("Expected pointer, got %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Struct:
|
||||
return r.extractIntoPtr(to, label)
|
||||
default:
|
||||
return fmt.Errorf("Expected pointer to struct, got: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
|
||||
// interface{} (to).
|
||||
//
|
||||
// NOTE: For internal use only
|
||||
//
|
||||
// `to` must be a pointer to an underlying slice type
|
||||
//
|
||||
// If provided, `label` will be filtered out of the response
|
||||
// body prior to `r` being unmarshalled into `to`.
|
||||
func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(to)
|
||||
if k := t.Kind(); k != reflect.Ptr {
|
||||
return fmt.Errorf("Expected pointer, got %v", k)
|
||||
}
|
||||
switch t.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
return r.extractIntoPtr(to, label)
|
||||
default:
|
||||
return fmt.Errorf("Expected pointer to slice, got: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrintJSON creates a string containing the full response body as
|
||||
// pretty-printed JSON. It's useful for capturing test fixtures and for
|
||||
// debugging extraction bugs. If you include its output in an issue related to
|
||||
// a buggy extraction function, we will all love you forever.
|
||||
func (r Result) PrettyPrintJSON() string {
|
||||
pretty, err := json.MarshalIndent(r.Body, "", " ")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return string(pretty)
|
||||
}
|
||||
|
||||
// ErrResult is an internal type to be used by individual resource packages, but
|
||||
// its methods will be available on a wide variety of user-facing embedding
|
||||
// types.
|
||||
//
|
||||
// It represents results that only contain a potential error and
|
||||
// nothing else. Usually, if the operation executed successfully, the Err field
|
||||
// will be nil; otherwise it will be stocked with a relevant error. Use the
|
||||
// ExtractErr method
|
||||
// to cleanly pull it out.
|
||||
type ErrResult struct {
|
||||
Result
|
||||
}
|
||||
|
||||
// ExtractErr is a function that extracts error information, or nil, from a result.
|
||||
func (r ErrResult) ExtractErr() error {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
/*
|
||||
HeaderResult is an internal type to be used by individual resource packages, but
|
||||
its methods will be available on a wide variety of user-facing embedding types.
|
||||
|
||||
It represents a result that only contains an error (possibly nil) and an
|
||||
http.Header. This is used, for example, by the objectstorage packages in
|
||||
openstack, because most of the operations don't return response bodies, but do
|
||||
have relevant information in headers.
|
||||
*/
|
||||
type HeaderResult struct {
|
||||
Result
|
||||
}
|
||||
|
||||
// ExtractHeader will return the http.Header and error from the HeaderResult.
|
||||
//
|
||||
// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
|
||||
func (r HeaderResult) ExtractInto(to interface{}) error {
|
||||
if r.Err != nil {
|
||||
return r.Err
|
||||
}
|
||||
|
||||
tmpHeaderMap := map[string]string{}
|
||||
for k, v := range r.Header {
|
||||
if len(v) > 0 {
|
||||
tmpHeaderMap[k] = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(tmpHeaderMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// RFC3339Milli describes a common time format used by some API responses.
|
||||
const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
|
||||
|
||||
type JSONRFC3339Milli time.Time
|
||||
|
||||
func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
|
||||
b := bytes.NewBuffer(data)
|
||||
dec := json.NewDecoder(b)
|
||||
var s string
|
||||
if err := dec.Decode(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := time.Parse(RFC3339Milli, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339Milli(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
|
||||
|
||||
type JSONRFC3339MilliNoZ time.Time
|
||||
|
||||
func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(RFC3339MilliNoZ, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339MilliNoZ(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
type JSONRFC1123 time.Time
|
||||
|
||||
func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(time.RFC1123, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC1123(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
type JSONUnix time.Time
|
||||
|
||||
func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
unix, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t = time.Unix(unix, 0)
|
||||
*jt = JSONUnix(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RFC3339NoZ is the time format used in Heat (Orchestration).
|
||||
const RFC3339NoZ = "2006-01-02T15:04:05"
|
||||
|
||||
type JSONRFC3339NoZ time.Time
|
||||
|
||||
func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
t, err := time.Parse(RFC3339NoZ, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*jt = JSONRFC3339NoZ(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Link is an internal type to be used in packages of collection resources that are
|
||||
paginated in a certain way.
|
||||
|
||||
It's a response substructure common to many paginated collection results that is
|
||||
used to point to related pages. Usually, the one we care about is the one with
|
||||
Rel field set to "next".
|
||||
*/
|
||||
type Link struct {
|
||||
Href string `json:"href"`
|
||||
Rel string `json:"rel"`
|
||||
}
|
||||
|
||||
/*
|
||||
ExtractNextURL is an internal function useful for packages of collection
|
||||
resources that are paginated in a certain way.
|
||||
|
||||
It attempts to extract the "next" URL from slice of Link structs, or
|
||||
"" if no such URL is present.
|
||||
*/
|
||||
func ExtractNextURL(links []Link) (string, error) {
|
||||
var url string
|
||||
|
||||
for _, l := range links {
|
||||
if l.Rel == "next" {
|
||||
url = l.Href
|
||||
}
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package gophercloud
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServiceClient stores details required to interact with a specific service API implemented by a provider.
|
||||
// Generally, you'll acquire these by calling the appropriate `New` method on a ProviderClient.
|
||||
type ServiceClient struct {
|
||||
// ProviderClient is a reference to the provider that implements this service.
|
||||
*ProviderClient
|
||||
|
||||
// Endpoint is the base URL of the service's API, acquired from a service catalog.
|
||||
// It MUST end with a /.
|
||||
Endpoint string
|
||||
|
||||
// ResourceBase is the base URL shared by the resources within a service's API. It should include
|
||||
// the API version and, like Endpoint, MUST end with a / if set. If not set, the Endpoint is used
|
||||
// as-is, instead.
|
||||
ResourceBase string
|
||||
|
||||
Microversion string
|
||||
}
|
||||
|
||||
// ResourceBaseURL returns the base URL of any resources used by this service. It MUST end with a /.
|
||||
func (client *ServiceClient) ResourceBaseURL() string {
|
||||
if client.ResourceBase != "" {
|
||||
return client.ResourceBase
|
||||
}
|
||||
return client.Endpoint
|
||||
}
|
||||
|
||||
// ServiceURL constructs a URL for a resource belonging to this provider.
|
||||
func (client *ServiceClient) ServiceURL(parts ...string) string {
|
||||
return client.ResourceBaseURL() + strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
// Get calls `Request` with the "GET" HTTP verb.
|
||||
func (client *ServiceClient) Get(url string, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("GET", url, opts)
|
||||
}
|
||||
|
||||
// Post calls `Request` with the "POST" HTTP verb.
|
||||
func (client *ServiceClient) Post(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.Reader); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("POST", url, opts)
|
||||
}
|
||||
|
||||
// Put calls `Request` with the "PUT" HTTP verb.
|
||||
func (client *ServiceClient) Put(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.Reader); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("PUT", url, opts)
|
||||
}
|
||||
|
||||
// Patch calls `Request` with the "PATCH" HTTP verb.
|
||||
func (client *ServiceClient) Patch(url string, JSONBody interface{}, JSONResponse interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.Reader); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("PATCH", url, opts)
|
||||
}
|
||||
|
||||
// Delete calls `Request` with the "DELETE" HTTP verb.
|
||||
func (client *ServiceClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if opts.MoreHeaders == nil {
|
||||
opts.MoreHeaders = make(map[string]string)
|
||||
}
|
||||
opts.MoreHeaders["X-OpenStack-Nova-API-Version"] = client.Microversion
|
||||
|
||||
return client.Request("DELETE", url, opts)
|
||||
}
|
|
@ -1,274 +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).
|
||||
|
||||
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 ./...
|
||||
```
|
||||
|
||||
To run all tests with verbose output:
|
||||
|
||||
```bash
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
To run tests that match certain [build tags]():
|
||||
|
||||
```bash
|
||||
go test -tags "foo bar" ./...
|
||||
```
|
||||
|
||||
To run tests for a particular sub-package:
|
||||
|
||||
```bash
|
||||
cd ./path/to/package && go test .
|
||||
```
|
||||
|
||||
## 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,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,57 +0,0 @@
|
|||
# Gophercloud Acceptance tests
|
||||
|
||||
The purpose of these acceptance tests is to validate that SDK features meet
|
||||
the requirements of a contract - to consumers, other parts of the library, and
|
||||
to a remote API.
|
||||
|
||||
> **Note:** Because every test will be run against a real API endpoint, you
|
||||
> may incur bandwidth and service charges for all the resource usage. These
|
||||
> tests *should* remove their remote products automatically. However, there may
|
||||
> be certain cases where this does not happen; always double-check to make sure
|
||||
> you have no stragglers left behind.
|
||||
|
||||
### Step 1. Set environment variables
|
||||
|
||||
A lot of tests rely on environment variables for configuration - so you will need
|
||||
to set them before running the suite. If you're testing against pure OpenStack APIs,
|
||||
you can download a file that contains all of these variables for you: just visit
|
||||
the `project/access_and_security` page in your control panel and click the "Download
|
||||
OpenStack RC File" button at the top right. For all other providers, you will need
|
||||
to set them manually.
|
||||
|
||||
#### Authentication
|
||||
|
||||
|Name|Description|
|
||||
|---|---|
|
||||
|`OS_USERNAME`|Your API username|
|
||||
|`OS_PASSWORD`|Your API password|
|
||||
|`OS_AUTH_URL`|The identity URL you need to authenticate|
|
||||
|`OS_TENANT_NAME`|Your API tenant name|
|
||||
|`OS_TENANT_ID`|Your API tenant ID|
|
||||
|`RS_USERNAME`|Your Rackspace username|
|
||||
|`RS_API_KEY`|Your Rackspace API key|
|
||||
|
||||
#### General
|
||||
|
||||
|Name|Description|
|
||||
|---|---|
|
||||
|`OS_REGION_NAME`|The region you want your resources to reside in|
|
||||
|`RS_REGION`|Rackspace region you want your resource to reside in|
|
||||
|
||||
#### Compute
|
||||
|
||||
|Name|Description|
|
||||
|---|---|
|
||||
|`OS_IMAGE_ID`|The ID of the image your want your server to be based on|
|
||||
|`OS_FLAVOR_ID`|The ID of the flavor you want your server to be based on|
|
||||
|`OS_FLAVOR_ID_RESIZE`|The ID of the flavor you want your server to be resized to|
|
||||
|`RS_IMAGE_ID`|The ID of the image you want servers to be created with|
|
||||
|`RS_FLAVOR_ID`|The ID of the flavor you want your server to be created with|
|
||||
|
||||
### 2. Run the test suite
|
||||
|
||||
From the root directory, run:
|
||||
|
||||
```
|
||||
./script/acceptancetest
|
||||
```
|
|
@ -1,50 +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.
|
||||
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)
|
||||
}
|
91
vendor/github.com/rackspace/gophercloud/openstack/common/extensions/fixtures.go
generated
vendored
91
vendor/github.com/rackspace/gophercloud/openstack/common/extensions/fixtures.go
generated
vendored
|
@ -1,91 +0,0 @@
|
|||
// +build fixtures
|
||||
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// ListOutput provides a single page of Extension results.
|
||||
const ListOutput = `
|
||||
{
|
||||
"extensions": [
|
||||
{
|
||||
"updated": "2013-01-20T00:00:00-00:00",
|
||||
"name": "Neutron Service Type Management",
|
||||
"links": [],
|
||||
"namespace": "http://docs.openstack.org/ext/neutron/service-type/api/v1.0",
|
||||
"alias": "service-type",
|
||||
"description": "API for retrieving service providers for Neutron advanced services"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
// GetOutput provides a single Extension result.
|
||||
const GetOutput = `
|
||||
{
|
||||
"extension": {
|
||||
"updated": "2013-02-03T10:00:00-00:00",
|
||||
"name": "agent",
|
||||
"links": [],
|
||||
"namespace": "http://docs.openstack.org/ext/agent/api/v2.0",
|
||||
"alias": "agent",
|
||||
"description": "The agent management extension."
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// ListedExtension is the Extension that should be parsed from ListOutput.
|
||||
var ListedExtension = Extension{
|
||||
Updated: "2013-01-20T00:00:00-00:00",
|
||||
Name: "Neutron Service Type Management",
|
||||
Links: []interface{}{},
|
||||
Namespace: "http://docs.openstack.org/ext/neutron/service-type/api/v1.0",
|
||||
Alias: "service-type",
|
||||
Description: "API for retrieving service providers for Neutron advanced services",
|
||||
}
|
||||
|
||||
// ExpectedExtensions is a slice containing the Extension that should be parsed from ListOutput.
|
||||
var ExpectedExtensions = []Extension{ListedExtension}
|
||||
|
||||
// SingleExtension is the Extension that should be parsed from GetOutput.
|
||||
var SingleExtension = &Extension{
|
||||
Updated: "2013-02-03T10:00:00-00:00",
|
||||
Name: "agent",
|
||||
Links: []interface{}{},
|
||||
Namespace: "http://docs.openstack.org/ext/agent/api/v2.0",
|
||||
Alias: "agent",
|
||||
Description: "The agent management extension.",
|
||||
}
|
||||
|
||||
// HandleListExtensionsSuccessfully creates an HTTP handler at `/extensions` on the test handler
|
||||
// mux that response with a list containing a single tenant.
|
||||
func HandleListExtensionsSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/extensions", 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)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleGetExtensionSuccessfully creates an HTTP handler at `/extensions/agent` that responds with
|
||||
// a JSON payload corresponding to SingleExtension.
|
||||
func HandleGetExtensionSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/extensions/agent", 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")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, GetOutput)
|
||||
})
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Package floatingip provides the ability to manage floating ips through
|
||||
// nova-network
|
||||
package floatingip
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue