update gophercloud dependencies

This commit is contained in:
Jesse Haka 2019-02-22 15:59:32 +02:00
parent d44c7fed95
commit 278e660653
63 changed files with 1653 additions and 167 deletions

5
Gopkg.lock generated
View File

@ -616,7 +616,7 @@
revision = "0c5108395e2debce0d731cf0287ddf7242066aba"
[[projects]]
digest = "1:a59e42230741fb00ffd95d61a731b6ee13500ad18400c5e0dd651acd3d30e4ba"
digest = "1:33cfd57691761d62c8f84c2748e3ea2690234b4d8b622856539a5175b7fc3bfd"
name = "github.com/gophercloud/gophercloud"
packages = [
".",
@ -636,6 +636,7 @@
"openstack/identity/v2/tenants",
"openstack/identity/v2/tokens",
"openstack/identity/v3/tokens",
"openstack/loadbalancer/v2/l7policies",
"openstack/loadbalancer/v2/listeners",
"openstack/loadbalancer/v2/loadbalancers",
"openstack/loadbalancer/v2/monitors",
@ -655,7 +656,7 @@
"pagination",
]
pruneopts = "UT"
revision = "f29afc2cceca860199ee88cd355a4d0a37b3fad2"
revision = "dcc6e84aef1b6713accea7d7d7252ad2bc0e7034"
[[projects]]
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"

View File

@ -455,4 +455,4 @@ required = [
revision = "bf70f2a70fb1b1f36d90d671a72795984eab0fcb"
[[override]]
name = "github.com/gophercloud/gophercloud"
revision = "f29afc2cceca860199ee88cd355a4d0a37b3fad2"
revision = "dcc6e84aef1b6713accea7d7d7252ad2bc0e7034"

View File

@ -1,21 +1,24 @@
language: go
sudo: false
install:
- go get golang.org/x/crypto/ssh
- go get -v -tags 'fixtures acceptance' ./...
- go get github.com/wadey/gocovmerge
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/goimports
- GO111MODULE=off go get golang.org/x/crypto/ssh
- GO111MODULE=off go get -v -tags 'fixtures acceptance' ./...
- GO111MODULE=off go get github.com/wadey/gocovmerge
- GO111MODULE=off go get github.com/mattn/goveralls
- GO111MODULE=off go get golang.org/x/tools/cmd/goimports
go:
- "1.10"
- "1.11"
- "tip"
env:
global:
- secure: "xSQsAG5wlL9emjbCdxzz/hYQsSpJ/bABO1kkbwMSISVcJ3Nk0u4ywF+LS4bgeOnwPfmFvNTOqVDu3RwEvMeWXSI76t1piCPcObutb2faKLVD/hLoAS76gYX+Z8yGWGHrSB7Do5vTPj1ERe2UljdrnsSeOXzoDwFxYRaZLX4bBOB4AyoGvRniil5QXPATiA1tsWX1VMicj8a4F8X+xeESzjt1Q5Iy31e7vkptu71bhvXCaoo5QhYwT+pLR9dN0S1b7Ro0KVvkRefmr1lUOSYd2e74h6Lc34tC1h3uYZCS4h47t7v5cOXvMNxinEj2C51RvbjvZI1RLVdkuAEJD1Iz4+Ote46nXbZ//6XRZMZz/YxQ13l7ux1PFjgEB6HAapmF5Xd8PRsgeTU9LRJxpiTJ3P5QJ3leS1va8qnziM5kYipj/Rn+V8g2ad/rgkRox9LSiR9VYZD2Pe45YCb1mTKSl2aIJnV7nkOqsShY5LNB4JZSg7xIffA+9YVDktw8dJlATjZqt7WvJJ49g6A61mIUV4C15q2JPGKTkZzDiG81NtmS7hFa7k0yaE2ELgYocbcuyUcAahhxntYTC0i23nJmEHVNiZmBO3u7EgpWe4KGVfumU+lt12tIn5b3dZRBBUk3QakKKozSK1QPHGpk/AZGrhu7H6l8to6IICKWtDcyMPQ="
- GO111MODULE=on
before_script:
- go vet ./...
script:
- ./script/coverage
- ./script/unittest
- ./script/format
after_success:
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out

View File

@ -22,6 +22,15 @@
global_env:
OS_BRANCH: stable/queens
- job:
name: gophercloud-acceptance-test-rocky
parent: gophercloud-acceptance-test
description: |
Run gophercloud acceptance test on rocky branch
vars:
global_env:
OS_BRANCH: stable/rocky
- job:
name: gophercloud-acceptance-test-pike
parent: gophercloud-acceptance-test
@ -80,6 +89,9 @@
recheck-queens:
jobs:
- gophercloud-acceptance-test-queens
recheck-rocky:
jobs:
- gophercloud-acceptance-test-rocky
periodic:
jobs:
- gophercloud-unittest

View File

@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"auth_options.go",
"auth_result.go",
"doc.go",
"endpoint_search.go",
"errors.go",

View File

@ -84,6 +84,12 @@ type AuthOptions struct {
// Scope determines the scoping of the authentication request.
Scope *AuthScope `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
}
// AuthScope allows a created token to be limited to a specific domain or project.
@ -142,7 +148,7 @@ 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"`
Password string `json:"password,omitempty"`
Domain *domainReq `json:"domain,omitempty"`
}
@ -154,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
ID string `json:"id"`
}
type applicationCredentialReq struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
User *userReq `json:"user,omitempty"`
Secret *string `json:"secret,omitempty"`
}
type identityReq struct {
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
}
type authReq struct {
@ -194,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID,
}
} else if opts.ApplicationCredentialID != "" {
// Configure the request for ApplicationCredentialID authentication.
// https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
// There are three kinds of possible application_credential requests
// 1. application_credential id + secret
// 2. application_credential name + secret + user_id
// 3. application_credential name + secret + username + domain_id / domain_name
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
ID: &opts.ApplicationCredentialID,
Secret: &opts.ApplicationCredentialSecret,
}
} else if opts.ApplicationCredentialName != "" {
if opts.ApplicationCredentialSecret == "" {
return nil, ErrAppCredMissingSecret{}
}
var userRequest *userReq
if opts.UserID != "" {
// UserID could be used without the domain information
userRequest = &userReq{
ID: &opts.UserID,
}
}
if userRequest == nil && opts.Username == "" {
// Make sure that Username or UserID are provided
return nil, ErrUsernameOrUserID{}
}
if userRequest == nil && opts.DomainID != "" {
userRequest = &userReq{
Name: &opts.Username,
Domain: &domainReq{ID: &opts.DomainID},
}
}
if userRequest == nil && opts.DomainName != "" {
userRequest = &userReq{
Name: &opts.Username,
Domain: &domainReq{Name: &opts.DomainName},
}
}
// Make sure that DomainID or DomainName are provided among Username
if userRequest == nil {
return nil, ErrDomainIDOrDomainName{}
}
req.Auth.Identity.Methods = []string{"application_credential"}
req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
Name: &opts.ApplicationCredentialName,
User: userRequest,
Secret: &opts.ApplicationCredentialSecret,
}
} else {
// If no password or token ID are available, authentication can't continue.
// If no password or token ID or ApplicationCredential are available, authentication can't continue.
return nil, ErrMissingPassword{}
}
} else {

View File

@ -0,0 +1,52 @@
package gophercloud
/*
AuthResult is the result from the request that was used to obtain a provider
client's Keystone token. It is returned from ProviderClient.GetAuthResult().
The following types satisfy this interface:
github.com/gophercloud/gophercloud/openstack/identity/v2/tokens.CreateResult
github.com/gophercloud/gophercloud/openstack/identity/v3/tokens.CreateResult
Usage example:
import (
"github.com/gophercloud/gophercloud"
tokens2 "github.com/gophercloud/gophercloud/openstack/identity/v2/tokens"
tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
)
func GetAuthenticatedUserID(providerClient *gophercloud.ProviderClient) (string, error) {
r := providerClient.GetAuthResult()
if r == nil {
//ProviderClient did not use openstack.Authenticate(), e.g. because token
//was set manually with ProviderClient.SetToken()
return "", errors.New("no AuthResult available")
}
switch r := r.(type) {
case tokens2.CreateResult:
u, err := r.ExtractUser()
if err != nil {
return "", err
}
return u.ID, nil
case tokens3.CreateResult:
u, err := r.ExtractUser()
if err != nil {
return "", err
}
return u.ID, nil
default:
panic(fmt.Sprintf("got unexpected AuthResult type %t", r))
}
}
Both implementing types share a lot of methods by name, like ExtractUser() in
this example. But those methods cannot be part of the AuthResult interface
because the return types are different (in this case, type tokens2.User vs.
type tokens3.User).
*/
type AuthResult interface {
ExtractTokenID() (string, error)
}

View File

@ -41,7 +41,7 @@ pass in the parent provider, like so:
opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts)
client, err := openstack.NewComputeV2(provider, opts)
Resources

