Upgrade gophercloud to v1.11.0

Update upup/pkg/fi/cloudup/openstacktasks/instance.go

Co-authored-by: Peter Rifel <rifelpet@users.noreply.github.com>
This commit is contained in:
Ole Markus With 2020-08-23 10:28:06 +02:00
parent 40662d04dd
commit 8c70787bab
70 changed files with 2374 additions and 1154 deletions

4
go.mod
View File

@ -46,7 +46,7 @@ replace k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.18.6
replace k8s.io/code-generator => k8s.io/code-generator v0.18.6 replace k8s.io/code-generator => k8s.io/code-generator v0.18.6
replace github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.9.0 replace github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.11.0
require ( require (
cloud.google.com/go v0.38.0 cloud.google.com/go v0.38.0
@ -76,7 +76,7 @@ require (
github.com/gogo/protobuf v1.3.1 github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.4.2 // indirect github.com/golang/protobuf v1.4.2 // indirect
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/gophercloud/gophercloud v0.7.1-0.20200116011225-46fdd1830e9a github.com/gophercloud/gophercloud v0.11.0
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl/v2 v2.3.0 github.com/hashicorp/hcl/v2 v2.3.0

4
go.sum
View File

@ -409,8 +409,8 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.9.0 h1:eJHQQFguQRv2FatH2d2VXH2ueTe2XzjgjwFjFS7SGcs= github.com/gophercloud/gophercloud v0.11.0 h1:pYMP9UZBdQa3lsfIZ1tZor4EbtxiuB6BHhocenkiH/E=
github.com/gophercloud/gophercloud v0.9.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= github.com/gophercloud/gophercloud v0.11.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/utils v0.0.0-20191020172814-bd86af96d544/go.mod h1:SZ9FTKibIotDtCrxAU/evccoyu1yhKST6hgBvwTB5Eg= github.com/gophercloud/utils v0.0.0-20191020172814-bd86af96d544/go.mod h1:SZ9FTKibIotDtCrxAU/evccoyu1yhKST6hgBvwTB5Eg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=

View File

@ -23,6 +23,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
os "github.com/gophercloud/gophercloud/openstack" os "github.com/gophercloud/gophercloud/openstack"
cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
@ -291,6 +293,8 @@ type OpenstackCloud interface {
GetImage(name string) (i *images.Image, err error) GetImage(name string) (i *images.Image, err error)
GetFlavor(name string) (f *flavors.Flavor, err error)
AssociateFloatingIPToInstance(serverID string, opts floatingips.AssociateOpts) (err error) AssociateFloatingIPToInstance(serverID string, opts floatingips.AssociateOpts) (err error)
ListServerFloatingIPs(id string) ([]*string, error) ListServerFloatingIPs(id string) ([]*string, error)

View File

@ -20,6 +20,8 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
@ -189,3 +191,29 @@ func listInstances(c OpenstackCloud, opt servers.ListOptsBuilder) ([]servers.Ser
return instances, wait.ErrWaitTimeout return instances, wait.ErrWaitTimeout
} }
} }
func (c *openstackCloud) GetFlavor(name string) (*flavors.Flavor, error) {
return getFlavor(c, name)
}
func getFlavor(c OpenstackCloud, name string) (*flavors.Flavor, error) {
opts := flavors.ListOpts{}
pager := flavors.ListDetail(c.ComputeClient(), opts)
page, err := pager.AllPages()
if err != nil {
return nil, fmt.Errorf("failed to list flavors: %v", err)
}
fs, err := flavors.ExtractFlavors(page)
if err != nil {
return nil, fmt.Errorf("failed to extract flavors: %v", err)
}
for _, f := range fs {
if f.Name == name {
return &f, nil
}
}
return nil, fmt.Errorf("could not find flavor with name %v", name)
}

View File

@ -19,6 +19,8 @@ package openstack
import ( import (
"fmt" "fmt"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes" cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
az "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" az "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones"
@ -331,6 +333,10 @@ func (c *MockCloud) GetImage(name string) (*images.Image, error) {
return getImage(c, name) return getImage(c, name)
} }
func (c *MockCloud) GetFlavor(name string) (*flavors.Flavor, error) {
return getFlavor(c, name)
}
func (c *MockCloud) GetInstance(id string) (*servers.Server, error) { func (c *MockCloud) GetInstance(id string) (*servers.Server, error) {
return getInstance(c, id) return getInstance(c, id)
} }

View File

@ -50,7 +50,6 @@ go_library(
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/servers:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/imageservice/v2/images:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/loadbalancers:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",

View File

@ -20,8 +20,6 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
@ -181,15 +179,21 @@ func (_ *Instance) RenderOpenstack(t *openstack.OpenstackAPITarget, a, e, change
cloud := t.Cloud.(openstack.OpenstackCloud) cloud := t.Cloud.(openstack.OpenstackCloud)
imageName := fi.StringValue(e.Image) imageName := fi.StringValue(e.Image)
imageRef, err := images.IDFromName(cloud.ImageClient(), imageName) image, err := cloud.GetImage(imageName)
if err != nil { if err != nil {
return fmt.Errorf("failed to find image %v: %v", imageName, err) return fmt.Errorf("failed to find image %v: %v", imageName, err)
} }
flavorName := fi.StringValue(e.Flavor)
flavor, err := cloud.GetFlavor(flavorName)
if err != nil {
return fmt.Errorf("failed to find flavor %v: %v", flavorName, err)
}
opt := servers.CreateOpts{ opt := servers.CreateOpts{
Name: fi.StringValue(e.Name), Name: fi.StringValue(e.Name),
ImageRef: imageRef, ImageRef: image.ID,
FlavorName: fi.StringValue(e.Flavor), FlavorRef: flavor.ID,
Networks: []servers.Network{ Networks: []servers.Network{
{ {
Port: fi.StringValue(e.Port.ID), Port: fi.StringValue(e.Port.ID),

View File

@ -1,4 +1,100 @@
## 0.10.0 (Unreleased) ## 0.12.0 (Unreleased)
## 0.11.0 (May 14, 2020)
UPGRADE NOTES
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
* All responses now have access to the returned headers. Please report any issues this has caused [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
* Changes have been made to the internal HTTP client to ensure response bodies are handled in a way that enables connections to be re-used more efficiently [GH-1952](https://github.com/gophercloud/gophercloud/pull/1952)
IMPROVEMENTS
* Added `objectstorage/v1/containers.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
* Added `objectstorage/v1/objects.BulkDelete` [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
* Object storage container and object names are now URL encoded [GH-1930](https://github.com/gophercloud/gophercloud/pull/1930)
* All responses now have access to the returned headers [GH-1942](https://github.com/gophercloud/gophercloud/pull/1942)
* Added `compute/v2/extensions/injectnetworkinfo.InjectNetworkInfo` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
* Added `compute/v2/extensions/resetnetwork.ResetNetwork` [GH-1941](https://github.com/gophercloud/gophercloud/pull/1941)
* Added `identity/v3/extensions/trusts.ListRoles` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
* Added `identity/v3/extensions/trusts.GetRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
* Added `identity/v3/extensions/trusts.CheckRole` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
* Added `identity/v3/extensions/oauth1.Create` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.CreateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.DeleteConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.ListConsumers` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.GetConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.UpdateConsumer` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.RequestToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.AuthorizeToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.CreateAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.GetAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.RevokeAccessToken` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.ListAccessTokens` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.ListAccessTokenRoles` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `identity/v3/extensions/oauth1.GetAccessTokenRole` [GH-1935](https://github.com/gophercloud/gophercloud/pull/1935)
* Added `networking/v2/extensions/agents.Update` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
* Added `networking/v2/extensions/agents.Delete` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
* Added `networking/v2/extensions/agents.ScheduleDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
* Added `networking/v2/extensions/agents.RemoveDHCPNetwork` [GH-1954](https://github.com/gophercloud/gophercloud/pull/1954)
* Added `identity/v3/projects.CreateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `identity/v3/projects.CreateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `identity/v3/projects.UpdateOpts.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `identity/v3/projects.UpdateOpts.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `identity/v3/projects.Project.Extra` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `identity/v3/projects.Options.Options` [GH-1951](https://github.com/gophercloud/gophercloud/pull/1951)
* Added `imageservice/v2/images.Image.OpenStackImageImportMethods` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
* Added `imageservice/v2/images.Image.OpenStackImageStoreIDs` [GH-1962](https://github.com/gophercloud/gophercloud/pull/1962)
BUG FIXES
* Changed`identity/v3/extensions/trusts.Trust.RemainingUses` from `bool` to `int` [GH-1939](https://github.com/gophercloud/gophercloud/pull/1939)
* Changed `identity/v3/applicationcredentials.CreateOpts.ExpiresAt` from `string` to `*time.Time` [GH-1937](https://github.com/gophercloud/gophercloud/pull/1937)
* Fixed issue with unmarshalling/decoding slices of composed structs [GH-1964](https://github.com/gophercloud/gophercloud/pull/1964)
## 0.10.0 (April 12, 2020)
UPGRADE NOTES
* The various `IDFromName` convenience functions have been moved to https://github.com/gophercloud/utils [GH-1897](https://github.com/gophercloud/gophercloud/pull/1897)
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
IMPROVEMENTS
* Added `blockstorage/extensions/volumeactions.SetBootable` [GH-1891](https://github.com/gophercloud/gophercloud/pull/1891)
* Added `blockstorage/extensions/backups.Export` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
* Added `blockstorage/extensions/backups.Import` [GH-1894](https://github.com/gophercloud/gophercloud/pull/1894)
* Added `placement/v1/resourceproviders.GetTraits` [GH-1899](https://github.com/gophercloud/gophercloud/pull/1899)
* Added the ability to authenticate with Amazon EC2 Credentials [GH-1900](https://github.com/gophercloud/gophercloud/pull/1900)
* Added ability to list Nova services by binary and host [GH-1904](https://github.com/gophercloud/gophercloud/pull/1904)
* Added `compute/v2/extensions/services.Update` [GH-1902](https://github.com/gophercloud/gophercloud/pull/1902)
* Added system scope to v3 authentication [GH-1908](https://github.com/gophercloud/gophercloud/pull/1908)
* Added `identity/v3/extensions/ec2tokens.ValidateS3Token` [GH-1906](https://github.com/gophercloud/gophercloud/pull/1906)
* Added `containerinfra/v1/clusters.Cluster.HealthStatus` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
* Added `containerinfra/v1/clusters.Cluster.HealthStatusReason` [GH-1910](https://github.com/gophercloud/gophercloud/pull/1910)
* Added `loadbalancer/v2/amphorae.Failover` [GH-1912](https://github.com/gophercloud/gophercloud/pull/1912)
* Added `identity/v3/extensions/ec2credentials.List` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
* Added `identity/v3/extensions/ec2credentials.Get` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
* Added `identity/v3/extensions/ec2credentials.Create` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
* Added `identity/v3/extensions/ec2credentials.Delete` [GH-1916](https://github.com/gophercloud/gophercloud/pull/1916)
* Added `ErrUnexpectedResponseCode.ResponseHeader` [GH-1919](https://github.com/gophercloud/gophercloud/pull/1919)
* Added support for TOTP authentication [GH-1922](https://github.com/gophercloud/gophercloud/pull/1922)
* `sharedfilesystems/v2/shares.GetExportLocations` was renamed to `sharedfilesystems/v2/shares.ListExportLocations` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
* Added `sharedfilesystems/v2/shares.GetExportLocation` [GH-1932](https://github.com/gophercloud/gophercloud/pull/1932)
* Added `sharedfilesystems/v2/shares.Revert` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
* Added `sharedfilesystems/v2/shares.ResetStatus` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
* Added `sharedfilesystems/v2/shares.ForceDelete` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
* Added `sharedfilesystems/v2/shares.Unmanage` [GH-1931](https://github.com/gophercloud/gophercloud/pull/1931)
* Added `blockstorage/v3/attachments.Create` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
* Added `blockstorage/v3/attachments.List` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
* Added `blockstorage/v3/attachments.Get` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
* Added `blockstorage/v3/attachments.Update` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
* Added `blockstorage/v3/attachments.Delete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
* Added `blockstorage/v3/attachments.Complete` [GH-1934](https://github.com/gophercloud/gophercloud/pull/1934)
BUG FIXES
* Fixed issue with Orchestration `get_file` only being able to read JSON and YAML files [GH-1915](https://github.com/gophercloud/gophercloud/pull/1915)
## 0.9.0 (March 10, 2020) ## 0.9.0 (March 10, 2020)

View File

@ -45,6 +45,9 @@ type AuthOptions struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
// Passcode is used in TOTP authentication method
Passcode string `json:"passcode,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username // At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional. // with Identity V3. Otherwise, either are optional.
DomainID string `json:"-"` DomainID string `json:"-"`
@ -98,6 +101,7 @@ type AuthScope struct {
ProjectName string ProjectName string
DomainID string DomainID string
DomainName string DomainName string
System bool
} }
// ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
@ -133,6 +137,8 @@ func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
return map[string]interface{}{"auth": authMap}, nil return map[string]interface{}{"auth": authMap}, nil
} }
// ToTokenV3CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v3 tokens package
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
type domainReq struct { type domainReq struct {
ID *string `json:"id,omitempty"` ID *string `json:"id,omitempty"`
@ -148,7 +154,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
type userReq struct { type userReq struct {
ID *string `json:"id,omitempty"` ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Password string `json:"password,omitempty"` Password *string `json:"password,omitempty"`
Passcode *string `json:"passcode,omitempty"`
Domain *domainReq `json:"domain,omitempty"` Domain *domainReq `json:"domain,omitempty"`
} }
@ -167,11 +174,16 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
Secret *string `json:"secret,omitempty"` Secret *string `json:"secret,omitempty"`
} }
type totpReq struct {
User *userReq `json:"user,omitempty"`
}
type identityReq struct { type identityReq struct {
Methods []string `json:"methods"` Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"` Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"` Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
TOTP *totpReq `json:"totp,omitempty"`
} }
type authReq struct { type authReq struct {
@ -186,7 +198,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
// if insufficient or incompatible information is present. // if insufficient or incompatible information is present.
var req request var req request
if opts.Password == "" { if opts.Password == "" && opts.Passcode == "" {
if opts.TokenID != "" { if opts.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters. // parameters.
@ -274,7 +286,14 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
} }
} else { } else {
// Password authentication. // Password authentication.
req.Auth.Identity.Methods = []string{"password"} if opts.Password != "" {
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "password")
}
// TOTP authentication.
if opts.Passcode != "" {
req.Auth.Identity.Methods = append(req.Auth.Identity.Methods, "totp")
}
// At least one of Username and UserID must be specified. // At least one of Username and UserID must be specified.
if opts.Username == "" && opts.UserID == "" { if opts.Username == "" && opts.UserID == "" {
@ -298,23 +317,46 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
} }
// Configure the request for Username and Password authentication with a DomainID. // Configure the request for Username and Password authentication with a DomainID.
req.Auth.Identity.Password = &passwordReq{ if opts.Password != "" {
User: userReq{ req.Auth.Identity.Password = &passwordReq{
Name: &opts.Username, User: userReq{
Password: opts.Password, Name: &opts.Username,
Domain: &domainReq{ID: &opts.DomainID}, Password: &opts.Password,
}, Domain: &domainReq{ID: &opts.DomainID},
},
}
}
if opts.Passcode != "" {
req.Auth.Identity.TOTP = &totpReq{
User: &userReq{
Name: &opts.Username,
Passcode: &opts.Passcode,
Domain: &domainReq{ID: &opts.DomainID},
},
}
} }
} }
if opts.DomainName != "" { if opts.DomainName != "" {
// Configure the request for Username and Password authentication with a DomainName. // Configure the request for Username and Password authentication with a DomainName.
req.Auth.Identity.Password = &passwordReq{ if opts.Password != "" {
User: userReq{ req.Auth.Identity.Password = &passwordReq{
Name: &opts.Username, User: userReq{
Password: opts.Password, Name: &opts.Username,
Domain: &domainReq{Name: &opts.DomainName}, Password: &opts.Password,
}, Domain: &domainReq{Name: &opts.DomainName},
},
}
}
if opts.Passcode != "" {
req.Auth.Identity.TOTP = &totpReq{
User: &userReq{
Name: &opts.Username,
Passcode: &opts.Passcode,
Domain: &domainReq{Name: &opts.DomainName},
},
}
} }
} }
} }
@ -329,8 +371,22 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
} }
// Configure the request for UserID and Password authentication. // Configure the request for UserID and Password authentication.
req.Auth.Identity.Password = &passwordReq{ if opts.Password != "" {
User: userReq{ID: &opts.UserID, Password: opts.Password}, req.Auth.Identity.Password = &passwordReq{
User: userReq{
ID: &opts.UserID,
Password: &opts.Password,
},
}
}
if opts.Passcode != "" {
req.Auth.Identity.TOTP = &totpReq{
User: &userReq{
ID: &opts.UserID,
Passcode: &opts.Passcode,
},
}
} }
} }
} }
@ -347,6 +403,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
return b, nil return b, nil
} }
// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in
// the v3 tokens package.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
// For backwards compatibility. // For backwards compatibility.
// If AuthOptions.Scope was not set, try to determine it. // If AuthOptions.Scope was not set, try to determine it.
@ -364,6 +422,14 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
} }
} }
if opts.Scope.System {
return map[string]interface{}{
"system": map[string]interface{}{
"all": true,
},
}, nil
}
if opts.Scope.ProjectName != "" { if opts.Scope.ProjectName != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied. // ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied. // ProjectID may not be supplied.
@ -433,5 +499,16 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
} }
func (opts AuthOptions) CanReauth() bool { func (opts AuthOptions) CanReauth() bool {
if opts.Passcode != "" {
// cannot reauth using TOTP passcode
return false
}
return opts.AllowReauth return opts.AllowReauth
} }
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v3 tokens package.
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
return nil, nil
}

View File

@ -2,6 +2,7 @@ package gophercloud
import ( import (
"fmt" "fmt"
"net/http"
"strings" "strings"
) )
@ -77,11 +78,12 @@ func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
// those listed in OkCodes is encountered. // those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct { type ErrUnexpectedResponseCode struct {
BaseError BaseError
URL string URL string
Method string Method string
Expected []int Expected []int
Actual int Actual int
Body []byte Body []byte
ResponseHeader http.Header
} }
func (e ErrUnexpectedResponseCode) Error() string { func (e ErrUnexpectedResponseCode) Error() string {

View File

@ -15,6 +15,8 @@ go_library(
deps = [ deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v2/tokens:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/utils:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/utils:go_default_library",
], ],

View File

@ -38,6 +38,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
username := os.Getenv("OS_USERNAME") username := os.Getenv("OS_USERNAME")
userID := os.Getenv("OS_USERID") userID := os.Getenv("OS_USERID")
password := os.Getenv("OS_PASSWORD") password := os.Getenv("OS_PASSWORD")
passcode := os.Getenv("OS_PASSCODE")
tenantID := os.Getenv("OS_TENANT_ID") tenantID := os.Getenv("OS_TENANT_ID")
tenantName := os.Getenv("OS_TENANT_NAME") tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID") domainID := os.Getenv("OS_DOMAIN_ID")
@ -73,8 +74,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
} }
} }
if password == "" && applicationCredentialID == "" && applicationCredentialName == "" { if password == "" && passcode == "" && applicationCredentialID == "" && applicationCredentialName == "" {
err := gophercloud.ErrMissingEnvironmentVariable{ err := gophercloud.ErrMissingEnvironmentVariable{
// silently ignore TOTP passcode warning, since it is not a common auth method
EnvironmentVariable: "OS_PASSWORD", EnvironmentVariable: "OS_PASSWORD",
} }
return nilOptions, err return nilOptions, err
@ -112,6 +114,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
UserID: userID, UserID: userID,
Username: username, Username: username,
Password: password, Password: password,
Passcode: passcode,
TenantID: tenantID, TenantID: tenantID,
TenantName: tenantName, TenantName: tenantName,
DomainID: domainID, DomainID: domainID,

View File

@ -60,9 +60,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -96,14 +97,16 @@ func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder
} }
url += query url += query
} }
_, r.Err = client.Delete(url, nil) resp, err := client.Delete(url, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves the Volume with the provided ID. To extract the Volume object // Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult. // from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -197,44 +200,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convienience function that returns a volume's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractVolumes(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "volume"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "volume"}
}
}

View File

@ -7,6 +7,8 @@ import (
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens" tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens"
"github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/openstack/utils" "github.com/gophercloud/gophercloud/openstack/utils"
) )
@ -68,7 +70,7 @@ Example:
ao, err := openstack.AuthOptionsFromEnv() ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(ao) provider, err := openstack.AuthenticatedClient(ao)
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"), Region: os.Getenv("OS_REGION_NAME"),
}) })
*/ */
@ -224,7 +226,15 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return err return err
} }
} else { } else {
result := tokens3.Create(v3Client, opts) var result tokens3.CreateResult
switch opts.(type) {
case *ec2tokens.AuthOptions:
result = ec2tokens.Create(v3Client, opts)
case *oauth1.AuthOptions:
result = oauth1.Create(v3Client, opts)
default:
result = tokens3.Create(v3Client, opts)
}
err = client.SetTokenAndAuthResult(result) err = client.SetTokenAndAuthResult(result)
if err != nil { if err != nil {
@ -255,6 +265,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
o := *ot o := *ot
o.AllowReauth = false o.AllowReauth = false
tao = &o tao = &o
case *ec2tokens.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
case *oauth1.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
default: default:
tao = opts tao = opts
} }

View File

@ -125,8 +125,9 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) (
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -36,21 +36,24 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get returns data about a previously created Floating IP. // Get returns data about a previously created Floating IP.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete requests the deletion of a previous allocated Floating IP. // Delete requests the deletion of a previous allocated Floating IP.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -81,7 +84,8 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(associateURL(client, serverID), b, nil, nil) resp, err := client.Post(associateURL(client, serverID), b, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -109,6 +113,7 @@ func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, op
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(disassociateURL(client, serverID), b, nil, nil) resp, err := client.Post(disassociateURL(client, serverID), b, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -67,20 +67,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get returns public data about a previously uploaded KeyPair. // Get returns public data about a previously uploaded KeyPair.
func Get(client *gophercloud.ServiceClient, name string) (r GetResult) { func Get(client *gophercloud.ServiceClient, name string) (r GetResult) {
_, r.Err = client.Get(getURL(client, name), &r.Body, nil) resp, err := client.Get(getURL(client, name), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete requests the deletion of a previous stored KeyPair from the server. // Delete requests the deletion of a previous stored KeyPair from the server.
func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, name string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, name), nil) resp, err := client.Delete(deleteURL(client, name), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -48,20 +48,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get returns data about a previously created ServerGroup. // Get returns data about a previously created ServerGroup.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete requests the deletion of a previously allocated ServerGroup. // Delete requests the deletion of a previously allocated ServerGroup.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -40,21 +40,24 @@ func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsB
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client, serverID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get returns public data about a previously created VolumeAttachment. // Get returns public data about a previously created VolumeAttachment.
func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r GetResult) { func Get(client *gophercloud.ServiceClient, serverID, attachmentID string) (r GetResult) {
_, r.Err = client.Get(getURL(client, serverID, attachmentID), &r.Body, nil) resp, err := client.Get(getURL(client, serverID, attachmentID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete requests the deletion of a previous stored VolumeAttachment from // Delete requests the deletion of a previous stored VolumeAttachment from
// the server. // the server.
func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, serverID, attachmentID string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, serverID, attachmentID), nil) resp, err := client.Delete(deleteURL(client, serverID, attachmentID), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -142,22 +142,25 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves details of a single flavor. Use Extract to convert its // Get retrieves details of a single flavor. Use Extract to convert its
// result into a Flavor. // result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete deletes the specified flavor ID. // Delete deletes the specified flavor ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -194,9 +197,10 @@ func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsB
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -224,20 +228,23 @@ func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAcces
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(accessActionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// ExtraSpecs requests all the extra-specs for the given flavor ID. // ExtraSpecs requests all the extra-specs for the given flavor ID.
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) { func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
_, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil) resp, err := client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) { func GetExtraSpec(client *gophercloud.ServiceClient, flavorID string, key string) (r GetExtraSpecResult) {
_, r.Err = client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil) resp, err := client.Get(extraSpecsGetURL(client, flavorID, key), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -264,9 +271,10 @@ func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts C
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(extraSpecsCreateURL(client, flavorID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -302,56 +310,19 @@ func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts Up
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(extraSpecUpdateURL(client, flavorID, key), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// DeleteExtraSpec will delete the key-value pair with the given key for the given // DeleteExtraSpec will delete the key-value pair with the given key for the given
// flavor ID. // flavor ID.
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) { func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
_, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{ resp, err := client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convienience function that returns a flavor's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
allPages, err := ListDetail(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractFlavors(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
err := &gophercloud.ErrResourceNotFound{}
err.ResourceType = "flavor"
err.Name = name
return "", err
case 1:
return id, nil
default:
err := &gophercloud.ErrMultipleResourcesFound{}
err.ResourceType = "flavor"
err.Name = name
err.Count = count
return "", err
}
}

View File

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

View File

@ -1,109 +0,0 @@
package images
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOptsBuilder allows extensions to add additional parameters to the
// ListDetail request.
type ListOptsBuilder interface {
ToImageListQuery() (string, error)
}
// ListOpts contain options filtering Images returned from a call to ListDetail.
type ListOpts struct {
// ChangesSince filters Images based on the last changed status (in date-time
// format).
ChangesSince string `q:"changes-since"`
// Limit limits the number of Images to return.
Limit int `q:"limit"`
// Mark is an Image UUID at which to set a marker.
Marker string `q:"marker"`
// Name is the name of the Image.
Name string `q:"name"`
// Server is the name of the Server (in URL format).
Server string `q:"server"`
// Status is the current status of the Image.
Status string `q:"status"`
// Type is the type of image (e.g. BASE, SERVER, ALL).
Type string `q:"type"`
}
// ToImageListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToImageListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// ListDetail enumerates the available images.
func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := listDetailURL(client)
if opts != nil {
query, err := opts.ToImageListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return ImagePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// Get returns data about a specific image by its ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
return
}
// Delete deletes the specified image ID.
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) {
count := 0
id := ""
allPages, err := ListDetail(client, nil).AllPages()
if err != nil {
return "", err
}
all, err := ExtractImages(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
err := &gophercloud.ErrResourceNotFound{}
err.ResourceType = "image"
err.Name = name
return "", err
case 1:
return id, nil
default:
err := &gophercloud.ErrMultipleResourcesFound{}
err.ResourceType = "image"
err.Name = name
err.Count = count
return "", err
}
}

View File

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

View File

@ -1,15 +0,0 @@
package images
import "github.com/gophercloud/gophercloud"
func listDetailURL(client *gophercloud.ServiceClient) string {
return client.ServiceURL("images", "detail")
}
func getURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}
func deleteURL(client *gophercloud.ServiceClient, id string) string {
return client.ServiceURL("images", id)
}

View File

@ -15,8 +15,6 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/flavors:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
], ],
) )

View File

@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -148,24 +146,14 @@ type CreateOpts struct {
// Name is the name to assign to the newly launched server. // Name is the name to assign to the newly launched server.
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
// ImageRef [optional; required if ImageName is not provided] is the ID or // ImageRef is the ID or full URL to the image that contains the
// full URL to the image that contains the server's OS and initial state. // server's OS and initial state.
// Also optional if using the boot-from-volume extension. // Also optional if using the boot-from-volume extension.
ImageRef string `json:"imageRef"` ImageRef string `json:"imageRef"`
// ImageName [optional; required if ImageRef is not provided] is the name of // FlavorRef is the ID or full URL to the flavor that describes the server's specs.
// the image that contains the server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageName string `json:"-"`
// FlavorRef [optional; required if FlavorName is not provided] is the ID or
// full URL to the flavor that describes the server's specs.
FlavorRef string `json:"flavorRef"` FlavorRef string `json:"flavorRef"`
// FlavorName [optional; required if FlavorRef is not provided] is the name of
// the flavor that describes the server's specs.
FlavorName string `json:"-"`
// SecurityGroups lists the names of the security groups to which this server // SecurityGroups lists the names of the security groups to which this server
// should belong. // should belong.
SecurityGroups []string `json:"-"` SecurityGroups []string `json:"-"`
@ -223,7 +211,6 @@ type CreateOpts struct {
// ToServerCreateMap assembles a request body based on the contents of a // ToServerCreateMap assembles a request body based on the contents of a
// CreateOpts. // CreateOpts.
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) { func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
sc := opts.ServiceClient
opts.ServiceClient = nil opts.ServiceClient = nil
b, err := gophercloud.BuildRequestBody(opts, "") b, err := gophercloud.BuildRequestBody(opts, "")
if err != nil { if err != nil {
@ -274,42 +261,6 @@ func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
} }
} }
// If ImageRef isn't provided, check if ImageName was provided to ascertain
// the image ID.
if opts.ImageRef == "" {
if opts.ImageName != "" {
if sc == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
imageID, err := images.IDFromName(sc, opts.ImageName)
if err != nil {
return nil, err
}
b["imageRef"] = imageID
}
}
// If FlavorRef isn't provided, use FlavorName to ascertain the flavor ID.
if opts.FlavorRef == "" {
if opts.FlavorName == "" {
err := ErrNeitherFlavorIDNorFlavorNameProvided{}
err.Argument = "FlavorRef/FlavorName"
return nil, err
}
if sc == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
flavorID, err := flavors.IDFromName(sc, opts.FlavorName)
if err != nil {
return nil, err
}
b["flavorRef"] = flavorID
}
if opts.Min != 0 { if opts.Min != 0 {
b["min_count"] = opts.Min b["min_count"] = opts.Min
} }
@ -328,28 +279,32 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(listURL(client), reqBody, &r.Body, nil) resp, err := client.Post(listURL(client), reqBody, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete requests that a server previously provisioned be removed from your // Delete requests that a server previously provisioned be removed from your
// account. // account.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// ForceDelete forces the deletion of a server. // ForceDelete forces the deletion of a server.
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) { func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil) resp, err := client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get requests details on a single server, by ID. // Get requests details on a single server, by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{ resp, err := client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203}, OkCodes: []int{200, 203},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -386,9 +341,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -400,7 +356,8 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri
"adminPass": newPassword, "adminPass": newPassword,
}, },
} }
_, r.Err = client.Post(actionURL(client, id), b, nil, nil) resp, err := client.Post(actionURL(client, id), b, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -441,7 +398,7 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
HardReboot (aka PowerCycle) starts the server instance by physically cutting HardReboot (aka PowerCycle) starts the server instance by physically cutting
power to the machine, or if a VM, terminating it at the hypervisor level. power to the machine, or if a VM, terminating it at the hypervisor level.
It's done. Caput. Full stop. It's done. Caput. Full stop.
Then, after a brief while, power is rtored or the VM instance restarted. Then, after a brief while, power is restored or the VM instance restarted.
SoftReboot (aka OSReboot) simply tells the OS to restart under its own SoftReboot (aka OSReboot) simply tells the OS to restart under its own
procedure. procedure.
@ -454,7 +411,8 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(actionURL(client, id), b, nil, nil) resp, err := client.Post(actionURL(client, id), b, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -470,11 +428,8 @@ type RebuildOpts struct {
// AdminPass is the server's admin password // AdminPass is the server's admin password
AdminPass string `json:"adminPass,omitempty"` AdminPass string `json:"adminPass,omitempty"`
// ImageID is the ID of the image you want your server to be provisioned on. // ImageRef is the ID of the image you want your server to be provisioned on.
ImageID string `json:"imageRef"` ImageRef string `json:"imageRef"`
// ImageName is readable name of an image.
ImageName string `json:"-"`
// Name to set the server to // Name to set the server to
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
@ -505,23 +460,6 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
return nil, err return nil, err
} }
// If ImageRef isn't provided, check if ImageName was provided to ascertain
// the image ID.
if opts.ImageID == "" {
if opts.ImageName != "" {
if opts.ServiceClient == nil {
err := ErrNoClientProvidedForIDByName{}
err.Argument = "ServiceClient"
return nil, err
}
imageID, err := images.IDFromName(opts.ServiceClient, opts.ImageName)
if err != nil {
return nil, err
}
b["imageRef"] = imageID
}
}
return map[string]interface{}{"rebuild": b}, nil return map[string]interface{}{"rebuild": b}, nil
} }
@ -533,7 +471,8 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, nil) resp, err := client.Post(actionURL(client, id), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -571,23 +510,26 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(actionURL(client, id), b, nil, nil) resp, err := client.Post(actionURL(client, id), b, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// ConfirmResize confirms a previous resize operation on a server. // ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details. // See Resize() for more details.
func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { func ConfirmResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{ resp, err := client.Post(actionURL(client, id), map[string]interface{}{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
OkCodes: []int{201, 202, 204}, OkCodes: []int{201, 202, 204},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// RevertResize cancels a previous resize operation on a server. // RevertResize cancels a previous resize operation on a server.
// See Resize() for more details. // See Resize() for more details.
func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) { func RevertResize(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil) resp, err := client.Post(actionURL(client, id), map[string]interface{}{"revertResize": nil}, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -623,15 +565,17 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Metadata requests all the metadata for the given server ID. // Metadata requests all the metadata for the given server ID.
func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) { func Metadata(client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
_, r.Err = client.Get(metadataURL(client, id), &r.Body, nil) resp, err := client.Get(metadataURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -650,9 +594,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -690,23 +635,26 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Metadatum requests the key-value pair with the given key for the given // Metadatum requests the key-value pair with the given key for the given
// server ID. // server ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) { func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil) resp, err := client.Get(metadatumURL(client, id, key), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// DeleteMetadatum will delete the key-value pair with the given key for the // DeleteMetadatum will delete the key-value pair with the given key for the
// given server ID. // given server ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) { func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
_, r.Err = client.Delete(metadatumURL(client, id, key), nil) resp, err := client.Delete(metadatumURL(client, id, key), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -759,52 +707,15 @@ func CreateImage(client *gophercloud.ServiceClient, id string, opts CreateImageO
resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{ resp, err := client.Post(actionURL(client, id), b, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
r.Err = err _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
return return
} }
// IDFromName is a convienience function that returns a server's ID given its
// name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
allPages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractServers(allPages)
if err != nil {
return "", err
}
for _, f := range all {
if f.Name == name {
count++
id = f.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "server"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "server"}
}
}
// GetPassword makes a request against the nova API to get the encrypted // GetPassword makes a request against the nova API to get the encrypted
// administrative password. // administrative password.
func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) { func GetPassword(client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
_, r.Err = client.Get(passwordURL(client, serverId), &r.Body, nil) resp, err := client.Get(passwordURL(client, serverId), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -833,8 +744,9 @@ func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowCo
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -57,7 +57,8 @@ func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsB
// Get implements the recordset Get request. // Get implements the recordset Get request.
func Get(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) { func Get(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r GetResult) {
_, r.Err = client.Get(rrsetURL(client, zoneID, rrsetID), &r.Body, nil) resp, err := client.Get(rrsetURL(client, zoneID, rrsetID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -103,9 +104,10 @@ func Create(client *gophercloud.ServiceClient, zoneID string, opts CreateOptsBui
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(baseURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(baseURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201, 202}, OkCodes: []int{201, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -158,16 +160,18 @@ func Update(client *gophercloud.ServiceClient, zoneID string, rrsetID string, op
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(rrsetURL(client, zoneID, rrsetID), &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(rrsetURL(client, zoneID, rrsetID), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete removes an existing RecordSet. // Delete removes an existing RecordSet.
func Delete(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, zoneID string, rrsetID string) (r DeleteResult) {
_, r.Err = client.Delete(rrsetURL(client, zoneID, rrsetID), &gophercloud.RequestOpts{ resp, err := client.Delete(rrsetURL(client, zoneID, rrsetID), &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -55,7 +55,8 @@ func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pa
// Get returns information about a zone, given its ID. // Get returns information about a zone, given its ID.
func Get(client *gophercloud.ServiceClient, zoneID string) (r GetResult) { func Get(client *gophercloud.ServiceClient, zoneID string) (r GetResult) {
_, r.Err = client.Get(zoneURL(client, zoneID), &r.Body, nil) resp, err := client.Get(zoneURL(client, zoneID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -110,9 +111,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(baseURL(client), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201, 202}, OkCodes: []int{201, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -158,17 +160,19 @@ func Update(client *gophercloud.ServiceClient, zoneID string, opts UpdateOptsBui
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Patch(zoneURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Patch(zoneURL(client, zoneID), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete implements a zone delete request. // Delete implements a zone delete request.
func Delete(client *gophercloud.ServiceClient, zoneID string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, zoneID string) (r DeleteResult) {
_, r.Err = client.Delete(zoneURL(client, zoneID), &gophercloud.RequestOpts{ resp, err := client.Delete(zoneURL(client, zoneID), &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
JSONResponse: &r.Body, JSONResponse: &r.Body,
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -7,7 +7,7 @@ Example of Creating a Service Client
ao, err := openstack.AuthOptionsFromEnv() ao, err := openstack.AuthOptionsFromEnv()
provider, err := openstack.AuthenticatedClient(ao) provider, err := openstack.AuthenticatedClient(ao)
client, err := openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ client, err := openstack.NewNetworkV2(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"), Region: os.Getenv("OS_REGION_NAME"),
}) })
*/ */

View File

@ -60,15 +60,17 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get requests details on a single tenant by ID. // Get requests details on a single tenant by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -103,14 +105,16 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(updateURL(client, id), &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete is the operation responsible for permanently deleting a tenant. // Delete is the operation responsible for permanently deleting a tenant.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -87,17 +87,19 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r Creat
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Post(CreateURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203}, OkCodes: []int{200, 203},
MoreHeaders: map[string]string{"X-Auth-Token": ""}, MoreHeaders: map[string]string{"X-Auth-Token": ""},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get validates and retrieves information for user's token. // Get validates and retrieves information for user's token.
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) { func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{ resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203}, OkCodes: []int{200, 203},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -0,0 +1,17 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"urls.go",
],
importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens",
importpath = "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens:go_default_library",
],
)

View File

@ -0,0 +1,41 @@
/*
Package tokens provides information and interaction with the EC2 token API
resource for the OpenStack Identity service.
For more information, see:
https://docs.openstack.org/api-ref/identity/v2-ext/
Example to Create a Token From an EC2 access and secret keys
var authOptions tokens.AuthOptionsBuilder
authOptions = &ec2tokens.AuthOptions{
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
Secret: "18f4f6761ada4e3795fa5273c30349b9",
}
token, err := ec2tokens.Create(identityClient, authOptions).ExtractToken()
if err != nil {
panic(err)
}
Example to auth a client using EC2 access and secret keys
client, err := openstack.NewClient("http://localhost:5000/v3")
if err != nil {
panic(err)
}
var authOptions tokens.AuthOptionsBuilder
authOptions = &ec2tokens.AuthOptions{
Access: "a7f1e798b7c2417cba4a02de97dc3cdc",
Secret: "18f4f6761ada4e3795fa5273c30349b9",
AllowReauth: true,
}
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
if err != nil {
panic(err)
}
*/
package ec2tokens

View File

@ -0,0 +1,377 @@
package ec2tokens
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"net/url"
"sort"
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
const (
// EC2CredentialsAwsRequestV4 is a constant, used to generate AWS
// Credential V4.
EC2CredentialsAwsRequestV4 = "aws4_request"
// EC2CredentialsHmacSha1V2 is a HMAC SHA1 signature method. Used to
// generate AWS Credential V2.
EC2CredentialsHmacSha1V2 = "HmacSHA1"
// EC2CredentialsHmacSha256V2 is a HMAC SHA256 signature method. Used
// to generate AWS Credential V2.
EC2CredentialsHmacSha256V2 = "HmacSHA256"
// EC2CredentialsAwsHmacV4 is an AWS signature V4 signing method.
// More details:
// https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
EC2CredentialsAwsHmacV4 = "AWS4-HMAC-SHA256"
// EC2CredentialsTimestampFormatV4 is an AWS signature V4 timestamp
// format.
EC2CredentialsTimestampFormatV4 = "20060102T150405Z"
// EC2CredentialsDateFormatV4 is an AWS signature V4 date format.
EC2CredentialsDateFormatV4 = "20060102"
)
// AuthOptions represents options for authenticating a user using EC2 credentials.
type AuthOptions struct {
// Access is the EC2 Credential Access ID.
Access string `json:"access" required:"true"`
// Secret is the EC2 Credential Secret, used to calculate signature.
// Not used, when a Signature is is.
Secret string `json:"-"`
// Host is a HTTP request Host header. Used to calculate an AWS
// signature V2. For signature V4 set the Host inside Headers map.
// Optional.
Host string `json:"host"`
// Path is a HTTP request path. Optional.
Path string `json:"path"`
// Verb is a HTTP request method. Optional.
Verb string `json:"verb"`
// Headers is a map of HTTP request headers. Optional.
Headers map[string]string `json:"headers"`
// Region is a region name to calculate an AWS signature V4. Optional.
Region string `json:"-"`
// Service is a service name to calculate an AWS signature V4. Optional.
Service string `json:"-"`
// Params is a map of GET method parameters. Optional.
Params map[string]string `json:"params"`
// AllowReauth allows Gophercloud to re-authenticate automatically
// if/when your token expires.
AllowReauth bool `json:"-"`
// Signature can be either a []byte (encoded to base64 automatically) or
// a string. You can set the singature explicitly, when you already know
// it. In this case default Params won't be automatically set. Optional.
Signature interface{} `json:"signature"`
// BodyHash is a HTTP request body sha256 hash. When nil and Signature
// is not set, a random hash is generated. Optional.
BodyHash *string `json:"body_hash"`
// Timestamp is a timestamp to calculate a V4 signature. Optional.
Timestamp *time.Time `json:"-"`
// Token is a []byte string (encoded to base64 automatically) which was
// signed by an EC2 secret key. Used by S3 tokens for validation only.
// Token must be set with a Signature. If a Signature is not provided,
// a Token will be generated automatically along with a Signature.
Token []byte `json:"token,omitempty"`
}
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
// for an AWS signature V2.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L133
func EC2CredentialsBuildCanonicalQueryStringV2(params map[string]string) string {
var keys []string
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, url.QueryEscape(params[k])))
}
return strings.Join(pairs, "&")
}
// EC2CredentialsBuildStringToSignV2 builds a string to sign an AWS signature
// V2.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L148
func EC2CredentialsBuildStringToSignV2(opts AuthOptions) []byte {
stringToSign := strings.Join([]string{
opts.Verb,
opts.Host,
opts.Path,
}, "\n")
return []byte(strings.Join([]string{
stringToSign,
EC2CredentialsBuildCanonicalQueryStringV2(opts.Params),
}, "\n"))
}
// EC2CredentialsBuildCanonicalQueryStringV2 builds a canonical query string
// for an AWS signature V4.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L244
func EC2CredentialsBuildCanonicalQueryStringV4(verb string, params map[string]string) string {
if verb == "POST" {
return ""
}
return EC2CredentialsBuildCanonicalQueryStringV2(params)
}
// EC2CredentialsBuildCanonicalHeadersV4 builds a canonical string based on
// "headers" map and "signedHeaders" string parameters.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L216
func EC2CredentialsBuildCanonicalHeadersV4(headers map[string]string, signedHeaders string) string {
headersLower := make(map[string]string, len(headers))
for k, v := range headers {
headersLower[strings.ToLower(k)] = v
}
var headersList []string
for _, h := range strings.Split(signedHeaders, ";") {
if v, ok := headersLower[h]; ok {
headersList = append(headersList, h+":"+v)
}
}
return strings.Join(headersList, "\n") + "\n"
}
// EC2CredentialsBuildSignatureKeyV4 builds a HMAC 256 signature key based on
// input parameters.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L169
func EC2CredentialsBuildSignatureKeyV4(secret, region, service string, date time.Time) []byte {
kDate := sumHMAC256([]byte("AWS4"+secret), []byte(date.Format(EC2CredentialsDateFormatV4)))
kRegion := sumHMAC256(kDate, []byte(region))
kService := sumHMAC256(kRegion, []byte(service))
return sumHMAC256(kService, []byte(EC2CredentialsAwsRequestV4))
}
// EC2CredentialsBuildStringToSignV4 builds an AWS v4 signature string to sign
// based on input parameters.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L251
func EC2CredentialsBuildStringToSignV4(opts AuthOptions, signedHeaders string, bodyHash string, date time.Time) []byte {
scope := strings.Join([]string{
date.Format(EC2CredentialsDateFormatV4),
opts.Region,
opts.Service,
EC2CredentialsAwsRequestV4,
}, "/")
canonicalRequest := strings.Join([]string{
opts.Verb,
opts.Path,
EC2CredentialsBuildCanonicalQueryStringV4(opts.Verb, opts.Params),
EC2CredentialsBuildCanonicalHeadersV4(opts.Headers, signedHeaders),
signedHeaders,
bodyHash,
}, "\n")
hash := sha256.Sum256([]byte(canonicalRequest))
return []byte(strings.Join([]string{
EC2CredentialsAwsHmacV4,
date.Format(EC2CredentialsTimestampFormatV4),
scope,
hex.EncodeToString(hash[:]),
}, "\n"))
}
// EC2CredentialsBuildSignatureV4 builds an AWS v4 signature based on input
// parameters.
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L285..L286
func EC2CredentialsBuildSignatureV4(key []byte, stringToSign []byte) string {
return hex.EncodeToString(sumHMAC256(key, stringToSign))
}
// EC2CredentialsBuildAuthorizationHeaderV4 builds an AWS v4 Authorization
// header based on auth parameters, date and signature
func EC2CredentialsBuildAuthorizationHeaderV4(opts AuthOptions, signedHeaders string, signature string, date time.Time) string {
return fmt.Sprintf("%s Credential=%s/%s/%s/%s/%s, SignedHeaders=%s, Signature=%s",
EC2CredentialsAwsHmacV4,
opts.Access,
date.Format(EC2CredentialsDateFormatV4),
opts.Region,
opts.Service,
EC2CredentialsAwsRequestV4,
signedHeaders,
signature)
}
// ToTokenV3ScopeMap is a dummy method to satisfy tokens.AuthOptionsBuilder
// interface.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
return nil, nil
}
// ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v3 tokens package.
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
return nil, nil
}
// CanReauth is a method method to satisfy tokens.AuthOptionsBuilder interface
func (opts *AuthOptions) CanReauth() bool {
return opts.AllowReauth
}
// ToTokenV3CreateMap formats an AuthOptions into a create request.
func (opts *AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "credentials")
if err != nil {
return nil, err
}
if opts.Signature != nil {
return b, nil
}
// calculate signature, when it is not set
c, _ := b["credentials"].(map[string]interface{})
h := interfaceToMap(c, "headers")
p := interfaceToMap(c, "params")
// detect and process a signature v2
if v, ok := p["SignatureVersion"]; ok && v == "2" {
if _, ok := c["body_hash"]; ok {
delete(c, "body_hash")
}
if _, ok := c["headers"]; ok {
delete(c, "headers")
}
if v, ok := p["SignatureMethod"]; ok {
// params is a map of strings
strToSign := EC2CredentialsBuildStringToSignV2(*opts)
switch v {
case EC2CredentialsHmacSha1V2:
// keystone uses this method only when HmacSHA256 is not available on the server side
// https://github.com/openstack/python-keystoneclient/blob/stable/train/keystoneclient/contrib/ec2/utils.py#L151..L156
c["signature"] = sumHMAC1([]byte(opts.Secret), strToSign)
return b, nil
case EC2CredentialsHmacSha256V2:
c["signature"] = sumHMAC256([]byte(opts.Secret), strToSign)
return b, nil
}
return nil, fmt.Errorf("unsupported signature method: %s", v)
}
return nil, fmt.Errorf("signature method must be provided")
} else if ok {
return nil, fmt.Errorf("unsupported signature version: %s", v)
}
// it is not a signature v2, but a signature v4
date := time.Now().UTC()
if opts.Timestamp != nil {
date = *opts.Timestamp
}
if v, _ := c["body_hash"]; v == nil {
// when body_hash is not set, generate a random one
c["body_hash"] = randomBodyHash()
}
signedHeaders, _ := h["X-Amz-SignedHeaders"]
stringToSign := EC2CredentialsBuildStringToSignV4(*opts, signedHeaders, c["body_hash"].(string), date)
key := EC2CredentialsBuildSignatureKeyV4(opts.Secret, opts.Region, opts.Service, date)
c["signature"] = EC2CredentialsBuildSignatureV4(key, stringToSign)
h["X-Amz-Date"] = date.Format(EC2CredentialsTimestampFormatV4)
h["Authorization"] = EC2CredentialsBuildAuthorizationHeaderV4(*opts, signedHeaders, c["signature"].(string), date)
// token is only used for S3 tokens validation and will be removed when using EC2 validation
c["token"] = stringToSign
return b, nil
}
// Create authenticates and either generates a new token from EC2 credentials
func Create(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
b, err := opts.ToTokenV3CreateMap(nil)
if err != nil {
r.Err = err
return
}
// delete "token" element, since it is used in s3tokens
deleteBodyElements(b, "token")
resp, err := c.Post(ec2tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// ValidateS3Token authenticates an S3 request using EC2 credentials. Doesn't
// generate a new token ID, but returns a tokens.CreateResult.
func ValidateS3Token(c *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
b, err := opts.ToTokenV3CreateMap(nil)
if err != nil {
r.Err = err
return
}
// delete unused element, since it is used in ec2tokens only
deleteBodyElements(b, "body_hash", "headers", "host", "params", "path", "verb")
resp, err := c.Post(s3tokensURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// The following are small helper functions used to help build the signature.
// sumHMAC1 is a func to implement the HMAC SHA1 signature method.
func sumHMAC1(key []byte, data []byte) []byte {
hash := hmac.New(sha1.New, key)
hash.Write(data)
return hash.Sum(nil)
}
// sumHMAC256 is a func to implement the HMAC SHA256 signature method.
func sumHMAC256(key []byte, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
// randomBodyHash is a func to generate a random sha256 hexdigest.
func randomBodyHash() string {
h := make([]byte, 64)
rand.Read(h)
return hex.EncodeToString(h)
}
// interfaceToMap is a func used to represent a "credentials" map element as a
// "map[string]string"
func interfaceToMap(c map[string]interface{}, key string) map[string]string {
// convert map[string]interface{} to map[string]string
m := make(map[string]string)
if v, _ := c[key].(map[string]interface{}); v != nil {
for k, v := range v {
m[k] = v.(string)
}
}
c[key] = m
return m
}
// deleteBodyElements deletes map body elements
func deleteBodyElements(b map[string]interface{}, elements ...string) {
if c, ok := b["credentials"].(map[string]interface{}); ok {
for _, k := range elements {
if _, ok := c[k]; ok {
delete(c, k)
}
}
}
}

View File

@ -0,0 +1,11 @@
package ec2tokens
import "github.com/gophercloud/gophercloud"
func ec2tokensURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("ec2tokens")
}
func s3tokensURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("s3tokens")
}

View File

@ -8,11 +8,12 @@ go_library(
"results.go", "results.go",
"urls.go", "urls.go",
], ],
importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images", importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1",
importpath = "github.com/gophercloud/gophercloud/openstack/compute/v2/images", importpath = "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/identity/v3/tokens:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library", "//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
], ],
) )

View File

@ -0,0 +1,123 @@
/*
Package oauth1 enables management of OpenStack OAuth1 tokens and Authentication.
Example to Create an OAuth1 Consumer
createConsumerOpts := oauth1.CreateConsumerOpts{
Description: "My consumer",
}
consumer, err := oauth1.CreateConsumer(identityClient, createConsumerOpts).Extract()
if err != nil {
panic(err)
}
// NOTE: Consumer secret is available only on create response
fmt.Printf("Consumer: %+v\n", consumer)
Example to Request an unauthorized OAuth1 token
requestTokenOpts := oauth1.RequestTokenOpts{
OAuthConsumerKey: consumer.ID,
OAuthConsumerSecret: consumer.Secret,
OAuthSignatureMethod: oauth1.HMACSHA1,
RequestedProjectID: projectID,
}
requestToken, err := oauth1.RequestToken(identityClient, requestTokenOpts).Extract()
if err != nil {
panic(err)
}
// NOTE: Request token secret is available only on request response
fmt.Printf("Request token: %+v\n", requestToken)
Example to Authorize an unauthorized OAuth1 token
authorizeTokenOpts := oauth1.AuthorizeTokenOpts{
Roles: []oauth1.Role{
{Name: "member"},
},
}
authToken, err := oauth1.AuthorizeToken(identityClient, requestToken.OAuthToken, authorizeTokenOpts).Extract()
if err != nil {
panic(err)
}
fmt.Printf("Verifier ID of the unauthorized Token: %+v\n", authToken.OAuthVerifier)
Example to Create an OAuth1 Access Token
accessTokenOpts := oauth1.CreateAccessTokenOpts{
OAuthConsumerKey: consumer.ID,
OAuthConsumerSecret: consumer.Secret,
OAuthToken: requestToken.OAuthToken,
OAuthTokenSecret: requestToken.OAuthTokenSecret,
OAuthVerifier: authToken.OAuthVerifier,
OAuthSignatureMethod: oauth1.HMACSHA1,
}
accessToken, err := oauth1.CreateAccessToken(identityClient, accessTokenOpts).Extract()
if err != nil {
panic(err)
}
// NOTE: Access token secret is available only on create response
fmt.Printf("OAuth1 Access Token: %+v\n", accessToken)
Example to List User's OAuth1 Access Tokens
allPages, err := oauth1.ListAccessTokens(identityClient, userID).AllPages()
if err != nil {
panic(err)
}
accessTokens, err := oauth1.ExtractAccessTokens(allPages)
if err != nil {
panic(err)
}
for _, accessToken := range accessTokens {
fmt.Printf("Access Token: %+v\n", accessToken)
}
Example to Authenticate a client using OAuth1 method
client, err := openstack.NewClient("http://localhost:5000/v3")
if err != nil {
panic(err)
}
authOptions := &oauth1.AuthOptions{
// consumer token, created earlier
OAuthConsumerKey: consumer.ID,
OAuthConsumerSecret: consumer.Secret,
// access token, created earlier
OAuthToken: accessToken.OAuthToken,
OAuthTokenSecret: accessToken.OAuthTokenSecret,
OAuthSignatureMethod: oauth1.HMACSHA1,
}
err = openstack.AuthenticateV3(client, authOptions, gophercloud.EndpointOpts{})
if err != nil {
panic(err)
}
Example to Create a Token using OAuth1 method
var oauth1Token struct {
tokens.Token
oauth1.TokenExt
}
createOpts := &oauth1.AuthOptions{
// consumer token, created earlier
OAuthConsumerKey: consumer.ID,
OAuthConsumerSecret: consumer.Secret,
// access token, created earlier
OAuthToken: accessToken.OAuthToken,
OAuthTokenSecret: accessToken.OAuthTokenSecret,
OAuthSignatureMethod: oauth1.HMACSHA1,
}
err := tokens.Create(identityClient, createOpts).ExtractInto(&oauth1Token)
if err != nil {
panic(err)
}
*/
package oauth1

View File

@ -0,0 +1,587 @@
package oauth1
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"io/ioutil"
"math/rand"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
"github.com/gophercloud/gophercloud/pagination"
)
// Type SignatureMethod is a OAuth1 SignatureMethod type.
type SignatureMethod string
const (
// HMACSHA1 is a recommended OAuth1 signature method.
HMACSHA1 SignatureMethod = "HMAC-SHA1"
// PLAINTEXT signature method is not recommended to be used in
// production environment.
PLAINTEXT SignatureMethod = "PLAINTEXT"
// OAuth1TokenContentType is a supported content type for an OAuth1
// token.
OAuth1TokenContentType = "application/x-www-form-urlencoded"
)
// AuthOptions represents options for authenticating a user using OAuth1 tokens.
type AuthOptions struct {
// OAuthConsumerKey is the OAuth1 Consumer Key.
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
// an OAuth1 request signature.
OAuthConsumerSecret string `required:"true"`
// OAuthToken is the OAuth1 Request Token.
OAuthToken string `q:"oauth_token" required:"true"`
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
// an OAuth1 request signature.
OAuthTokenSecret string `required:"true"`
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
// "PLAINTEXT" is not recommended for production usage.
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
// timestamp will be used.
OAuthTimestamp *time.Time
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
// uniquely generated for each request. Will be generated automatically
// when it is not set.
OAuthNonce string `q:"oauth_nonce"`
// AllowReauth allows Gophercloud to re-authenticate automatically
// if/when your token expires.
AllowReauth bool
}
// ToTokenV3HeadersMap builds the headers required for an OAuth1-based create
// request.
func (opts AuthOptions) ToTokenV3HeadersMap(headerOpts map[string]interface{}) (map[string]string, error) {
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
if err != nil {
return nil, err
}
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
method := headerOpts["method"].(string)
u := headerOpts["url"].(string)
stringToSign := buildStringToSign(method, u, q.Query())
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
authHeader := buildAuthHeader(q.Query(), signature)
headers := map[string]string{
"Authorization": authHeader,
"X-Auth-Token": "",
}
return headers, nil
}
// ToTokenV3ScopeMap allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
// interface.
func (opts AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
return nil, nil
}
// CanReauth allows AuthOptions to satisfy the tokens.AuthOptionsBuilder
// interface.
func (opts AuthOptions) CanReauth() bool {
return opts.AllowReauth
}
// ToTokenV3CreateMap builds a create request body.
func (opts AuthOptions) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) {
// identityReq defines the "identity" portion of an OAuth1-based authentication
// create request body.
type identityReq struct {
Methods []string `json:"methods"`
OAuth1 struct{} `json:"oauth1"`
}
// authReq defines the "auth" portion of an OAuth1-based authentication
// create request body.
type authReq struct {
Identity identityReq `json:"identity"`
}
// oauth1Request defines how an OAuth1-based authentication create
// request body looks.
type oauth1Request struct {
Auth authReq `json:"auth"`
}
var req oauth1Request
req.Auth.Identity.Methods = []string{"oauth1"}
return gophercloud.BuildRequestBody(req, "")
}
// Create authenticates and either generates a new OpenStack token from an
// OAuth1 token.
func Create(client *gophercloud.ServiceClient, opts tokens.AuthOptionsBuilder) (r tokens.CreateResult) {
b, err := opts.ToTokenV3CreateMap(nil)
if err != nil {
r.Err = err
return
}
headerOpts := map[string]interface{}{
"method": "POST",
"url": authURL(client),
}
h, err := opts.ToTokenV3HeadersMap(headerOpts)
if err != nil {
r.Err = err
return
}
resp, err := client.Post(authURL(client), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// CreateConsumerOptsBuilder allows extensions to add additional parameters to
// the CreateConsumer request.
type CreateConsumerOptsBuilder interface {
ToOAuth1CreateConsumerMap() (map[string]interface{}, error)
}
// CreateConsumerOpts provides options used to create a new Consumer.
type CreateConsumerOpts struct {
// Description is the consumer description.
Description string `json:"description"`
}
// ToOAuth1CreateConsumerMap formats a CreateConsumerOpts into a create request.
func (opts CreateConsumerOpts) ToOAuth1CreateConsumerMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "consumer")
}
// Create creates a new Consumer.
func CreateConsumer(client *gophercloud.ServiceClient, opts CreateConsumerOptsBuilder) (r CreateConsumerResult) {
b, err := opts.ToOAuth1CreateConsumerMap()
if err != nil {
r.Err = err
return
}
resp, err := client.Post(consumersURL(client), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete deletes a Consumer.
func DeleteConsumer(client *gophercloud.ServiceClient, id string) (r DeleteConsumerResult) {
resp, err := client.Delete(consumerURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// List enumerates Consumers.
func ListConsumers(client *gophercloud.ServiceClient) pagination.Pager {
return pagination.NewPager(client, consumersURL(client), func(r pagination.PageResult) pagination.Page {
return ConsumersPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// GetConsumer retrieves details on a single Consumer by ID.
func GetConsumer(client *gophercloud.ServiceClient, id string) (r GetConsumerResult) {
resp, err := client.Get(consumerURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// UpdateConsumerOpts provides options used to update a consumer.
type UpdateConsumerOpts struct {
// Description is the consumer description.
Description string `json:"description"`
}
// ToOAuth1UpdateConsumerMap formats an UpdateConsumerOpts into a consumer update
// request.
func (opts UpdateConsumerOpts) ToOAuth1UpdateConsumerMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "consumer")
}
// UpdateConsumer updates an existing Consumer.
func UpdateConsumer(client *gophercloud.ServiceClient, id string, opts UpdateConsumerOpts) (r UpdateConsumerResult) {
b, err := opts.ToOAuth1UpdateConsumerMap()
if err != nil {
r.Err = err
return
}
resp, err := client.Patch(consumerURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// RequestTokenOptsBuilder allows extensions to add additional parameters to the
// RequestToken request.
type RequestTokenOptsBuilder interface {
ToOAuth1RequestTokenHeaders(string, string) (map[string]string, error)
}
// RequestTokenOpts provides options used to get a consumer unauthorized
// request token.
type RequestTokenOpts struct {
// OAuthConsumerKey is the OAuth1 Consumer Key.
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
// an OAuth1 request signature.
OAuthConsumerSecret string `required:"true"`
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
// "PLAINTEXT" is not recommended for production usage.
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
// timestamp will be used.
OAuthTimestamp *time.Time
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
// uniquely generated for each request. Will be generated automatically
// when it is not set.
OAuthNonce string `q:"oauth_nonce"`
// RequestedProjectID is a Project ID a consumer user requested an
// access to.
RequestedProjectID string `h:"Requested-Project-Id"`
}
// ToOAuth1RequestTokenHeaders formats a RequestTokenOpts into a map of request
// headers.
func (opts RequestTokenOpts) ToOAuth1RequestTokenHeaders(method, u string) (map[string]string, error) {
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "oob")
if err != nil {
return nil, err
}
h, err := gophercloud.BuildHeaders(opts)
if err != nil {
return nil, err
}
signatureKeys := []string{opts.OAuthConsumerSecret}
stringToSign := buildStringToSign(method, u, q.Query())
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
authHeader := buildAuthHeader(q.Query(), signature)
h["Authorization"] = authHeader
return h, nil
}
// RequestToken requests an unauthorized OAuth1 Token.
func RequestToken(client *gophercloud.ServiceClient, opts RequestTokenOptsBuilder) (r TokenResult) {
h, err := opts.ToOAuth1RequestTokenHeaders("POST", requestTokenURL(client))
if err != nil {
r.Err = err
return
}
resp, err := client.Post(requestTokenURL(client), nil, nil, &gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201},
KeepResponseBody: true,
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
if r.Err != nil {
return
}
defer resp.Body.Close()
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
return
}
r.Body, r.Err = ioutil.ReadAll(resp.Body)
return
}
// AuthorizeTokenOptsBuilder allows extensions to add additional parameters to
// the AuthorizeToken request.
type AuthorizeTokenOptsBuilder interface {
ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error)
}
// AuthorizeTokenOpts provides options used to authorize a request token.
type AuthorizeTokenOpts struct {
Roles []Role `json:"roles"`
}
// Role is a struct representing a role object in a AuthorizeTokenOpts struct.
type Role struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
// ToOAuth1AuthorizeTokenMap formats an AuthorizeTokenOpts into an authorize token
// request.
func (opts AuthorizeTokenOpts) ToOAuth1AuthorizeTokenMap() (map[string]interface{}, error) {
for _, r := range opts.Roles {
if r == (Role{}) {
return nil, fmt.Errorf("role must not be empty")
}
}
return gophercloud.BuildRequestBody(opts, "")
}
// AuthorizeToken authorizes an unauthorized consumer token.
func AuthorizeToken(client *gophercloud.ServiceClient, id string, opts AuthorizeTokenOptsBuilder) (r AuthorizeTokenResult) {
b, err := opts.ToOAuth1AuthorizeTokenMap()
if err != nil {
r.Err = err
return
}
resp, err := client.Put(authorizeTokenURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// CreateAccessTokenOptsBuilder allows extensions to add additional parameters
// to the CreateAccessToken request.
type CreateAccessTokenOptsBuilder interface {
ToOAuth1CreateAccessTokenHeaders(string, string) (map[string]string, error)
}
// CreateAccessTokenOpts provides options used to create an OAuth1 token.
type CreateAccessTokenOpts struct {
// OAuthConsumerKey is the OAuth1 Consumer Key.
OAuthConsumerKey string `q:"oauth_consumer_key" required:"true"`
// OAuthConsumerSecret is the OAuth1 Consumer Secret. Used to generate
// an OAuth1 request signature.
OAuthConsumerSecret string `required:"true"`
// OAuthToken is the OAuth1 Request Token.
OAuthToken string `q:"oauth_token" required:"true"`
// OAuthTokenSecret is the OAuth1 Request Token Secret. Used to generate
// an OAuth1 request signature.
OAuthTokenSecret string `required:"true"`
// OAuthVerifier is the OAuth1 verification code.
OAuthVerifier string `q:"oauth_verifier" required:"true"`
// OAuthSignatureMethod is the OAuth1 signature method the Consumer used
// to sign the request. Supported values are "HMAC-SHA1" or "PLAINTEXT".
// "PLAINTEXT" is not recommended for production usage.
OAuthSignatureMethod SignatureMethod `q:"oauth_signature_method" required:"true"`
// OAuthTimestamp is an OAuth1 request timestamp. If nil, current Unix
// timestamp will be used.
OAuthTimestamp *time.Time
// OAuthNonce is an OAuth1 request nonce. Nonce must be a random string,
// uniquely generated for each request. Will be generated automatically
// when it is not set.
OAuthNonce string `q:"oauth_nonce"`
}
// ToOAuth1CreateAccessTokenHeaders formats a CreateAccessTokenOpts into a map of
// request headers.
func (opts CreateAccessTokenOpts) ToOAuth1CreateAccessTokenHeaders(method, u string) (map[string]string, error) {
q, err := buildOAuth1QueryString(opts, opts.OAuthTimestamp, "")
if err != nil {
return nil, err
}
signatureKeys := []string{opts.OAuthConsumerSecret, opts.OAuthTokenSecret}
stringToSign := buildStringToSign(method, u, q.Query())
signature := url.QueryEscape(signString(opts.OAuthSignatureMethod, stringToSign, signatureKeys))
authHeader := buildAuthHeader(q.Query(), signature)
headers := map[string]string{
"Authorization": authHeader,
}
return headers, nil
}
// CreateAccessToken creates a new OAuth1 Access Token
func CreateAccessToken(client *gophercloud.ServiceClient, opts CreateAccessTokenOptsBuilder) (r TokenResult) {
h, err := opts.ToOAuth1CreateAccessTokenHeaders("POST", createAccessTokenURL(client))
if err != nil {
r.Err = err
return
}
resp, err := client.Post(createAccessTokenURL(client), nil, nil, &gophercloud.RequestOpts{
MoreHeaders: h,
OkCodes: []int{201},
KeepResponseBody: true,
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
if r.Err != nil {
return
}
defer resp.Body.Close()
if v := r.Header.Get("Content-Type"); v != OAuth1TokenContentType {
r.Err = fmt.Errorf("unsupported Content-Type: %q", v)
return
}
r.Body, r.Err = ioutil.ReadAll(resp.Body)
return
}
// GetAccessToken retrieves details on a single OAuth1 access token by an ID.
func GetAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r GetAccessTokenResult) {
resp, err := client.Get(userAccessTokenURL(client, userID, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// RevokeAccessToken revokes an OAuth1 access token.
func RevokeAccessToken(client *gophercloud.ServiceClient, userID string, id string) (r RevokeAccessTokenResult) {
resp, err := client.Delete(userAccessTokenURL(client, userID, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// ListAccessTokens enumerates authorized access tokens.
func ListAccessTokens(client *gophercloud.ServiceClient, userID string) pagination.Pager {
url := userAccessTokensURL(client, userID)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AccessTokensPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// ListAccessTokenRoles enumerates authorized access token roles.
func ListAccessTokenRoles(client *gophercloud.ServiceClient, userID string, id string) pagination.Pager {
url := userAccessTokenRolesURL(client, userID, id)
return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
return AccessTokenRolesPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// GetAccessTokenRole retrieves details on a single OAuth1 access token role by
// an ID.
func GetAccessTokenRole(client *gophercloud.ServiceClient, userID string, id string, roleID string) (r GetAccessTokenRoleResult) {
resp, err := client.Get(userAccessTokenRoleURL(client, userID, id, roleID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// The following are small helper functions used to help build the signature.
// buildOAuth1QueryString builds a URLEncoded parameters string specific for
// OAuth1-based requests.
func buildOAuth1QueryString(opts interface{}, timestamp *time.Time, callback string) (*url.URL, error) {
q, err := gophercloud.BuildQueryString(opts)
if err != nil {
return nil, err
}
query := q.Query()
if timestamp != nil {
// use provided timestamp
query.Set("oauth_timestamp", strconv.FormatInt(timestamp.Unix(), 10))
} else {
// use current timestamp
query.Set("oauth_timestamp", strconv.FormatInt(time.Now().UTC().Unix(), 10))
}
if query.Get("oauth_nonce") == "" {
// when nonce is not set, generate a random one
query.Set("oauth_nonce", strconv.FormatInt(rand.Int63(), 10)+query.Get("oauth_timestamp"))
}
if callback != "" {
query.Set("oauth_callback", callback)
}
query.Set("oauth_version", "1.0")
return &url.URL{RawQuery: query.Encode()}, nil
}
// buildStringToSign builds a string to be signed.
func buildStringToSign(method string, u string, query url.Values) []byte {
parsedURL, _ := url.Parse(u)
p := parsedURL.Port()
s := parsedURL.Scheme
// Default scheme port must be stripped
if s == "http" && p == "80" || s == "https" && p == "443" {
parsedURL.Host = strings.TrimSuffix(parsedURL.Host, ":"+p)
}
// Ensure that URL doesn't contain queries
parsedURL.RawQuery = ""
v := strings.Join(
[]string{method, url.QueryEscape(parsedURL.String()), url.QueryEscape(query.Encode())}, "&")
return []byte(v)
}
// signString signs a string using an OAuth1 signature method.
func signString(signatureMethod SignatureMethod, strToSign []byte, signatureKeys []string) string {
var key []byte
for i, k := range signatureKeys {
key = append(key, []byte(url.QueryEscape(k))...)
if i == 0 {
key = append(key, '&')
}
}
var signedString string
switch signatureMethod {
case PLAINTEXT:
signedString = string(key)
default:
h := hmac.New(sha1.New, key)
h.Write(strToSign)
signedString = base64.StdEncoding.EncodeToString(h.Sum(nil))
}
return signedString
}
// buildAuthHeader generates an OAuth1 Authorization header with a signature
// calculated using an OAuth1 signature method.
func buildAuthHeader(query url.Values, signature string) string {
var authHeader []string
var keys []string
for k := range query {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
for _, v := range query[k] {
authHeader = append(authHeader, fmt.Sprintf("%s=%q", k, url.QueryEscape(v)))
}
}
authHeader = append(authHeader, fmt.Sprintf("oauth_signature=%q", signature))
return "OAuth " + strings.Join(authHeader, ", ")
}

View File

@ -0,0 +1,305 @@
package oauth1
import (
"encoding/json"
"net/url"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// Consumer represents a delegated authorization request between two
// identities.
type Consumer struct {
ID string `json:"id"`
Secret string `json:"secret"`
Description string `json:"description"`
}
type consumerResult struct {
gophercloud.Result
}
// CreateConsumerResult is the response from a Create operation. Call its
// Extract method to interpret it as a Consumer.
type CreateConsumerResult struct {
consumerResult
}
// UpdateConsumerResult is the response from a Create operation. Call its
// Extract method to interpret it as a Consumer.
type UpdateConsumerResult struct {
consumerResult
}
// DeleteConsumerResult is the response from a Delete operation. Call its
// ExtractErr to determine if the request succeeded or failed.
type DeleteConsumerResult struct {
gophercloud.ErrResult
}
// ConsumersPage is a single page of Region results.
type ConsumersPage struct {
pagination.LinkedPageBase
}
// GetConsumerResult is the response from a Get operation. Call its Extract
// method to interpret it as a Consumer.
type GetConsumerResult struct {
consumerResult
}
// IsEmpty determines whether or not a page of Consumers contains any results.
func (c ConsumersPage) IsEmpty() (bool, error) {
consumers, err := ExtractConsumers(c)
return len(consumers) == 0, err
}
// NextPageURL extracts the "next" link from the links section of the result.
func (c ConsumersPage) NextPageURL() (string, error) {
var s struct {
Links struct {
Next string `json:"next"`
Previous string `json:"previous"`
} `json:"links"`
}
err := c.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Links.Next, err
}
// ExtractConsumers returns a slice of Consumers contained in a single page of
// results.
func ExtractConsumers(r pagination.Page) ([]Consumer, error) {
var s struct {
Consumers []Consumer `json:"consumers"`
}
err := (r.(ConsumersPage)).ExtractInto(&s)
return s.Consumers, err
}
// Extract interprets any consumer result as a Consumer.
func (c consumerResult) Extract() (*Consumer, error) {
var s struct {
Consumer *Consumer `json:"consumer"`
}
err := c.ExtractInto(&s)
return s.Consumer, err
}
// Token contains an OAuth1 token.
type Token struct {
// OAuthToken is the key value for the oauth token that the Identity API returns.
OAuthToken string `q:"oauth_token"`
// OAuthTokenSecret is the secret value associated with the OAuth Token.
OAuthTokenSecret string `q:"oauth_token_secret"`
// OAUthExpiresAt is the date and time when an OAuth token expires.
OAUthExpiresAt *time.Time `q:"-"`
}
// TokenResult is a struct to handle
// "Content-Type: application/x-www-form-urlencoded" response.
type TokenResult struct {
gophercloud.Result
Body []byte
}
// Extract interprets any OAuth1 token result as a Token.
func (r TokenResult) Extract() (*Token, error) {
if r.Err != nil {
return nil, r.Err
}
values, err := url.ParseQuery(string(r.Body))
if err != nil {
return nil, err
}
token := &Token{
OAuthToken: values.Get("oauth_token"),
OAuthTokenSecret: values.Get("oauth_token_secret"),
}
if v := values.Get("oauth_expires_at"); v != "" {
if t, err := time.Parse(gophercloud.RFC3339Milli, v); err != nil {
return nil, err
} else {
token.OAUthExpiresAt = &t
}
}
return token, nil
}
// AuthorizedToken contains an OAuth1 authorized token info.
type AuthorizedToken struct {
// OAuthVerifier is the ID of the token verifier.
OAuthVerifier string `json:"oauth_verifier"`
}
type AuthorizeTokenResult struct {
gophercloud.Result
}
// Extract interprets AuthorizeTokenResult result as a AuthorizedToken.
func (r AuthorizeTokenResult) Extract() (*AuthorizedToken, error) {
var s struct {
AuthorizedToken *AuthorizedToken `json:"token"`
}
err := r.ExtractInto(&s)
return s.AuthorizedToken, err
}
// AccessToken represents an AccessToken response as a struct.
type AccessToken struct {
ID string `json:"id"`
ConsumerID string `json:"consumer_id"`
ProjectID string `json:"project_id"`
AuthorizingUserID string `json:"authorizing_user_id"`
ExpiresAt *time.Time `json:"-"`
}
func (r *AccessToken) UnmarshalJSON(b []byte) error {
type tmp AccessToken
var s struct {
tmp
ExpiresAt *gophercloud.JSONRFC3339Milli `json:"expires_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = AccessToken(s.tmp)
if s.ExpiresAt != nil {
t := time.Time(*s.ExpiresAt)
r.ExpiresAt = &t
}
return nil
}
type GetAccessTokenResult struct {
gophercloud.Result
}
// Extract interprets any GetAccessTokenResult result as an AccessToken.
func (r GetAccessTokenResult) Extract() (*AccessToken, error) {
var s struct {
AccessToken *AccessToken `json:"access_token"`
}
err := r.ExtractInto(&s)
return s.AccessToken, err
}
// RevokeAccessTokenResult is the response from a Delete operation. Call its
// ExtractErr to determine if the request succeeded or failed.
type RevokeAccessTokenResult struct {
gophercloud.ErrResult
}
// AccessTokensPage is a single page of Access Tokens results.
type AccessTokensPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
func (r AccessTokensPage) IsEmpty() (bool, error) {
accessTokens, err := ExtractAccessTokens(r)
return len(accessTokens) == 0, err
}
// NextPageURL extracts the "next" link from the links section of the result.
func (r AccessTokensPage) NextPageURL() (string, error) {
var s struct {
Links struct {
Next string `json:"next"`
Previous string `json:"previous"`
} `json:"links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Links.Next, err
}
// ExtractAccessTokens returns a slice of AccessTokens contained in a single
// page of results.
func ExtractAccessTokens(r pagination.Page) ([]AccessToken, error) {
var s struct {
AccessTokens []AccessToken `json:"access_tokens"`
}
err := (r.(AccessTokensPage)).ExtractInto(&s)
return s.AccessTokens, err
}
// AccessTokenRole represents an Access Token Role struct.
type AccessTokenRole struct {
ID string `json:"id"`
Name string `json:"name"`
DomainID string `json:"domain_id"`
}
// AccessTokenRolesPage is a single page of Access Token roles results.
type AccessTokenRolesPage struct {
pagination.LinkedPageBase
}
// IsEmpty determines whether or not a an AccessTokensPage contains any results.
func (r AccessTokenRolesPage) IsEmpty() (bool, error) {
accessTokenRoles, err := ExtractAccessTokenRoles(r)
return len(accessTokenRoles) == 0, err
}
// NextPageURL extracts the "next" link from the links section of the result.
func (r AccessTokenRolesPage) NextPageURL() (string, error) {
var s struct {
Links struct {
Next string `json:"next"`
Previous string `json:"previous"`
} `json:"links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return s.Links.Next, err
}
// ExtractAccessTokenRoles returns a slice of AccessTokenRole contained in a
// single page of results.
func ExtractAccessTokenRoles(r pagination.Page) ([]AccessTokenRole, error) {
var s struct {
AccessTokenRoles []AccessTokenRole `json:"roles"`
}
err := (r.(AccessTokenRolesPage)).ExtractInto(&s)
return s.AccessTokenRoles, err
}
type GetAccessTokenRoleResult struct {
gophercloud.Result
}
// Extract interprets any GetAccessTokenRoleResult result as an AccessTokenRole.
func (r GetAccessTokenRoleResult) Extract() (*AccessTokenRole, error) {
var s struct {
AccessTokenRole *AccessTokenRole `json:"role"`
}
err := r.ExtractInto(&s)
return s.AccessTokenRole, err
}
// OAuth1 is an OAuth1 object, returned in OAuth1 token result.
type OAuth1 struct {
AccessTokenID string `json:"access_token_id"`
ConsumerID string `json:"consumer_id"`
}
// TokenExt represents an extension of the base token result.
type TokenExt struct {
OAuth1 OAuth1 `json:"OS-OAUTH1"`
}

View File

@ -0,0 +1,43 @@
package oauth1
import "github.com/gophercloud/gophercloud"
func consumersURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("OS-OAUTH1", "consumers")
}
func consumerURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("OS-OAUTH1", "consumers", id)
}
func requestTokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("OS-OAUTH1", "request_token")
}
func authorizeTokenURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("OS-OAUTH1", "authorize", id)
}
func createAccessTokenURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("OS-OAUTH1", "access_token")
}
func userAccessTokensURL(c *gophercloud.ServiceClient, userID string) string {
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens")
}
func userAccessTokenURL(c *gophercloud.ServiceClient, userID string, id string) string {
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id)
}
func userAccessTokenRolesURL(c *gophercloud.ServiceClient, userID string, id string) string {
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles")
}
func userAccessTokenRoleURL(c *gophercloud.ServiceClient, userID string, id string, roleID string) string {
return c.ServiceURL("users", userID, "OS-OAUTH1", "access_tokens", id, "roles", roleID)
}
func authURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("auth", "tokens")
}

View File

@ -8,6 +8,7 @@ type Scope struct {
ProjectName string ProjectName string
DomainID string DomainID string
DomainName string DomainName string
System bool
} }
// AuthOptionsBuilder provides the ability for extensions to add additional // AuthOptionsBuilder provides the ability for extensions to add additional
@ -16,6 +17,7 @@ type AuthOptionsBuilder interface {
// ToTokenV3CreateMap assembles the Create request body, returning an error // ToTokenV3CreateMap assembles the Create request body, returning an error
// if parameters are missing or inconsistent. // if parameters are missing or inconsistent.
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error) ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error)
ToTokenV3ScopeMap() (map[string]interface{}, error) ToTokenV3ScopeMap() (map[string]interface{}, error)
CanReauth() bool CanReauth() bool
} }
@ -36,6 +38,9 @@ type AuthOptions struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
// Passcode is used in TOTP authentication method
Passcode string `json:"passcode,omitempty"`
// At most one of DomainID and DomainName must be provided if using Username // At most one of DomainID and DomainName must be provided if using Username
// with Identity V3. Otherwise, either are optional. // with Identity V3. Otherwise, either are optional.
DomainID string `json:"-"` DomainID string `json:"-"`
@ -67,6 +72,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
Username: opts.Username, Username: opts.Username,
UserID: opts.UserID, UserID: opts.UserID,
Password: opts.Password, Password: opts.Password,
Passcode: opts.Passcode,
DomainID: opts.DomainID, DomainID: opts.DomainID,
DomainName: opts.DomainName, DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth, AllowReauth: opts.AllowReauth,
@ -79,7 +85,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
return gophercloudAuthOpts.ToTokenV3CreateMap(scope) return gophercloudAuthOpts.ToTokenV3CreateMap(scope)
} }
// ToTokenV3CreateMap builds a scope request body from AuthOptions. // ToTokenV3ScopeMap builds a scope request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) { func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
scope := gophercloud.AuthScope(opts.Scope) scope := gophercloud.AuthScope(opts.Scope)
@ -93,10 +99,21 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
} }
func (opts *AuthOptions) CanReauth() bool { func (opts *AuthOptions) CanReauth() bool {
if opts.Passcode != "" {
// cannot reauth using TOTP passcode
return false
}
return opts.AllowReauth return opts.AllowReauth
} }
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string { // ToTokenV3HeadersMap allows AuthOptions to satisfy the AuthOptionsBuilder
// interface in the v3 tokens package.
func (opts *AuthOptions) ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error) {
return nil, nil
}
func subjectTokenHeaders(subjectToken string) map[string]string {
return map[string]string{ return map[string]string{
"X-Subject-Token": subjectToken, "X-Subject-Token": subjectToken,
} }
@ -120,30 +137,24 @@ func Create(c *gophercloud.ServiceClient, opts AuthOptionsBuilder) (r CreateResu
resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Post(tokenURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{"X-Auth-Token": ""}, MoreHeaders: map[string]string{"X-Auth-Token": ""},
}) })
r.Err = err _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
if resp != nil {
r.Header = resp.Header
}
return return
} }
// Get validates and retrieves information about another token. // Get validates and retrieves information about another token.
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) { func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{ resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token), MoreHeaders: subjectTokenHeaders(token),
OkCodes: []int{200, 203}, OkCodes: []int{200, 203},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
// Validate determines if a specified token is valid or not. // Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) { func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{ resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token), MoreHeaders: subjectTokenHeaders(token),
OkCodes: []int{200, 204, 404}, OkCodes: []int{200, 204, 404},
}) })
if err != nil { if err != nil {
@ -155,8 +166,9 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
// Revoke immediately makes specified token invalid. // Revoke immediately makes specified token invalid.
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) { func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{ resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token), MoreHeaders: subjectTokenHeaders(token),
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -206,19 +206,22 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err r.Err = err
return r return r
} }
_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}}) resp, err := client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete implements image delete request. // Delete implements image delete request.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get implements image get request. // Get implements image get request.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) { func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil) resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -229,10 +232,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err r.Err = err
return r return r
} }
_, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"}, MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -378,39 +382,3 @@ func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} {
return updateMap return updateMap
} }
// IDFromName is a convienience function that returns an image's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractImages(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "image"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "image"}
}
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"time" "time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
@ -86,13 +87,22 @@ type Image struct {
// VirtualSize is the virtual size of the image // VirtualSize is the virtual size of the image
VirtualSize int64 `json:"virtual_size"` VirtualSize int64 `json:"virtual_size"`
// OpenStackImageImportMethods is a slice listing the types of import
// methods available in the cloud.
OpenStackImageImportMethods []string `json:"-"`
// OpenStackImageStoreIDs is a slice listing the store IDs available in
// the cloud.
OpenStackImageStoreIDs []string `json:"-"`
} }
func (r *Image) UnmarshalJSON(b []byte) error { func (r *Image) UnmarshalJSON(b []byte) error {
type tmp Image type tmp Image
var s struct { var s struct {
tmp tmp
SizeBytes interface{} `json:"size"` SizeBytes interface{} `json:"size"`
OpenStackImageImportMethods string `json:"openstack-image-import-methods"`
OpenStackImageStoreIDs string `json:"openstack-image-store-ids"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -120,9 +130,18 @@ func (r *Image) UnmarshalJSON(b []byte) error {
if resultMap, ok := result.(map[string]interface{}); ok { if resultMap, ok := result.(map[string]interface{}); ok {
delete(resultMap, "self") delete(resultMap, "self")
delete(resultMap, "size") delete(resultMap, "size")
delete(resultMap, "openstack-image-import-methods")
delete(resultMap, "openstack-image-store-ids")
r.Properties = internal.RemainingKeys(Image{}, resultMap) r.Properties = internal.RemainingKeys(Image{}, resultMap)
} }
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 {
r.OpenStackImageImportMethods = v
}
if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageStoreIDs), splitFunc); len(v) > 0 {
r.OpenStackImageStoreIDs = v
}
return err return err
} }
@ -133,6 +152,20 @@ type commonResult struct {
// Extract interprets any commonResult as an Image. // Extract interprets any commonResult as an Image.
func (r commonResult) Extract() (*Image, error) { func (r commonResult) Extract() (*Image, error) {
var s *Image var s *Image
if v, ok := r.Body.(map[string]interface{}); ok {
for k, h := range r.Header {
if strings.ToLower(k) == "openstack-image-import-methods" {
for _, s := range h {
v["openstack-image-import-methods"] = s
}
}
if strings.ToLower(k) == "openstack-image-store-ids" {
for _, s := range h {
v["openstack-image-store-ids"] = s
}
}
}
}
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return s, err
} }
@ -200,3 +233,8 @@ func ExtractImages(r pagination.Page) ([]Image, error) {
err := (r.(ImagePage)).ExtractInto(&s) err := (r.(ImagePage)).ExtractInto(&s)
return s.Images, err return s.Images, err
} }
// splitFunc is a helper function used to avoid a slice of empty strings.
func splitFunc(c rune) bool {
return c == ','
}

View File

@ -80,7 +80,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -137,13 +138,15 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
// Get retrieves a particular l7policy based on its unique ID. // Get retrieves a particular l7policy based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular l7policy based on its unique ID. // Delete will permanently delete a particular l7policy based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -208,9 +211,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -254,7 +258,8 @@ func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOp
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(ruleRootURL(c, policyID), b, &r.Body, nil) resp, err := c.Post(ruleRootURL(c, policyID), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -309,13 +314,15 @@ func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOpts
// GetRule retrieves a particular L7Policy Rule based on its unique ID. // GetRule retrieves a particular L7Policy Rule based on its unique ID.
func GetRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r GetRuleResult) { func GetRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r GetRuleResult) {
_, r.Err = c.Get(ruleResourceURL(c, policyID, ruleID), &r.Body, nil) resp, err := c.Get(ruleResourceURL(c, policyID, ruleID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// DeleteRule will remove a Rule from a particular L7Policy. // DeleteRule will remove a Rule from a particular L7Policy.
func DeleteRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r DeleteRuleResult) { func DeleteRule(c *gophercloud.ServiceClient, policyID string, ruleID string) (r DeleteRuleResult) {
_, r.Err = c.Delete(ruleResourceURL(c, policyID, ruleID), nil) resp, err := c.Delete(ruleResourceURL(c, policyID, ruleID), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -369,8 +376,9 @@ func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, op
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -154,13 +154,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular Listeners based on its unique ID. // Get retrieves a particular Listeners based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -235,20 +237,23 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateR
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular Listeners based on its unique ID. // Delete will permanently delete a particular Listeners based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// GetStats will return the shows the current statistics of a particular Listeners. // GetStats will return the shows the current statistics of a particular Listeners.
func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) {
_, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) resp, err := c.Get(statisticsRootURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -133,13 +133,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular Loadbalancer based on its unique ID. // Get retrieves a particular Loadbalancer based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -179,9 +181,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateR
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -216,26 +219,30 @@ func Delete(c *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r
} }
url += query url += query
} }
_, r.Err = c.Delete(url, nil) resp, err := c.Delete(url, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// GetStatuses will return the status of a particular LoadBalancer. // GetStatuses will return the status of a particular LoadBalancer.
func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) { func GetStatuses(c *gophercloud.ServiceClient, id string) (r GetStatusesResult) {
_, r.Err = c.Get(statusRootURL(c, id), &r.Body, nil) resp, err := c.Get(statusRootURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// GetStats will return the shows the current statistics of a particular LoadBalancer. // GetStats will return the shows the current statistics of a particular LoadBalancer.
func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) { func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) {
_, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil) resp, err := c.Get(statisticsRootURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Failover performs a failover of a load balancer. // Failover performs a failover of a load balancer.
func Failover(c *gophercloud.ServiceClient, id string) (r FailoverResult) { func Failover(c *gophercloud.ServiceClient, id string) (r FailoverResult) {
_, r.Err = c.Put(failoverRootURL(c, id), nil, nil, &gophercloud.RequestOpts{ resp, err := c.Put(failoverRootURL(c, id), nil, nil, &gophercloud.RequestOpts{
OkCodes: []int{202}, OkCodes: []int{202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -166,13 +166,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular Health Monitor based on its unique ID. // Get retrieves a particular Health Monitor based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -235,14 +237,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 202}, OkCodes: []int{200, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular Monitor based on its unique ID. // Delete will permanently delete a particular Monitor based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -130,13 +130,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular pool based on its unique ID. // Get retrieves a particular pool based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -177,15 +179,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular pool based on its unique ID. // Delete will permanently delete a particular pool based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -298,13 +302,15 @@ func CreateMember(c *gophercloud.ServiceClient, poolID string, opts CreateMember
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(memberRootURL(c, poolID), b, &r.Body, nil) resp, err := c.Post(memberRootURL(c, poolID), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// GetMember retrieves a particular Pool Member based on its unique ID. // GetMember retrieves a particular Pool Member based on its unique ID.
func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r GetMemberResult) { func GetMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r GetMemberResult) {
_, r.Err = c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil) resp, err := c.Get(memberResourceURL(c, poolID, memberID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -343,9 +349,10 @@ func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string,
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(memberResourceURL(c, poolID, memberID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202}, OkCodes: []int{200, 201, 202},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -413,13 +420,15 @@ func BatchUpdateMembers(c *gophercloud.ServiceClient, poolID string, opts []Batc
b := map[string]interface{}{"members": members} b := map[string]interface{}{"members": members}
_, r.Err = c.Put(memberRootURL(c, poolID), b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}}) resp, err := c.Put(memberRootURL(c, poolID), b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// DisassociateMember will remove and disassociate a Member from a particular // DisassociateMember will remove and disassociate a Member from a particular
// Pool. // Pool.
func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) { func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) {
_, r.Err = c.Delete(memberResourceURL(c, poolID, memberID), nil) resp, err := c.Delete(memberResourceURL(c, poolID, memberID), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -29,53 +29,59 @@ func ReplaceAll(client *gophercloud.ServiceClient, resourceType string, resource
r.Err = err r.Err = err
return return
} }
_, r.Err = client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Put(url, &b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// List all tags on a resource // List all tags on a resource
func List(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r ListResult) { func List(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r ListResult) {
url := listURL(client, resourceType, resourceID) url := listURL(client, resourceType, resourceID)
_, r.Err = client.Get(url, &r.Body, &gophercloud.RequestOpts{ resp, err := client.Get(url, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// DeleteAll deletes all tags on a resource // DeleteAll deletes all tags on a resource
func DeleteAll(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r DeleteResult) { func DeleteAll(client *gophercloud.ServiceClient, resourceType string, resourceID string) (r DeleteResult) {
url := deleteAllURL(client, resourceType, resourceID) url := deleteAllURL(client, resourceType, resourceID)
_, r.Err = client.Delete(url, &gophercloud.RequestOpts{ resp, err := client.Delete(url, &gophercloud.RequestOpts{
OkCodes: []int{204}, OkCodes: []int{204},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Add a tag on a resource // Add a tag on a resource
func Add(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r AddResult) { func Add(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r AddResult) {
url := addURL(client, resourceType, resourceID, tag) url := addURL(client, resourceType, resourceID, tag)
_, r.Err = client.Put(url, nil, nil, &gophercloud.RequestOpts{ resp, err := client.Put(url, nil, nil, &gophercloud.RequestOpts{
OkCodes: []int{201}, OkCodes: []int{201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete a tag on a resource // Delete a tag on a resource
func Delete(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r DeleteResult) {
url := deleteURL(client, resourceType, resourceID, tag) url := deleteURL(client, resourceType, resourceID, tag)
_, r.Err = client.Delete(url, &gophercloud.RequestOpts{ resp, err := client.Delete(url, &gophercloud.RequestOpts{
OkCodes: []int{204}, OkCodes: []int{204},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Confirm if a tag exists on a resource // Confirm if a tag exists on a resource
func Confirm(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r ConfirmResult) { func Confirm(client *gophercloud.ServiceClient, resourceType string, resourceID string, tag string) (r ConfirmResult) {
url := confirmURL(client, resourceType, resourceID, tag) url := confirmURL(client, resourceType, resourceID, tag)
_, r.Err = client.Get(url, nil, &gophercloud.RequestOpts{ resp, err := client.Get(url, nil, &gophercloud.RequestOpts{
OkCodes: []int{204}, OkCodes: []int{204},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -116,13 +116,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular floating IP resource based on its unique ID. // Get retrieves a particular floating IP resource based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -167,9 +169,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -177,6 +180,7 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
// ensure this is what you want - you can also disassociate the IP from existing // ensure this is what you want - you can also disassociate the IP from existing
// internal ports. // internal ports.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -84,13 +84,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular router based on its unique ID. // Get retrieves a particular router based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -126,15 +128,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular router based on its unique ID. // Delete will permanently delete a particular router based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -182,9 +186,10 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOpts
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(addInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -226,8 +231,9 @@ func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfa
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(removeInterfaceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -76,7 +76,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -109,58 +110,24 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
return return
} }
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200}, OkCodes: []int{200},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular security group based on its unique ID. // Get retrieves a particular security group based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular security group based on its // Delete will permanently delete a particular security group based on its
// unique ID. // unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convenience function that returns a security group's ID,
// given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractGroups(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"}
}
}

View File

@ -141,19 +141,22 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil) resp, err := c.Post(rootURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Get retrieves a particular security group rule based on its unique ID. // Get retrieves a particular security group rule based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil) resp, err := c.Get(resourceURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete will permanently delete a particular security group rule based on its // Delete will permanently delete a particular security group rule based on its
// unique ID. // unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil) resp, err := c.Delete(resourceURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }

View File

@ -60,7 +60,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
// Get retrieves a specific network based on its unique ID. // Get retrieves a specific network based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(getURL(c, id), &r.Body, nil) resp, err := c.Get(getURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -99,7 +100,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(createURL(c), b, &r.Body, nil) resp, err := c.Post(createURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -130,51 +132,16 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(updateURL(c, networkID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete accepts a unique ID and deletes the network associated with it. // Delete accepts a unique ID and deletes the network associated with it.
func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, networkID string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, networkID), nil) resp, err := c.Delete(deleteURL(c, networkID), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convenience function that returns a network's ID, given
// its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractNetworks(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "network"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "network"}
}
}

View File

@ -97,7 +97,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
// Get retrieves a specific port based on its unique ID. // Get retrieves a specific port based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(getURL(c, id), &r.Body, nil) resp, err := c.Get(getURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -136,7 +137,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(createURL(c), b, &r.Body, nil) resp, err := c.Post(createURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -171,51 +173,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete accepts a unique ID and deletes the port associated with it. // Delete accepts a unique ID and deletes the port associated with it.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, id), nil) resp, err := c.Delete(deleteURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convenience function that returns a port's ID,
// given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractPorts(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "port"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "port"}
}
}

View File

@ -69,7 +69,8 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
// Get retrieves a specific subnet based on its unique ID. // Get retrieves a specific subnet based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) { func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(getURL(c, id), &r.Body, nil) resp, err := c.Get(getURL(c, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -160,7 +161,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Post(createURL(c), b, &r.Body, nil) resp, err := c.Post(createURL(c), b, &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -219,51 +221,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err r.Err = err
return return
} }
_, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ resp, err := c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201}, OkCodes: []int{200, 201},
}) })
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete accepts a unique ID and deletes the subnet associated with it. // Delete accepts a unique ID and deletes the subnet associated with it.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, id), nil) resp, err := c.Delete(deleteURL(c, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// IDFromName is a convenience function that returns a subnet's ID,
// given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
listOpts := ListOpts{
Name: name,
}
pages, err := List(client, listOpts).AllPages()
if err != nil {
return "", err
}
all, err := ExtractSubnets(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "subnet"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "subnet"}
}
}

View File

@ -39,10 +39,7 @@ func Get(c *gophercloud.ServiceClient, opts GetOptsBuilder) (r GetResult) {
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{204}, OkCodes: []int{204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -92,9 +89,6 @@ func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) (r UpdateResul
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{201, 202, 204}, OkCodes: []int{201, 202, 204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }

View File

@ -2,7 +2,6 @@ package accounts
import ( import (
"encoding/json" "encoding/json"
"strconv"
"strings" "strings"
"time" "time"
@ -17,7 +16,7 @@ type UpdateResult struct {
// UpdateHeader represents the headers returned in the response from an Update // UpdateHeader represents the headers returned in the response from an Update
// request. // request.
type UpdateHeader struct { type UpdateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
@ -27,8 +26,7 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -37,16 +35,6 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
*r = UpdateHeader(s.tmp) *r = UpdateHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return err return err
@ -55,18 +43,18 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
// Extract will return a struct of headers returned from a call to Get. To // Extract will return a struct of headers returned from a call to Get. To
// obtain a map of headers, call the Extract method on the GetResult. // obtain a map of headers, call the Extract method on the GetResult.
func (r UpdateResult) Extract() (*UpdateHeader, error) { func (r UpdateResult) Extract() (*UpdateHeader, error) {
var s *UpdateHeader var s UpdateHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// GetHeader represents the headers returned in the response from a Get request. // GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct { type GetHeader struct {
BytesUsed int64 `json:"-"` BytesUsed int64 `json:"X-Account-Bytes-Used,string"`
QuotaBytes *int64 `json:"-"` QuotaBytes *int64 `json:"X-Account-Meta-Quota-Bytes,string"`
ContainerCount int64 `json:"-"` ContainerCount int64 `json:"X-Account-Container-Count,string"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ObjectCount int64 `json:"-"` ObjectCount int64 `json:"X-Account-Object-Count,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"` TempURLKey string `json:"X-Account-Meta-Temp-URL-Key"`
@ -78,12 +66,7 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var s struct { var s struct {
tmp tmp
BytesUsed string `json:"X-Account-Bytes-Used"` Date string `json:"Date"`
QuotaBytes string `json:"X-Account-Meta-Quota-Bytes"`
ContentLength string `json:"Content-Length"`
ContainerCount string `json:"X-Account-Container-Count"`
ObjectCount string `json:"X-Account-Object-Count"`
Date string `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -92,57 +75,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
*r = GetHeader(s.tmp) *r = GetHeader(s.tmp)
switch s.BytesUsed {
case "":
r.BytesUsed = 0
default:
r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64)
if err != nil {
return err
}
}
switch s.QuotaBytes {
case "":
r.QuotaBytes = nil
default:
v, err := strconv.ParseInt(s.QuotaBytes, 10, 64)
if err != nil {
return err
}
r.QuotaBytes = &v
}
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
switch s.ObjectCount {
case "":
r.ObjectCount = 0
default:
r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64)
if err != nil {
return err
}
}
switch s.ContainerCount {
case "":
r.ContainerCount = 0
default:
r.ContainerCount, err = strconv.ParseInt(s.ContainerCount, 10, 64)
if err != nil {
return err
}
}
if s.Date != "" { if s.Date != "" {
r.Date, err = time.Parse(time.RFC1123, s.Date) r.Date, err = time.Parse(time.RFC1123, s.Date)
} }
@ -157,9 +89,9 @@ type GetResult struct {
// Extract will return a struct of headers returned from a call to Get. // Extract will return a struct of headers returned from a call to Get.
func (r GetResult) Extract() (*GetHeader, error) { func (r GetResult) Extract() (*GetHeader, error) {
var s *GetHeader var s GetHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // ExtractMetadata is a function that takes a GetResult (of type *http.Response)

View File

@ -1,6 +1,9 @@
package containers package containers
import ( import (
"net/url"
"strings"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -104,21 +107,39 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB
h[k] = v h[k] = v
} }
} }
resp, err := c.Request("PUT", createURL(c, containerName), &gophercloud.RequestOpts{ resp, err := c.Request("PUT", createURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{201, 202, 204}, OkCodes: []int{201, 202, 204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header return
resp.Body.Close() }
// BulkDelete is a function that bulk deletes containers.
func BulkDelete(c *gophercloud.ServiceClient, containers []string) (r BulkDeleteResult) {
// urlencode container names to be on the safe side
// https://github.com/openstack/swift/blob/stable/train/swift/common/middleware/bulk.py#L160
// https://github.com/openstack/swift/blob/stable/train/swift/common/swob.py#L302
encodedContainers := make([]string, len(containers))
for i, v := range containers {
encodedContainers[i] = url.QueryEscape(v)
} }
r.Err = err b := strings.NewReader(strings.Join(encodedContainers, "\n") + "\n")
resp, err := c.Post(bulkDeleteURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{
"Accept": "application/json",
"Content-Type": "text/plain",
},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
// Delete is a function that deletes a container. // Delete is a function that deletes a container.
func Delete(c *gophercloud.ServiceClient, containerName string) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, containerName string) (r DeleteResult) {
_, r.Err = c.Delete(deleteURL(c, containerName), nil) resp, err := c.Delete(deleteURL(c, url.QueryEscape(containerName)), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return return
} }
@ -180,14 +201,11 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB
h[k] = v h[k] = v
} }
} }
resp, err := c.Request("POST", updateURL(c, containerName), &gophercloud.RequestOpts{ resp, err := c.Request("POST", updateURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{201, 202, 204}, OkCodes: []int{201, 202, 204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -223,13 +241,10 @@ func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder
h[k] = v h[k] = v
} }
} }
resp, err := c.Head(getURL(c, containerName), &gophercloud.RequestOpts{ resp, err := c.Head(getURL(c, url.QueryEscape(containerName)), &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{200, 204}, OkCodes: []int{200, 204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }

View File

@ -3,7 +3,6 @@ package containers
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"strings" "strings"
"time" "time"
@ -92,11 +91,11 @@ func ExtractNames(page pagination.Page) ([]string, error) {
// GetHeader represents the headers returned in the response from a Get request. // GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct { type GetHeader struct {
AcceptRanges string `json:"Accept-Ranges"` AcceptRanges string `json:"Accept-Ranges"`
BytesUsed int64 `json:"-"` BytesUsed int64 `json:"X-Container-Bytes-Used,string"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
ObjectCount int64 `json:"-"` ObjectCount int64 `json:"X-Container-Object-Count,string"`
Read []string `json:"-"` Read []string `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
VersionsLocation string `json:"X-Versions-Location"` VersionsLocation string `json:"X-Versions-Location"`
@ -111,12 +110,9 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var s struct { var s struct {
tmp tmp
BytesUsed string `json:"X-Container-Bytes-Used"` Write string `json:"X-Container-Write"`
ContentLength string `json:"Content-Length"` Read string `json:"X-Container-Read"`
ObjectCount string `json:"X-Container-Object-Count"` Date gophercloud.JSONRFC1123 `json:"Date"`
Write string `json:"X-Container-Write"`
Read string `json:"X-Container-Read"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -125,36 +121,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
*r = GetHeader(s.tmp) *r = GetHeader(s.tmp)
switch s.BytesUsed {
case "":
r.BytesUsed = 0
default:
r.BytesUsed, err = strconv.ParseInt(s.BytesUsed, 10, 64)
if err != nil {
return err
}
}
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
switch s.ObjectCount {
case "":
r.ObjectCount = 0
default:
r.ObjectCount, err = strconv.ParseInt(s.ObjectCount, 10, 64)
if err != nil {
return err
}
}
r.Read = strings.Split(s.Read, ",") r.Read = strings.Split(s.Read, ",")
r.Write = strings.Split(s.Write, ",") r.Write = strings.Split(s.Write, ",")
@ -170,9 +136,9 @@ type GetResult struct {
// Extract will return a struct of headers returned from a call to Get. // Extract will return a struct of headers returned from a call to Get.
func (r GetResult) Extract() (*GetHeader, error) { func (r GetResult) Extract() (*GetHeader, error) {
var s *GetHeader var s GetHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@ -194,7 +160,7 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) {
// CreateHeader represents the headers returned in the response from a Create // CreateHeader represents the headers returned in the response from a Create
// request. // request.
type CreateHeader struct { type CreateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -204,8 +170,7 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader type tmp CreateHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -214,16 +179,6 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
*r = CreateHeader(s.tmp) *r = CreateHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return err return err
@ -238,15 +193,15 @@ type CreateResult struct {
// Extract will return a struct of headers returned from a call to Create. // Extract will return a struct of headers returned from a call to Create.
// To extract the headers from the HTTP response, call its Extract method. // To extract the headers from the HTTP response, call its Extract method.
func (r CreateResult) Extract() (*CreateHeader, error) { func (r CreateResult) Extract() (*CreateHeader, error) {
var s *CreateHeader var s CreateHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// UpdateHeader represents the headers returned in the response from a Update // UpdateHeader represents the headers returned in the response from a Update
// request. // request.
type UpdateHeader struct { type UpdateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -256,8 +211,7 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -266,16 +220,6 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
*r = UpdateHeader(s.tmp) *r = UpdateHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return err return err
@ -289,15 +233,15 @@ type UpdateResult struct {
// Extract will return a struct of headers returned from a call to Update. // Extract will return a struct of headers returned from a call to Update.
func (r UpdateResult) Extract() (*UpdateHeader, error) { func (r UpdateResult) Extract() (*UpdateHeader, error) {
var s *UpdateHeader var s UpdateHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// DeleteHeader represents the headers returned in the response from a Delete // DeleteHeader represents the headers returned in the response from a Delete
// request. // request.
type DeleteHeader struct { type DeleteHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -307,8 +251,7 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader type tmp DeleteHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -317,30 +260,42 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
*r = DeleteHeader(s.tmp) *r = DeleteHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return err return err
} }
// DeleteResult represents the result of a delete operation. To extract the // DeleteResult represents the result of a delete operation. To extract the
// the headers from the HTTP response, call its Extract method. // headers from the HTTP response, call its Extract method.
type DeleteResult struct { type DeleteResult struct {
gophercloud.HeaderResult gophercloud.HeaderResult
} }
// Extract will return a struct of headers returned from a call to Delete. // Extract will return a struct of headers returned from a call to Delete.
func (r DeleteResult) Extract() (*DeleteHeader, error) { func (r DeleteResult) Extract() (*DeleteHeader, error) {
var s *DeleteHeader var s DeleteHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
}
type BulkDeleteResponse struct {
ResponseStatus string `json:"Response Status"`
ResponseBody string `json:"Response Body"`
Errors [][]string `json:"Errors"`
NumberDeleted int `json:"Number Deleted"`
NumberNotFound int `json:"Number Not Found"`
}
// BulkDeleteResult represents the result of a bulk delete operation. To extract
// the response object from the HTTP response, call its Extract method.
type BulkDeleteResult struct {
gophercloud.Result
}
// Extract will return a BulkDeleteResponse struct returned from a BulkDelete
// call.
func (r BulkDeleteResult) Extract() (*BulkDeleteResponse, error) {
var s BulkDeleteResponse
err := r.ExtractInto(&s)
return &s, err
} }

View File

@ -21,3 +21,7 @@ func deleteURL(c *gophercloud.ServiceClient, container string) string {
func updateURL(c *gophercloud.ServiceClient, container string) string { func updateURL(c *gophercloud.ServiceClient, container string) string {
return createURL(c, container) return createURL(c, container)
} }
func bulkDeleteURL(c *gophercloud.ServiceClient) string {
return c.Endpoint + "?bulk-delete=true"
}

View File

@ -98,6 +98,10 @@ Example to Download an Object's Data
containerName := "my_container" containerName := "my_container"
object := objects.Download(objectStorageClient, containerName, objectName, nil) object := objects.Download(objectStorageClient, containerName, objectName, nil)
if object.Err != nil {
panic(object.Err)
}
// if "ExtractContent" method is not called, the HTTP connection will remain consumed
content, err := object.ExtractContent() content, err := object.ExtractContent()
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/url"
"strings" "strings"
"time" "time"
@ -53,7 +54,7 @@ func (opts ListOpts) ToObjectListParams() (bool, string, error) {
func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager { func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"} headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
url := listURL(c, containerName) url := listURL(c, url.QueryEscape(containerName))
if opts != nil { if opts != nil {
full, query, err := opts.ToObjectListParams() full, query, err := opts.ToObjectListParams()
if err != nil { if err != nil {
@ -112,7 +113,7 @@ func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, er
// To extract just the content, pass the DownloadResult response to the // To extract just the content, pass the DownloadResult response to the
// ExtractContent function. // ExtractContent function.
func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) { func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) {
url := downloadURL(c, containerName, objectName) url := downloadURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
h := make(map[string]string) h := make(map[string]string)
if opts != nil { if opts != nil {
headers, query, err := opts.ToObjectDownloadParams() headers, query, err := opts.ToObjectDownloadParams()
@ -127,14 +128,11 @@ func Download(c *gophercloud.ServiceClient, containerName, objectName string, op
} }
resp, err := c.Get(url, nil, &gophercloud.RequestOpts{ resp, err := c.Get(url, nil, &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{200, 206, 304}, OkCodes: []int{200, 206, 304},
KeepResponseBody: true,
}) })
if resp != nil { r.Body, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
r.Body = resp.Body
}
r.Err = err
return return
} }
@ -221,7 +219,7 @@ func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, str
// checksum, the failed request will automatically be retried up to a maximum // checksum, the failed request will automatically be retried up to a maximum
// of 3 times. // of 3 times.
func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) { func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) {
url := createURL(c, containerName, objectName) url := createURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
h := make(map[string]string) h := make(map[string]string)
var b io.Reader var b io.Reader
if opts != nil { if opts != nil {
@ -237,14 +235,10 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts
b = tmpB b = tmpB
} }
resp, err := c.Put(url, nil, nil, &gophercloud.RequestOpts{ resp, err := c.Put(url, b, nil, &gophercloud.RequestOpts{
RawBody: b,
MoreHeaders: h, MoreHeaders: h,
}) })
r.Err = err _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
if resp != nil {
r.Header = resp.Header
}
return return
} }
@ -289,15 +283,12 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C
h[k] = v h[k] = v
} }
url := copyURL(c, containerName, objectName) url := copyURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{ resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{201}, OkCodes: []int{201},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -320,7 +311,7 @@ func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
// Delete is a function that deletes an object. // Delete is a function that deletes an object.
func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) { func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) {
url := deleteURL(c, containerName, objectName) url := deleteURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
if opts != nil { if opts != nil {
query, err := opts.ToObjectDeleteQuery() query, err := opts.ToObjectDeleteQuery()
if err != nil { if err != nil {
@ -330,10 +321,7 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts
url += query url += query
} }
resp, err := c.Delete(url, nil) resp, err := c.Delete(url, nil)
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -368,7 +356,7 @@ func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) {
// the custom metadata, pass the GetResult response to the ExtractMetadata // the custom metadata, pass the GetResult response to the ExtractMetadata
// function. // function.
func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) { func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) {
url := getURL(c, containerName, objectName) url := getURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
h := make(map[string]string) h := make(map[string]string)
if opts != nil { if opts != nil {
headers, query, err := opts.ToObjectGetParams() headers, query, err := opts.ToObjectGetParams()
@ -386,10 +374,7 @@ func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts Ge
MoreHeaders: h, MoreHeaders: h,
OkCodes: []int{200, 204}, OkCodes: []int{200, 204},
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -437,14 +422,11 @@ func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts
h[k] = v h[k] = v
} }
} }
url := updateURL(c, containerName, objectName) url := updateURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{ resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{
MoreHeaders: h, MoreHeaders: h,
}) })
if resp != nil { _, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
r.Header = resp.Header
}
r.Err = err
return return
} }
@ -483,7 +465,7 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
} }
duration := time.Duration(opts.TTL) * time.Second duration := time.Duration(opts.TTL) * time.Second
expiry := time.Now().Add(duration).Unix() expiry := time.Now().Add(duration).Unix()
getHeader, err := containers.Get(c, containerName, nil).Extract() getHeader, err := containers.Get(c, url.QueryEscape(containerName), nil).Extract()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -497,7 +479,7 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
tempURLKey = getHeader.TempURLKey tempURLKey = getHeader.TempURLKey
} }
secretKey := []byte(tempURLKey) secretKey := []byte(tempURLKey)
url := getURL(c, containerName, objectName) url := getURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
splitPath := strings.Split(url, opts.Split) splitPath := strings.Split(url, opts.Split)
baseURL, objectPath := splitPath[0], splitPath[1] baseURL, objectPath := splitPath[0], splitPath[1]
objectPath = opts.Split + objectPath objectPath = opts.Split + objectPath
@ -507,3 +489,27 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
hexsum := fmt.Sprintf("%x", hash.Sum(nil)) hexsum := fmt.Sprintf("%x", hash.Sum(nil))
return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil
} }
// BulkDelete is a function that bulk deletes objects.
func BulkDelete(c *gophercloud.ServiceClient, container string, objects []string) (r BulkDeleteResult) {
// urlencode object names to be on the safe side
// https://github.com/openstack/swift/blob/stable/train/swift/common/middleware/bulk.py#L160
// https://github.com/openstack/swift/blob/stable/train/swift/common/swob.py#L302
encodedObjects := make([]string, len(objects))
for i, v := range objects {
encodedObjects[i] = strings.Join([]string{
url.QueryEscape(container),
url.QueryEscape(v)},
"/")
}
b := strings.NewReader(strings.Join(encodedObjects, "\n") + "\n")
resp, err := c.Post(bulkDeleteURL(c), b, &r.Body, &gophercloud.RequestOpts{
MoreHeaders: map[string]string{
"Accept": "application/json",
"Content-Type": "text/plain",
},
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -6,7 +6,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@ -134,7 +133,7 @@ type DownloadHeader struct {
AcceptRanges string `json:"Accept-Ranges"` AcceptRanges string `json:"Accept-Ranges"`
ContentDisposition string `json:"Content-Disposition"` ContentDisposition string `json:"Content-Disposition"`
ContentEncoding string `json:"Content-Encoding"` ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
DeleteAt time.Time `json:"-"` DeleteAt time.Time `json:"-"`
@ -149,7 +148,6 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
type tmp DownloadHeader type tmp DownloadHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -162,16 +160,6 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
*r = DownloadHeader(s.tmp) *r = DownloadHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
switch t := s.StaticLargeObject.(type) { switch t := s.StaticLargeObject.(type) {
case string: case string:
if t == "True" || t == "true" { if t == "True" || t == "true" {
@ -197,9 +185,9 @@ type DownloadResult struct {
// Extract will return a struct of headers returned from a call to Download. // Extract will return a struct of headers returned from a call to Download.
func (r DownloadResult) Extract() (*DownloadHeader, error) { func (r DownloadResult) Extract() (*DownloadHeader, error) {
var s *DownloadHeader var s DownloadHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// ExtractContent is a function that takes a DownloadResult's io.Reader body // ExtractContent is a function that takes a DownloadResult's io.Reader body
@ -216,7 +204,6 @@ func (r *DownloadResult) ExtractContent() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.Body.Close()
return body, nil return body, nil
} }
@ -224,7 +211,7 @@ func (r *DownloadResult) ExtractContent() ([]byte, error) {
type GetHeader struct { type GetHeader struct {
ContentDisposition string `json:"Content-Disposition"` ContentDisposition string `json:"Content-Disposition"`
ContentEncoding string `json:"Content-Encoding"` ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
DeleteAt time.Time `json:"-"` DeleteAt time.Time `json:"-"`
@ -239,7 +226,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader type tmp GetHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"` DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -252,16 +238,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
*r = GetHeader(s.tmp) *r = GetHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
switch t := s.StaticLargeObject.(type) { switch t := s.StaticLargeObject.(type) {
case string: case string:
if t == "True" || t == "true" { if t == "True" || t == "true" {
@ -286,9 +262,9 @@ type GetResult struct {
// Extract will return a struct of headers returned from a call to Get. // Extract will return a struct of headers returned from a call to Get.
func (r GetResult) Extract() (*GetHeader, error) { func (r GetResult) Extract() (*GetHeader, error) {
var s *GetHeader var s GetHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// ExtractMetadata is a function that takes a GetResult (of type *http.Response) // ExtractMetadata is a function that takes a GetResult (of type *http.Response)
@ -310,7 +286,7 @@ func (r GetResult) ExtractMetadata() (map[string]string, error) {
// CreateHeader represents the headers returned in the response from a // CreateHeader represents the headers returned in the response from a
// Create request. // Create request.
type CreateHeader struct { type CreateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
ETag string `json:"Etag"` ETag string `json:"Etag"`
@ -322,9 +298,8 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader type tmp CreateHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"` LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -333,16 +308,6 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
*r = CreateHeader(s.tmp) *r = CreateHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
r.LastModified = time.Time(s.LastModified) r.LastModified = time.Time(s.LastModified)
@ -360,15 +325,15 @@ func (r CreateResult) Extract() (*CreateHeader, error) {
//if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) { //if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) {
// return nil, ErrWrongChecksum{} // return nil, ErrWrongChecksum{}
//} //}
var s *CreateHeader var s CreateHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// UpdateHeader represents the headers returned in the response from a // UpdateHeader represents the headers returned in the response from a
// Update request. // Update request.
type UpdateHeader struct { type UpdateHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -378,8 +343,7 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader type tmp UpdateHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -388,16 +352,6 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
*r = UpdateHeader(s.tmp) *r = UpdateHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return nil return nil
@ -410,15 +364,15 @@ type UpdateResult struct {
// Extract will return a struct of headers returned from a call to Update. // Extract will return a struct of headers returned from a call to Update.
func (r UpdateResult) Extract() (*UpdateHeader, error) { func (r UpdateResult) Extract() (*UpdateHeader, error) {
var s *UpdateHeader var s UpdateHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// DeleteHeader represents the headers returned in the response from a // DeleteHeader represents the headers returned in the response from a
// Delete request. // Delete request.
type DeleteHeader struct { type DeleteHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
Date time.Time `json:"-"` Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"` TransID string `json:"X-Trans-Id"`
@ -428,8 +382,7 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader type tmp DeleteHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"` Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
} }
err := json.Unmarshal(b, &s) err := json.Unmarshal(b, &s)
if err != nil { if err != nil {
@ -438,16 +391,6 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
*r = DeleteHeader(s.tmp) *r = DeleteHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
return nil return nil
@ -460,15 +403,15 @@ type DeleteResult struct {
// Extract will return a struct of headers returned from a call to Delete. // Extract will return a struct of headers returned from a call to Delete.
func (r DeleteResult) Extract() (*DeleteHeader, error) { func (r DeleteResult) Extract() (*DeleteHeader, error) {
var s *DeleteHeader var s DeleteHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
} }
// CopyHeader represents the headers returned in the response from a // CopyHeader represents the headers returned in the response from a
// Copy request. // Copy request.
type CopyHeader struct { type CopyHeader struct {
ContentLength int64 `json:"-"` ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"` ContentType string `json:"Content-Type"`
CopiedFrom string `json:"X-Copied-From"` CopiedFrom string `json:"X-Copied-From"`
CopiedFromLastModified time.Time `json:"-"` CopiedFromLastModified time.Time `json:"-"`
@ -482,7 +425,6 @@ func (r *CopyHeader) UnmarshalJSON(b []byte) error {
type tmp CopyHeader type tmp CopyHeader
var s struct { var s struct {
tmp tmp
ContentLength string `json:"Content-Length"`
CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"` CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"`
Date gophercloud.JSONRFC1123 `json:"Date"` Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"` LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -494,16 +436,6 @@ func (r *CopyHeader) UnmarshalJSON(b []byte) error {
*r = CopyHeader(s.tmp) *r = CopyHeader(s.tmp)
switch s.ContentLength {
case "":
r.ContentLength = 0
default:
r.ContentLength, err = strconv.ParseInt(s.ContentLength, 10, 64)
if err != nil {
return err
}
}
r.Date = time.Time(s.Date) r.Date = time.Time(s.Date)
r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified) r.CopiedFromLastModified = time.Time(s.CopiedFromLastModified)
r.LastModified = time.Time(s.LastModified) r.LastModified = time.Time(s.LastModified)
@ -518,9 +450,31 @@ type CopyResult struct {
// Extract will return a struct of headers returned from a call to Copy. // Extract will return a struct of headers returned from a call to Copy.
func (r CopyResult) Extract() (*CopyHeader, error) { func (r CopyResult) Extract() (*CopyHeader, error) {
var s *CopyHeader var s CopyHeader
err := r.ExtractInto(&s) err := r.ExtractInto(&s)
return s, err return &s, err
}
type BulkDeleteResponse struct {
ResponseStatus string `json:"Response Status"`
ResponseBody string `json:"Response Body"`
Errors [][]string `json:"Errors"`
NumberDeleted int `json:"Number Deleted"`
NumberNotFound int `json:"Number Not Found"`
}
// BulkDeleteResult represents the result of a bulk delete operation. To extract
// the response object from the HTTP response, call its Extract method.
type BulkDeleteResult struct {
gophercloud.Result
}
// Extract will return a BulkDeleteResponse struct returned from a BulkDelete
// call.
func (r BulkDeleteResult) Extract() (*BulkDeleteResponse, error) {
var s BulkDeleteResponse
err := r.ExtractInto(&s)
return &s, err
} }
// extractLastMarker is a function that takes a page of objects and returns the // extractLastMarker is a function that takes a page of objects and returns the

View File

@ -31,3 +31,7 @@ func downloadURL(c *gophercloud.ServiceClient, container, object string) string
func updateURL(c *gophercloud.ServiceClient, container, object string) string { func updateURL(c *gophercloud.ServiceClient, container, object string) string {
return copyURL(c, container, object) return copyURL(c, container, object)
} }
func bulkDeleteURL(c *gophercloud.ServiceClient) string {
return c.Endpoint + "?bulk-delete=true"
}

View File

@ -54,7 +54,8 @@ func PageResultFromParsed(resp *http.Response, body interface{}) PageResult {
// Request performs an HTTP request and extracts the http.Response from the result. // 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) { func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
return client.Get(url, nil, &gophercloud.RequestOpts{ return client.Get(url, nil, &gophercloud.RequestOpts{
MoreHeaders: headers, MoreHeaders: headers,
OkCodes: []int{200, 204, 300}, OkCodes: []int{200, 204, 300},
KeepResponseBody: true,
}) })
} }

View File

@ -305,6 +305,9 @@ type RequestOpts struct {
// ErrorContext specifies the resource error type to return if an error is encountered. // 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. // This lets resources override default error messages based on the response status code.
ErrorContext error ErrorContext error
// KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP
// response body is considered for further use. Valid when JSONResponse is nil.
KeepResponseBody bool
} }
// requestState contains temporary state for a single ProviderClient.Request() call. // requestState contains temporary state for a single ProviderClient.Request() call.
@ -346,6 +349,11 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
contentType = &applicationJSON contentType = &applicationJSON
} }
// Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil
if options.KeepResponseBody && options.JSONResponse != nil {
return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil")
}
if options.RawBody != nil { if options.RawBody != nil {
body = options.RawBody body = options.RawBody
} }
@ -384,9 +392,6 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
req.Header.Set(k, v) req.Header.Set(k, v)
} }
// Set connection parameter to close the connection immediately when we've got the response
req.Close = true
prereqtok := req.Header.Get("X-Auth-Token") prereqtok := req.Header.Get("X-Auth-Token")
// Issue the request. // Issue the request.
@ -414,11 +419,12 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
body, _ := ioutil.ReadAll(resp.Body) body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
respErr := ErrUnexpectedResponseCode{ respErr := ErrUnexpectedResponseCode{
URL: url, URL: url,
Method: method, Method: method,
Expected: options.OkCodes, Expected: options.OkCodes,
Actual: resp.StatusCode, Actual: resp.StatusCode,
Body: body, Body: body,
ResponseHeader: resp.Header,
} }
errType := options.ErrorContext errType := options.ErrorContext
@ -513,25 +519,40 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
// Parse the response body as JSON, if requested to do so. // Parse the response body as JSON, if requested to do so.
if options.JSONResponse != nil { if options.JSONResponse != nil {
defer resp.Body.Close() defer resp.Body.Close()
// Don't decode JSON when there is no content
if resp.StatusCode == http.StatusNoContent {
// read till EOF, otherwise the connection will be closed and cannot be reused
_, err = io.Copy(ioutil.Discard, resp.Body)
return resp, err
}
if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil { if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
return nil, err return nil, err
} }
} }
// Close unused body to allow the HTTP connection to be reused
if !options.KeepResponseBody && options.JSONResponse == nil {
defer resp.Body.Close()
// read till EOF, otherwise the connection will be closed and cannot be reused
if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
return nil, err
}
}
return resp, nil return resp, nil
} }
func defaultOkCodes(method string) []int { func defaultOkCodes(method string) []int {
switch { switch method {
case method == "GET": case "GET", "HEAD":
return []int{200} return []int{200}
case method == "POST": case "POST":
return []int{201, 202} return []int{201, 202}
case method == "PUT": case "PUT":
return []int{201, 202} return []int{201, 202}
case method == "PATCH": case "PATCH":
return []int{200, 202, 204} return []int{200, 202, 204}
case method == "DELETE": case "DELETE":
return []int{202, 204} return []int{202, 204}
} }

View File

@ -131,6 +131,18 @@ func (r Result) extractIntoPtr(to interface{}, label string) error {
// fields of the struct or composed extension struct // fields of the struct or composed extension struct
// at the end of this method. // at the end of this method.
toValue.Set(newSlice) toValue.Set(newSlice)
// jtopjian: This was put into place to resolve the issue
// described at
// https://github.com/gophercloud/gophercloud/issues/1963
//
// This probably isn't the best fix, but it appears to
// be resolving the issue, so I'm going to implement it
// for now.
//
// For future readers, this entire case statement could
// use a review.
return nil
} }
} }
case reflect.Struct: case reflect.Struct:

View File

@ -152,3 +152,11 @@ func (client *ServiceClient) Request(method, url string, options *RequestOpts) (
} }
return client.ProviderClient.Request(method, url, options) return client.ProviderClient.Request(method, url, options)
} }
// ParseResponse is a helper function to parse http.Response to constituents.
func ParseResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, error) {
if resp != nil {
return resp.Body, resp.Header, err
}
return nil, nil, err
}

7
vendor/modules.txt vendored
View File

@ -300,7 +300,7 @@ github.com/googleapis/gax-go/v2
github.com/googleapis/gnostic/OpenAPIv2 github.com/googleapis/gnostic/OpenAPIv2
github.com/googleapis/gnostic/compiler github.com/googleapis/gnostic/compiler
github.com/googleapis/gnostic/extensions github.com/googleapis/gnostic/extensions
# github.com/gophercloud/gophercloud v0.7.1-0.20200116011225-46fdd1830e9a => github.com/gophercloud/gophercloud v0.9.0 # github.com/gophercloud/gophercloud v0.11.0 => github.com/gophercloud/gophercloud v0.11.0
## explicit ## explicit
github.com/gophercloud/gophercloud github.com/gophercloud/gophercloud
github.com/gophercloud/gophercloud/internal github.com/gophercloud/gophercloud/internal
@ -314,12 +314,13 @@ github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhint
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups
github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach
github.com/gophercloud/gophercloud/openstack/compute/v2/flavors github.com/gophercloud/gophercloud/openstack/compute/v2/flavors
github.com/gophercloud/gophercloud/openstack/compute/v2/images
github.com/gophercloud/gophercloud/openstack/compute/v2/servers github.com/gophercloud/gophercloud/openstack/compute/v2/servers
github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets
github.com/gophercloud/gophercloud/openstack/dns/v2/zones github.com/gophercloud/gophercloud/openstack/dns/v2/zones
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens github.com/gophercloud/gophercloud/openstack/identity/v2/tokens
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/ec2tokens
github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/oauth1
github.com/gophercloud/gophercloud/openstack/identity/v3/tokens github.com/gophercloud/gophercloud/openstack/identity/v3/tokens
github.com/gophercloud/gophercloud/openstack/imageservice/v2/images github.com/gophercloud/gophercloud/openstack/imageservice/v2/images
github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/apiversions
@ -1363,4 +1364,4 @@ sigs.k8s.io/yaml
# k8s.io/cli-runtime => k8s.io/cli-runtime v0.18.6 # k8s.io/cli-runtime => k8s.io/cli-runtime v0.18.6
# k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.18.6 # k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.18.6
# k8s.io/code-generator => k8s.io/code-generator v0.18.6 # k8s.io/code-generator => k8s.io/code-generator v0.18.6
# github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.9.0 # github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.11.0