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

View File

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

View File

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

View File

@ -22,6 +22,15 @@
global_env: global_env:
OS_BRANCH: stable/queens 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: - job:
name: gophercloud-acceptance-test-pike name: gophercloud-acceptance-test-pike
parent: gophercloud-acceptance-test parent: gophercloud-acceptance-test
@ -80,6 +89,9 @@
recheck-queens: recheck-queens:
jobs: jobs:
- gophercloud-acceptance-test-queens - gophercloud-acceptance-test-queens
recheck-rocky:
jobs:
- gophercloud-acceptance-test-rocky
periodic: periodic:
jobs: jobs:
- gophercloud-unittest - gophercloud-unittest

View File

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

View File

@ -84,6 +84,12 @@ type AuthOptions struct {
// Scope determines the scoping of the authentication request. // Scope determines the scoping of the authentication request.
Scope *AuthScope `json:"-"` 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. // 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 { type userReq struct {
ID *string `json:"id,omitempty"` ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Password string `json:"password"` Password string `json:"password,omitempty"`
Domain *domainReq `json:"domain,omitempty"` Domain *domainReq `json:"domain,omitempty"`
} }
@ -154,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
ID string `json:"id"` 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 { type identityReq struct {
Methods []string `json:"methods"` Methods []string `json:"methods"`
Password *passwordReq `json:"password,omitempty"` Password *passwordReq `json:"password,omitempty"`
Token *tokenReq `json:"token,omitempty"` Token *tokenReq `json:"token,omitempty"`
ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
} }
type authReq struct { type authReq struct {
@ -194,8 +208,68 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s
req.Auth.Identity.Token = &tokenReq{ req.Auth.Identity.Token = &tokenReq{
ID: opts.TokenID, 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 { } 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{} return nil, ErrMissingPassword{}
} }
} else { } 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"} opts := gophercloud.EndpointOpts{Region: "RegionOne"}
client := openstack.NewComputeV2(provider, opts) client, err := openstack.NewComputeV2(provider, opts)
Resources Resources

View File

@ -451,3 +451,10 @@ type ErrScopeEmpty struct{ BaseError }
func (e ErrScopeEmpty) Error() string { func (e ErrScopeEmpty) Error() string {
return "You must provide either a Project or Domain in a Scope" 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") tenantName := os.Getenv("OS_TENANT_NAME")
domainID := os.Getenv("OS_DOMAIN_ID") domainID := os.Getenv("OS_DOMAIN_ID")
domainName := os.Getenv("OS_DOMAIN_NAME") 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 OS_PROJECT_ID is set, overwrite tenantID with the value.
if v := os.Getenv("OS_PROJECT_ID"); v != "" { if v := os.Getenv("OS_PROJECT_ID"); v != "" {
@ -56,29 +59,55 @@ func AuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
return nilOptions, err return nilOptions, err
} }
if username == "" && userID == "" { if userID == "" && username == "" {
err := gophercloud.ErrMissingAnyoneOfEnvironmentVariables{ // Empty username and userID could be ignored, when applicationCredentialID and applicationCredentialSecret are set
EnvironmentVariables: []string{"OS_USERNAME", "OS_USERID"}, 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{ err := gophercloud.ErrMissingEnvironmentVariable{
EnvironmentVariable: "OS_PASSWORD", EnvironmentVariable: "OS_PASSWORD",
} }
return nilOptions, err 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{ ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL, IdentityEndpoint: authURL,
UserID: userID, UserID: userID,
Username: username, Username: username,
Password: password, Password: password,
TenantID: tenantID, TenantID: tenantID,
TenantName: tenantName, TenantName: tenantName,
DomainID: domainID, DomainID: domainID,
DomainName: domainName, DomainName: domainName,
ApplicationCredentialID: applicationCredentialID,
ApplicationCredentialName: applicationCredentialName,
ApplicationCredentialSecret: applicationCredentialSecret,
} }
return ao, nil return ao, nil

View File

@ -61,9 +61,37 @@ func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r Create
return 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. // Delete will delete the existing Volume with the provided ID.
func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) { func Delete(client *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) (r DeleteResult) {
_, r.Err = client.Delete(deleteURL(client, id), nil) 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 return
} }
@ -145,8 +173,8 @@ type UpdateOptsBuilder interface {
// to the volumes.Update function. For more information about the parameters, see // to the volumes.Update function. For more information about the parameters, see
// the Volume object. // the Volume object.
type UpdateOpts struct { type UpdateOpts struct {
Name string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
Description string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
Metadata map[string]string `json:"metadata,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) result := tokens2.Create(v2Client, v2Opts)
token, err := result.ExtractToken() err = client.SetTokenAndAuthResult(result)
if err != nil { if err != nil {
return err 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`, // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once // this should retry authentication only once
tac := *client tac := *client
tac.SetThrowaway(true)
tac.ReauthFunc = nil tac.ReauthFunc = nil
tac.TokenID = "" tac.SetTokenAndAuthResult(nil)
tao := options tao := options
tao.AllowReauth = false tao.AllowReauth = false
client.ReauthFunc = func() error { client.ReauthFunc = func() error {
@ -159,11 +160,10 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
if err != nil { if err != nil {
return err return err
} }
client.TokenID = tac.TokenID client.CopyTokenFrom(&tac)
return nil return nil
} }
} }
client.TokenID = token.ID
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) { client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
return V2EndpointURL(catalog, opts) return V2EndpointURL(catalog, opts)
} }
@ -189,7 +189,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
result := tokens3.Create(v3Client, opts) result := tokens3.Create(v3Client, opts)
token, err := result.ExtractToken() err = client.SetTokenAndAuthResult(result)
if err != nil { if err != nil {
return err return err
} }
@ -199,15 +199,14 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
return err return err
} }
client.TokenID = token.ID
if opts.CanReauth() { if opts.CanReauth() {
// here we're creating a throw-away client (tac). it's a copy of the user's provider client, but // 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`, // with the token and reauth func zeroed out. combined with setting `AllowReauth` to `false`,
// this should retry authentication only once // this should retry authentication only once
tac := *client tac := *client
tac.SetThrowaway(true)
tac.ReauthFunc = nil tac.ReauthFunc = nil
tac.TokenID = "" tac.SetTokenAndAuthResult(nil)
var tao tokens3.AuthOptionsBuilder var tao tokens3.AuthOptionsBuilder
switch ot := opts.(type) { switch ot := opts.(type) {
case *gophercloud.AuthOptions: case *gophercloud.AuthOptions:
@ -226,7 +225,7 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, opts tokens3.Au
if err != nil { if err != nil {
return err return err
} }
client.TokenID = tac.TokenID client.CopyTokenFrom(&tac)
return nil return nil
} }
} }
@ -305,6 +304,12 @@ func initClientOpts(client *gophercloud.ProviderClient, eo gophercloud.EndpointO
return sc, nil 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 // NewObjectStorageV1 creates a ServiceClient that may be used with the v1
// object storage package. // object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) { 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) { func NewContainerInfraV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
return initClientOpts(client, eo, "container-infra") 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 = [ srcs = [
"doc.go", "doc.go",
"errors.go", "errors.go",
"microversions.go",
"requests.go", "requests.go",
"results.go", "results.go",
"urls.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 // ServiceClient will allow calls to be made to retrieve an image or
// flavor ID by name. // flavor ID by name.
ServiceClient *gophercloud.ServiceClient `json:"-"` 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 // ToServerCreateMap assembles a request body based on the contents of a

View File

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

View File

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

View File

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

View File

@ -135,6 +135,21 @@ func (r CreateResult) ExtractToken() (*Token, error) {
}, nil }, 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 // ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token. // with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) { func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {

View File

@ -52,19 +52,28 @@ type AuthOptions struct {
// authentication token ID. // authentication token ID.
TokenID string `json:"-"` 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:"-"` Scope Scope `json:"-"`
} }
// ToTokenV3CreateMap builds a request body from AuthOptions. // ToTokenV3CreateMap builds a request body from AuthOptions.
func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) { func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
gophercloudAuthOpts := gophercloud.AuthOptions{ gophercloudAuthOpts := gophercloud.AuthOptions{
Username: opts.Username, Username: opts.Username,
UserID: opts.UserID, UserID: opts.UserID,
Password: opts.Password, Password: opts.Password,
DomainID: opts.DomainID, DomainID: opts.DomainID,
DomainName: opts.DomainName, DomainName: opts.DomainName,
AllowReauth: opts.AllowReauth, AllowReauth: opts.AllowReauth,
TokenID: opts.TokenID, TokenID: opts.TokenID,
ApplicationCredentialID: opts.ApplicationCredentialID,
ApplicationCredentialName: opts.ApplicationCredentialName,
ApplicationCredentialSecret: opts.ApplicationCredentialSecret,
} }
return gophercloudAuthOpts.ToTokenV3CreateMap(scope) return gophercloudAuthOpts.ToTokenV3CreateMap(scope)

View File

@ -102,6 +102,13 @@ func (r commonResult) ExtractToken() (*Token, error) {
return &s, err 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 // ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token. // with the user's Token.
func (r commonResult) ExtractServiceCatalog() (*ServiceCatalog, error) { 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"], visibility = ["//visibility:public"],
deps = [ deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination: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" listenerID := "d67d56a6-4a86-4688-a282-f46444705c64"
i1001 := 1001 i1001 := 1001
i181000 := 181000
updateOpts := listeners.UpdateOpts{ updateOpts := listeners.UpdateOpts{
ConnLimit: &i1001, ConnLimit: &i1001,
TimeoutClientData: &i181000,
TimeoutMemberData: &i181000,
} }
listener, err := listeners.Update(networkClient, listenerID, updateOpts).Extract() listener, err := listeners.Update(networkClient, listenerID, updateOpts).Extract()

View File

@ -11,6 +11,8 @@ type Protocol string
// Supported attributes for create/update operations. // Supported attributes for create/update operations.
const ( const (
ProtocolTCP Protocol = "TCP" ProtocolTCP Protocol = "TCP"
ProtocolUDP Protocol = "UDP"
ProtocolPROXY Protocol = "PROXY"
ProtocolHTTP Protocol = "HTTP" ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS" ProtocolHTTPS Protocol = "HTTPS"
) )
@ -27,19 +29,21 @@ type ListOptsBuilder interface {
// sort by a particular listener attribute. SortDir sets the direction, and is // sort by a particular listener attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination. // either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct { type ListOpts struct {
ID string `q:"id"` ID string `q:"id"`
Name string `q:"name"` Name string `q:"name"`
AdminStateUp *bool `q:"admin_state_up"` AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"` ProjectID string `q:"project_id"`
LoadbalancerID string `q:"loadbalancer_id"` LoadbalancerID string `q:"loadbalancer_id"`
DefaultPoolID string `q:"default_pool_id"` DefaultPoolID string `q:"default_pool_id"`
Protocol string `q:"protocol"` Protocol string `q:"protocol"`
ProtocolPort int `q:"protocol_port"` ProtocolPort int `q:"protocol_port"`
ConnectionLimit int `q:"connection_limit"` ConnectionLimit int `q:"connection_limit"`
Limit int `q:"limit"` Limit int `q:"limit"`
Marker string `q:"marker"` Marker string `q:"marker"`
SortKey string `q:"sort_key"` SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"` 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. // 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) // The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN). // or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"` 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. // ToListenerCreateMap builds a request body from CreateOpts.
@ -149,10 +159,13 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options for updating a Listener. // UpdateOpts represents options for updating a Listener.
type UpdateOpts struct { type UpdateOpts struct {
// Human-readable name for the Listener. Does not have to be unique. // 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. // 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. // The maximum number of connections allowed for the Listener.
ConnLimit *int `json:"connection_limit,omitempty"` 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) // The administrative state of the Listener. A valid value is true (UP)
// or false (DOWN). // or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"` 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. // ToListenerUpdateMap builds a request body from UpdateOpts.
func (opts UpdateOpts) ToListenerUpdateMap() (map[string]interface{}, error) { 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 // Update is an operation which modifies the attributes of the specified

View File

@ -2,6 +2,7 @@ package listeners
import ( import (
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -55,9 +56,18 @@ type Listener struct {
// Pools are the pools which are part of this listener. // Pools are the pools which are part of this listener.
Pools []pools.Pool `json:"pools"` 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. // The provisioning status of the Listener.
// This value is ACTIVE, PENDING_* or ERROR. // This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"` 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 { type Stats struct {

View File

@ -14,6 +14,7 @@ go_library(
deps = [ deps = [
"//vendor/github.com/gophercloud/gophercloud:go_default_library", "//vendor/github.com/gophercloud/gophercloud:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners:go_default_library", "//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools:go_default_library",
"//vendor/github.com/gophercloud/gophercloud/pagination: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", VipAddress: "10.30.176.48",
Flavor: "medium", Flavor: "medium",
Provider: "haproxy", Provider: "haproxy",
Tags: []string{"test", "stage"},
} }
lb, err := loadbalancers.Create(networkClient, createOpts).Extract() lb, err := loadbalancers.Create(networkClient, createOpts).Extract()
@ -41,12 +42,10 @@ Example to Create a Load Balancer
Example to Update a Load Balancer Example to Update a Load Balancer
lbID := "d67d56a6-4a86-4688-a282-f46444705c64" lbID := "d67d56a6-4a86-4688-a282-f46444705c64"
name := "new-name"
i1001 := 1001
updateOpts := loadbalancers.UpdateOpts{ updateOpts := loadbalancers.UpdateOpts{
Name: "new-name", Name: &name,
} }
lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract() lb, err := loadbalancers.Update(networkClient, lbID, updateOpts).Extract()
if err != nil { if err != nil {
panic(err) panic(err)
@ -80,5 +79,14 @@ Example to Get the Statistics of a Load Balancer
if err != nil { if err != nil {
panic(err) 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 package loadbalancers

View File

@ -17,22 +17,27 @@ type ListOptsBuilder interface {
// sort by a particular attribute. SortDir sets the direction, and is // sort by a particular attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination. // either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct { type ListOpts struct {
Description string `q:"description"` Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"` AdminStateUp *bool `q:"admin_state_up"`
ProjectID string `q:"project_id"` ProjectID string `q:"project_id"`
ProvisioningStatus string `q:"provisioning_status"` ProvisioningStatus string `q:"provisioning_status"`
VipAddress string `q:"vip_address"` VipAddress string `q:"vip_address"`
VipPortID string `q:"vip_port_id"` VipPortID string `q:"vip_port_id"`
VipSubnetID string `q:"vip_subnet_id"` VipSubnetID string `q:"vip_subnet_id"`
ID string `q:"id"` VipNetworkID string `q:"vip_network_id"`
OperatingStatus string `q:"operating_status"` ID string `q:"id"`
Name string `q:"name"` OperatingStatus string `q:"operating_status"`
Flavor string `q:"flavor"` Name string `q:"name"`
Provider string `q:"provider"` Flavor string `q:"flavor"`
Limit int `q:"limit"` Provider string `q:"provider"`
Marker string `q:"marker"` Limit int `q:"limit"`
SortKey string `q:"sort_key"` Marker string `q:"marker"`
SortDir string `q:"sort_dir"` 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. // ToLoadBalancerListQuery formats a ListOpts into a query string.
@ -76,11 +81,16 @@ type CreateOpts struct {
// Human-readable description for the Loadbalancer. // Human-readable description for the Loadbalancer.
Description string `json:"description,omitempty"` 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 // only create Loadbalancers on networks authorized by policy (e.g. networks
// that belong to them or networks that are shared). // that belong to them or networks that are shared).
VipSubnetID string `json:"vip_subnet_id" required:"true"` 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. // ProjectID is the UUID of the project who owns the Loadbalancer.
// Only administrative users can specify a project UUID other than their own. // Only administrative users can specify a project UUID other than their own.
ProjectID string `json:"project_id,omitempty"` ProjectID string `json:"project_id,omitempty"`
@ -97,6 +107,9 @@ type CreateOpts struct {
// The name of the provider. // The name of the provider.
Provider string `json:"provider,omitempty"` Provider string `json:"provider,omitempty"`
// Tags is a set of resource tags.
Tags []string `json:"tags,omitempty"`
} }
// ToLoadBalancerCreateMap builds a request body from CreateOpts. // ToLoadBalancerCreateMap builds a request body from CreateOpts.
@ -134,14 +147,17 @@ type UpdateOptsBuilder interface {
// operation. // operation.
type UpdateOpts struct { type UpdateOpts struct {
// Human-readable name for the Loadbalancer. Does not have to be unique. // 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. // 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) // The administrative state of the Loadbalancer. A valid value is true (UP)
// or false (DOWN). // or false (DOWN).
AdminStateUp *bool `json:"admin_state_up,omitempty"` 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. // 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) _, r.Err = c.Get(statisticsRootURL(c, id), &r.Body, nil)
return 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 ( import (
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/listeners"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/pools"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
) )
@ -34,6 +35,10 @@ type LoadBalancer struct {
// Loadbalancer address. // Loadbalancer address.
VipSubnetID string `json:"vip_subnet_id"` 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. // The unique ID for the LoadBalancer.
ID string `json:"id"` ID string `json:"id"`
@ -51,6 +56,13 @@ type LoadBalancer struct {
// Listeners are the listeners related to this Loadbalancer. // Listeners are the listeners related to this Loadbalancer.
Listeners []listeners.Listener `json:"listeners"` 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. // StatusTree represents the status of a loadbalancer.
@ -180,3 +192,9 @@ type UpdateResult struct {
type DeleteResult struct { type DeleteResult struct {
gophercloud.ErrResult 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" resourcePath = "loadbalancers"
statusPath = "status" statusPath = "status"
statisticsPath = "stats" statisticsPath = "stats"
failoverPath = "failover"
) )
func rootURL(c *gophercloud.ServiceClient) string { 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 { func statisticsRootURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, resourcePath, id, statisticsPath) 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"` ExpectedCodes string `json:"expected_codes,omitempty"`
// The Name of the Monitor. // 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) // The administrative state of the Monitor. A valid value is true (UP)
// or false (DOWN). // or false (DOWN).

View File

@ -74,6 +74,9 @@ type Monitor struct {
// The provisioning status of the Monitor. // The provisioning status of the Monitor.
// This value is ACTIVE, PENDING_* or ERROR. // This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"` 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 // 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" poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
weight := 10
createOpts := pools.CreateMemberOpts{ createOpts := pools.CreateMemberOpts{
Name: "db", Name: "db",
SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9", SubnetID: "1981f108-3c48-48d2-b908-30f7d28532c9",
Address: "10.0.2.11", Address: "10.0.2.11",
ProtocolPort: 80, ProtocolPort: 80,
Weight: 10, Weight: &weight,
} }
member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract() member, err := pools.CreateMember(networkClient, poolID, createOpts).Extract()
@ -101,9 +102,10 @@ Example to Update a Member
poolID := "d67d56a6-4a86-4688-a282-f46444705c64" poolID := "d67d56a6-4a86-4688-a282-f46444705c64"
memberID := "64dba99f-8af8-4200-8882-e32a0660f23e" memberID := "64dba99f-8af8-4200-8882-e32a0660f23e"
weight := 4
updateOpts := pools.UpdateMemberOpts{ updateOpts := pools.UpdateMemberOpts{
Name: "new-name", Name: "new-name",
Weight: 4, Weight: &weight,
} }
member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract() member, err := pools.UpdateMember(networkClient, poolID, memberID, updateOpts).Extract()
@ -120,5 +122,33 @@ Example to Delete a Member
if err != nil { if err != nil {
panic(err) 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 package pools

View File

@ -66,6 +66,8 @@ const (
LBMethodSourceIp LBMethod = "SOURCE_IP" LBMethodSourceIp LBMethod = "SOURCE_IP"
ProtocolTCP Protocol = "TCP" ProtocolTCP Protocol = "TCP"
ProtocolUDP Protocol = "UDP"
ProtocolPROXY Protocol = "PROXY"
ProtocolHTTP Protocol = "HTTP" ProtocolHTTP Protocol = "HTTP"
ProtocolHTTPS Protocol = "HTTPS" ProtocolHTTPS Protocol = "HTTPS"
) )
@ -85,7 +87,7 @@ type CreateOpts struct {
LBMethod LBMethod `json:"lb_algorithm" required:"true"` LBMethod LBMethod `json:"lb_algorithm" required:"true"`
// The protocol used by the pool members, you can use either // 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"` Protocol Protocol `json:"protocol" required:"true"`
// The Loadbalancer on which the members of the pool will be associated with. // The Loadbalancer on which the members of the pool will be associated with.
@ -148,10 +150,10 @@ type UpdateOptsBuilder interface {
// operation. // operation.
type UpdateOpts struct { type UpdateOpts struct {
// Name of the pool. // Name of the pool.
Name string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
// Human-readable description for the pool. // 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 // The algorithm used to distribute load between the members of the pool. The
// current specification supports LBMethodRoundRobin, LBMethodLeastConnections // current specification supports LBMethodRoundRobin, LBMethodLeastConnections
@ -261,10 +263,10 @@ type CreateMemberOpts struct {
ProjectID string `json:"project_id,omitempty"` ProjectID string `json:"project_id,omitempty"`
// A positive integer value that indicates the relative portion of traffic // A positive integer value that indicates the relative portion of traffic
// that this member should receive from the pool. For example, a member with // 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 // a weight of 10 receives five times as much traffic as a member with a
// weight of 2. // 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 // If you omit this parameter, LBaaS uses the vip_subnet_id parameter value
// for the subnet UUID. // for the subnet UUID.
@ -307,13 +309,13 @@ type UpdateMemberOptsBuilder interface {
// operation. // operation.
type UpdateMemberOpts struct { type UpdateMemberOpts struct {
// Name of the Member. // 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 // A positive integer value that indicates the relative portion of traffic
// that this member should receive from the pool. For example, a member with // 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 // a weight of 10 receives five times as much traffic as a member with a
// weight of 2. // 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) // The administrative state of the Pool. A valid value is true (UP)
// or false (DOWN). // or false (DOWN).
@ -338,6 +340,36 @@ func UpdateMember(c *gophercloud.ServiceClient, poolID string, memberID string,
return 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 // DisassociateMember will remove and disassociate a Member from a particular
// Pool. // Pool.
func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) { func DeleteMember(c *gophercloud.ServiceClient, poolID string, memberID string) (r DeleteMemberResult) {

View File

@ -1,6 +1,9 @@
package pools package pools
import ( import (
"encoding/json"
"time"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors" "github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/monitors"
"github.com/gophercloud/gophercloud/pagination" "github.com/gophercloud/gophercloud/pagination"
@ -96,6 +99,9 @@ type Pool struct {
// The provisioning status of the pool. // The provisioning status of the pool.
// This value is ACTIVE, PENDING_* or ERROR. // This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"` 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 // 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. // The provisioning status of the pool.
// This value is ACTIVE, PENDING_* or ERROR. // This value is ACTIVE, PENDING_* or ERROR.
ProvisioningStatus string `json:"provisioning_status"` 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 // MemberPage is the page returned by a pager when traversing over a
@ -247,6 +271,23 @@ type commonMemberResult struct {
gophercloud.Result 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. // ExtractMember is a function that accepts a result and extracts a member.
func (r commonMemberResult) Extract() (*Member, error) { func (r commonMemberResult) Extract() (*Member, error) {
var s struct { var s struct {
@ -274,6 +315,12 @@ type UpdateMemberResult struct {
commonMemberResult 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. // DeleteMemberResult represents the result of a DeleteMember operation.
// Call its ExtractErr method to determine if the request succeeded or failed. // Call its ExtractErr method to determine if the request succeeded or failed.
type DeleteMemberResult struct { type DeleteMemberResult struct {

View File

@ -52,7 +52,7 @@ Example to Disassociate a Floating IP with a Port
fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7" fipID := "2f245a7b-796b-4f26-9cf9-9e82d248fda7"
updateOpts := floatingips.UpdateOpts{ updateOpts := floatingips.UpdateOpts{
PortID: nil, PortID: new(string),
} }
fip, err := floatingips.Update(networkingClient, fipID, updateOpts).Extract() 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. // either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct { type ListOpts struct {
ID string `q:"id"` ID string `q:"id"`
Description string `q:"description"`
FloatingNetworkID string `q:"floating_network_id"` FloatingNetworkID string `q:"floating_network_id"`
PortID string `q:"port_id"` PortID string `q:"port_id"`
FixedIP string `q:"fixed_ip_address"` FixedIP string `q:"fixed_ip_address"`
@ -24,6 +25,10 @@ type ListOpts struct {
SortDir string `q:"sort_dir"` SortDir string `q:"sort_dir"`
RouterID string `q:"router_id"` RouterID string `q:"router_id"`
Status string `q:"status"` 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 // 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 // resource. The only required fields are FloatingNetworkID and PortID which
// refer to the external network and internal port respectively. // refer to the external network and internal port respectively.
type CreateOpts struct { type CreateOpts struct {
Description string `json:"description,omitempty"`
FloatingNetworkID string `json:"floating_network_id" required:"true"` FloatingNetworkID string `json:"floating_network_id" required:"true"`
FloatingIP string `json:"floating_ip_address,omitempty"` FloatingIP string `json:"floating_ip_address,omitempty"`
PortID string `json:"port_id,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 // 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. // ID. To disassociate the floating IP from all ports, provide an empty string.
type UpdateOpts struct { 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 // ToFloatingIPUpdateMap allows UpdateOpts to satisfy the UpdateOptsBuilder
// interface // interface
func (opts UpdateOpts) ToFloatingIPUpdateMap() (map[string]interface{}, error) { 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 // 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 is the unique identifier for the floating IP instance.
ID string `json:"id"` 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 // FloatingNetworkID is the UUID of the external network where the floating
// IP is to be created. // IP is to be created.
FloatingNetworkID string `json:"floating_network_id"` 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 is the ID of the router used for this floating IP.
RouterID string `json:"router_id"` RouterID string `json:"router_id"`
// Tags optionally set via extensions/attributestags
Tags []string `json:"tags"`
} }
type commonResult struct { type commonResult struct {

View File

@ -13,6 +13,7 @@ import (
type ListOpts struct { type ListOpts struct {
ID string `q:"id"` ID string `q:"id"`
Name string `q:"name"` Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"` AdminStateUp *bool `q:"admin_state_up"`
Distributed *bool `q:"distributed"` Distributed *bool `q:"distributed"`
Status string `q:"status"` Status string `q:"status"`
@ -22,6 +23,10 @@ type ListOpts struct {
Marker string `q:"marker"` Marker string `q:"marker"`
SortKey string `q:"sort_key"` SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"` 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 // List returns a Pager which allows you to iterate over a collection of
@ -51,6 +56,7 @@ type CreateOptsBuilder interface {
// no required values. // no required values.
type CreateOpts struct { type CreateOpts struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"` Distributed *bool `json:"distributed,omitempty"`
TenantID string `json:"tenant_id,omitempty"` TenantID string `json:"tenant_id,omitempty"`
@ -97,6 +103,7 @@ type UpdateOptsBuilder interface {
// UpdateOpts contains the values used when updating a router. // UpdateOpts contains the values used when updating a router.
type UpdateOpts struct { 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"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
Distributed *bool `json:"distributed,omitempty"` Distributed *bool `json:"distributed,omitempty"`
GatewayInfo *GatewayInfo `json:"external_gateway_info,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 // ExternalFixedIP is the IP address and subnet ID of the external gateway of a
// router. // router.
type ExternalFixedIP struct { type ExternalFixedIP struct {
IPAddress string `json:"ip_address"` IPAddress string `json:"ip_address,omitempty"`
SubnetID string `json:"subnet_id"` SubnetID string `json:"subnet_id"`
} }
@ -51,6 +51,9 @@ type Router struct {
// unique. // unique.
Name string `json:"name"` Name string `json:"name"`
// Description for the router.
Description string `json:"description"`
// ID is the unique identifier for the router. // ID is the unique identifier for the router.
ID string `json:"id"` 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. // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available. // Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"` 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 // 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 // sort by a particular network attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination. // either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct { type ListOpts struct {
ID string `q:"id"` ID string `q:"id"`
Name string `q:"name"` Name string `q:"name"`
TenantID string `q:"tenant_id"` Description string `q:"description"`
ProjectID string `q:"project_id"` TenantID string `q:"tenant_id"`
Limit int `q:"limit"` ProjectID string `q:"project_id"`
Marker string `q:"marker"` Limit int `q:"limit"`
SortKey string `q:"sort_key"` Marker string `q:"marker"`
SortDir string `q:"sort_dir"` 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 // 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"` Name string `json:"name,omitempty"`
// Describes the security group. // Describes the security group.
Description string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
} }
// ToSecGroupUpdateMap builds a request body from UpdateOpts. // 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 is the project owner of the security group.
ProjectID string `json:"project_id"` 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 // 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"` Direction string `q:"direction"`
EtherType string `q:"ethertype"` EtherType string `q:"ethertype"`
ID string `q:"id"` ID string `q:"id"`
Description string `q:"description"`
PortRangeMax int `q:"port_range_max"` PortRangeMax int `q:"port_range_max"`
PortRangeMin int `q:"port_range_min"` PortRangeMin int `q:"port_range_min"`
Protocol string `q:"protocol"` Protocol string `q:"protocol"`
@ -88,6 +89,9 @@ type CreateOpts struct {
// group rule is applied. // group rule is applied.
Direction RuleDirection `json:"direction" required:"true"` 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 // Must be "IPv4" or "IPv6", and addresses represented in CIDR must match the
// ingress or egress rules. // ingress or egress rules.
EtherType RuleEtherType `json:"ethertype" required:"true"` 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. // instance. An egress rule is applied to traffic leaving the instance.
Direction string Direction string
// Descripton of the rule
Description string `json:"description"`
// Must be IPv4 or IPv6, and addresses represented in CIDR must match the // Must be IPv4 or IPv6, and addresses represented in CIDR must match the
// ingress or egress rules. // ingress or egress rules.
EtherType string `json:"ethertype"` EtherType string `json:"ethertype"`

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct { type ListOpts struct {
Status string `q:"status"` Status string `q:"status"`
Name string `q:"name"` Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"` AdminStateUp *bool `q:"admin_state_up"`
TenantID string `q:"tenant_id"` TenantID string `q:"tenant_id"`
ProjectID string `q:"project_id"` ProjectID string `q:"project_id"`
@ -28,6 +29,10 @@ type ListOpts struct {
Limit int `q:"limit"` Limit int `q:"limit"`
SortKey string `q:"sort_key"` SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"` 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. // ToNetworkListQuery formats a ListOpts into a query string.
@ -69,6 +74,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct { type CreateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"` Shared *bool `json:"shared,omitempty"`
TenantID string `json:"tenant_id,omitempty"` TenantID string `json:"tenant_id,omitempty"`
ProjectID string `json:"project_id,omitempty"` ProjectID string `json:"project_id,omitempty"`
@ -105,9 +111,10 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents options used to update a network. // UpdateOpts represents options used to update a network.
type UpdateOpts struct { type UpdateOpts struct {
AdminStateUp *bool `json:"admin_state_up,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Shared *bool `json:"shared,omitempty"` Description *string `json:"description,omitempty"`
Shared *bool `json:"shared,omitempty"`
} }
// ToNetworkUpdateMap builds a request body from UpdateOpts. // 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. // Human-readable name for the network. Might not be unique.
Name string `json:"name"` Name string `json:"name"`
// Description for the network
Description string `json:"description"`
// The administrative state of network. If false (down), the network does not // The administrative state of network. If false (down), the network does not
// forward packets. // forward packets.
AdminStateUp bool `json:"admin_state_up"` 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. // Availability zone hints groups network nodes that run services like DHCP, L3, FW, and others.
// Used to make network resources highly available. // Used to make network resources highly available.
AvailabilityZoneHints []string `json:"availability_zone_hints"` 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 // NetworkPage is the page returned by a pager when traversing over a

View File

@ -19,6 +19,7 @@ type ListOptsBuilder interface {
type ListOpts struct { type ListOpts struct {
Status string `q:"status"` Status string `q:"status"`
Name string `q:"name"` Name string `q:"name"`
Description string `q:"description"`
AdminStateUp *bool `q:"admin_state_up"` AdminStateUp *bool `q:"admin_state_up"`
NetworkID string `q:"network_id"` NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"` TenantID string `q:"tenant_id"`
@ -31,6 +32,10 @@ type ListOpts struct {
Marker string `q:"marker"` Marker string `q:"marker"`
SortKey string `q:"sort_key"` SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"` 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. // ToPortListQuery formats a ListOpts into a query string.
@ -76,6 +81,7 @@ type CreateOptsBuilder interface {
type CreateOpts struct { type CreateOpts struct {
NetworkID string `json:"network_id" required:"true"` NetworkID string `json:"network_id" required:"true"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AdminStateUp *bool `json:"admin_state_up,omitempty"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
MACAddress string `json:"mac_address,omitempty"` MACAddress string `json:"mac_address,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"` FixedIPs interface{} `json:"fixed_ips,omitempty"`
@ -112,11 +118,12 @@ type UpdateOptsBuilder interface {
// UpdateOpts represents the attributes used when updating an existing port. // UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct { 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"` AdminStateUp *bool `json:"admin_state_up,omitempty"`
FixedIPs interface{} `json:"fixed_ips,omitempty"` FixedIPs interface{} `json:"fixed_ips,omitempty"`
DeviceID string `json:"device_id,omitempty"` DeviceID *string `json:"device_id,omitempty"`
DeviceOwner string `json:"device_owner,omitempty"` DeviceOwner *string `json:"device_owner,omitempty"`
SecurityGroups *[]string `json:"security_groups,omitempty"` SecurityGroups *[]string `json:"security_groups,omitempty"`
AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,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. // Human-readable name for the port. Might not be unique.
Name string `json:"name"` Name string `json:"name"`
// Describes the port.
Description string `json:"description"`
// Administrative state of port. If false (down), port does not forward // Administrative state of port. If false (down), port does not forward
// packets. // packets.
AdminStateUp bool `json:"admin_state_up"` AdminStateUp bool `json:"admin_state_up"`
@ -101,6 +104,9 @@ type Port struct {
// Identifies the list of IP addresses the port will recognize/accept // Identifies the list of IP addresses the port will recognize/accept
AllowedAddressPairs []AddressPair `json:"allowed_address_pairs"` 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 // 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. // `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct { type ListOpts struct {
Name string `q:"name"` Name string `q:"name"`
Description string `q:"description"`
EnableDHCP *bool `q:"enable_dhcp"` EnableDHCP *bool `q:"enable_dhcp"`
NetworkID string `q:"network_id"` NetworkID string `q:"network_id"`
TenantID string `q:"tenant_id"` TenantID string `q:"tenant_id"`
@ -33,6 +34,10 @@ type ListOpts struct {
Marker string `q:"marker"` Marker string `q:"marker"`
SortKey string `q:"sort_key"` SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"` 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. // 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 is a human-readable name of the subnet.
Name string `json:"name,omitempty"` 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 // The UUID of the project who owns the Subnet. Only administrative users
// can specify a project UUID other than their own. // can specify a project UUID other than their own.
TenantID string `json:"tenant_id,omitempty"` 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 is the id of the subnet pool that subnet should be associated to.
SubnetPoolID string `json:"subnetpool_id,omitempty"` 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. // ToSubnetCreateMap builds a request body from CreateOpts.
@ -163,6 +175,9 @@ type UpdateOpts struct {
// Name is a human-readable name of the subnet. // Name is a human-readable name of the subnet.
Name string `json:"name,omitempty"` 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 are IP Address pools that will be available for DHCP.
AllocationPools []AllocationPool `json:"allocation_pools,omitempty"` 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. // Human-readable name for the subnet. Might not be unique.
Name string `json:"name"` Name string `json:"name"`
// Description for the subnet.
Description string `json:"description"`
// IP version, either `4' or `6'. // IP version, either `4' or `6'.
IPVersion int `json:"ip_version"` 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 is the id of the subnet pool associated with the subnet.
SubnetPoolID string `json:"subnetpool_id"` 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 // 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"` DetectContentType bool `h:"X-Detect-Content-Type"`
IfNoneMatch string `h:"If-None-Match"` IfNoneMatch string `h:"If-None-Match"`
VersionsLocation string `h:"X-Versions-Location"` VersionsLocation string `h:"X-Versions-Location"`
HistoryLocation string `h:"X-History-Location"`
} }
// ToContainerCreateMap formats a CreateOpts into a map of headers. // ToContainerCreateMap formats a CreateOpts into a map of headers.
@ -137,6 +138,8 @@ type UpdateOpts struct {
DetectContentType bool `h:"X-Detect-Content-Type"` DetectContentType bool `h:"X-Detect-Content-Type"`
RemoveVersionsLocation string `h:"X-Remove-Versions-Location"` RemoveVersionsLocation string `h:"X-Remove-Versions-Location"`
VersionsLocation string `h:"X-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. // ToContainerUpdateMap formats a UpdateOpts into a map of headers.

View File

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

View File

@ -120,6 +120,22 @@ func BuildRequestBody(opts interface{}, parent string) (map[string]interface{},
continue 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 v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
if zero { if zero {
//fmt.Printf("value before change: %+v\n", optsValue.Field(i)) //fmt.Printf("value before change: %+v\n", optsValue.Field(i))

View File

@ -3,6 +3,7 @@ package gophercloud
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -71,26 +72,36 @@ type ProviderClient struct {
// authentication functions for different Identity service versions. // authentication functions for different Identity service versions.
ReauthFunc func() error 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 mut *sync.RWMutex
reauthmut *reauthlock reauthmut *reauthlock
authResult AuthResult
} }
type reauthlock struct { type reauthlock struct {
sync.RWMutex sync.RWMutex
reauthing bool reauthing bool
reauthingErr error
done *sync.Cond
} }
// AuthenticatedHeaders returns a map of HTTP headers that are common for all // 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) { func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
if client.IsThrowaway() {
return
}
if client.reauthmut != nil { if client.reauthmut != nil {
client.reauthmut.RLock() client.reauthmut.Lock()
if client.reauthmut.reauthing { for client.reauthmut.reauthing {
client.reauthmut.RUnlock() client.reauthmut.done.Wait()
return
} }
client.reauthmut.RUnlock() client.reauthmut.Unlock()
} }
t := client.Token() t := client.Token()
if t == "" { if t == "" {
@ -106,6 +117,20 @@ func (client *ProviderClient) UseTokenLock() {
client.reauthmut = new(reauthlock) 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 // 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 // call this method to access the token instead of the TokenID field
func (client *ProviderClient) Token() string { 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 // 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) { func (client *ProviderClient) SetToken(t string) {
if client.mut != nil { if client.mut != nil {
client.mut.Lock() client.mut.Lock()
defer client.mut.Unlock() defer client.mut.Unlock()
} }
client.TokenID = t client.TokenID = t
client.authResult = nil
} }
//Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is // SetTokenAndAuthResult safely sets the value of the auth token in the
//called because of a 401 response, the caller may pass the previous token. In // ProviderClient and also records the AuthResult that was returned from the
//this case, the reauthentication can be skipped if another thread has already // token creation request. Applications may call this in a custom ReauthFunc.
//reauthenticated in the meantime. If no previous token is known, an empty func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
//string should be passed instead to force unconditional reauthentication. 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) { func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.ReauthFunc == nil { if client.ReauthFunc == nil {
return nil return nil
@ -139,11 +222,25 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
if client.mut == nil { if client.mut == nil {
return client.ReauthFunc() 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() client.mut.Lock()
defer client.mut.Unlock() defer client.mut.Unlock()
client.reauthmut.Lock() client.reauthmut.Lock()
client.reauthmut.reauthing = true client.reauthmut.reauthing = true
client.reauthmut.done = sync.NewCond(client.reauthmut)
client.reauthmut.reauthingErr = nil
client.reauthmut.Unlock() client.reauthmut.Unlock()
if previousToken == "" || client.TokenID == previousToken { if previousToken == "" || client.TokenID == previousToken {
@ -152,6 +249,8 @@ func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
client.reauthmut.Lock() client.reauthmut.Lock()
client.reauthmut.reauthing = false client.reauthmut.reauthing = false
client.reauthmut.reauthingErr = err
client.reauthmut.done.Broadcast()
client.reauthmut.Unlock() client.reauthmut.Unlock()
return 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. // io.ReadSeeker as-is. Default the content-type to application/json.
if options.JSONBody != nil { if options.JSONBody != nil {
if options.RawBody != 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) 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 // Allow default OkCodes if none explicitly set
if options.OkCodes == nil { okc := options.OkCodes
options.OkCodes = defaultOkCodes(method) if okc == nil {
okc = defaultOkCodes(method)
} }
// Validate the HTTP response status. // Validate the HTTP response status.
var ok bool var ok bool
for _, code := range options.OkCodes { for _, code := range okc {
if resp.StatusCode == code { if resp.StatusCode == code {
ok = true ok = true
break break
@ -295,11 +395,7 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts)
seeker.Seek(0, 0) 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) resp, err = client.Request(method, url, options)
client.ReauthFunc = reauthFunc
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case *ErrUnexpectedResponseCode: 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 { if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
for _, v := range m[label].([]interface{}) { if mSlice, ok := m[label].([]interface{}); ok {
// For each iteration of the slice, we create a new struct. for _, v := range mSlice {
// This is to work around a bug where elements of a slice // For each iteration of the slice, we create a new struct.
// are reused and not overwritten when the same copy of the // This is to work around a bug where elements of a slice
// struct is used: // 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://github.com/golang/go/issues/21092
// https://play.golang.org/p/NHo3ywlPZli // https://github.com/golang/go/issues/24155
newType := reflect.New(typeOfV).Elem() // https://play.golang.org/p/NHo3ywlPZli
newType := reflect.New(typeOfV).Elem()
b, err := json.Marshal(v) 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)
if err != nil { if err != nil {
return err 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 // "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 opts.MoreHeaders["X-OpenStack-Manila-API-Version"] = client.Microversion
case "volume": case "volume":
opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion opts.MoreHeaders["X-OpenStack-Volume-API-Version"] = client.Microversion
case "baremetal":
opts.MoreHeaders["X-OpenStack-Ironic-API-Version"] = client.Microversion
} }
if client.Type != "" { if client.Type != "" {