View File

@ -451,3 +451,10 @@ type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope"
}
// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name
type ErrAppCredMissingSecret struct{ BaseError }
func (e ErrAppCredMissingSecret) Error() string {
return "You must provide an Application Credential Secret"
}

7
vendor/github.com/gophercloud/gophercloud/go.mod generated vendored Normal file
View File

@ -0,0 +1,7 @@
module github.com/gophercloud/gophercloud
require (
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect
gopkg.in/yaml.v2 v2.2.2
)

8
vendor/github.com/gophercloud/gophercloud/go.sum generated vendored Normal file
View File

@ -0,0 +1,8 @@
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -38,6 +38,9 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME")
applicationCredentialID := os.Getenv("OS_APPLICATION_CREDENTIAL_ID")
applicationCredentialName := os.Getenv("OS_APPLICATION_CREDENTIAL_NAME")
applicationCredentialSecret := os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET")
// If OS_PROJECT_ID is set, overwrite tenantID with the value.
if v := os.Getenv("OS_PROJECT_ID"); v != "" {
@ -56,29 +59,55 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
return nilOptions, err
}
if username == "" && userID == "" {
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERNAME", "OS_USERID"},
if userID == "" && username == "" {
// Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set
if applicationCredentialID == "" && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
}
return nilOptions, err
}
return nilOptions, err
}
if password == "" {
if password == "" && applicationCredentialID == "" && applicationCredentialName == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD",
}
return nilOptions, err
}
if (applicationCredentialID != "" || applicationCredentialName != "") && applicationCredentialSecret == "" {
err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_APPLICATION_CREDENTIAL_SECRET",
}
return nilOptions, err
}
if applicationCredentialID == "" && applicationCredentialName != "" && applicationCredentialSecret != "" {
if userID == "" && username == "" {
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_USERID", "OS_USERNAME"},
}
}
if username != "" && domainID == "" && domainName == "" {
return nilOptions, gophercloud.ErrMissingAnyoneOfEnvironmentVariables{
EnvironmentVariables: []string{"OS_DOMAIN_ID", "OS_DOMAIN_NAME"},
}
}
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
IdentityEndpoint: authURL,
UserID: userID,
Username: username,
Password: password,
TenantID: tenantID,
TenantName: tenantName,
DomainID: domainID,
DomainName: domainName,
ApplicationCredentialID: applicationCredentialID,
ApplicationCredentialName: applicationCredentialName,
ApplicationCredentialSecret: applicationCredentialSecret,
}
return ao, nil

View File

@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return
}
// DeleteOptsBuilder allows extensions to add additional parameters to the
// Delete request.
type DeleteOptsBuilder interface {
ToVolumeDeleteQuery() (string, error)
}
// DeleteOpts contains options for deleting a Volume. This object is passed to
// the volumes.Delete function.
type DeleteOpts struct {
// Delete all snapshots of this volume as well.
Cascade bool `q:"cascade"`
}
// ToLoadBalancerDeleteQuery formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToVolumeDeleteQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil)
func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
url := deleteURL(client, id)
if opts != nil {
query, err := opts.ToVolumeDeleteQuery()
if err != nil {
r.Err = err
return
}
url += query
}
_, r.Err = client.Delete(url, nil)
return
}
@ -145,8 +173,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see
// the Volume object.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}

View File

@ -135,7 +135,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
result := tokens2.Create(v2Client, v2Opts)
token, err := result.ExtractToken()
err = client.SetTokenAndAuthResult(result)
if err != nil {
return err
}
@ -150,8 +150,9 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.SetThrowaway(true)
tac.ReauthFunc = nil
tac.TokenID = ""
tac.SetTokenAndAuthResult(nil)
tao := options
tao.AllowReauth = false
client.ReauthFunc = func() error {
@ -159,11 +160,10 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
if err != nil {
return err
}
client.TokenID = tac.TokenID
client.CopyTokenFrom(&tac)
return nil
}
}
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts)
}
@ -189,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
result := tokens3.Create(v3Client, opts)
token, err := result.ExtractToken()
err = client.SetTokenAndAuthResult(result)
if err != nil {
return err
}
@ -199,15 +199,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return err
}
client.TokenID = token.ID
if opts.CanReauth() {
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but
// with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once
tac := *client
tac.SetThrowaway(true)
tac.ReauthFunc = nil
tac.TokenID = ""
tac.SetTokenAndAuthResult(nil)
var tao tokens3.AuthOptionsBuilder
switch ot := opts.(type) {
case *gophercloud.AuthOptions:
@ -226,7 +225,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
if err != nil {
return err
}
client.TokenID = tac.TokenID
client.CopyTokenFrom(&tac)
return nil
}
}
@ -305,6 +304,12 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO
return sc, nil
}
// NewBareMetalV1 creates a ServiceClient that may be used with the v1
// bare metal package.
func NewBareMetalV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "baremetal")
}
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1
// object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
@ -420,3 +425,8 @@ func NewKeyManagerV1(client *gophercloud.ProviderClient, eo gophercloud.Endpoint
func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container-infra")
}
// NewWorkflowV2 creates a ServiceClient that may be used with the v2 workflow management package.
func NewWorkflowV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "workflowv2")
}

View File

