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 github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.9.0
replace github.com/gophercloud/gophercloud => github.com/gophercloud/gophercloud v0.11.0
require (
cloud.google.com/go v0.38.0
@ -76,7 +76,7 @@ require (
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.4.2 // indirect
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/hashicorp/hcl v1.0.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.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
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.9.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/gophercloud v0.11.0 h1:pYMP9UZBdQa3lsfIZ1tZor4EbtxiuB6BHhocenkiH/E=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=

View File

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

View File

@ -20,6 +20,8 @@ import (
"fmt"
"time"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/mitchellh/mapstructure"
"k8s.io/apimachinery/pkg/util/wait"
@ -189,3 +191,29 @@ func listInstances(c OpenstackCloud, opt servers.ListOptsBuilder) ([]servers.Ser
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 (
"fmt"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud"
cinder "github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
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)
}
func (c *MockCloud) GetFlavor(name string) (*flavors.Flavor, error) {
return getFlavor(c, name)
}
func (c *MockCloud) GetInstance(id string) (*servers.Server, error) {
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/servergroups: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/loadbalancers:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",

View File

@ -20,8 +20,6 @@ import (
"fmt"
"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/floatingips"
"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)
imageName := fi.StringValue(e.Image)
imageRef, err := images.IDFromName(cloud.ImageClient(), imageName)
image, err := cloud.GetImage(imageName)
if err != nil {
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{
Name: fi.StringValue(e.Name),
ImageRef: imageRef,
FlavorName: fi.StringValue(e.Flavor),
Name: fi.StringValue(e.Name),
ImageRef: image.ID,
FlavorRef: flavor.ID,
Networks: []servers.Network{
{
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)

View File

@ -45,6 +45,9 @@ type AuthOptions struct {
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
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"-"`
@ -98,6 +101,7 @@ type AuthScope struct {
ProjectName string
DomainID string
DomainName string
System bool
}
// 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
}
// 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) {
type domainReq struct {
ID *string `json:"id,omitempty"`
@ -148,7 +154,8 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
type userReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Password string `json:"password,omitempty"`
Password *string `json:"password,omitempty"`
Passcode *string `json:"passcode,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"`
}
type totpReq struct {
User *userReq `json:"user,omitempty"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
TOTP *totpReq `json:"totp,omitempty"`
}
type authReq struct {
@ -186,7 +198,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
// if insufficient or incompatible information is present.
var req request
if opts.Password == "" {
if opts.Password == "" && opts.Passcode == "" {
if opts.TokenID != "" {
// Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
// parameters.
@ -274,7 +286,14 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
}
} else {
// 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.
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.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{ID: &opts.DomainID},
},
if opts.Password != "" {
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
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 != "" {
// Configure the request for Username and Password authentication with a DomainName.
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
Password: opts.Password,
Domain: &domainReq{Name: &opts.DomainName},
},
if opts.Password != "" {
req.Auth.Identity.Password = &passwordReq{
User: userReq{
Name: &opts.Username,
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.
req.Auth.Identity.Password = &passwordReq{
User: userReq{ID: &opts.UserID, Password: opts.Password},
if 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
}
// ToTokenV3ScopeMap builds a scope from AuthOptions and satisfies interface in
// the v3 tokens package.
func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
// For backwards compatibility.
// 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 != "" {
// ProjectName provided: either DomainID or DomainName must also be supplied.
// ProjectID may not be supplied.
@ -433,5 +499,16 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
}
func (opts AuthOptions) CanReauth() bool {
if opts.Passcode != "" {
// cannot reauth using TOTP passcode
return false
}
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 (
"fmt"
"net/http"
"strings"
)
@ -77,11 +78,12 @@ func (e ErrMissingAnyoneOfEnvironmentVariables) Error() string {
// those listed in OkCodes is encountered.
type ErrUnexpectedResponseCode struct {
BaseError
URL string
Method string
Expected []int
Actual int
Body []byte
URL string
Method string
Expected []int
Actual int
Body []byte
ResponseHeader http.Header
}
func (e ErrUnexpectedResponseCode) Error() string {

View File

@ -15,6 +15,8 @@ go_library(
deps = [
"//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/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/utils:go_default_library",
],

View File

@ -38,6 +38,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
username := os.Getenv("OS_USERNAME")
userID := os.Getenv("OS_USERID")
password := os.Getenv("OS_PASSWORD")
passcode := os.Getenv("OS_PASSCODE")
tenantID := os.Getenv("OS_TENANT_ID")
tenantName := os.Getenv("OS_TENANT_NAME")
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{
// silently ignore TOTP passcode warning, since it is not a common auth method
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err
@ -112,6 +114,7 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
UserID: userID,
Username: username,
Password: password,
Passcode: passcode,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,

View File

@ -60,9 +60,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -96,14 +97,16 @@ func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder
}
url += query
}
_, r.Err = client.Delete(url, nil)
resp, err := client.Delete(url, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get retrieves the Volume with the provided ID. To extract the Volume object
// from the response, call the Extract method on the GetResult.
func Get(client *gophercloud.ServiceClient, id string) (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
}
@ -197,44 +200,9 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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"
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"
"github.com/gophercloud/gophercloud/openstack/utils"
)
@ -68,7 +70,7 @@ Example:
ao, err := openstack.AuthOptionsFromEnv()
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"),
})
*/
@ -224,7 +226,15 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return err
}
} 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)
if err != nil {
@ -255,6 +265,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
o := *ot
o.AllowReauth = false
tao = &o
case *ec2tokens.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
case *oauth1.AuthOptions:
o := *ot
o.AllowReauth = false
tao = &o
default:
tao = opts
}

View File

@ -125,8 +125,9 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) (
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -36,21 +36,24 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get returns data about a previously created Floating IP.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete requests the deletion of a previous allocated Floating IP.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -81,7 +84,8 @@ func AssociateInstance(client *gophercloud.ServiceClient, serverID string, opts
r.Err = err
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
}
@ -109,6 +113,7 @@ func DisassociateInstance(client *gophercloud.ServiceClient, serverID string, op
r.Err = err
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
}

View File

@ -67,20 +67,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get returns public data about a previously uploaded KeyPair.
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
}
// Delete requests the deletion of a previous stored KeyPair from the server.
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
}

View File

@ -48,20 +48,23 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get returns data about a previously created ServerGroup.
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
}
// Delete requests the deletion of a previously allocated ServerGroup.
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
}

View File

@ -40,21 +40,24 @@ func Create(client *gophercloud.ServiceClient, serverID string, opts CreateOptsB
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get returns public data about a previously created VolumeAttachment.
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
}
// Delete requests the deletion of a previous stored VolumeAttachment from
// the server.
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
}

View File

@ -142,22 +142,25 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get retrieves details of a single flavor. Use Extract to convert its
// result into a Flavor.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete deletes the specified flavor ID.
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
}
@ -194,9 +197,10 @@ func AddAccess(client *gophercloud.ServiceClient, id string, opts AddAccessOptsB
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -224,20 +228,23 @@ func RemoveAccess(client *gophercloud.ServiceClient, id string, opts RemoveAcces
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// ExtraSpecs requests all the extra-specs for the given flavor ID.
func ListExtraSpecs(client *gophercloud.ServiceClient, flavorID string) (r ListExtraSpecsResult) {
_, r.Err = client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
resp, err := client.Get(extraSpecsListURL(client, flavorID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
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
}
@ -264,9 +271,10 @@ func CreateExtraSpecs(client *gophercloud.ServiceClient, flavorID string, opts C
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -302,56 +310,19 @@ func UpdateExtraSpec(client *gophercloud.ServiceClient, flavorID string, opts Up
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// DeleteExtraSpec will delete the key-value pair with the given key for the given
// flavor ID.
func DeleteExtraSpec(client *gophercloud.ServiceClient, flavorID, key string) (r DeleteExtraSpecResult) {
_, r.Err = client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
resp, err := client.Delete(extraSpecDeleteURL(client, flavorID, key), &gophercloud.RequestOpts{
OkCodes: []int{200},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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"],
deps = [
"//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",
],
)

View File

@ -6,8 +6,6 @@ import (
"fmt"
"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"
)
@ -148,24 +146,14 @@ type CreateOpts struct {
// Name is the name to assign to the newly launched server.
Name string `json:"name" required:"true"`
// ImageRef [optional; required if ImageName is not provided] is the ID or
// full URL to the image that contains the server's OS and initial state.
// ImageRef is the ID or full URL to the image that contains the
// server's OS and initial state.
// Also optional if using the boot-from-volume extension.
ImageRef string `json:"imageRef"`
// ImageName [optional; required if ImageRef is not provided] is the name of
// the image that contains the server's OS and initial state.
// 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 is the ID or full URL to the flavor that describes the server's specs.
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
// should belong.
SecurityGroups []string `json:"-"`
@ -223,7 +211,6 @@ type CreateOpts struct {
// ToServerCreateMap assembles a request body based on the contents of a
// CreateOpts.
func (opts CreateOpts) ToServerCreateMap() (map[string]interface{}, error) {
sc := opts.ServiceClient
opts.ServiceClient = nil
b, err := gophercloud.BuildRequestBody(opts, "")
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 {
b["min_count"] = opts.Min
}
@ -328,28 +279,32 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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
}
// Delete requests that a server previously provisioned be removed from your
// account.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// ForceDelete forces the deletion of a server.
func ForceDelete(client *gophercloud.ServiceClient, id string) (r ActionResult) {
_, r.Err = client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
resp, err := client.Post(actionURL(client, id), map[string]interface{}{"forceDelete": ""}, nil, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get requests details on a single server, by ID.
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -386,9 +341,10 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -400,7 +356,8 @@ func ChangeAdminPassword(client *gophercloud.ServiceClient, id, newPassword stri
"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
}
@ -441,7 +398,7 @@ func (opts RebootOpts) ToServerRebootMap() (map[string]interface{}, error) {
HardReboot (aka PowerCycle) starts the server instance by physically cutting
power to the machine, or if a VM, terminating it at the hypervisor level.
It's done. Caput. Full stop.
Then, after a brief while, power is rtored or the VM instance restarted.
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
procedure.
@ -454,7 +411,8 @@ func Reboot(client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder
r.Err = err
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
}
@ -470,11 +428,8 @@ type RebuildOpts struct {
// AdminPass is the server's admin password
AdminPass string `json:"adminPass,omitempty"`
// ImageID is the ID of the image you want your server to be provisioned on.
ImageID string `json:"imageRef"`
// ImageName is readable name of an image.
ImageName string `json:"-"`
// ImageRef is the ID of the image you want your server to be provisioned on.
ImageRef string `json:"imageRef"`
// Name to set the server to
Name string `json:"name,omitempty"`
@ -505,23 +460,6 @@ func (opts RebuildOpts) ToServerRebuildMap() (map[string]interface{}, error) {
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
}
@ -533,7 +471,8 @@ func Rebuild(client *gophercloud.ServiceClient, id string, opts RebuildOptsBuild
r.Err = err
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
}
@ -571,23 +510,26 @@ func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder
r.Err = err
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
}
// ConfirmResize confirms a previous resize operation on a server.
// See Resize() for more details.
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// RevertResize cancels a previous resize operation on a server.
// See Resize() for more details.
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
}
@ -623,15 +565,17 @@ func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetad
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Metadata requests all the metadata for the given server ID.
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
}
@ -650,9 +594,10 @@ func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMet
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -690,23 +635,26 @@ func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts Metadatu
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Metadatum requests the key-value pair with the given key for the given
// server ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
_, r.Err = client.Get(metadatumURL(client, id, key), &r.Body, nil)
resp, err := client.Get(metadatumURL(client, id, key), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// DeleteMetadatum will delete the key-value pair with the given key for the
// given server ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
_, r.Err = client.Delete(metadatumURL(client, id, key), nil)
resp, err := client.Delete(metadatumURL(client, id, key), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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{
OkCodes: []int{202},
})
r.Err = err
r.Header = resp.Header
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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
// administrative password.
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
}
@ -833,8 +744,9 @@ func ShowConsoleOutput(client *gophercloud.ServiceClient, id string, opts ShowCo
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -57,7 +57,8 @@ func ListByZone(client *gophercloud.ServiceClient, zoneID string, opts ListOptsB
// Get implements the recordset Get request.
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
}
@ -103,9 +104,10 @@ func Create(client *gophercloud.ServiceClient, zoneID string, opts CreateOptsBui
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -158,16 +160,18 @@ func Update(client *gophercloud.ServiceClient, zoneID string, rrsetID string, op
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete removes an existing RecordSet.
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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.
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
}
@ -110,9 +111,10 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -158,17 +160,19 @@ func Update(client *gophercloud.ServiceClient, zoneID string, opts UpdateOptsBui
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete implements a zone delete request.
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},
JSONResponse: &r.Body,
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -7,7 +7,7 @@ Example of Creating a Service Client
ao, err := openstack.AuthOptionsFromEnv()
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"),
})
*/

View File

@ -60,15 +60,17 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get requests details on a single tenant by ID.
func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
resp, err := client.Get(getURL(client, id), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -103,14 +105,16 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete is the operation responsible for permanently deleting a tenant.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
resp, err := client.Delete(deleteURL(client, id), nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -87,17 +87,19 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) (r Creat
r.Err = err
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},
MoreHeaders: map[string]string{"X-Auth-Token": ""},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get validates and retrieves information for user's token.
func Get(client *gophercloud.ServiceClient, token string) (r GetResult) {
_, r.Err = client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
resp, err := client.Get(GetURL(client, token), &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 203},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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",
"urls.go",
],
importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/openstack/compute/v2/images",
importpath = "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/identity/v3/extensions/oauth1",
visibility = ["//visibility:public"],
deps = [
"//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",
],
)

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
DomainID string
DomainName string
System bool
}
// 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
// if parameters are missing or inconsistent.
ToTokenV3CreateMap(map[string]interface{}) (map[string]interface{}, error)
ToTokenV3HeadersMap(map[string]interface{}) (map[string]string, error)
ToTokenV3ScopeMap() (map[string]interface{}, error)
CanReauth() bool
}
@ -36,6 +38,9 @@ type AuthOptions struct {
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
// with Identity V3. Otherwise, either are optional.
DomainID string `json:"-"`
@ -67,6 +72,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
Passcode: opts.Passcode,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
@ -79,7 +85,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
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) {
scope := gophercloud.AuthScope(opts.Scope)
@ -93,10 +99,21 @@ func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
}
func (opts *AuthOptions) CanReauth() bool {
if opts.Passcode != "" {
// cannot reauth using TOTP passcode
return false
}
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{
"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{
MoreHeaders: map[string]string{"X-Auth-Token": ""},
})
r.Err = err
if resp != nil {
r.Header = resp.Header
}
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get validates and retrieves information about another token.
func Get(c *gophercloud.ServiceClient, token string) (r GetResult) {
resp, err := c.Get(tokenURL(c), &r.Body, &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
MoreHeaders: subjectTokenHeaders(token),
OkCodes: []int{200, 203},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Validate determines if a specified token is valid or not.
func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
resp, err := c.Head(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
MoreHeaders: subjectTokenHeaders(token),
OkCodes: []int{200, 204, 404},
})
if err != nil {
@ -155,8 +166,9 @@ func Validate(c *gophercloud.ServiceClient, token string) (bool, error) {
// Revoke immediately makes specified token invalid.
func Revoke(c *gophercloud.ServiceClient, token string) (r RevokeResult) {
_, r.Err = c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(c, token),
resp, err := c.Delete(tokenURL(c), &gophercloud.RequestOpts{
MoreHeaders: subjectTokenHeaders(token),
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -206,19 +206,22 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
r.Err = err
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
}
// Delete implements image delete request.
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
}
// Get implements image get request.
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
}
@ -229,10 +232,11 @@ func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder
r.Err = err
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},
MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -378,39 +382,3 @@ func (r UpdateImageProperty) ToImagePatchMap() map[string]interface{} {
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"
"fmt"
"reflect"
"strings"
"time"
"github.com/gophercloud/gophercloud"
@ -86,13 +87,22 @@ type Image struct {
// VirtualSize is the virtual size of the image
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 {
type tmp Image
var s struct {
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)
if err != nil {
@ -120,9 +130,18 @@ func (r *Image) UnmarshalJSON(b []byte) error {
if resultMap, ok := result.(map[string]interface{}); ok {
delete(resultMap, "self")
delete(resultMap, "size")
delete(resultMap, "openstack-image-import-methods")
delete(resultMap, "openstack-image-store-ids")
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
}
@ -133,6 +152,20 @@ type commonResult struct {
// Extract interprets any commonResult as an Image.
func (r commonResult) Extract() (*Image, error) {
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)
return s, err
}
@ -200,3 +233,8 @@ func ExtractImages(r pagination.Page) ([]Image, error) {
err := (r.(ImagePage)).ExtractInto(&s)
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
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
}
@ -137,13 +138,15 @@ func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
// Get retrieves a particular l7policy based on its unique ID.
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
}
// Delete will permanently delete a particular l7policy based on its unique ID.
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
}
@ -208,9 +211,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -254,7 +258,8 @@ func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOp
r.Err = err
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
}
@ -309,13 +314,15 @@ func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOpts
// GetRule retrieves a particular L7Policy Rule based on its unique ID.
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
}
// DeleteRule will remove a Rule from a particular L7Policy.
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
}
@ -369,8 +376,9 @@ func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, op
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -154,13 +154,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular Listeners based on its unique ID.
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
}
@ -235,20 +237,23 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateR
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete will permanently delete a particular Listeners based on its unique ID.
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
}
// GetStats will return the shows the current statistics of a particular Listeners.
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
}

View File

@ -133,13 +133,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular Loadbalancer based on its unique ID.
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
}
@ -179,9 +181,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) (r UpdateR
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -216,26 +219,30 @@ func Delete(c *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r
}
url += query
}
_, r.Err = c.Delete(url, nil)
resp, err := c.Delete(url, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// GetStatuses will return the status of a particular LoadBalancer.
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
}
// GetStats will return the shows the current statistics of a particular LoadBalancer.
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
}
// Failover performs a failover of a load balancer.
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -166,13 +166,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular Health Monitor based on its unique ID.
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
}
@ -235,14 +237,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete will permanently delete a particular Monitor based on its unique ID.
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
}

View File

@ -130,13 +130,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular pool based on its unique ID.
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
}
@ -177,15 +179,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete will permanently delete a particular pool based on its unique ID.
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
}
@ -298,13 +302,15 @@ func CreateMember(c *gophercloud.ServiceClient, poolID string, opts CreateMember
r.Err = err
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
}
// GetMember retrieves a particular Pool Member based on its unique ID.
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
}
@ -343,9 +349,10 @@ func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string,
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -413,13 +420,15 @@ func BatchUpdateMembers(c *gophercloud.ServiceClient, poolID string, opts []Batc
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
}
// DisassociateMember will remove and disassociate a Member from a particular
// Pool.
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
}

View File

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

View File

@ -116,13 +116,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular floating IP resource based on its unique ID.
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
}
@ -167,9 +169,10 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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
// internal ports.
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
}

View File

@ -84,13 +84,15 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
// Get retrieves a particular router based on its unique ID.
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
}
@ -126,15 +128,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete will permanently delete a particular router based on its unique ID.
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
}
@ -182,9 +186,10 @@ func AddInterface(c *gophercloud.ServiceClient, id string, opts AddInterfaceOpts
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -226,8 +231,9 @@ func RemoveInterface(c *gophercloud.ServiceClient, id string, opts RemoveInterfa
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -76,7 +76,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
@ -109,58 +110,24 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Get retrieves a particular security group based on its unique ID.
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
}
// Delete will permanently delete a particular security group based on its
// unique ID.
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
}
// 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
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
}
// Get retrieves a particular security group rule based on its unique ID.
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
}
// Delete will permanently delete a particular security group rule based on its
// unique ID.
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
}

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.
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
}
@ -99,7 +100,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
@ -130,51 +132,16 @@ func Update(c *gophercloud.ServiceClient, networkID string, opts UpdateOptsBuild
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete accepts a unique ID and deletes the network associated with it.
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
}
// 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.
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
}
@ -136,7 +137,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
@ -171,51 +173,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete accepts a unique ID and deletes the port associated with it.
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
}
// 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.
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
}
@ -160,7 +161,8 @@ func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResul
r.Err = err
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
}
@ -219,51 +221,16 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r
r.Err = err
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},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// Delete accepts a unique ID and deletes the subnet associated with it.
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
}
// 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,
OkCodes: []int{204},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -92,9 +89,6 @@ func Update(c *gophercloud.ServiceClient, opts UpdateOptsBuilder) (r UpdateResul
MoreHeaders: h,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

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

View File

@ -1,6 +1,9 @@
package containers
import (
"net/url"
"strings"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
@ -104,21 +107,39 @@ func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOptsB
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,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
r.Header = resp.Header
resp.Body.Close()
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
// 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
}
// Delete is a function that deletes a container.
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
}
@ -180,14 +201,11 @@ func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOptsB
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,
OkCodes: []int{201, 202, 204},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -223,13 +241,10 @@ func Get(c *gophercloud.ServiceClient, containerName string, opts GetOptsBuilder
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,
OkCodes: []int{200, 204},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

View File

@ -3,7 +3,6 @@ package containers
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
@ -92,11 +91,11 @@ func ExtractNames(page pagination.Page) ([]string, error) {
// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
AcceptRanges string `json:"Accept-Ranges"`
BytesUsed int64 `json:"-"`
ContentLength int64 `json:"-"`
BytesUsed int64 `json:"X-Container-Bytes-Used,string"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
ObjectCount int64 `json:"-"`
ObjectCount int64 `json:"X-Container-Object-Count,string"`
Read []string `json:"-"`
TransID string `json:"X-Trans-Id"`
VersionsLocation string `json:"X-Versions-Location"`
@ -111,12 +110,9 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader
var s struct {
tmp
BytesUsed string `json:"X-Container-Bytes-Used"`
ContentLength string `json:"Content-Length"`
ObjectCount string `json:"X-Container-Object-Count"`
Write string `json:"X-Container-Write"`
Read string `json:"X-Container-Read"`
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)
if err != nil {
@ -125,36 +121,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
*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.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.
func (r GetResult) Extract() (*GetHeader, error) {
var s *GetHeader
var s GetHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// 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
// request.
type CreateHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"`
@ -204,8 +170,7 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -214,16 +179,6 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
*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)
return err
@ -238,15 +193,15 @@ type CreateResult struct {
// 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.
func (r CreateResult) Extract() (*CreateHeader, error) {
var s *CreateHeader
var s CreateHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// UpdateHeader represents the headers returned in the response from a Update
// request.
type UpdateHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"`
@ -256,8 +211,7 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -266,16 +220,6 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
*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)
return err
@ -289,15 +233,15 @@ type UpdateResult struct {
// Extract will return a struct of headers returned from a call to Update.
func (r UpdateResult) Extract() (*UpdateHeader, error) {
var s *UpdateHeader
var s UpdateHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// DeleteHeader represents the headers returned in the response from a Delete
// request.
type DeleteHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"`
@ -307,8 +251,7 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -317,30 +260,42 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
*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)
return err
}
// 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 {
gophercloud.HeaderResult
}
// Extract will return a struct of headers returned from a call to Delete.
func (r DeleteResult) Extract() (*DeleteHeader, error) {
var s *DeleteHeader
var s DeleteHeader
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 {
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"
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()
if err != nil {
panic(err)

View File

@ -8,6 +8,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/url"
"strings"
"time"
@ -53,7 +54,7 @@ func (opts ListOpts) ToObjectListParams() (bool, string, error) {
func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
url := listURL(c, containerName)
url := listURL(c, url.QueryEscape(containerName))
if opts != nil {
full, query, err := opts.ToObjectListParams()
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
// ExtractContent function.
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)
if opts != nil {
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{
MoreHeaders: h,
OkCodes: []int{200, 206, 304},
MoreHeaders: h,
OkCodes: []int{200, 206, 304},
KeepResponseBody: true,
})
if resp != nil {
r.Header = resp.Header
r.Body = resp.Body
}
r.Err = err
r.Body, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
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
// of 3 times.
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)
var b io.Reader
if opts != nil {
@ -237,14 +235,10 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts
b = tmpB
}
resp, err := c.Put(url, nil, nil, &gophercloud.RequestOpts{
RawBody: b,
resp, err := c.Put(url, b, nil, &gophercloud.RequestOpts{
MoreHeaders: h,
})
r.Err = err
if resp != nil {
r.Header = resp.Header
}
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -289,15 +283,12 @@ func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts C
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{
MoreHeaders: h,
OkCodes: []int{201},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -320,7 +311,7 @@ func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
// Delete is a function that deletes an object.
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 {
query, err := opts.ToObjectDeleteQuery()
if err != nil {
@ -330,10 +321,7 @@ func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts
url += query
}
resp, err := c.Delete(url, nil)
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -368,7 +356,7 @@ func (opts GetOpts) ToObjectGetParams() (map[string]string, string, error) {
// the custom metadata, pass the GetResult response to the ExtractMetadata
// function.
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)
if opts != nil {
headers, query, err := opts.ToObjectGetParams()
@ -386,10 +374,7 @@ func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts Ge
MoreHeaders: h,
OkCodes: []int{200, 204},
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -437,14 +422,11 @@ func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts
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{
MoreHeaders: h,
})
if resp != nil {
r.Header = resp.Header
}
r.Err = err
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}
@ -483,7 +465,7 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
}
duration := time.Duration(opts.TTL) * time.Second
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 {
return "", err
}
@ -497,7 +479,7 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
tempURLKey = getHeader.TempURLKey
}
secretKey := []byte(tempURLKey)
url := getURL(c, containerName, objectName)
url := getURL(c, url.QueryEscape(containerName), url.QueryEscape(objectName))
splitPath := strings.Split(url, opts.Split)
baseURL, objectPath := splitPath[0], splitPath[1]
objectPath = opts.Split + objectPath
@ -507,3 +489,27 @@ func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName strin
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
}
// 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/ioutil"
"net/url"
"strconv"
"strings"
"time"
@ -134,7 +133,7 @@ type DownloadHeader struct {
AcceptRanges string `json:"Accept-Ranges"`
ContentDisposition string `json:"Content-Disposition"`
ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
DeleteAt time.Time `json:"-"`
@ -149,7 +148,6 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
type tmp DownloadHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -162,16 +160,6 @@ func (r *DownloadHeader) UnmarshalJSON(b []byte) error {
*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) {
case string:
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.
func (r DownloadResult) Extract() (*DownloadHeader, error) {
var s *DownloadHeader
var s DownloadHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// 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 {
return nil, err
}
r.Body.Close()
return body, nil
}
@ -224,7 +211,7 @@ func (r *DownloadResult) ExtractContent() ([]byte, error) {
type GetHeader struct {
ContentDisposition string `json:"Content-Disposition"`
ContentEncoding string `json:"Content-Encoding"`
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
DeleteAt time.Time `json:"-"`
@ -239,7 +226,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
type tmp GetHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
DeleteAt gophercloud.JSONUnix `json:"X-Delete-At"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -252,16 +238,6 @@ func (r *GetHeader) UnmarshalJSON(b []byte) error {
*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) {
case string:
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.
func (r GetResult) Extract() (*GetHeader, error) {
var s *GetHeader
var s GetHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// 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
// Create request.
type CreateHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
ETag string `json:"Etag"`
@ -322,9 +298,8 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
type tmp CreateHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -333,16 +308,6 @@ func (r *CreateHeader) UnmarshalJSON(b []byte) error {
*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.LastModified = time.Time(s.LastModified)
@ -360,15 +325,15 @@ func (r CreateResult) Extract() (*CreateHeader, error) {
//if r.Header.Get("ETag") != fmt.Sprintf("%x", localChecksum) {
// return nil, ErrWrongChecksum{}
//}
var s *CreateHeader
var s CreateHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// UpdateHeader represents the headers returned in the response from a
// Update request.
type UpdateHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"`
@ -378,8 +343,7 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
type tmp UpdateHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -388,16 +352,6 @@ func (r *UpdateHeader) UnmarshalJSON(b []byte) error {
*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)
return nil
@ -410,15 +364,15 @@ type UpdateResult struct {
// Extract will return a struct of headers returned from a call to Update.
func (r UpdateResult) Extract() (*UpdateHeader, error) {
var s *UpdateHeader
var s UpdateHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// DeleteHeader represents the headers returned in the response from a
// Delete request.
type DeleteHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
Date time.Time `json:"-"`
TransID string `json:"X-Trans-Id"`
@ -428,8 +382,7 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
type tmp DeleteHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
Date gophercloud.JSONRFC1123 `json:"Date"`
Date gophercloud.JSONRFC1123 `json:"Date"`
}
err := json.Unmarshal(b, &s)
if err != nil {
@ -438,16 +391,6 @@ func (r *DeleteHeader) UnmarshalJSON(b []byte) error {
*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)
return nil
@ -460,15 +403,15 @@ type DeleteResult struct {
// Extract will return a struct of headers returned from a call to Delete.
func (r DeleteResult) Extract() (*DeleteHeader, error) {
var s *DeleteHeader
var s DeleteHeader
err := r.ExtractInto(&s)
return s, err
return &s, err
}
// CopyHeader represents the headers returned in the response from a
// Copy request.
type CopyHeader struct {
ContentLength int64 `json:"-"`
ContentLength int64 `json:"Content-Length,string"`
ContentType string `json:"Content-Type"`
CopiedFrom string `json:"X-Copied-From"`
CopiedFromLastModified time.Time `json:"-"`
@ -482,7 +425,6 @@ func (r *CopyHeader) UnmarshalJSON(b []byte) error {
type tmp CopyHeader
var s struct {
tmp
ContentLength string `json:"Content-Length"`
CopiedFromLastModified gophercloud.JSONRFC1123 `json:"X-Copied-From-Last-Modified"`
Date gophercloud.JSONRFC1123 `json:"Date"`
LastModified gophercloud.JSONRFC1123 `json:"Last-Modified"`
@ -494,16 +436,6 @@ func (r *CopyHeader) UnmarshalJSON(b []byte) error {
*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.CopiedFromLastModified = time.Time(s.CopiedFromLastModified)
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.
func (r CopyResult) Extract() (*CopyHeader, error) {
var s *CopyHeader
var s CopyHeader
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

View File

@ -31,3 +31,7 @@ func downloadURL(c *gophercloud.ServiceClient, container, object string) string
func updateURL(c *gophercloud.ServiceClient, container, object string) string {
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.
func Request(client *gophercloud.ServiceClient, headers map[string]string, url string) (*http.Response, error) {
return client.Get(url, nil, &gophercloud.RequestOpts{
MoreHeaders: headers,
OkCodes: []int{200, 204, 300},
MoreHeaders: headers,
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.
// This lets resources override default error messages based on the response status code.
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.
@ -346,6 +349,11 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
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 {
body = options.RawBody
}
@ -384,9 +392,6 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
req.Header.Set(k, v)
}
// Set connection parameter to close the connection immediately when we've got the response
req.Close = true
prereqtok := req.Header.Get("X-Auth-Token")
// Issue the request.
@ -414,11 +419,12 @@ func (client *ProviderClient) doRequest(method, url string, options *RequestOpts
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
respErr := ErrUnexpectedResponseCode{
URL: url,
Method: method,
Expected: options.OkCodes,
Actual: resp.StatusCode,
Body: body,
URL: url,
Method: method,
Expected: options.OkCodes,
Actual: resp.StatusCode,
Body: body,
ResponseHeader: resp.Header,
}
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.
if options.JSONResponse != nil {
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 {
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
}
func defaultOkCodes(method string) []int {
switch {
case method == "GET":
switch method {
case "GET", "HEAD":
return []int{200}
case method == "POST":
case "POST":
return []int{201, 202}
case method == "PUT":
case "PUT":
return []int{201, 202}
case method == "PATCH":
case "PATCH":
return []int{200, 202, 204}
case method == "DELETE":
case "DELETE":
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
// at the end of this method.
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:

View File

@ -152,3 +152,11 @@ func (client *ServiceClient) Request(method, url string, options *RequestOpts) (
}
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/compiler
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
github.com/gophercloud/gophercloud
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/volumeattach
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/dns/v2/recordsets
github.com/gophercloud/gophercloud/openstack/dns/v2/zones
github.com/gophercloud/gophercloud/openstack/identity/v2/tenants
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/imageservice/v2/images
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/kube-controller-manager => k8s.io/kube-controller-manager 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