@ -5,6 +5,7 @@ go_library(
srcs = [
"doc.go",
"errors.go",
"microversions.go",
"requests.go",
"results.go",
"urls.go",

View File

@ -0,0 +1,11 @@
package servers
// ExtractTags will extract the tags of a server.
// This requires the client to be set to microversion 2.26 or later.
func (r serverResult) ExtractTags() ([]string, error) {
var s struct {
Tags []string `json:"tags"`
}
err := r.ExtractInto(&s)
return s.Tags, err
}

View File

@ -189,6 +189,10 @@ type CreateOpts struct {
// ServiceClient will allow calls to be made to retrieve an image or
// flavor ID by name.
ServiceClient *gophercloud.ServiceClient `json:"-"`
// Tags allows a server to be tagged with single-word metadata.
// Requires microversion 2.52 or later.
Tags []string `json:"tags,omitempty"`
}
// ToServerCreateMap assembles a request body based on the contents of a

View File

@ -119,7 +119,7 @@ type UpdateOptsBuilder interface {
// RecordSet.
type UpdateOpts struct {
// Description is a description of the RecordSet.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// TTL is the time to live of the RecordSet.
TTL int `json:"ttl,omitempty"`

View File

@ -134,7 +134,7 @@ type UpdateOpts struct {
Masters []string `json:"masters,omitempty"`
// Description of the zone.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
}
// ToZoneUpdateMap formats an UpdateOpts structure into a request body.

View File

@ -85,7 +85,7 @@ type UpdateOpts struct {
Name string `json:"name,omitempty"`
// Description is the description of the tenant.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// Enabled sets the tenant status to enabled or disabled.
Enabled *bool `json:"enabled,omitempty"`

View File

@ -135,6 +135,21 @@ func (r CreateResult) ExtractToken() (*Token, error) {
}, nil
}
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
// string is the same as the ID field of the Token struct returned from
// ExtractToken().
func (r CreateResult) ExtractTokenID() (string, error) {
var s struct {
Access struct {
Token struct {
ID string `json:"id"`
} `json:"token"`
} `json:"access"`
}
err := r.ExtractInto(&s)
return s.Access.Token.ID, err
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {

View File

@ -52,19 +52,28 @@ type AuthOptions struct {
// authentication token ID.
TokenID string `json:"-"`
// Authentication through Application Credentials requires supplying name, project and secret
// For project we can use TenantID
ApplicationCredentialID string `json:"-"`
ApplicationCredentialName string `json:"-"`
ApplicationCredentialSecret string `json:"-"`
Scope Scope `json:"-"`
}
// ToTokenV3CreateMap builds a request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
gophercloudAuthOpts := gophercloud.AuthOptions{
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
Username: opts.Username,
UserID: opts.UserID,
Password: opts.Password,
DomainID: opts.DomainID,
DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID,
ApplicationCredentialID: opts.ApplicationCredentialID,
ApplicationCredentialName: opts.ApplicationCredentialName,
ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
}
return gophercloudAuthOpts.ToTokenV3CreateMap(scope)

View File

@ -102,6 +102,13 @@ func (r commonResult) ExtractToken() (*Token, error) {
return &s, err
}
// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
// string is the same as the ID field of the Token struct returned from
// ExtractToken().
func (r CreateResult) ExtractTokenID() (string, error) {
return r.Header.Get("X-Subject-Token"), r.Err
}
// ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token.
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) {

View File

@ -0,0 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"requests.go",
"results.go",
"urls.go",
],
importmap = "k8s.io/kops/vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies",
importpath = "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],
)

View File

@ -0,0 +1,123 @@
/*
Package l7policies provides information and interaction with L7Policies and
Rules of the LBaaS v2 extension for the OpenStack Networking service.
Example to Create a L7Policy
createOpts := l7policies.CreateOpts{
Name: "redirect-example.com",
ListenerID: "023f2e34-7806-443b-bfae-16c324569a3d",
Action: l7policies.ActionRedirectToURL,
RedirectURL: "http://www.example.com",
}
l7policy, err := l7policies.Create(lbClient, createOpts).Extract()
if err != nil {
panic(err)
}
Example to List L7Policies
listOpts := l7policies.ListOpts{
ListenerID: "c79a4468-d788-410c-bf79-9a8ef6354852",
}
allPages, err := l7policies.List(lbClient, listOpts).AllPages()
if err != nil {
panic(err)
}
allL7Policies, err := l7policies.ExtractL7Policies(allPages)
if err != nil {
panic(err)
}
for _, l7policy := range allL7Policies {
fmt.Printf("%+v\n", l7policy)
}
Example to Get a L7Policy
l7policy, err := l7policies.Get(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d").Extract()
if err != nil {
panic(err)
}
Example to Delete a L7Policy
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
err := l7policies.Delete(lbClient, l7policyID).ExtractErr()
if err != nil {
panic(err)
}
Example to Update a L7Policy
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
name := "new-name"
updateOpts := l7policies.UpdateOpts{
Name: &name,
}
l7policy, err := l7policies.Update(lbClient, l7policyID, updateOpts).Extract()
if err != nil {
panic(err)
}
Example to Create a Rule
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
createOpts := l7policies.CreateRuleOpts{
RuleType: l7policies.TypePath,
CompareType: l7policies.CompareTypeRegex,
Value: "/images*",
}
rule, err := l7policies.CreateRule(lbClient, l7policyID, createOpts).Extract()
if err != nil {
panic(err)
}
Example to List L7 Rules
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
listOpts := l7policies.ListRulesOpts{
RuleType: l7policies.TypePath,
}
allPages, err := l7policies.ListRules(lbClient, l7policyID, listOpts).AllPages()
if err != nil {
panic(err)
}
allRules, err := l7policies.ExtractRules(allPages)
if err != nil {
panic(err)
}
for _, rule := allRules {
fmt.Printf("%+v\n", rule)
}
Example to Get a l7 rule
l7rule, err := l7policies.GetRule(lbClient, "023f2e34-7806-443b-bfae-16c324569a3d", "53ad8ab8-40fa-11e8-a508-00224d6b7bc1").Extract()
if err != nil {
panic(err)
}
Example to Delete a l7 rule
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e"
err := l7policies.DeleteRule(lbClient, l7policyID, ruleID).ExtractErr()
if err != nil {
panic(err)
}
Example to Update a Rule
l7policyID := "d67d56a6-4a86-4688-a282-f46444705c64"
ruleID := "64dba99f-8af8-4200-8882-e32a0660f23e"
updateOpts := l7policies.UpdateRuleOpts{
RuleType: l7policies.TypePath,
CompareType: l7policies.CompareTypeRegex,
Value: "/images/special*",
}
rule, err := l7policies.UpdateRule(lbClient, l7policyID, ruleID, updateOpts).Extract()
if err != nil {
panic(err)
}
*/
package l7policies

View File

@ -0,0 +1,376 @@
package l7policies
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// CreateOptsBuilder allows extensions to add additional parameters to the
// Create request.
type CreateOptsBuilder interface {
ToL7PolicyCreateMap() (map[string]interface{}, error)
}
type Action string
type RuleType string
type CompareType string
const (
ActionRedirectToPool Action = "REDIRECT_TO_POOL"
ActionRedirectToURL Action = "REDIRECT_TO_URL"
ActionReject Action = "REJECT"
TypeCookie RuleType = "COOKIE"
TypeFileType RuleType = "FILE_TYPE"
TypeHeader RuleType = "HEADER"
TypeHostName RuleType = "HOST_NAME"
TypePath RuleType = "PATH"
CompareTypeContains CompareType = "CONTAINS"
CompareTypeEndWith CompareType = "ENDS_WITH"
CompareTypeEqual CompareType = "EQUAL_TO"
CompareTypeRegex CompareType = "REGEX"
CompareTypeStartWith CompareType = "STARTS_WITH"
)
// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
// Name of the L7 policy.
Name string `json:"name,omitempty"`
// The ID of the listener.
ListenerID string `json:"listener_id" required:"true"`
// The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT.
Action Action `json:"action" required:"true"`
// The position of this policy on the listener.
Position int32 `json:"position,omitempty"`
// A human-readable description for the resource.
Description string `json:"description,omitempty"`
// ProjectID is the UUID of the project who owns the L7 policy in octavia.
// Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id,omitempty"`
// Requests matching this policy will be redirected to the pool with this ID.
// Only valid if action is REDIRECT_TO_POOL.
RedirectPoolID string `json:"redirect_pool_id,omitempty"`
// Requests matching this policy will be redirected to this URL.
// Only valid if action is REDIRECT_TO_URL.
RedirectURL string `json:"redirect_url,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToL7PolicyCreateMap builds a request body from CreateOpts.
func (opts CreateOpts) ToL7PolicyCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "l7policy")
}
// Create accepts a CreateOpts struct and uses the values to create a new l7policy.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToL7PolicyCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
ToL7PolicyListQuery() (string, error)
}
// ListOpts allows the filtering and sorting of paginated collections through
// the API.
type ListOpts struct {
Name string `q:"name"`
Description string `q:"description"`
ListenerID string `q:"listener_id"`
Action string `q:"action"`
ProjectID string `q:"project_id"`
RedirectPoolID string `q:"redirect_pool_id"`
RedirectURL string `q:"redirect_url"`
Position int32 `q:"position"`
AdminStateUp bool `q:"admin_state_up"`
ID string `q:"id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// ToL7PolicyListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToL7PolicyListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// List returns a Pager which allows you to iterate over a collection of
// l7policies. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
//
// Default policy settings return only those l7policies that are owned by the
// project who submits the request, unless an admin user submits the request.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
url := rootURL(c)
if opts != nil {
query, err := opts.ToL7PolicyListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return L7PolicyPage{pagination.LinkedPageBase{PageResult: r}}
})
}
// 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)
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)
return
}
// UpdateOptsBuilder allows extensions to add additional parameters to the
// Update request.
type UpdateOptsBuilder interface {
ToL7PolicyUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts is the common options struct used in this package's Update
// operation.
type UpdateOpts struct {
// Name of the L7 policy, empty string is allowed.
Name *string `json:"name,omitempty"`
// The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT.
Action Action `json:"action,omitempty"`
// The position of this policy on the listener.
Position int32 `json:"position,omitempty"`
// A human-readable description for the resource, empty string is allowed.
Description *string `json:"description,omitempty"`
// Requests matching this policy will be redirected to the pool with this ID.
// Only valid if action is REDIRECT_TO_POOL.
RedirectPoolID *string `json:"redirect_pool_id,omitempty"`
// Requests matching this policy will be redirected to this URL.
// Only valid if action is REDIRECT_TO_URL.
RedirectURL *string `json:"redirect_url,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToL7PolicyUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToL7PolicyUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "l7policy")
if err != nil {
return nil, err
}
m := b["l7policy"].(map[string]interface{})
if m["redirect_pool_id"] == "" {
m["redirect_pool_id"] = nil
}
if m["redirect_url"] == "" {
m["redirect_url"] = nil
}
return b, nil
}
// Update allows l7policy to be updated.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToL7PolicyUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// CreateRuleOpts is the common options struct used in this package's CreateRule
// operation.
type CreateRuleOpts struct {
// The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH.
RuleType RuleType `json:"type" required:"true"`
// The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH.
CompareType CompareType `json:"compare_type" required:"true"`
// The value to use for the comparison. For example, the file type to compare.
Value string `json:"value" required:"true"`
// ProjectID is the UUID of the project who owns the rule in octavia.
// Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id,omitempty"`
// The key to use for the comparison. For example, the name of the cookie to evaluate.
Key string `json:"key,omitempty"`
// When true the logic of the rule is inverted. For example, with invert true,
// equal to would become not equal to. Default is false.
Invert bool `json:"invert,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToRuleCreateMap builds a request body from CreateRuleOpts.
func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "rule")
}
// CreateRule will create and associate a Rule with a particular L7Policy.
func CreateRule(c *gophercloud.ServiceClient, policyID string, opts CreateRuleOpts) (r CreateRuleResult) {
b, err := opts.ToRuleCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(ruleRootURL(c, policyID), b, &r.Body, nil)
return
}
// ListRulesOptsBuilder allows extensions to add additional parameters to the
// ListRules request.
type ListRulesOptsBuilder interface {
ToRulesListQuery() (string, error)
}
// ListRulesOpts allows the filtering and sorting of paginated collections
// through the API.
type ListRulesOpts struct {
RuleType RuleType `q:"type"`
ProjectID string `q:"project_id"`
CompareType CompareType `q:"compare_type"`
Value string `q:"value"`
Key string `q:"key"`
Invert bool `q:"invert"`
AdminStateUp bool `q:"admin_state_up"`
ID string `q:"id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// ToRulesListQuery formats a ListOpts into a query string.
func (opts ListRulesOpts) ToRulesListQuery() (string, error) {
q, err := gophercloud.BuildQueryString(opts)
return q.String(), err
}
// ListRules returns a Pager which allows you to iterate over a collection of
// rules. It accepts a ListRulesOptsBuilder, which allows you to filter and
// sort the returned collection for greater efficiency.
//
// Default policy settings return only those rules that are owned by the
// project who submits the request, unless an admin user submits the request.
func ListRules(c *gophercloud.ServiceClient, policyID string, opts ListRulesOptsBuilder) pagination.Pager {
url := ruleRootURL(c, policyID)
if opts != nil {
query, err := opts.ToRulesListQuery()
if err != nil {
return pagination.Pager{Err: err}
}
url += query
}
return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
return RulePage{pagination.LinkedPageBase{PageResult: r}}
})
}
// 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)
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)
return
}
// UpdateRuleOptsBuilder allows to add additional parameters to the PUT request.
type UpdateRuleOptsBuilder interface {
ToRuleUpdateMap() (map[string]interface{}, error)
}
// UpdateRuleOpts is the common options struct used in this package's Update
// operation.
type UpdateRuleOpts struct {
// The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH.
RuleType RuleType `json:"type,omitempty"`
// The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH.
CompareType CompareType `json:"compare_type,omitempty"`
// The value to use for the comparison. For example, the file type to compare.
Value string `json:"value,omitempty"`
// The key to use for the comparison. For example, the name of the cookie to evaluate.
Key *string `json:"key,omitempty"`
// When true the logic of the rule is inverted. For example, with invert true,
// equal to would become not equal to. Default is false.
Invert *bool `json:"invert,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
}
// ToRuleUpdateMap builds a request body from UpdateRuleOpts.
func (opts UpdateRuleOpts) ToRuleUpdateMap() (map[string]interface{}, error) {
b, err := gophercloud.BuildRequestBody(opts, "rule")
if err != nil {
return nil, err
}
if m := b["rule"].(map[string]interface{}); m["key"] == "" {
m["key"] = nil
}
return b, nil
}
// UpdateRule allows Rule to be updated.
func UpdateRule(c *gophercloud.ServiceClient, policyID string, ruleID string, opts UpdateRuleOptsBuilder) (r UpdateRuleResult) {
b, err := opts.ToRuleUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Put(ruleResourceURL(c, policyID, ruleID), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201, 202},
})
return
}

View File

@ -0,0 +1,237 @@
package l7policies
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// L7Policy is a collection of L7 rules associated with a Listener, and which
// may also have an association to a back-end pool.
type L7Policy struct {
// The unique ID for the L7 policy.
ID string `json:"id"`
// Name of the L7 policy.
Name string `json:"name"`
// The ID of the listener.
ListenerID string `json:"listener_id"`
// The L7 policy action. One of REDIRECT_TO_POOL, REDIRECT_TO_URL, or REJECT.
Action string `json:"action"`
// The position of this policy on the listener.
Position int32 `json:"position"`
// A human-readable description for the resource.
Description string `json:"description"`
// ProjectID is the UUID of the project who owns the L7 policy in octavia.
// Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id"`
// Requests matching this policy will be redirected to the pool with this ID.
// Only valid if action is REDIRECT_TO_POOL.
RedirectPoolID string `json:"redirect_pool_id"`
// Requests matching this policy will be redirected to this URL.
// Only valid if action is REDIRECT_TO_URL.
RedirectURL string `json:"redirect_url"`
// The administrative state of the L7 policy, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up"`
// The provisioning status of the L7 policy.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the L7 policy.
OperatingStatus string `json:"operating_status"`
// Rules are List of associated L7 rule IDs.
Rules []Rule `json:"rules"`
}
// Rule represents layer 7 load balancing rule.
type Rule struct {
// The unique ID for the L7 rule.
ID string `json:"id"`
// The L7 rule type. One of COOKIE, FILE_TYPE, HEADER, HOST_NAME, or PATH.
RuleType string `json:"type"`
// The comparison type for the L7 rule. One of CONTAINS, ENDS_WITH, EQUAL_TO, REGEX, or STARTS_WITH.
CompareType string `json:"compare_type"`
// The value to use for the comparison. For example, the file type to compare.
Value string `json:"value"`
// ProjectID is the UUID of the project who owns the rule in octavia.
// Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id"`
// The key to use for the comparison. For example, the name of the cookie to evaluate.
Key string `json:"key"`
// When true the logic of the rule is inverted. For example, with invert true,
// equal to would become not equal to. Default is false.
Invert bool `json:"invert"`
// The administrative state of the L7 rule, which is up (true) or down (false).
AdminStateUp bool `json:"admin_state_up"`
// The provisioning status of the L7 rule.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the L7 policy.
OperatingStatus string `json:"operating_status"`
}
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a l7policy.
func (r commonResult) Extract() (*L7Policy, error) {
var s struct {
L7Policy *L7Policy `json:"l7policy"`
}
err := r.ExtractInto(&s)
return s.L7Policy, err
}
// CreateResult represents the result of a Create operation. Call its Extract
// method to interpret the result as a L7Policy.
type CreateResult struct {
commonResult
}
// L7PolicyPage is the page returned by a pager when traversing over a
// collection of l7policies.
type L7PolicyPage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of l7policies has reached
// the end of a page and the pager seeks to traverse over a new one. In order
// to do this, it needs to construct the next page's URL.
func (r L7PolicyPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"l7policies_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a L7PolicyPage struct is empty.
func (r L7PolicyPage) IsEmpty() (bool, error) {
is, err := ExtractL7Policies(r)
return len(is) == 0, err
}
// ExtractL7Policies accepts a Page struct, specifically a L7PolicyPage struct,
// and extracts the elements into a slice of L7Policy structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractL7Policies(r pagination.Page) ([]L7Policy, error) {
var s struct {
L7Policies []L7Policy `json:"l7policies"`
}
err := (r.(L7PolicyPage)).ExtractInto(&s)
return s.L7Policies, err
}
// GetResult represents the result of a Get operation. Call its Extract
// method to interpret the result as a L7Policy.
type GetResult struct {
commonResult
}
// DeleteResult represents the result of a Delete operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type DeleteResult struct {
gophercloud.ErrResult
}
// UpdateResult represents the result of an Update operation. Call its Extract
// method to interpret the result as a L7Policy.
type UpdateResult struct {
commonResult
}
type commonRuleResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a rule.
func (r commonRuleResult) Extract() (*Rule, error) {
var s struct {
Rule *Rule `json:"rule"`
}
err := r.ExtractInto(&s)
return s.Rule, err
}
// CreateRuleResult represents the result of a CreateRule operation.
// Call its Extract method to interpret it as a Rule.
type CreateRuleResult struct {
commonRuleResult
}
// RulePage is the page returned by a pager when traversing over a
// collection of Rules in a L7Policy.
type RulePage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of rules has reached
// the end of a page and the pager seeks to traverse over a new one. In order
// to do this, it needs to construct the next page's URL.
func (r RulePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"rules_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a RulePage struct is empty.
func (r RulePage) IsEmpty() (bool, error) {
is, err := ExtractRules(r)
return len(is) == 0, err
}
// ExtractRules accepts a Page struct, specifically a RulePage struct,
// and extracts the elements into a slice of Rules structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractRules(r pagination.Page) ([]Rule, error) {
var s struct {
Rules []Rule `json:"rules"`
}
err := (r.(RulePage)).ExtractInto(&s)
return s.Rules, err
}
// GetRuleResult represents the result of a GetRule operation.
// Call its Extract method to interpret it as a Rule.
type GetRuleResult struct {
commonRuleResult
}
// DeleteRuleResult represents the result of a DeleteRule operation.
// Call its ExtractErr method to determine if the request succeeded or failed.
type DeleteRuleResult struct {
gophercloud.ErrResult
}
// UpdateRuleResult represents the result of an UpdateRule operation.
// Call its Extract method to interpret it as a Rule.
type UpdateRuleResult struct {
commonRuleResult
}

View File

@ -0,0 +1,25 @@
package l7policies
import "github.com/gophercloud/gophercloud"
const (
rootPath = "lbaas"
resourcePath = "l7policies"
rulePath = "rules"
)
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(rootPath, resourcePath)
}
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id)
}
func ruleRootURL(c *gophercloud.ServiceClient, policyID string) string {
return c.ServiceURL(rootPath, resourcePath, policyID, rulePath)
}
func ruleResourceURL(c *gophercloud.ServiceClient, policyID string, ruleID string) string {
return c.ServiceURL(rootPath, resourcePath, policyID, rulePath, ruleID)
}

View File

@ -13,6 +13,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],

View File

@ -43,8 +43,11 @@ Example to Update a Listener
listenerID := "d67d56a6-4a86-4688-a282-f46444705c64"
i1001 := 1001
i181000 := 181000
updateOpts := listeners.UpdateOpts{
ConnLimit: &i1001,
TimeoutClientData: &i181000,
TimeoutMemberData: &i181000,
}
listener, err := listeners.Update(networkClient, listenerID, updateOpts).Extract()

View File

@ -11,6 +11,8 @@ type Protocol string
// Supported attributes for create/update operations.
const (
ProtocolTCP Protocol = "TCP"
ProtocolUDP Protocol = "UDP"
ProtocolPROXY Protocol = "PROXY"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
)
@ -27,19 +29,21 @@ type ListOptsBuilder interface {
// sort by a particular listener attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"`
LoadbalancerID string `q:"loadbalancer_id"`
DefaultPoolID string `q:"default_pool_id"`
Protocol string `q:"protocol"`
ProtocolPort int `q:"protocol_port"`
ConnectionLimit int `q:"connection_limit"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
ID string `q:"id"`
Name string `q:"name"`
AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"`
LoadbalancerID string `q:"loadbalancer_id"`
DefaultPoolID string `q:"default_pool_id"`
Protocol string `q:"protocol"`
ProtocolPort int `q:"protocol_port"`
ConnectionLimit int `q:"connection_limit"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
TimeoutClientData *int `q:"timeout_client_data"`
TimeoutMemberData *int `q:"timeout_member_data"`
}
// ToListenerListQuery formats a ListOpts into a query string.
@ -110,6 +114,12 @@ type CreateOpts struct {
// The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
// Frontend client inactivity timeout in milliseconds
TimeoutClientData *int `json:"timeout_client_data,omitempty"`
// Backend member inactivity timeout in milliseconds
TimeoutMemberData *int `json:"timeout_member_data,omitempty"`
}
// ToListenerCreateMap builds a request body from CreateOpts.
@ -149,10 +159,13 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options for updating a Listener.
type UpdateOpts struct {
// Human-readable name for the Listener. Does not have to be unique.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// The ID of the default pool with which the Listener is associated.
DefaultPoolID *string `json:"default_pool_id,omitempty"`
// Human-readable description for the Listener.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The maximum number of connections allowed for the Listener.
ConnLimit *int `json:"connection_limit,omitempty"`
@ -166,11 +179,26 @@ type UpdateOpts struct {
// The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
// Frontend client inactivity timeout in milliseconds
TimeoutClientData *int `json:"timeout_client_data,omitempty"`
// Backend member inactivity timeout in milliseconds
TimeoutMemberData *int `json:"timeout_member_data,omitempty"`
}
// ToListenerUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "listener")
b, err := gophercloud.BuildRequestBody(opts, "listener")
if err != nil {
return nil, err
}
if m := b["listener"].(map[string]interface{}); m["default_pool_id"] == "" {
m["default_pool_id"] = nil
}
return b, nil
}
// Update is an operation which modifies the attributes of the specified

View File

@ -2,6 +2,7 @@ package listeners
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
"github.com/gophercloud/gophercloud/pagination"
)
@ -55,9 +56,18 @@ type Listener struct {
// Pools are the pools which are part of this listener.
Pools []pools.Pool `json:"pools"`
// L7policies are the L7 policies which are part of this listener.
L7Policies []l7policies.L7Policy `json:"l7policies"`
// The provisioning status of the Listener.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// Frontend client inactivity timeout in milliseconds
TimeoutClientData int `json:"timeout_client_data"`
// Backend member inactivity timeout in milliseconds
TimeoutMemberData int `json:"timeout_member_data"`
}
type Stats struct {

View File

@ -14,6 +14,7 @@ go_library(
deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination:go_default_library",
],
)

View File

@ -31,6 +31,7 @@ Example to Create a Load Balancer
VipAddress: "10.30.176.48",
Flavor: "medium",
Provider: "haproxy",
Tags: []string{"test", "stage"},
}
lb, err := loadbalancers.Create(networkClient, createOpts).Extract()
@ -41,12 +42,10 @@ Example to Create a Load Balancer
Example to Update a Load Balancer
lbID := "d67d56a6-4a86-4688-a282-f46444705c64"
i1001 := 1001
name := "new-name"
updateOpts := loadbalancers.UpdateOpts{
Name: "new-name",
Name: &name,
}
lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract()
if err != nil {
panic(err)
@ -80,5 +79,14 @@ Example to Get the Statistics of a Load Balancer
if err != nil {
panic(err)
}
Example to Failover a Load Balancers
lbID := "d67d56a6-4a86-4688-a282-f46444705c64"
err := loadbalancers.Failover(networkClient, lbID).ExtractErr()
if err != nil {
panic(err)
}
*/
package loadbalancers

View File

@ -17,22 +17,27 @@ type ListOptsBuilder interface {
// sort by a particular attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"`
ProvisioningStatus string `q:"provisioning_status"`
VipAddress string `q:"vip_address"`
VipPortID string `q:"vip_port_id"`
VipSubnetID string `q:"vip_subnet_id"`
ID string `q:"id"`
OperatingStatus string `q:"operating_status"`
Name string `q:"name"`
Flavor string `q:"flavor"`
Provider string `q:"provider"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"`
ProvisioningStatus string `q:"provisioning_status"`
VipAddress string `q:"vip_address"`
VipPortID string `q:"vip_port_id"`
VipSubnetID string `q:"vip_subnet_id"`
VipNetworkID string `q:"vip_network_id"`
ID string `q:"id"`
OperatingStatus string `q:"operating_status"`
Name string `q:"name"`
Flavor string `q:"flavor"`
Provider string `q:"provider"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags []string `q:"tags"`
TagsAny []string `q:"tags-any"`
TagsNot []string `q:"not-tags"`
TagsNotAny []string `q:"not-tags-any"`
}
// ToLoadBalancerListQuery formats a ListOpts into a query string.
@ -76,11 +81,16 @@ type CreateOpts struct {
// Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"`
// The network on which to allocate the Loadbalancer's address. A project can
// The subnet on which to allocate the Loadbalancer's address. A project can
// only create Loadbalancers on networks authorized by policy (e.g. networks
// that belong to them or networks that are shared).
VipSubnetID string `json:"vip_subnet_id" required:"true"`
// The network on which to allocate the Loadbalancer's address. A tenant can
// only create Loadbalancers on networks authorized by policy (e.g. networks
// that belong to them or networks that are shared).
VipNetworkID string `json:"vip_network_id,omitempty"`
// ProjectID is the UUID of the project who owns the Loadbalancer.
// Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id,omitempty"`
@ -97,6 +107,9 @@ type CreateOpts struct {
// The name of the provider.
Provider string `json:"provider,omitempty"`
// Tags is a set of resource tags.
Tags []string `json:"tags,omitempty"`
}
// ToLoadBalancerCreateMap builds a request body from CreateOpts.
@ -134,14 +147,17 @@ type UpdateOptsBuilder interface {
// operation.
type UpdateOpts struct {
// Human-readable name for the Loadbalancer. Does not have to be unique.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"`
// Tags is a set of resource tags.
Tags *[]string `json:"tags,omitempty"`
}
// ToLoadBalancerUpdateMap builds a request body from UpdateOpts.
@ -209,3 +225,11 @@ func GetStats(c *gophercloud.ServiceClient, id string) (r StatsResult) {
_, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil)
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{
OkCodes: []int{202},
})
return
}

View File

@ -3,6 +3,7 @@ package loadbalancers
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
"github.com/gophercloud/gophercloud/pagination"
)
@ -34,6 +35,10 @@ type LoadBalancer struct {
// Loadbalancer address.
VipSubnetID string `json:"vip_subnet_id"`
// The UUID of the network on which to allocate the virtual IP for the
// Loadbalancer address.
VipNetworkID string `json:"vip_network_id"`
// The unique ID for the LoadBalancer.
ID string `json:"id"`
@ -51,6 +56,13 @@ type LoadBalancer struct {
// Listeners are the listeners related to this Loadbalancer.
Listeners []listeners.Listener `json:"listeners"`
// Pools are the pools related to this Loadbalancer.
Pools []pools.Pool `json:"pools"`
// Tags is a list of resource tags. Tags are arbitrarily defined strings
// attached to the resource.
Tags []string `json:"tags"`
}
// StatusTree represents the status of a loadbalancer.
@ -180,3 +192,9 @@ type UpdateResult struct {
type DeleteResult struct {
gophercloud.ErrResult
}
// FailoverResult represents the result of a failover operation. Call its
// ExtractErr method to determine if the request succeeded or failed.
type FailoverResult struct {
gophercloud.ErrResult
}

View File

@ -7,6 +7,7 @@ const (
resourcePath = "loadbalancers"
statusPath = "status"
statisticsPath = "stats"
failoverPath = "failover"
)
func rootURL(c *gophercloud.ServiceClient) string {
@ -24,3 +25,7 @@ func statusRootURL(c *gophercloud.ServiceClient, id string) string {
func statisticsRootURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id, statisticsPath)
}
func failoverRootURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id, failoverPath)
}

View File

@ -223,7 +223,7 @@ type UpdateOpts struct {
ExpectedCodes string `json:"expected_codes,omitempty"`
// The Name of the Monitor.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// The administrative state of the Monitor. A valid value is true (UP)
// or false (DOWN).

View File

@ -74,6 +74,9 @@ type Monitor struct {
// The provisioning status of the Monitor.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the monitor.
OperatingStatus string `json:"operating_status"`
}
// MonitorPage is the page returned by a pager when traversing over a

View File

@ -83,12 +83,13 @@ Example to Create a Member
poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
weight := 10
createOpts := pools.CreateMemberOpts{
Name: "db",
SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9",
Address: "10.0.2.11",
ProtocolPort: 80,
Weight: 10,
Weight: &weight,
}
member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract()
@ -101,9 +102,10 @@ Example to Update a Member
poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
memberID := "64dba99f-8af8-4200-8882-e32a0660f23e"
weight := 4
updateOpts := pools.UpdateMemberOpts{
Name: "new-name",
Weight: 4,
Weight: &weight,
}
member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract()
@ -120,5 +122,33 @@ Example to Delete a Member
if err != nil {
panic(err)
}
Example to Update Members:
poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
weight_1 := 20
member1 := pools.BatchUpdateMemberOpts{
Address: "192.0.2.16",
ProtocolPort: 80,
Name: "web-server-1",
SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa",
Weight: &weight_1,
}
weight_2 := 10
member2 := pools.BatchUpdateMemberOpts{
Address: "192.0.2.17",
ProtocolPort: 80,
Name: "web-server-2",
Weight: &weight_2,
SubnetID: "bbb35f84-35cc-4b2f-84c2-a6a29bba68aa",
}
members := []pools.BatchUpdateMemberOpts{member1, member2}
err := pools.BatchUpdateMembers(networkClient, poolID, members).ExtractErr()
if err != nil {
panic(err)
}
*/
package pools

View File

@ -66,6 +66,8 @@ const (
LBMethodSourceIp LBMethod = "SOURCE_IP"
ProtocolTCP Protocol = "TCP"
ProtocolUDP Protocol = "UDP"
ProtocolPROXY Protocol = "PROXY"
ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS"
)
@ -85,7 +87,7 @@ type CreateOpts struct {
LBMethod LBMethod `json:"lb_algorithm" required:"true"`
// The protocol used by the pool members, you can use either
// ProtocolTCP, ProtocolHTTP, or ProtocolHTTPS.
// ProtocolTCP, ProtocolUDP, ProtocolPROXY, ProtocolHTTP, or ProtocolHTTPS.
Protocol Protocol `json:"protocol" required:"true"`
// The Loadbalancer on which the members of the pool will be associated with.
@ -148,10 +150,10 @@ type UpdateOptsBuilder interface {
// operation.
type UpdateOpts struct {
// Name of the pool.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// Human-readable description for the pool.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
// The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections
@ -261,10 +263,10 @@ type CreateMemberOpts struct {
ProjectID string `json:"project_id,omitempty"`
// A positive integer value that indicates the relative portion of traffic
// that this member should receive from the pool. For example, a member with
// a weight of 10 receives five times as much traffic as a member with a
// that this member should receive from the pool. For example, a member with
// a weight of 10 receives five times as much traffic as a member with a
// weight of 2.
Weight int `json:"weight,omitempty"`
Weight *int `json:"weight,omitempty"`
// If you omit this parameter, LBaaS uses the vip_subnet_id parameter value
// for the subnet UUID.
@ -307,13 +309,13 @@ type UpdateMemberOptsBuilder interface {
// operation.
type UpdateMemberOpts struct {
// Name of the Member.
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
// A positive integer value that indicates the relative portion of traffic
// that this member should receive from the pool. For example, a member with
// a weight of 10 receives five times as much traffic as a member with a
// weight of 2.
Weight int `json:"weight,omitempty"`
Weight *int `json:"weight,omitempty"`
// The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN).
@ -338,6 +340,36 @@ func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string,
return
}
// BatchUpdateMemberOptsBuilder allows extensions to add additional parameters to the BatchUpdateMembers request.
type BatchUpdateMemberOptsBuilder interface {
ToBatchMemberUpdateMap() (map[string]interface{}, error)
}
type BatchUpdateMemberOpts CreateMemberOpts
// ToBatchMemberUpdateMap builds a request body from BatchUpdateMemberOpts.
func (opts BatchUpdateMemberOpts) ToBatchMemberUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "")
}
// BatchUpdateMembers updates the pool members in batch
func BatchUpdateMembers(c *gophercloud.ServiceClient, poolID string, opts []BatchUpdateMemberOpts) (r UpdateMembersResult) {
var members []map[string]interface{}
for _, opt := range opts {
b, err := opt.ToBatchMemberUpdateMap()
if err != nil {
r.Err = err
return
}
members = append(members, b)
}
b := map[string]interface{}{"members": members}
_, r.Err = c.Put(memberRootURL(c, poolID), b, nil, &gophercloud.RequestOpts{OkCodes: []int{202}})
return
}
// DisassociateMember will remove and disassociate a Member from a particular
// Pool.
func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) {

View File

@ -1,6 +1,9 @@
package pools
import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors"
"github.com/gophercloud/gophercloud/pagination"
@ -96,6 +99,9 @@ type Pool struct {
// The provisioning status of the pool.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// The operating status of the pool.
OperatingStatus string `json:"operating_status"`
}
// PoolPage is the page returned by a pager when traversing over a
@ -204,6 +210,24 @@ type Member struct {
// The provisioning status of the pool.
// This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"`
// DateTime when the member was created
CreatedAt time.Time `json:"-"`
// DateTime when the member was updated
UpdatedAt time.Time `json:"-"`
// The operating status of the member
OperatingStatus string `json:"operating_status"`
// Is the member a backup? Backup members only receive traffic when all non-backup members are down.
Backup bool `json:"backup"`
// An alternate IP address used for health monitoring a backend member.
MonitorAddress string `json:"monitor_address"`
// An alternate protocol port used for health monitoring a backend member.
MonitorPort int `json:"monitor_port"`
}
// MemberPage is the page returned by a pager when traversing over a
@ -247,6 +271,23 @@ type commonMemberResult struct {
gophercloud.Result
}
func (r *Member) UnmarshalJSON(b []byte) error {
type tmp Member
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339NoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339NoZ `json:"updated_at"`
}
err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = Member(s.tmp)
r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)
return nil
}
// ExtractMember is a function that accepts a result and extracts a member.
func (r commonMemberResult) Extract() (*Member, error) {
var s struct {
@ -274,6 +315,12 @@ type UpdateMemberResult struct {
commonMemberResult
}
// UpdateMembersResult represents the result of an UpdateMembers operation.
// Call its ExtractErr method to determine if the request succeeded or failed.
type UpdateMembersResult struct {
gophercloud.ErrResult
}
// DeleteMemberResult represents the result of a DeleteMember operation.
// Call its ExtractErr method to determine if the request succeeded or failed.
type DeleteMemberResult struct {

View File

@ -52,7 +52,7 @@ Example to Disassociate a Floating IP with a Port
fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7"
updateOpts := floatingips.UpdateOpts{
PortID: nil,
PortID: new(string),
}
fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract()

View File

@ -12,6 +12,7 @@ import (
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Description string `q:"description"`
FloatingNetworkID string `q:"floating_network_id"`
PortID string `q:"port_id"`
FixedIP string `q:"fixed_ip_address"`
@ -24,6 +25,10 @@ type ListOpts struct {
SortDir string `q:"sort_dir"`
RouterID string `q:"router_id"`
Status string `q:"status"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -50,6 +55,7 @@ type CreateOptsBuilder interface {
// resource. The only required fields are FloatingNetworkID and PortID which
// refer to the external network and internal port respectively.
type CreateOpts struct {
Description string `json:"description,omitempty"`
FloatingNetworkID string `json:"floating_network_id" required:"true"`
FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id,omitempty"`
@ -116,13 +122,24 @@ type UpdateOptsBuilder interface {
// linked to. To associate the floating IP with a new internal port, provide its
// ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct {
PortID *string `json:"port_id"`
Description *string `json:"description,omitempty"`
PortID *string `json:"port_id,omitempty"`
FixedIP string `json:"fixed_ip_address,omitempty"`
}
// ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
// interface
func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "floatingip")
b, err := gophercloud.BuildRequestBody(opts, "floatingip")
if err != nil {
return nil, err
}
if m := b["floatingip"].(map[string]interface{}); m["port_id"] == "" {
m["port_id"] = nil
}
return b, nil
}
// Update allows floating IP resources to be updated. Currently, the only way to

View File

@ -15,6 +15,9 @@ type FloatingIP struct {
// ID is the unique identifier for the floating IP instance.
ID string `json:"id"`
// Description for the floating IP instance.
Description string `json:"description"`
// FloatingNetworkID is the UUID of the external network where the floating
// IP is to be created.
FloatingNetworkID string `json:"floating_network_id"`
@ -42,6 +45,9 @@ type FloatingIP struct {
// RouterID is the ID of the router used for this floating IP.
RouterID string `json:"router_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
type commonResult struct {

View File

@ -13,6 +13,7 @@ import (
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
Distributed *bool `q:"distributed"`
Status string `q:"status"`
@ -22,6 +23,10 @@ type ListOpts struct {
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -51,6 +56,7 @@ type CreateOptsBuilder interface {
// no required values.
type CreateOpts struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
@ -97,6 +103,7 @@ type UpdateOptsBuilder interface {
// UpdateOpts contains the values used when updating a router.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`

View File

@ -16,7 +16,7 @@ type GatewayInfo struct {
// ExternalFixedIP is the IP address and subnet ID of the external gateway of a
// router.
type ExternalFixedIP struct {
IPAddress string `json:"ip_address"`
IPAddress string `json:"ip_address,omitempty"`
SubnetID string `json:"subnet_id"`
}
@ -51,6 +51,9 @@ type Router struct {
// unique.
Name string `json:"name"`
// Description for the router.
Description string `json:"description"`
// ID is the unique identifier for the router.
ID string `json:"id"`
@ -67,6 +70,9 @@ type Router struct {
// Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// RouterPage is the page returned by a pager when traversing over a

View File

@ -11,14 +11,19 @@ import (
// sort by a particular network attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
ID string `q:"id"`
Name string `q:"name"`
Description string `q:"description"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// List returns a Pager which allows you to iterate over a collection of
@ -88,7 +93,7 @@ type UpdateOpts struct {
Name string `json:"name,omitempty"`
// Describes the security group.
Description string `json:"description,omitempty"`
Description *string `json:"description,omitempty"`
}
// ToSecGroupUpdateMap builds a request body from UpdateOpts.

View File

@ -27,6 +27,9 @@ type SecGroup struct {
// ProjectID is the project owner of the security group.
ProjectID string `json:"project_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// SecGroupPage is the page returned by a pager when traversing over a

View File

@ -14,6 +14,7 @@ type ListOpts struct {
Direction string `q:"direction"`
EtherType string `q:"ethertype"`
ID string `q:"id"`
Description string `q:"description"`
PortRangeMax int `q:"port_range_max"`
PortRangeMin int `q:"port_range_min"`
Protocol string `q:"protocol"`
@ -88,6 +89,9 @@ type CreateOpts struct {
// group rule is applied.
Direction RuleDirection `json:"direction" required:"true"`
// String description of each rule, optional
Description string `json:"description,omitempty"`
// Must be "IPv4" or "IPv6", and addresses represented in CIDR must match the
// ingress or egress rules.
EtherType RuleEtherType `json:"ethertype" required:"true"`

View File

@ -17,6 +17,9 @@ type SecGroupRule struct {
// instance. An egress rule is applied to traffic leaving the instance.
Direction string
// Descripton of the rule
Description string `json:"description"`
// Must be IPv4 or IPv6, and addresses represented in CIDR must match the
// ingress or egress rules.
EtherType string `json:"ethertype"`

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct {
Status string `q:"status"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"`
@ -28,6 +29,10 @@ type ListOpts struct {
Limit int `q:"limit"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToNetworkListQuery formats a ListOpts into a query string.
@ -69,6 +74,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
ProjectID string `json:"project_id,omitempty"`
@ -105,9 +111,10 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options used to update a network.
type UpdateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Shared *bool `json:"shared,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"`
}
// ToNetworkUpdateMap builds a request body from UpdateOpts.

View File

@ -52,6 +52,9 @@ type Network struct {
// Human-readable name for the network. Might not be unique.
Name string `json:"name"`
// Description for the network
Description string `json:"description"`
// The administrative state of network. If false (down), the network does not
// forward packets.
AdminStateUp bool `json:"admin_state_up"`
@ -76,6 +79,9 @@ type Network struct {
// Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// NetworkPage is the page returned by a pager when traversing over a

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct {
Status string `q:"status"`
Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"`
NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"`
@ -31,6 +32,10 @@ type ListOpts struct {
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToPortListQuery formats a ListOpts into a query string.
@ -76,6 +81,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct {
NetworkID string `json:"network_id" required:"true"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
MACAddress string `json:"mac_address,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"`
@ -112,11 +118,12 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
Name string `json:"name,omitempty"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"`
DeviceID string `json:"device_id,omitempty"`
DeviceOwner string `json:"device_owner,omitempty"`
DeviceID *string `json:"device_id,omitempty"`
DeviceOwner *string `json:"device_owner,omitempty"`
SecurityGroups *[]string `json:"security_groups,omitempty"`
AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"`
}

View File

@ -68,6 +68,9 @@ type Port struct {
// Human-readable name for the port. Might not be unique.
Name string `json:"name"`
// Describes the port.
Description string `json:"description"`
// Administrative state of port. If false (down), port does not forward
// packets.
AdminStateUp bool `json:"admin_state_up"`
@ -101,6 +104,9 @@ type Port struct {
// Identifies the list of IP addresses the port will recognize/accept
AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// PortPage is the page returned by a pager when traversing over a collection

View File

@ -18,6 +18,7 @@ type ListOptsBuilder interface {
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
Name string `q:"name"`
Description string `q:"description"`
EnableDHCP *bool `q:"enable_dhcp"`
NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"`
@ -33,6 +34,10 @@ type ListOpts struct {
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
Tags string `q:"tags"`
TagsAny string `q:"tags-any"`
NotTags string `q:"not-tags"`
NotTagsAny string `q:"not-tags-any"`
}
// ToSubnetListQuery formats a ListOpts into a query string.
@ -85,6 +90,9 @@ type CreateOpts struct {
// Name is a human-readable name of the subnet.
Name string `json:"name,omitempty"`
// Description of the subnet.
Description string `json:"description,omitempty"`
// The UUID of the project who owns the Subnet. Only administrative users
// can specify a project UUID other than their own.
TenantID string `json:"tenant_id,omitempty"`
@ -123,6 +131,10 @@ type CreateOpts struct {
// SubnetPoolID is the id of the subnet pool that subnet should be associated to.
SubnetPoolID string `json:"subnetpool_id,omitempty"`
// Prefixlen is used when user creates a subnet from the subnetpool. It will
// overwrite the "default_prefixlen" value of the referenced subnetpool.
Prefixlen int `json:"prefixlen,omitempty"`
}
// ToSubnetCreateMap builds a request body from CreateOpts.
@ -163,6 +175,9 @@ type UpdateOpts struct {
// Name is a human-readable name of the subnet.
Name string `json:"name,omitempty"`
// Description of the subnet.
Description *string `json:"description,omitempty"`
// AllocationPools are IP Address pools that will be available for DHCP.
AllocationPools []AllocationPool `json:"allocation_pools,omitempty"`

View File

@ -68,6 +68,9 @@ type Subnet struct {
// Human-readable name for the subnet. Might not be unique.
Name string `json:"name"`
// Description for the subnet.
Description string `json:"description"`
// IP version, either `4' or `6'.
IPVersion int `json:"ip_version"`
@ -106,6 +109,9 @@ type Subnet struct {
// SubnetPoolID is the id of the subnet pool associated with the subnet.
SubnetPoolID string `json:"subnetpool_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
}
// SubnetPage is the page returned by a pager when traversing over a collection

View File

@ -74,6 +74,7 @@ type CreateOpts struct {
DetectContentType bool `h:"X-Detect-Content-Type"`
IfNoneMatch string `h:"If-None-Match"`
VersionsLocation string `h:"X-Versions-Location"`
HistoryLocation string `h:"X-History-Location"`
}
// ToContainerCreateMap formats a CreateOpts into a map of headers.
@ -137,6 +138,8 @@ type UpdateOpts struct {
DetectContentType bool `h:"X-Detect-Content-Type"`
RemoveVersionsLocation string `h:"X-Remove-Versions-Location"`
VersionsLocation string `h:"X-Versions-Location"`
RemoveHistoryLocation string `h:"X-Remove-History-Location"`
HistoryLocation string `h:"X-History-Location"`
}
// ToContainerUpdateMap formats a UpdateOpts into a map of headers.

View File

@ -100,6 +100,7 @@ type GetHeader struct {
Read []string `json:"-"`
TransID string `json:"X-Trans-Id"`
VersionsLocation string `json:"X-Versions-Location"`
HistoryLocation string `json:"X-History-Location"`
Write []string `json:"-"`
StoragePolicy string `json:"X-Storage-Policy"`
}

View File

@ -120,6 +120,22 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{},
continue
}
if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
sliceValue := v
if sliceValue.Kind() == reflect.Ptr {
sliceValue = sliceValue.Elem()
}
for i := 0; i < sliceValue.Len(); i++ {
element := sliceValue.Index(i)
if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
_, err := BuildRequestBody(element.Interface(), "")
if err != nil {
return nil, err
}
}
}
}
if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
if zero {
//fmt.Printf("value before change: %+v\n", optsValue.Field(i))

View File

@ -3,6 +3,7 @@ package gophercloud
import (
"bytes"
"encoding/json"
"errors"
"io"
"io/ioutil"
"net/http"
@ -71,26 +72,36 @@ type ProviderClient struct {
// authentication functions for different Identity service versions.
ReauthFunc func() error
// Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client
// with the token and reauth func zeroed. Such client can be used to perform reauthorization.
Throwaway bool
mut *sync.RWMutex
reauthmut *reauthlock
authResult AuthResult
}
type reauthlock struct {
sync.RWMutex
reauthing bool
reauthing bool
reauthingErr error
done *sync.Cond
}
// AuthenticatedHeaders returns a map of HTTP headers that are common for all
// authenticated service requests.
// authenticated service requests. Blocks if Reauthenticate is in progress.
func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
if client.IsThrowaway() {
return
}
if client.reauthmut != nil {
client.reauthmut.RLock()
if client.reauthmut.reauthing {
client.reauthmut.RUnlock()
return
client.reauthmut.Lock()
for client.reauthmut.reauthing {
client.reauthmut.done.Wait()
}
client.reauthmut.RUnlock()
client.reauthmut.Unlock()
}
t := client.Token()
if t == "" {
@ -106,6 +117,20 @@ func (client *ProviderClient) UseTokenLock() {
client.reauthmut = new(reauthlock)
}
// GetAuthResult returns the result from the request that was used to obtain a
// provider client's Keystone token.
//
// The result is nil when authentication has not yet taken place, when the token
// was set manually with SetToken(), or when a ReauthFunc was used that does not
// record the AuthResult.
func (client *ProviderClient) GetAuthResult() AuthResult {
if client.mut != nil {
client.mut.RLock()
defer client.mut.RUnlock()
}
return client.authResult
}
// Token safely reads the value of the auth token from the ProviderClient. Applications should
// call this method to access the token instead of the TokenID field
func (client *ProviderClient) Token() string {
@ -117,20 +142,78 @@ func (client *ProviderClient) Token() string {
}
// SetToken safely sets the value of the auth token in the ProviderClient. Applications may
// use this method in a custom ReauthFunc
// use this method in a custom ReauthFunc.
//
// WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead.
func (client *ProviderClient) SetToken(t string) {
if client.mut != nil {
client.mut.Lock()
defer client.mut.Unlock()
}
client.TokenID = t
client.authResult = nil
}
//Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
//called because of a 401 response, the caller may pass the previous token. In
//this case, the reauthentication can be skipped if another thread has already
//reauthenticated in the meantime. If no previous token is known, an empty
//string should be passed instead to force unconditional reauthentication.
// SetTokenAndAuthResult safely sets the value of the auth token in the
// ProviderClient and also records the AuthResult that was returned from the
// token creation request. Applications may call this in a custom ReauthFunc.
func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
tokenID := ""
var err error
if r != nil {
tokenID, err = r.ExtractTokenID()
if err != nil {
return err
}
}
if client.mut != nil {
client.mut.Lock()
defer client.mut.Unlock()
}
client.TokenID = tokenID
client.authResult = r
return nil
}
// CopyTokenFrom safely copies the token from another ProviderClient into the
// this one.
func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) {
if client.mut != nil {
client.mut.Lock()
defer client.mut.Unlock()
}
if other.mut != nil && other.mut != client.mut {
other.mut.RLock()
defer other.mut.RUnlock()
}
client.TokenID = other.TokenID
client.authResult = other.authResult
}
// IsThrowaway safely reads the value of the client Throwaway field.
func (client *ProviderClient) IsThrowaway() bool {
if client.reauthmut != nil {
client.reauthmut.RLock()
defer client.reauthmut.RUnlock()
}
return client.Throwaway
}
// SetThrowaway safely sets the value of the client Throwaway field.
func (client *ProviderClient) SetThrowaway(v bool) {
if client.reauthmut != nil {
client.reauthmut.Lock()
defer client.reauthmut.Unlock()
}
client.Throwaway = v
}
// Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
// called because of a 401 response, the caller may pass the previous token. In
// this case, the reauthentication can be skipped if another thread has already
// reauthenticated in the meantime. If no previous token is known, an empty
// string should be passed instead to force unconditional reauthentication.
func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.ReauthFunc == nil {
return nil
@ -139,11 +222,25 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.mut == nil {
return client.ReauthFunc()
}
client.reauthmut.Lock()
if client.reauthmut.reauthing {
for !client.reauthmut.reauthing {
client.reauthmut.done.Wait()
}
err = client.reauthmut.reauthingErr
client.reauthmut.Unlock()
return err
}
client.reauthmut.Unlock()
client.mut.Lock()
defer client.mut.Unlock()
client.reauthmut.Lock()
client.reauthmut.reauthing = true
client.reauthmut.done = sync.NewCond(client.reauthmut)
client.reauthmut.reauthingErr = nil
client.reauthmut.Unlock()
if previousToken == "" || client.TokenID == previousToken {
@ -152,6 +249,8 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
client.reauthmut.Lock()
client.reauthmut.reauthing = false
client.reauthmut.reauthingErr = err
client.reauthmut.done.Broadcast()
client.reauthmut.Unlock()
return
}
@ -192,7 +291,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
// io.ReadSeeker as-is. Default the content-type to application/json.
if options.JSONBody != nil {
if options.RawBody != nil {
panic("Please provide only one of JSONBody or RawBody to gophercloud.Request().")
return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
}
rendered, err := json.Marshal(options.JSONBody)
@ -251,13 +350,14 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
}
// Allow default OkCodes if none explicitly set
if options.OkCodes == nil {
options.OkCodes = defaultOkCodes(method)
okc := options.OkCodes
if okc == nil {
okc = defaultOkCodes(method)
}
// Validate the HTTP response status.
var ok bool
for _, code := range options.OkCodes {
for _, code := range okc {
if resp.StatusCode == code {
ok = true
break
@ -295,11 +395,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
seeker.Seek(0, 0)
}
}
// make a new call to request with a nil reauth func in order to avoid infinite loop
reauthFunc := client.ReauthFunc
client.ReauthFunc = nil
resp, err = client.Request(method, url, options)
client.ReauthFunc = reauthFunc
if err != nil {
switch err.(type) {
case *ErrUnexpectedResponseCode:

View File

@ -90,38 +90,40 @@ func (r Result) extractIntoPtr(to interface{}, label string) error {
if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
for _, v := range m[label].([]interface{}) {
// For each iteration of the slice, we create a new struct.
// This is to work around a bug where elements of a slice
// are reused and not overwritten when the same copy of the
// struct is used:
//
// https://github.com/golang/go/issues/21092
// https://github.com/golang/go/issues/24155
// https://play.golang.org/p/NHo3ywlPZli
newType := reflect.New(typeOfV).Elem()
if mSlice, ok := m[label].([]interface{}); ok {
for _, v := range mSlice {
// For each iteration of the slice, we create a new struct.
// This is to work around a bug where elements of a slice
// are reused and not overwritten when the same copy of the
// struct is used:
//
// https://github.com/golang/go/issues/21092
// https://github.com/golang/go/issues/24155
// https://play.golang.org/p/NHo3ywlPZli
newType := reflect.New(typeOfV).Elem()
b, err := json.Marshal(v)
if err != nil {
return err
}
// This is needed for structs with an UnmarshalJSON method.
// Technically this is just unmarshalling the response into
// a struct that is never used, but it's good enough to
// trigger the UnmarshalJSON method.
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
// Unmarshal is used rather than NewDecoder to also work
// around the above-mentioned bug.
err = json.Unmarshal(b, s)
b, err := json.Marshal(v)
if err != nil {
return err
}
}
newSlice = reflect.Append(newSlice, newType)
// This is needed for structs with an UnmarshalJSON method.
// Technically this is just unmarshalling the response into
// a struct that is never used, but it's good enough to
// trigger the UnmarshalJSON method.
for i := 0; i < newType.NumField(); i++ {
s := newType.Field(i).Addr().Interface()
// Unmarshal is used rather than NewDecoder to also work
// around the above-mentioned bug.
err = json.Unmarshal(b, s)
if err != nil {
return err
}
}
newSlice = reflect.Append(newSlice, newType)
}
}
// "to" should now be properly modeled to receive the

View File

@ -129,6 +129,8 @@ func (client *ServiceClient) setMicroversionHeader(opts *RequestOpts) {
opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
case "volume":
opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
case "baremetal":
opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion
}
if client.Type != "" {