Merge pull request #23131 from AlbertoPeon/up_gophercloud
Auto commit by PR queue bot
This commit is contained in:
commit
050980b472
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@ -908,8 +908,8 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rackspace/gophercloud",
|
||||
"Comment": "v1.0.0-665-gf928634",
|
||||
"Rev": "f92863476c034f851073599c09d90cd61ee95b3d"
|
||||
"Comment": "v1.0.0-842-g8992d74",
|
||||
"Rev": "8992d7483a06748dea706e4716d042a4a9e73918"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/russross/blackfriday",
|
||||
|
21
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
21
Godeps/_workspace/src/github.com/rackspace/gophercloud/.travis.yml
generated
vendored
@ -1,17 +1,20 @@
|
||||
language: go
|
||||
sudo: false
|
||||
install:
|
||||
- go get golang.org/x/crypto/ssh
|
||||
- go get -v -tags 'fixtures acceptance' ./...
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
script: script/cibuild
|
||||
after_success:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
env:
|
||||
- COVERALLS_TOKEN=2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- export PATH=$PATH:$HOME/gopath/bin/
|
||||
- goveralls 2k7PTU3xa474Hymwgdj6XjqenNfGTNkO8
|
||||
sudo: false
|
||||
- go get github.com/pierrre/gotestcover
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/gotestcover -v -tags=fixtures -coverprofile=cover.out ./...
|
||||
after_success:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out
|
||||
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/CONTRIBUTING.md
generated
vendored
@ -11,7 +11,8 @@ As a contributor you will need to setup your workspace in a slightly different
|
||||
way than just downloading it. Here are the basic installation instructions:
|
||||
|
||||
1. Configure your `$GOPATH` and run `go get` as described in the main
|
||||
[README](/README.md#how-to-install).
|
||||
[README](/README.md#how-to-install) but add `-tags "fixtures acceptance"` to
|
||||
get dependencies for unit and acceptance tests.
|
||||
|
||||
2. Move into the directory that houses your local repository:
|
||||
|
||||
@ -158,25 +159,25 @@ deleted after the test suite finishes.
|
||||
To run all tests:
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
go test -tags fixtures ./...
|
||||
```
|
||||
|
||||
To run all tests with verbose output:
|
||||
|
||||
```bash
|
||||
go test -v ./...
|
||||
go test -v -tags fixtures ./...
|
||||
```
|
||||
|
||||
To run tests that match certain [build tags]():
|
||||
|
||||
```bash
|
||||
go test -tags "foo bar" ./...
|
||||
go test -tags "fixtures foo bar" ./...
|
||||
```
|
||||
|
||||
To run tests for a particular sub-package:
|
||||
|
||||
```bash
|
||||
cd ./path/to/package && go test .
|
||||
cd ./path/to/package && go test -tags fixtures .
|
||||
```
|
||||
|
||||
## Basic style guide
|
||||
|
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.md
generated
vendored
4
Godeps/_workspace/src/github.com/rackspace/gophercloud/README.md
generated
vendored
@ -1,5 +1,5 @@
|
||||
# Gophercloud: the OpenStack SDK for Go
|
||||
[](https://travis-ci.org/rackspace/gophercloud)
|
||||
# Gophercloud: an OpenStack SDK for Go
|
||||
[](https://travis-ci.org/rackspace/gophercloud) [](https://coveralls.io/r/rackspace/gophercloud)
|
||||
|
||||
Gophercloud is a flexible SDK that allows you to consume and work with OpenStack
|
||||
clouds in a simple and idiomatic way using golang. Many services are supported,
|
||||
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/pkg.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/blockstorage/v1/pkg.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// The v1 package contains acceptance tests for the Openstack Cinder V1 service.
|
||||
|
||||
package v1
|
70
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/db/v1/common.go
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/db/v1/common.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
// +build acceptance db
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/instances"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
)
|
||||
|
||||
func newClient(t *testing.T) *gophercloud.ServiceClient {
|
||||
ao, err := openstack.AuthOptionsFromEnv()
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
client, err := openstack.AuthenticatedClient(ao)
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
c, err := openstack.NewDBV1(client, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type context struct {
|
||||
test *testing.T
|
||||
client *gophercloud.ServiceClient
|
||||
instanceID string
|
||||
DBIDs []string
|
||||
users []string
|
||||
}
|
||||
|
||||
func newContext(t *testing.T) context {
|
||||
return context{
|
||||
test: t,
|
||||
client: newClient(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (c context) Logf(msg string, args ...interface{}) {
|
||||
if len(args) > 0 {
|
||||
c.test.Logf(msg, args...)
|
||||
} else {
|
||||
c.test.Log(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c context) AssertNoErr(err error) {
|
||||
th.AssertNoErr(c.test, err)
|
||||
}
|
||||
|
||||
func (c context) WaitUntilActive(id string) {
|
||||
err := gophercloud.WaitFor(60, func() (bool, error) {
|
||||
inst, err := instances.Get(c.client, id).Extract()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inst.Status == "ACTIVE" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
c.AssertNoErr(err)
|
||||
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/db/v1/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/openstack/db/v1/pkg.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package v1
|
73
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/db/v1/common.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/db/v1/common.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// +build acceptance db rackspace
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/acceptance/tools"
|
||||
"github.com/rackspace/gophercloud/rackspace"
|
||||
"github.com/rackspace/gophercloud/rackspace/db/v1/instances"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
)
|
||||
|
||||
func newClient(t *testing.T) *gophercloud.ServiceClient {
|
||||
opts, err := rackspace.AuthOptionsFromEnv()
|
||||
th.AssertNoErr(t, err)
|
||||
opts = tools.OnlyRS(opts)
|
||||
|
||||
client, err := rackspace.AuthenticatedClient(opts)
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
c, err := rackspace.NewDBV1(client, gophercloud.EndpointOpts{
|
||||
Region: "IAD",
|
||||
})
|
||||
th.AssertNoErr(t, err)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type context struct {
|
||||
test *testing.T
|
||||
client *gophercloud.ServiceClient
|
||||
instanceID string
|
||||
DBIDs []string
|
||||
replicaID string
|
||||
backupID string
|
||||
configGroupID string
|
||||
users []string
|
||||
}
|
||||
|
||||
func newContext(t *testing.T) context {
|
||||
return context{
|
||||
test: t,
|
||||
client: newClient(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (c context) Logf(msg string, args ...interface{}) {
|
||||
if len(args) > 0 {
|
||||
c.test.Logf(msg, args...)
|
||||
} else {
|
||||
c.test.Log(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c context) AssertNoErr(err error) {
|
||||
th.AssertNoErr(c.test, err)
|
||||
}
|
||||
|
||||
func (c context) WaitUntilActive(id string) {
|
||||
err := gophercloud.WaitFor(60, func() (bool, error) {
|
||||
inst, err := instances.Get(c.client, id).Extract()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if inst.Status == "ACTIVE" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
c.AssertNoErr(err)
|
||||
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/db/v1/pkg.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/acceptance/rackspace/db/v1/pkg.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package v1
|
@ -2,6 +2,7 @@ package apiversions
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"net/url"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
@ -11,5 +12,7 @@ func getURL(c *gophercloud.ServiceClient, version string) string {
|
||||
}
|
||||
|
||||
func listURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("")
|
||||
u, _ := url.Parse(c.ServiceURL(""))
|
||||
u.Path = "/"
|
||||
return u.String()
|
||||
}
|
||||
|
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go
generated
vendored
54
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/client.go
generated
vendored
@ -3,6 +3,7 @@ package openstack
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
|
||||
@ -64,8 +65,8 @@ func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.Provider
|
||||
// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
|
||||
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
|
||||
versions := []*utils.Version{
|
||||
&utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
|
||||
&utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
|
||||
{ID: v20, Priority: 20, Suffix: "/v2.0/"},
|
||||
{ID: v30, Priority: 30, Suffix: "/v3/"},
|
||||
}
|
||||
|
||||
chosen, endpoint, err := utils.ChooseVersion(client, versions)
|
||||
@ -110,7 +111,7 @@ func v2auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
||||
if options.AllowReauth {
|
||||
client.ReauthFunc = func() error {
|
||||
client.TokenID = ""
|
||||
return AuthenticateV2(client, options)
|
||||
return v2auth(client, endpoint, options)
|
||||
}
|
||||
}
|
||||
client.TokenID = token.ID
|
||||
@ -167,7 +168,8 @@ func v3auth(client *gophercloud.ProviderClient, endpoint string, options gopherc
|
||||
|
||||
if options.AllowReauth {
|
||||
client.ReauthFunc = func() error {
|
||||
return AuthenticateV3(client, options)
|
||||
client.TokenID = ""
|
||||
return v3auth(client, endpoint, options)
|
||||
}
|
||||
}
|
||||
client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
|
||||
@ -197,6 +199,40 @@ func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClien
|
||||
}
|
||||
}
|
||||
|
||||
func NewIdentityAdminV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("identity")
|
||||
eo.Availability = gophercloud.AvailabilityAdmin
|
||||
|
||||
url, err := client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Force using v2 API
|
||||
if strings.Contains(url, "/v3") {
|
||||
url = strings.Replace(url, "/v3", "/v2.0", -1)
|
||||
}
|
||||
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
||||
func NewIdentityAdminV3(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("identity")
|
||||
eo.Availability = gophercloud.AvailabilityAdmin
|
||||
|
||||
url, err := client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Force using v3 API
|
||||
if strings.Contains(url, "/v2.0") {
|
||||
url = strings.Replace(url, "/v2.0", "/v3", -1)
|
||||
}
|
||||
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
||||
// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
|
||||
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("object-store")
|
||||
@ -261,3 +297,13 @@ func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.Endpo
|
||||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
||||
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
|
||||
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("database")
|
||||
url, err := client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ const (
|
||||
Volume SourceType = "volume"
|
||||
Snapshot SourceType = "snapshot"
|
||||
Image SourceType = "image"
|
||||
Blank SourceType = "blank"
|
||||
)
|
||||
|
||||
// BlockDevice is a structure with options for booting a server instance
|
||||
@ -32,6 +33,9 @@ type BlockDevice struct {
|
||||
// and "local".
|
||||
DestinationType string `json:"destination_type"`
|
||||
|
||||
// GuestFormat [optional] specifies the format of the block device.
|
||||
GuestFormat string `json:"guest_format"`
|
||||
|
||||
// SourceType [required] must be one of: "volume", "snapshot", "image".
|
||||
SourceType SourceType `json:"source_type"`
|
||||
|
||||
@ -82,6 +86,9 @@ func (opts CreateOptsExt) ToServerCreateMap() (map[string]interface{}, error) {
|
||||
if bd.DestinationType != "" {
|
||||
blockDevice[i]["destination_type"] = bd.DestinationType
|
||||
}
|
||||
if bd.GuestFormat != "" {
|
||||
blockDevice[i]["guest_format"] = bd.GuestFormat
|
||||
}
|
||||
|
||||
}
|
||||
serverMap["block_device_mapping_v2"] = blockDevice
|
||||
@ -99,6 +106,11 @@ func Create(client *gophercloud.ServiceClient, opts servers.CreateOptsBuilder) s
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete imageName and flavorName that come from ToServerCreateMap().
|
||||
// As of Liberty, Boot From Volume is failing if they are passed.
|
||||
delete(reqBody["server"].(map[string]interface{}), "imageName")
|
||||
delete(reqBody["server"].(map[string]interface{}), "flavorName")
|
||||
|
||||
_, res.Err = client.Post(createURL(client), reqBody, &res.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 202},
|
||||
})
|
||||
|
@ -72,6 +72,41 @@ func mockCreateRuleResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func mockCreateRuleResponseICMPZero(t *testing.T) {
|
||||
th.Mux.HandleFunc(rootPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "POST")
|
||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
||||
|
||||
th.TestJSONRequest(t, r, `
|
||||
{
|
||||
"security_group_default_rule": {
|
||||
"ip_protocol": "ICMP",
|
||||
"from_port": 0,
|
||||
"to_port": 0,
|
||||
"cidr": "10.10.12.0/24"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"security_group_default_rule": {
|
||||
"from_port": 0,
|
||||
"id": "{ruleID}",
|
||||
"ip_protocol": "ICMP",
|
||||
"ip_range": {
|
||||
"cidr": "10.10.12.0/24"
|
||||
},
|
||||
"to_port": 0
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
func mockGetRuleResponse(t *testing.T, ruleID string) {
|
||||
url := rootPath + "/" + ruleID
|
||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -2,6 +2,7 @@ package defsecrules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
@ -42,10 +43,10 @@ type CreateOptsBuilder interface {
|
||||
func (opts CreateOpts) ToRuleCreateMap() (map[string]interface{}, error) {
|
||||
rule := make(map[string]interface{})
|
||||
|
||||
if opts.FromPort == 0 {
|
||||
if opts.FromPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
||||
return rule, errors.New("A FromPort must be set")
|
||||
}
|
||||
if opts.ToPort == 0 {
|
||||
if opts.ToPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
||||
return rule, errors.New("A ToPort must be set")
|
||||
}
|
||||
if opts.IPProtocol == "" {
|
||||
|
@ -155,6 +155,25 @@ func HandleAssociateSuccessfully(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// HandleFixedAssociateSucessfully configures the test server to respond to a Post request
|
||||
// to associate an allocated floating IP with a specific fixed IP address
|
||||
func HandleAssociateFixedSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/servers/4d8c3732-a248-40ed-bebc-539a6ffd25c0/action", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "POST")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
th.TestJSONRequest(t, r, `
|
||||
{
|
||||
"addFloatingIp": {
|
||||
"address": "10.10.10.2",
|
||||
"fixed_address": "166.78.185.201"
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleDisassociateSuccessfully configures the test server to respond to a Post request
|
||||
// to disassociate an allocated floating IP
|
||||
func HandleDisassociateSuccessfully(t *testing.T) {
|
||||
|
@ -26,6 +26,18 @@ type CreateOpts struct {
|
||||
Pool string
|
||||
}
|
||||
|
||||
// AssociateOpts specifies the required information to associate or disassociate a floating IP to an instance
|
||||
type AssociateOpts struct {
|
||||
// ServerID is the UUID of the server
|
||||
ServerID string
|
||||
|
||||
// FixedIP is an optional fixed IP address of the server
|
||||
FixedIP string
|
||||
|
||||
// FloatingIP is the floating IP to associate with an instance
|
||||
FloatingIP string
|
||||
}
|
||||
|
||||
// ToFloatingIPCreateMap constructs a request body from CreateOpts.
|
||||
func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
|
||||
if opts.Pool == "" {
|
||||
@ -35,6 +47,26 @@ func (opts CreateOpts) ToFloatingIPCreateMap() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{"pool": opts.Pool}, nil
|
||||
}
|
||||
|
||||
// ToAssociateMap constructs a request body from AssociateOpts.
|
||||
func (opts AssociateOpts) ToAssociateMap() (map[string]interface{}, error) {
|
||||
if opts.ServerID == "" {
|
||||
return nil, errors.New("Required field missing for floating IP association: ServerID")
|
||||
}
|
||||
|
||||
if opts.FloatingIP == "" {
|
||||
return nil, errors.New("Required field missing for floating IP association: FloatingIP")
|
||||
}
|
||||
|
||||
associateInfo := map[string]interface{}{
|
||||
"serverId": opts.ServerID,
|
||||
"floatingIp": opts.FloatingIP,
|
||||
"fixedIp": opts.FixedIP,
|
||||
}
|
||||
|
||||
return associateInfo, nil
|
||||
|
||||
}
|
||||
|
||||
// Create requests the creation of a new floating IP
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
@ -68,6 +100,7 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
// association / disassociation
|
||||
|
||||
// Associate pairs an allocated floating IP with an instance
|
||||
// Deprecated. Use AssociateInstance.
|
||||
func Associate(client *gophercloud.ServiceClient, serverId, fip string) AssociateResult {
|
||||
var res AssociateResult
|
||||
|
||||
@ -79,7 +112,33 @@ func Associate(client *gophercloud.ServiceClient, serverId, fip string) Associat
|
||||
return res
|
||||
}
|
||||
|
||||
// AssociateInstance pairs an allocated floating IP with an instance.
|
||||
func AssociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) AssociateResult {
|
||||
var res AssociateResult
|
||||
|
||||
associateInfo, err := opts.ToAssociateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
addFloatingIp := make(map[string]interface{})
|
||||
addFloatingIp["address"] = associateInfo["floatingIp"].(string)
|
||||
|
||||
// fixedIp is not required
|
||||
if associateInfo["fixedIp"] != "" {
|
||||
addFloatingIp["fixed_address"] = associateInfo["fixedIp"].(string)
|
||||
}
|
||||
|
||||
serverId := associateInfo["serverId"].(string)
|
||||
|
||||
reqBody := map[string]interface{}{"addFloatingIp": addFloatingIp}
|
||||
_, res.Err = client.Post(associateURL(client, serverId), reqBody, nil, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// Disassociate decouples an allocated floating IP from an instance
|
||||
// Deprecated. Use DisassociateInstance.
|
||||
func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) DisassociateResult {
|
||||
var res DisassociateResult
|
||||
|
||||
@ -90,3 +149,23 @@ func Disassociate(client *gophercloud.ServiceClient, serverId, fip string) Disas
|
||||
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// DisassociateInstance decouples an allocated floating IP from an instance
|
||||
func DisassociateInstance(client *gophercloud.ServiceClient, opts AssociateOpts) DisassociateResult {
|
||||
var res DisassociateResult
|
||||
|
||||
associateInfo, err := opts.ToAssociateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
removeFloatingIp := make(map[string]interface{})
|
||||
removeFloatingIp["address"] = associateInfo["floatingIp"].(string)
|
||||
reqBody := map[string]interface{}{"removeFloatingIp": removeFloatingIp}
|
||||
|
||||
serverId := associateInfo["serverId"].(string)
|
||||
|
||||
_, res.Err = client.Post(disassociateURL(client, serverId), reqBody, nil, nil)
|
||||
return res
|
||||
}
|
||||
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package quotasets provides information and interaction with QuotaSet
|
||||
// extension for the OpenStack Compute service.
|
||||
package quotasets
|
59
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/fixtures.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
// +build fixtures
|
||||
|
||||
package quotasets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
"github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// GetOutput is a sample response to a Get call.
|
||||
const GetOutput = `
|
||||
{
|
||||
"quota_set" : {
|
||||
"instances" : 25,
|
||||
"security_groups" : 10,
|
||||
"security_group_rules" : 20,
|
||||
"cores" : 200,
|
||||
"injected_file_content_bytes" : 10240,
|
||||
"injected_files" : 5,
|
||||
"metadata_items" : 128,
|
||||
"ram" : 200000,
|
||||
"keypairs" : 10,
|
||||
"injected_file_path_bytes" : 255
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const FirstTenantID = "555544443333222211110000ffffeeee"
|
||||
|
||||
// FirstQuotaSet is the first result in ListOutput.
|
||||
var FirstQuotaSet = QuotaSet{
|
||||
FixedIps: 0,
|
||||
FloatingIps: 0,
|
||||
InjectedFileContentBytes: 10240,
|
||||
InjectedFilePathBytes: 255,
|
||||
InjectedFiles: 5,
|
||||
KeyPairs: 10,
|
||||
MetadataItems: 128,
|
||||
Ram: 200000,
|
||||
SecurityGroupRules: 20,
|
||||
SecurityGroups: 10,
|
||||
Cores: 200,
|
||||
Instances: 25,
|
||||
}
|
||||
|
||||
// HandleGetSuccessfully configures the test server to respond to a Get request for sample tenant
|
||||
func HandleGetSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/os-quota-sets/"+FirstTenantID, func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "GET")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
fmt.Fprintf(w, GetOutput)
|
||||
})
|
||||
}
|
12
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/requests.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
package quotasets
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
// Get returns public data about a previously created QuotaSet.
|
||||
func Get(client *gophercloud.ServiceClient, tenantID string) GetResult {
|
||||
var res GetResult
|
||||
_, res.Err = client.Get(getURL(client, tenantID), &res.Body, nil)
|
||||
return res
|
||||
}
|
86
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/results.go
generated
vendored
Normal file
86
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/results.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package quotasets
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// QuotaSet is a set of operational limits that allow for control of compute usage.
|
||||
type QuotaSet struct {
|
||||
//ID is tenant associated with this quota_set
|
||||
ID string `mapstructure:"id"`
|
||||
//FixedIps is number of fixed ips alloted this quota_set
|
||||
FixedIps int `mapstructure:"fixed_ips"`
|
||||
// FloatingIps is number of floating ips alloted this quota_set
|
||||
FloatingIps int `mapstructure:"floating_ips"`
|
||||
// InjectedFileContentBytes is content bytes allowed for each injected file
|
||||
InjectedFileContentBytes int `mapstructure:"injected_file_content_bytes"`
|
||||
// InjectedFilePathBytes is allowed bytes for each injected file path
|
||||
InjectedFilePathBytes int `mapstructure:"injected_file_path_bytes"`
|
||||
// InjectedFiles is injected files allowed for each project
|
||||
InjectedFiles int `mapstructure:"injected_files"`
|
||||
// KeyPairs is number of ssh keypairs
|
||||
KeyPairs int `mapstructure:"keypairs"`
|
||||
// MetadataItems is number of metadata items allowed for each instance
|
||||
MetadataItems int `mapstructure:"metadata_items"`
|
||||
// Ram is megabytes allowed for each instance
|
||||
Ram int `mapstructure:"ram"`
|
||||
// SecurityGroupRules is rules allowed for each security group
|
||||
SecurityGroupRules int `mapstructure:"security_group_rules"`
|
||||
// SecurityGroups security groups allowed for each project
|
||||
SecurityGroups int `mapstructure:"security_groups"`
|
||||
// Cores is number of instance cores allowed for each project
|
||||
Cores int `mapstructure:"cores"`
|
||||
// Instances is number of instances allowed for each project
|
||||
Instances int `mapstructure:"instances"`
|
||||
}
|
||||
|
||||
// QuotaSetPage stores a single, only page of QuotaSet results from a List call.
|
||||
type QuotaSetPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines whether or not a QuotaSetsetPage is empty.
|
||||
func (page QuotaSetPage) IsEmpty() (bool, error) {
|
||||
ks, err := ExtractQuotaSets(page)
|
||||
return len(ks) == 0, err
|
||||
}
|
||||
|
||||
// ExtractQuotaSets interprets a page of results as a slice of QuotaSets.
|
||||
func ExtractQuotaSets(page pagination.Page) ([]QuotaSet, error) {
|
||||
var resp struct {
|
||||
QuotaSets []QuotaSet `mapstructure:"quotas"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(QuotaSetPage).Body, &resp)
|
||||
results := make([]QuotaSet, len(resp.QuotaSets))
|
||||
for i, q := range resp.QuotaSets {
|
||||
results[i] = q
|
||||
}
|
||||
return results, err
|
||||
}
|
||||
|
||||
type quotaResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract is a method that attempts to interpret any QuotaSet resource response as a QuotaSet struct.
|
||||
func (r quotaResult) Extract() (*QuotaSet, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var res struct {
|
||||
QuotaSet *QuotaSet `json:"quota_set" mapstructure:"quota_set"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &res)
|
||||
return res.QuotaSet, err
|
||||
}
|
||||
|
||||
// GetResult is the response from a Get operation. Call its Extract method to interpret it
|
||||
// as a QuotaSet.
|
||||
type GetResult struct {
|
||||
quotaResult
|
||||
}
|
13
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/urls.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/compute/v2/extensions/quotasets/urls.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
package quotasets
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
const resourcePath = "os-quota-sets"
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL(resourcePath)
|
||||
}
|
||||
|
||||
func getURL(c *gophercloud.ServiceClient, tenantID string) string {
|
||||
return c.ServiceURL(resourcePath, tenantID)
|
||||
}
|
@ -216,6 +216,42 @@ func mockAddRuleResponse(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func mockAddRuleResponseICMPZero(t *testing.T) {
|
||||
th.Mux.HandleFunc("/os-security-group-rules", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "POST")
|
||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
||||
|
||||
th.TestJSONRequest(t, r, `
|
||||
{
|
||||
"security_group_rule": {
|
||||
"from_port": 0,
|
||||
"ip_protocol": "ICMP",
|
||||
"to_port": 0,
|
||||
"parent_group_id": "{groupID}",
|
||||
"cidr": "0.0.0.0/0"
|
||||
}
|
||||
} `)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
fmt.Fprintf(w, `
|
||||
{
|
||||
"security_group_rule": {
|
||||
"from_port": 0,
|
||||
"group": {},
|
||||
"ip_protocol": "ICMP",
|
||||
"to_port": 0,
|
||||
"parent_group_id": "{groupID}",
|
||||
"ip_range": {
|
||||
"cidr": "0.0.0.0/0"
|
||||
},
|
||||
"id": "{ruleID}"
|
||||
}
|
||||
}`)
|
||||
})
|
||||
}
|
||||
|
||||
func mockDeleteRuleResponse(t *testing.T, ruleID string) {
|
||||
url := fmt.Sprintf("/os-security-group-rules/%s", ruleID)
|
||||
th.Mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -2,6 +2,7 @@ package secgroups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
@ -181,10 +182,10 @@ func (opts CreateRuleOpts) ToRuleCreateMap() (map[string]interface{}, error) {
|
||||
if opts.ParentGroupID == "" {
|
||||
return rule, errors.New("A ParentGroupID must be set")
|
||||
}
|
||||
if opts.FromPort == 0 {
|
||||
if opts.FromPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
||||
return rule, errors.New("A FromPort must be set")
|
||||
}
|
||||
if opts.ToPort == 0 {
|
||||
if opts.ToPort == 0 && strings.ToUpper(opts.IPProtocol) != "ICMP" {
|
||||
return rule, errors.New("A ToPort must be set")
|
||||
}
|
||||
if opts.IPProtocol == "" {
|
||||
|
@ -235,6 +235,12 @@ const SingleServerBody = `
|
||||
}
|
||||
`
|
||||
|
||||
const ServerPasswordBody = `
|
||||
{
|
||||
"password": "xlozO3wLCBRWAa2yDjCCVx8vwNPypxnypmRYDa/zErlQ+EzPe1S/Gz6nfmC52mOlOSCRuUOmG7kqqgejPof6M7bOezS387zjq4LSvvwp28zUknzy4YzfFGhnHAdai3TxUJ26pfQCYrq8UTzmKF2Bq8ioSEtVVzM0A96pDh8W2i7BOz6MdoiVyiev/I1K2LsuipfxSJR7Wdke4zNXJjHHP2RfYsVbZ/k9ANu+Nz4iIH8/7Cacud/pphH7EjrY6a4RZNrjQskrhKYed0YERpotyjYk1eDtRe72GrSiXteqCM4biaQ5w3ruS+AcX//PXk3uJ5kC7d67fPXaVz4WaQRYMg=="
|
||||
}
|
||||
`
|
||||
|
||||
var (
|
||||
// ServerHerp is a Server struct that should correspond to the first result in ServerListBody.
|
||||
ServerHerp = Server{
|
||||
@ -399,6 +405,18 @@ func HandleServerDeletionSuccessfully(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// HandleServerForceDeletionSuccessfully sets up the test server to respond to a server force deletion
|
||||
// request.
|
||||
func HandleServerForceDeletionSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/servers/asdfasdfasdf/action", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "POST")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
th.TestJSONRequest(t, r, `{ "forceDelete": "" }`)
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
})
|
||||
}
|
||||
|
||||
// HandleServerGetSuccessfully sets up the test server to respond to a server Get request.
|
||||
func HandleServerGetSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/servers/1234asdf", func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -662,3 +680,13 @@ func HandleCreateServerImageSuccessfully(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// HandlePasswordGetSuccessfully sets up the test server to respond to a password Get request.
|
||||
func HandlePasswordGetSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/servers/1234asdf/os-server-password", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "GET")
|
||||
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
|
||||
th.TestHeader(t, r, "Accept", "application/json")
|
||||
|
||||
fmt.Fprintf(w, ServerPasswordBody)
|
||||
})
|
||||
}
|
||||
|
@ -303,6 +303,17 @@ func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
return res
|
||||
}
|
||||
|
||||
func ForceDelete(client *gophercloud.ServiceClient, id string) ActionResult {
|
||||
var req struct {
|
||||
ForceDelete string `json:"forceDelete"`
|
||||
}
|
||||
|
||||
var res ActionResult
|
||||
_, res.Err = client.Post(actionURL(client, id), req, nil, nil)
|
||||
return res
|
||||
|
||||
}
|
||||
|
||||
// Get requests details on a single server, by ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var result GetResult
|
||||
@ -850,3 +861,12 @@ func IDFromName(client *gophercloud.ServiceClient, name string) (string, error)
|
||||
return "", fmt.Errorf("Found %d servers matching %s", serverCount, name)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPassword makes a request against the nova API to get the encrypted administrative password.
|
||||
func GetPassword(client *gophercloud.ServiceClient, serverId string) GetPasswordResult {
|
||||
var res GetPasswordResult
|
||||
_, res.Err = client.Request("GET", passwordURL(client, serverId), gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package servers
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"path"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
@ -82,6 +84,47 @@ type CreateImageResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetPasswordResult represent the result of a get os-server-password operation.
|
||||
type GetPasswordResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// ExtractPassword gets the encrypted password.
|
||||
// If privateKey != nil the password is decrypted with the private key.
|
||||
// If privateKey == nil the encrypted password is returned and can be decrypted with:
|
||||
// echo '<pwd>' | base64 -D | openssl rsautl -decrypt -inkey <private_key>
|
||||
func (r GetPasswordResult) ExtractPassword(privateKey *rsa.PrivateKey) (string, error) {
|
||||
|
||||
if r.Err != nil {
|
||||
return "", r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
if err == nil && privateKey != nil && response.Password != "" {
|
||||
return decryptPassword(response.Password, privateKey)
|
||||
}
|
||||
return response.Password, err
|
||||
}
|
||||
|
||||
func decryptPassword(encryptedPassword string, privateKey *rsa.PrivateKey) (string, error) {
|
||||
b64EncryptedPassword := make([]byte, base64.StdEncoding.DecodedLen(len(encryptedPassword)))
|
||||
|
||||
n, err := base64.StdEncoding.Decode(b64EncryptedPassword, []byte(encryptedPassword))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to base64 decode encrypted password: %s", err)
|
||||
}
|
||||
password, err := rsa.DecryptPKCS1v15(nil, privateKey, b64EncryptedPassword[0:n])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to decrypt password: %s", err)
|
||||
}
|
||||
|
||||
return string(password), nil
|
||||
}
|
||||
|
||||
// ExtractImageID gets the ID of the newly created server image from the header
|
||||
func (res CreateImageResult) ExtractImageID() (string, error) {
|
||||
if res.Err != nil {
|
||||
|
@ -45,3 +45,7 @@ func listAddressesURL(client *gophercloud.ServiceClient, id string) string {
|
||||
func listAddressesByNetworkURL(client *gophercloud.ServiceClient, id, network string) string {
|
||||
return client.ServiceURL("servers", id, "ips", network)
|
||||
}
|
||||
|
||||
func passwordURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("servers", id, "os-server-password")
|
||||
}
|
||||
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/doc.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/doc.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Package configurations provides information and interaction with the
|
||||
// configuration API resource in the Rackspace Database service.
|
||||
//
|
||||
// A configuration group is a collection of key/value pairs which define how a
|
||||
// particular database operates. These key/value pairs are specific to each
|
||||
// datastore type and serve like settings. Some directives are capable of being
|
||||
// applied dynamically, while other directives require a server restart to take
|
||||
// effect. The configuration group can be applied to an instance at creation or
|
||||
// applied to an existing instance to modify the behavior of the running
|
||||
// datastore on the instance.
|
||||
package configurations
|
157
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/fixtures.go
generated
vendored
Normal file
157
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
package configurations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp = "2015-11-12T14:22:42Z"
|
||||
timeVal, _ = time.Parse(time.RFC3339, timestamp)
|
||||
)
|
||||
|
||||
var singleConfigJSON = `
|
||||
{
|
||||
"created": "` + timestamp + `",
|
||||
"datastore_name": "mysql",
|
||||
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"datastore_version_name": "5.6",
|
||||
"description": "example_description",
|
||||
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
"name": "example-configuration-name",
|
||||
"updated": "` + timestamp + `"
|
||||
}
|
||||
`
|
||||
|
||||
var singleConfigWithValuesJSON = `
|
||||
{
|
||||
"created": "` + timestamp + `",
|
||||
"datastore_name": "mysql",
|
||||
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"datastore_version_name": "5.6",
|
||||
"description": "example description",
|
||||
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
"instance_count": 0,
|
||||
"name": "example-configuration-name",
|
||||
"updated": "` + timestamp + `",
|
||||
"values": {
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var (
|
||||
ListConfigsJSON = fmt.Sprintf(`{"configurations": [%s]}`, singleConfigJSON)
|
||||
GetConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigJSON)
|
||||
CreateConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigWithValuesJSON)
|
||||
)
|
||||
|
||||
var CreateReq = `
|
||||
{
|
||||
"configuration": {
|
||||
"datastore": {
|
||||
"type": "a00000a0-00a0-0a00-00a0-000a000000aa",
|
||||
"version": "b00000b0-00b0-0b00-00b0-000b000000bb"
|
||||
},
|
||||
"description": "example description",
|
||||
"name": "example-configuration-name",
|
||||
"values": {
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var UpdateReq = `
|
||||
{
|
||||
"configuration": {
|
||||
"values": {
|
||||
"connect_timeout": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var ListInstancesJSON = `
|
||||
{
|
||||
"instances": [
|
||||
{
|
||||
"id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
|
||||
"name": "json_rack_instance"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var ListParamsJSON = `
|
||||
{
|
||||
"configuration-parameters": [
|
||||
{
|
||||
"max": 1,
|
||||
"min": 0,
|
||||
"name": "innodb_file_per_table",
|
||||
"restart_required": true,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 4294967296,
|
||||
"min": 0,
|
||||
"name": "key_buffer_size",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 65535,
|
||||
"min": 2,
|
||||
"name": "connect_timeout",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 4294967296,
|
||||
"min": 0,
|
||||
"name": "join_buffer_size",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var GetParamJSON = `
|
||||
{
|
||||
"max": 1,
|
||||
"min": 0,
|
||||
"name": "innodb_file_per_table",
|
||||
"restart_required": true,
|
||||
"type": "integer"
|
||||
}
|
||||
`
|
||||
|
||||
var ExampleConfig = Config{
|
||||
Created: timeVal,
|
||||
DatastoreName: "mysql",
|
||||
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
DatastoreVersionName: "5.6",
|
||||
Description: "example_description",
|
||||
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
Name: "example-configuration-name",
|
||||
Updated: timeVal,
|
||||
}
|
||||
|
||||
var ExampleConfigWithValues = Config{
|
||||
Created: timeVal,
|
||||
DatastoreName: "mysql",
|
||||
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
DatastoreVersionName: "5.6",
|
||||
Description: "example description",
|
||||
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
Name: "example-configuration-name",
|
||||
Updated: timeVal,
|
||||
Values: map[string]interface{}{
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120,
|
||||
},
|
||||
}
|
287
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/requests.go
generated
vendored
Normal file
287
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/requests.go
generated
vendored
Normal file
@ -0,0 +1,287 @@
|
||||
package configurations
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/instances"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all of the available configurations.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return ConfigPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, baseURL(client), pageFn)
|
||||
}
|
||||
|
||||
// CreateOptsBuilder is a top-level interface which renders a JSON map.
|
||||
type CreateOptsBuilder interface {
|
||||
ToConfigCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DatastoreOpts is the primary options struct for creating and modifying
|
||||
// how configuration resources are associated with datastores.
|
||||
type DatastoreOpts struct {
|
||||
// [OPTIONAL] The type of datastore. Defaults to "MySQL".
|
||||
Type string
|
||||
|
||||
// [OPTIONAL] The specific version of a datastore. Defaults to "5.6".
|
||||
Version string
|
||||
}
|
||||
|
||||
// ToMap renders a JSON map for a datastore setting.
|
||||
func (opts DatastoreOpts) ToMap() (map[string]string, error) {
|
||||
datastore := map[string]string{}
|
||||
|
||||
if opts.Type != "" {
|
||||
datastore["type"] = opts.Type
|
||||
}
|
||||
|
||||
if opts.Version != "" {
|
||||
datastore["version"] = opts.Version
|
||||
}
|
||||
|
||||
return datastore, nil
|
||||
}
|
||||
|
||||
// CreateOpts is the struct responsible for configuring new configurations.
|
||||
type CreateOpts struct {
|
||||
// [REQUIRED] The configuration group name
|
||||
Name string
|
||||
|
||||
// [REQUIRED] A map of user-defined configuration settings that will define
|
||||
// how each associated datastore works. Each key/value pair is specific to a
|
||||
// datastore type.
|
||||
Values map[string]interface{}
|
||||
|
||||
// [OPTIONAL] Associates the configuration group with a particular datastore.
|
||||
Datastore *DatastoreOpts
|
||||
|
||||
// [OPTIONAL] A human-readable explanation for the group.
|
||||
Description string
|
||||
}
|
||||
|
||||
// ToConfigCreateMap casts a CreateOpts struct into a JSON map.
|
||||
func (opts CreateOpts) ToConfigCreateMap() (map[string]interface{}, error) {
|
||||
if opts.Name == "" {
|
||||
return nil, errors.New("Name is a required field")
|
||||
}
|
||||
if len(opts.Values) == 0 {
|
||||
return nil, errors.New("Values must be a populated map")
|
||||
}
|
||||
|
||||
config := map[string]interface{}{
|
||||
"name": opts.Name,
|
||||
"values": opts.Values,
|
||||
}
|
||||
|
||||
if opts.Datastore != nil {
|
||||
ds, err := opts.Datastore.ToMap()
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config["datastore"] = ds
|
||||
}
|
||||
|
||||
if opts.Description != "" {
|
||||
config["description"] = opts.Description
|
||||
}
|
||||
|
||||
return map[string]interface{}{"configuration": config}, nil
|
||||
}
|
||||
|
||||
// Create will create a new configuration group.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToConfigCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("POST", baseURL(client), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONBody: &reqBody,
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Get will retrieve the details for a specified configuration group.
|
||||
func Get(client *gophercloud.ServiceClient, configID string) GetResult {
|
||||
var res GetResult
|
||||
|
||||
_, res.Err = client.Request("GET", resourceURL(client, configID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// UpdateOptsBuilder is the top-level interface for casting update options into
|
||||
// JSON maps.
|
||||
type UpdateOptsBuilder interface {
|
||||
ToConfigUpdateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// UpdateOpts is the struct responsible for modifying existing configurations.
|
||||
type UpdateOpts struct {
|
||||
// [OPTIONAL] The configuration group name
|
||||
Name string
|
||||
|
||||
// [OPTIONAL] A map of user-defined configuration settings that will define
|
||||
// how each associated datastore works. Each key/value pair is specific to a
|
||||
// datastore type.
|
||||
Values map[string]interface{}
|
||||
|
||||
// [OPTIONAL] Associates the configuration group with a particular datastore.
|
||||
Datastore *DatastoreOpts
|
||||
|
||||
// [OPTIONAL] A human-readable explanation for the group.
|
||||
Description string
|
||||
}
|
||||
|
||||
// ToConfigUpdateMap will cast an UpdateOpts struct into a JSON map.
|
||||
func (opts UpdateOpts) ToConfigUpdateMap() (map[string]interface{}, error) {
|
||||
config := map[string]interface{}{}
|
||||
|
||||
if opts.Name != "" {
|
||||
config["name"] = opts.Name
|
||||
}
|
||||
|
||||
if opts.Description != "" {
|
||||
config["description"] = opts.Description
|
||||
}
|
||||
|
||||
if opts.Datastore != nil {
|
||||
ds, err := opts.Datastore.ToMap()
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
config["datastore"] = ds
|
||||
}
|
||||
|
||||
if len(opts.Values) > 0 {
|
||||
config["values"] = opts.Values
|
||||
}
|
||||
|
||||
return map[string]interface{}{"configuration": config}, nil
|
||||
}
|
||||
|
||||
// Update will modify an existing configuration group by performing a merge
|
||||
// between new and existing values. If the key already exists, the new value
|
||||
// will overwrite. All other keys will remain unaffected.
|
||||
func Update(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) UpdateResult {
|
||||
var res UpdateResult
|
||||
|
||||
reqBody, err := opts.ToConfigUpdateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("PATCH", resourceURL(client, configID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONBody: &reqBody,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Replace will modify an existing configuration group by overwriting the
|
||||
// entire parameter group with the new values provided. Any existing keys not
|
||||
// included in UpdateOptsBuilder will be deleted.
|
||||
func Replace(client *gophercloud.ServiceClient, configID string, opts UpdateOptsBuilder) ReplaceResult {
|
||||
var res ReplaceResult
|
||||
|
||||
reqBody, err := opts.ToConfigUpdateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("PUT", resourceURL(client, configID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
JSONBody: &reqBody,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete will permanently delete a configuration group. Please note that
|
||||
// config groups cannot be deleted whilst still attached to running instances -
|
||||
// you must detach and then delete them.
|
||||
func Delete(client *gophercloud.ServiceClient, configID string) DeleteResult {
|
||||
var res DeleteResult
|
||||
|
||||
_, res.Err = client.Request("DELETE", resourceURL(client, configID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ListInstances will list all the instances associated with a particular
|
||||
// configuration group.
|
||||
func ListInstances(client *gophercloud.ServiceClient, configID string) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return instances.InstancePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
return pagination.NewPager(client, instancesURL(client, configID), pageFn)
|
||||
}
|
||||
|
||||
// ListDatastoreParams will list all the available and supported parameters
|
||||
// that can be used for a particular datastore ID and a particular version.
|
||||
// For example, if you are wondering how you can configure a MySQL 5.6 instance,
|
||||
// you can use this operation (you will need to retrieve the MySQL datastore ID
|
||||
// by using the datastores API).
|
||||
func ListDatastoreParams(client *gophercloud.ServiceClient, datastoreID, versionID string) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return ParamPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
return pagination.NewPager(client, listDSParamsURL(client, datastoreID, versionID), pageFn)
|
||||
}
|
||||
|
||||
// GetDatastoreParam will retrieve information about a specific configuration
|
||||
// parameter. For example, you can use this operation to understand more about
|
||||
// "innodb_file_per_table" configuration param for MySQL datastores. You will
|
||||
// need the param's ID first, which can be attained by using the ListDatastoreParams
|
||||
// operation.
|
||||
func GetDatastoreParam(client *gophercloud.ServiceClient, datastoreID, versionID, paramID string) ParamResult {
|
||||
var res ParamResult
|
||||
|
||||
_, res.Err = client.Request("GET", getDSParamURL(client, datastoreID, versionID, paramID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ListGlobalParams is similar to ListDatastoreParams but does not require a
|
||||
// DatastoreID.
|
||||
func ListGlobalParams(client *gophercloud.ServiceClient, versionID string) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return ParamPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
return pagination.NewPager(client, listGlobalParamsURL(client, versionID), pageFn)
|
||||
}
|
||||
|
||||
// GetGlobalParam is similar to GetDatastoreParam but does not require a
|
||||
// DatastoreID.
|
||||
func GetGlobalParam(client *gophercloud.ServiceClient, versionID, paramID string) ParamResult {
|
||||
var res ParamResult
|
||||
|
||||
_, res.Err = client.Request("GET", getGlobalParamURL(client, versionID, paramID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
197
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/results.go
generated
vendored
Normal file
197
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/results.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
package configurations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Config represents a configuration group API resource.
|
||||
type Config struct {
|
||||
Created time.Time `mapstructure:"-"`
|
||||
Updated time.Time `mapstructure:"-"`
|
||||
DatastoreName string `mapstructure:"datastore_name"`
|
||||
DatastoreVersionID string `mapstructure:"datastore_version_id"`
|
||||
DatastoreVersionName string `mapstructure:"datastore_version_name"`
|
||||
Description string
|
||||
ID string
|
||||
Name string
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
// ConfigPage contains a page of Config resources in a paginated collection.
|
||||
type ConfigPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty indicates whether a ConfigPage is empty.
|
||||
func (r ConfigPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractConfigs(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
}
|
||||
|
||||
// ExtractConfigs will retrieve a slice of Config structs from a page.
|
||||
func ExtractConfigs(page pagination.Page) ([]Config, error) {
|
||||
casted := page.(ConfigPage).Body
|
||||
|
||||
var resp struct {
|
||||
Configs []Config `mapstructure:"configurations" json:"configurations"`
|
||||
}
|
||||
|
||||
if err := mapstructure.Decode(casted, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vals []interface{}
|
||||
switch casted.(type) {
|
||||
case map[string]interface{}:
|
||||
vals = casted.(map[string]interface{})["configurations"].([]interface{})
|
||||
case map[string][]interface{}:
|
||||
vals = casted.(map[string][]interface{})["configurations"]
|
||||
default:
|
||||
return resp.Configs, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
|
||||
}
|
||||
|
||||
for i, v := range vals {
|
||||
val := v.(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Configs, err
|
||||
}
|
||||
resp.Configs[i].Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Configs, err
|
||||
}
|
||||
resp.Configs[i].Updated = updatedTime
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Configs, nil
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract will retrieve a Config resource from an operation result.
|
||||
func (r commonResult) Extract() (*Config, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Config Config `mapstructure:"configuration"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
val := r.Body.(map[string]interface{})["configuration"].(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Config, err
|
||||
}
|
||||
response.Config.Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Config, err
|
||||
}
|
||||
response.Config.Updated = updatedTime
|
||||
}
|
||||
|
||||
return &response.Config, err
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a Create operation.
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// UpdateResult represents the result of an Update operation.
|
||||
type UpdateResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// ReplaceResult represents the result of a Replace operation.
|
||||
type ReplaceResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of a Delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Param represents a configuration parameter API resource.
|
||||
type Param struct {
|
||||
Max int
|
||||
Min int
|
||||
Name string
|
||||
RestartRequired bool `mapstructure:"restart_required" json:"restart_required"`
|
||||
Type string
|
||||
}
|
||||
|
||||
// ParamPage contains a page of Param resources in a paginated collection.
|
||||
type ParamPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty indicates whether a ParamPage is empty.
|
||||
func (r ParamPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractParams(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
}
|
||||
|
||||
// ExtractParams will retrieve a slice of Param structs from a page.
|
||||
func ExtractParams(page pagination.Page) ([]Param, error) {
|
||||
casted := page.(ParamPage).Body
|
||||
|
||||
var resp struct {
|
||||
Params []Param `mapstructure:"configuration-parameters" json:"configuration-parameters"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &resp)
|
||||
return resp.Params, err
|
||||
}
|
||||
|
||||
// ParamResult represents the result of an operation which retrieves details
|
||||
// about a particular configuration param.
|
||||
type ParamResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract will retrieve a param from an operation result.
|
||||
func (r ParamResult) Extract() (*Param, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var param Param
|
||||
|
||||
err := mapstructure.Decode(r.Body, ¶m)
|
||||
return ¶m, err
|
||||
}
|
31
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/urls.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/configurations/urls.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package configurations
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("configurations")
|
||||
}
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient, configID string) string {
|
||||
return c.ServiceURL("configurations", configID)
|
||||
}
|
||||
|
||||
func instancesURL(c *gophercloud.ServiceClient, configID string) string {
|
||||
return c.ServiceURL("configurations", configID, "instances")
|
||||
}
|
||||
|
||||
func listDSParamsURL(c *gophercloud.ServiceClient, datastoreID, versionID string) string {
|
||||
return c.ServiceURL("datastores", datastoreID, "versions", versionID, "parameters")
|
||||
}
|
||||
|
||||
func getDSParamURL(c *gophercloud.ServiceClient, datastoreID, versionID, paramID string) string {
|
||||
return c.ServiceURL("datastores", datastoreID, "versions", versionID, "parameters", paramID)
|
||||
}
|
||||
|
||||
func listGlobalParamsURL(c *gophercloud.ServiceClient, versionID string) string {
|
||||
return c.ServiceURL("datastores", "versions", versionID, "parameters")
|
||||
}
|
||||
|
||||
func getGlobalParamURL(c *gophercloud.ServiceClient, versionID, paramID string) string {
|
||||
return c.ServiceURL("datastores", "versions", versionID, "parameters", paramID)
|
||||
}
|
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/doc.go
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// Package flavors provides information and interaction with the database API
|
||||
// resource in the OpenStack Database service.
|
||||
//
|
||||
// A database, when referred to here, refers to the database engine running on
|
||||
// an instance.
|
||||
package databases
|
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/fixtures.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
package databases
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/testhelper/fixture"
|
||||
)
|
||||
|
||||
var (
|
||||
instanceID = "{instanceID}"
|
||||
resURL = "/instances/" + instanceID + "/databases"
|
||||
)
|
||||
|
||||
var createDBsReq = `
|
||||
{
|
||||
"databases": [
|
||||
{
|
||||
"character_set": "utf8",
|
||||
"collate": "utf8_general_ci",
|
||||
"name": "testingdb"
|
||||
},
|
||||
{
|
||||
"name": "sampledb"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var listDBsResp = `
|
||||
{
|
||||
"databases": [
|
||||
{
|
||||
"name": "anotherexampledb"
|
||||
},
|
||||
{
|
||||
"name": "exampledb"
|
||||
},
|
||||
{
|
||||
"name": "nextround"
|
||||
},
|
||||
{
|
||||
"name": "sampledb"
|
||||
},
|
||||
{
|
||||
"name": "testingdb"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
func HandleCreate(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL, "POST", createDBsReq, "", 202)
|
||||
}
|
||||
|
||||
func HandleList(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL, "GET", "", listDBsResp, 200)
|
||||
}
|
||||
|
||||
func HandleDelete(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL+"/{dbName}", "DELETE", "", "", 202)
|
||||
}
|
115
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/requests.go
generated
vendored
Normal file
115
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/requests.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
package databases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsBuilder builds create options
|
||||
type CreateOptsBuilder interface {
|
||||
ToDBCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DatabaseOpts is the struct responsible for configuring a database; often in
|
||||
// the context of an instance.
|
||||
type CreateOpts struct {
|
||||
// [REQUIRED] Specifies the name of the database. Valid names can be composed
|
||||
// of the following characters: letters (either case); numbers; these
|
||||
// characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is
|
||||
// permitted anywhere. Prohibited characters that are forbidden include:
|
||||
// single quotes, double quotes, back quotes, semicolons, commas, backslashes,
|
||||
// and forward slashes.
|
||||
Name string
|
||||
|
||||
// [OPTIONAL] Set of symbols and encodings. The default character set is
|
||||
// "utf8". See http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for
|
||||
// supported character sets.
|
||||
CharSet string
|
||||
|
||||
// [OPTIONAL] Set of rules for comparing characters in a character set. The
|
||||
// default value for collate is "utf8_general_ci". See
|
||||
// http://dev.mysql.com/doc/refman/5.1/en/charset-mysql.html for supported
|
||||
// collations.
|
||||
Collate string
|
||||
}
|
||||
|
||||
// ToMap is a helper function to convert individual DB create opt structures
|
||||
// into sub-maps.
|
||||
func (opts CreateOpts) ToMap() (map[string]string, error) {
|
||||
if opts.Name == "" {
|
||||
return nil, fmt.Errorf("Name is a required field")
|
||||
}
|
||||
if len(opts.Name) > 64 {
|
||||
return nil, fmt.Errorf("Name must be less than 64 chars long")
|
||||
}
|
||||
|
||||
db := map[string]string{"name": opts.Name}
|
||||
|
||||
if opts.CharSet != "" {
|
||||
db["character_set"] = opts.CharSet
|
||||
}
|
||||
if opts.Collate != "" {
|
||||
db["collate"] = opts.Collate
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// BatchCreateOpts allows for multiple databases to created and modified.
|
||||
type BatchCreateOpts []CreateOpts
|
||||
|
||||
// ToDBCreateMap renders a JSON map for creating DBs.
|
||||
func (opts BatchCreateOpts) ToDBCreateMap() (map[string]interface{}, error) {
|
||||
dbs := make([]map[string]string, len(opts))
|
||||
for i, db := range opts {
|
||||
dbMap, err := db.ToMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbs[i] = dbMap
|
||||
}
|
||||
return map[string]interface{}{"databases": dbs}, nil
|
||||
}
|
||||
|
||||
// Create will create a new database within the specified instance. If the
|
||||
// specified instance does not exist, a 404 error will be returned.
|
||||
func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToDBCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("POST", baseURL(client, instanceID), gophercloud.RequestOpts{
|
||||
JSONBody: &reqBody,
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// List will list all of the databases for a specified instance. Note: this
|
||||
// operation will only return user-defined databases; it will exclude system
|
||||
// databases like "mysql", "information_schema", "lost+found" etc.
|
||||
func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
|
||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return DBPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, baseURL(client, instanceID), createPageFn)
|
||||
}
|
||||
|
||||
// Delete will permanently delete the database within a specified instance.
|
||||
// All contained data inside the database will also be permanently deleted.
|
||||
func Delete(client *gophercloud.ServiceClient, instanceID, dbName string) DeleteResult {
|
||||
var res DeleteResult
|
||||
|
||||
_, res.Err = client.Request("DELETE", dbURL(client, instanceID, dbName), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
72
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/results.go
generated
vendored
Normal file
72
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/results.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
package databases
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Database represents a Database API resource.
|
||||
type Database struct {
|
||||
// Specifies the name of the MySQL database.
|
||||
Name string
|
||||
|
||||
// Set of symbols and encodings. The default character set is utf8.
|
||||
CharSet string
|
||||
|
||||
// Set of rules for comparing characters in a character set. The default
|
||||
// value for collate is utf8_general_ci.
|
||||
Collate string
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a Create operation.
|
||||
type CreateResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of a Delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// DBPage represents a single page of a paginated DB collection.
|
||||
type DBPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty checks to see whether the collection is empty.
|
||||
func (page DBPage) IsEmpty() (bool, error) {
|
||||
dbs, err := ExtractDBs(page)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(dbs) == 0, nil
|
||||
}
|
||||
|
||||
// NextPageURL will retrieve the next page URL.
|
||||
func (page DBPage) NextPageURL() (string, error) {
|
||||
type resp struct {
|
||||
Links []gophercloud.Link `mapstructure:"databases_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
}
|
||||
|
||||
// ExtractDBs will convert a generic pagination struct into a more
|
||||
// relevant slice of DB structs.
|
||||
func ExtractDBs(page pagination.Page) ([]Database, error) {
|
||||
casted := page.(DBPage).Body
|
||||
|
||||
var response struct {
|
||||
Databases []Database `mapstructure:"databases"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &response)
|
||||
return response.Databases, err
|
||||
}
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/urls.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/databases/urls.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package databases
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient, instanceID string) string {
|
||||
return c.ServiceURL("instances", instanceID, "databases")
|
||||
}
|
||||
|
||||
func dbURL(c *gophercloud.ServiceClient, instanceID, dbName string) string {
|
||||
return c.ServiceURL("instances", instanceID, "databases", dbName)
|
||||
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package datastores provides information and interaction with the datastore
|
||||
// API resource in the Rackspace Database service.
|
||||
package datastores
|
100
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/fixtures.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
package datastores
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
const version1JSON = `
|
||||
{
|
||||
"id": "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/v1.0/1234/datastores/versions/b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/datastores/versions/b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "5.1"
|
||||
}
|
||||
`
|
||||
|
||||
const version2JSON = `
|
||||
{
|
||||
"id": "c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/v1.0/1234/datastores/versions/c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/datastores/versions/c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "5.2"
|
||||
}
|
||||
`
|
||||
|
||||
var versionsJSON = fmt.Sprintf(`"versions": [%s, %s]`, version1JSON, version2JSON)
|
||||
|
||||
var singleDSJSON = fmt.Sprintf(`
|
||||
{
|
||||
"default_version": "c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
"id": "10000000-0000-0000-0000-000000000001",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/v1.0/1234/datastores/10000000-0000-0000-0000-000000000001",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://10.240.28.38:8779/datastores/10000000-0000-0000-0000-000000000001",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "mysql",
|
||||
%s
|
||||
}
|
||||
`, versionsJSON)
|
||||
|
||||
var (
|
||||
ListDSResp = fmt.Sprintf(`{"datastores":[%s]}`, singleDSJSON)
|
||||
GetDSResp = fmt.Sprintf(`{"datastore":%s}`, singleDSJSON)
|
||||
ListVersionsResp = fmt.Sprintf(`{%s}`, versionsJSON)
|
||||
GetVersionResp = fmt.Sprintf(`{"version":%s}`, version1JSON)
|
||||
)
|
||||
|
||||
var ExampleVersion1 = Version{
|
||||
ID: "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
Links: []gophercloud.Link{
|
||||
gophercloud.Link{Rel: "self", Href: "https://10.240.28.38:8779/v1.0/1234/datastores/versions/b00000b0-00b0-0b00-00b0-000b000000bb"},
|
||||
gophercloud.Link{Rel: "bookmark", Href: "https://10.240.28.38:8779/datastores/versions/b00000b0-00b0-0b00-00b0-000b000000bb"},
|
||||
},
|
||||
Name: "5.1",
|
||||
}
|
||||
|
||||
var exampleVersion2 = Version{
|
||||
ID: "c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
Links: []gophercloud.Link{
|
||||
gophercloud.Link{Rel: "self", Href: "https://10.240.28.38:8779/v1.0/1234/datastores/versions/c00000b0-00c0-0c00-00c0-000b000000cc"},
|
||||
gophercloud.Link{Rel: "bookmark", Href: "https://10.240.28.38:8779/datastores/versions/c00000b0-00c0-0c00-00c0-000b000000cc"},
|
||||
},
|
||||
Name: "5.2",
|
||||
}
|
||||
|
||||
var ExampleVersions = []Version{ExampleVersion1, exampleVersion2}
|
||||
|
||||
var ExampleDatastore = Datastore{
|
||||
DefaultVersion: "c00000b0-00c0-0c00-00c0-000b000000cc",
|
||||
ID: "10000000-0000-0000-0000-000000000001",
|
||||
Links: []gophercloud.Link{
|
||||
gophercloud.Link{Rel: "self", Href: "https://10.240.28.38:8779/v1.0/1234/datastores/10000000-0000-0000-0000-000000000001"},
|
||||
gophercloud.Link{Rel: "bookmark", Href: "https://10.240.28.38:8779/datastores/10000000-0000-0000-0000-000000000001"},
|
||||
},
|
||||
Name: "mysql",
|
||||
Versions: ExampleVersions,
|
||||
}
|
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/requests.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/requests.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package datastores
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all available datastore types that instances can use.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return DatastorePage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
return pagination.NewPager(client, baseURL(client), pageFn)
|
||||
}
|
||||
|
||||
// Get will retrieve the details of a specified datastore type.
|
||||
func Get(client *gophercloud.ServiceClient, datastoreID string) GetResult {
|
||||
var res GetResult
|
||||
|
||||
_, res.Err = client.Request("GET", resourceURL(client, datastoreID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ListVersions will list all of the available versions for a specified
|
||||
// datastore type.
|
||||
func ListVersions(client *gophercloud.ServiceClient, datastoreID string) pagination.Pager {
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return VersionPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
return pagination.NewPager(client, versionsURL(client, datastoreID), pageFn)
|
||||
}
|
||||
|
||||
// GetVersion will retrieve the details of a specified datastore version.
|
||||
func GetVersion(client *gophercloud.ServiceClient, datastoreID, versionID string) GetVersionResult {
|
||||
var res GetVersionResult
|
||||
|
||||
_, res.Err = client.Request("GET", versionURL(client, datastoreID, versionID), gophercloud.RequestOpts{
|
||||
OkCodes: []int{200},
|
||||
JSONResponse: &res.Body,
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
123
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/results.go
generated
vendored
Normal file
123
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/results.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
package datastores
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Version represents a version API resource. Multiple versions belong to a Datastore.
|
||||
type Version struct {
|
||||
ID string
|
||||
Links []gophercloud.Link
|
||||
Name string
|
||||
}
|
||||
|
||||
// Datastore represents a Datastore API resource.
|
||||
type Datastore struct {
|
||||
DefaultVersion string `json:"default_version" mapstructure:"default_version"`
|
||||
ID string
|
||||
Links []gophercloud.Link
|
||||
Name string
|
||||
Versions []Version
|
||||
}
|
||||
|
||||
// DatastorePartial is a meta structure which is used in various API responses.
|
||||
// It is a lightweight and truncated version of a full Datastore resource,
|
||||
// offering details of the Version, Type and VersionID only.
|
||||
type DatastorePartial struct {
|
||||
Version string
|
||||
Type string
|
||||
VersionID string `json:"version_id" mapstructure:"version_id"`
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetVersionResult represents the result of getting a version.
|
||||
type GetVersionResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// DatastorePage represents a page of datastore resources.
|
||||
type DatastorePage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty indicates whether a Datastore collection is empty.
|
||||
func (r DatastorePage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractDatastores(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
}
|
||||
|
||||
// ExtractDatastores retrieves a slice of datastore structs from a paginated
|
||||
// collection.
|
||||
func ExtractDatastores(page pagination.Page) ([]Datastore, error) {
|
||||
casted := page.(DatastorePage).Body
|
||||
|
||||
var resp struct {
|
||||
Datastores []Datastore `mapstructure:"datastores" json:"datastores"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &resp)
|
||||
return resp.Datastores, err
|
||||
}
|
||||
|
||||
// Extract retrieves a single Datastore struct from an operation result.
|
||||
func (r GetResult) Extract() (*Datastore, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Datastore Datastore `mapstructure:"datastore"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
return &response.Datastore, err
|
||||
}
|
||||
|
||||
// DatastorePage represents a page of version resources.
|
||||
type VersionPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty indicates whether a collection of version resources is empty.
|
||||
func (r VersionPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractVersions(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
}
|
||||
|
||||
// ExtractVersions retrieves a slice of versions from a paginated collection.
|
||||
func ExtractVersions(page pagination.Page) ([]Version, error) {
|
||||
casted := page.(VersionPage).Body
|
||||
|
||||
var resp struct {
|
||||
Versions []Version `mapstructure:"versions" json:"versions"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &resp)
|
||||
return resp.Versions, err
|
||||
}
|
||||
|
||||
// Extract retrieves a single Version struct from an operation result.
|
||||
func (r GetVersionResult) Extract() (*Version, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Version Version `mapstructure:"version"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
return &response.Version, err
|
||||
}
|
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/urls.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/datastores/urls.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package datastores
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("datastores")
|
||||
}
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient, dsID string) string {
|
||||
return c.ServiceURL("datastores", dsID)
|
||||
}
|
||||
|
||||
func versionsURL(c *gophercloud.ServiceClient, dsID string) string {
|
||||
return c.ServiceURL("datastores", dsID, "versions")
|
||||
}
|
||||
|
||||
func versionURL(c *gophercloud.ServiceClient, dsID, versionID string) string {
|
||||
return c.ServiceURL("datastores", dsID, "versions", versionID)
|
||||
}
|
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/doc.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Package flavors provides information and interaction with the flavor API
|
||||
// resource in the OpenStack Database service.
|
||||
//
|
||||
// A flavor is an available hardware configuration for a database instance.
|
||||
// Each flavor has a unique combination of disk space, memory capacity and
|
||||
// priority for CPU time.
|
||||
package flavors
|
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/fixtures.go
generated
vendored
Normal file
50
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/testhelper/fixture"
|
||||
)
|
||||
|
||||
const flavor = `
|
||||
{
|
||||
"id": %d,
|
||||
"links": [
|
||||
{
|
||||
"href": "https://openstack.example.com/v1.0/1234/flavors/%d",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://openstack.example.com/flavors/%d",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"name": "%s",
|
||||
"ram": %d
|
||||
}
|
||||
`
|
||||
|
||||
var (
|
||||
flavorID = "{flavorID}"
|
||||
_baseURL = "/flavors"
|
||||
resURL = "/flavors/" + flavorID
|
||||
)
|
||||
|
||||
var (
|
||||
flavor1 = fmt.Sprintf(flavor, 1, 1, 1, "m1.tiny", 512)
|
||||
flavor2 = fmt.Sprintf(flavor, 2, 2, 2, "m1.small", 1024)
|
||||
flavor3 = fmt.Sprintf(flavor, 3, 3, 3, "m1.medium", 2048)
|
||||
flavor4 = fmt.Sprintf(flavor, 4, 4, 4, "m1.large", 4096)
|
||||
|
||||
listFlavorsResp = fmt.Sprintf(`{"flavors":[%s, %s, %s, %s]}`, flavor1, flavor2, flavor3, flavor4)
|
||||
getFlavorResp = fmt.Sprintf(`{"flavor": %s}`, flavor1)
|
||||
)
|
||||
|
||||
func HandleList(t *testing.T) {
|
||||
fixture.SetupHandler(t, _baseURL, "GET", "", listFlavorsResp, 200)
|
||||
}
|
||||
|
||||
func HandleGet(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL, "GET", "", getFlavorResp, 200)
|
||||
}
|
29
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/requests.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/requests.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all available hardware flavors that an instance can use. The
|
||||
// operation is identical to the one supported by the Nova API, but without the
|
||||
// "disk" property.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
createPage := func(r pagination.PageResult) pagination.Page {
|
||||
return FlavorPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, listURL(client), createPage)
|
||||
}
|
||||
|
||||
// Get will retrieve information for a specified hardware flavor.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var gr GetResult
|
||||
|
||||
_, gr.Err = client.Request("GET", getURL(client, id), gophercloud.RequestOpts{
|
||||
JSONResponse: &gr.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return gr
|
||||
}
|
92
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/results.go
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/results.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// GetResult temporarily holds the response from a Get call.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract provides access to the individual Flavor returned by the Get function.
|
||||
func (gr GetResult) Extract() (*Flavor, error) {
|
||||
if gr.Err != nil {
|
||||
return nil, gr.Err
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Flavor Flavor `mapstructure:"flavor"`
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &result,
|
||||
})
|
||||
|
||||
err = decoder.Decode(gr.Body)
|
||||
return &result.Flavor, err
|
||||
}
|
||||
|
||||
// Flavor records represent (virtual) hardware configurations for server resources in a region.
|
||||
type Flavor struct {
|
||||
// The flavor's unique identifier.
|
||||
ID string `mapstructure:"id"`
|
||||
|
||||
// The RAM capacity for the flavor.
|
||||
RAM int `mapstructure:"ram"`
|
||||
|
||||
// The Name field provides a human-readable moniker for the flavor.
|
||||
Name string `mapstructure:"name"`
|
||||
|
||||
// Links to access the flavor.
|
||||
Links []gophercloud.Link
|
||||
}
|
||||
|
||||
// FlavorPage contains a single page of the response from a List call.
|
||||
type FlavorPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty determines if a page contains any results.
|
||||
func (p FlavorPage) IsEmpty() (bool, error) {
|
||||
flavors, err := ExtractFlavors(p)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(flavors) == 0, nil
|
||||
}
|
||||
|
||||
// NextPageURL uses the response's embedded link reference to navigate to the next page of results.
|
||||
func (p FlavorPage) NextPageURL() (string, error) {
|
||||
type resp struct {
|
||||
Links []gophercloud.Link `mapstructure:"flavors_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(p.Body, &r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
}
|
||||
|
||||
// ExtractFlavors provides access to the list of flavors in a page acquired from the List operation.
|
||||
func ExtractFlavors(page pagination.Page) ([]Flavor, error) {
|
||||
casted := page.(FlavorPage).Body
|
||||
var container struct {
|
||||
Flavors []Flavor `mapstructure:"flavors"`
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||
WeaklyTypedInput: true,
|
||||
Result: &container,
|
||||
})
|
||||
|
||||
err = decoder.Decode(casted)
|
||||
|
||||
return container.Flavors, err
|
||||
}
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/urls.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/flavors/urls.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package flavors
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func getURL(client *gophercloud.ServiceClient, id string) string {
|
||||
return client.ServiceURL("flavors", id)
|
||||
}
|
||||
|
||||
func listURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("flavors")
|
||||
}
|
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/doc.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Package instances provides information and interaction with the instance API
|
||||
// resource in the OpenStack Database service.
|
||||
//
|
||||
// A database instance is an isolated database environment with compute and
|
||||
// storage resources in a single tenant environment on a shared physical host
|
||||
// machine.
|
||||
package instances
|
169
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/fixtures.go
generated
vendored
Normal file
169
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/flavors"
|
||||
"github.com/rackspace/gophercloud/testhelper/fixture"
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp = "2015-11-12T14:22:42Z"
|
||||
timeVal, _ = time.Parse(time.RFC3339, timestamp)
|
||||
)
|
||||
|
||||
var instance = `
|
||||
{
|
||||
"created": "` + timestamp + `",
|
||||
"datastore": {
|
||||
"type": "mysql",
|
||||
"version": "5.6"
|
||||
},
|
||||
"flavor": {
|
||||
"id": "1",
|
||||
"links": [
|
||||
{
|
||||
"href": "https://my-openstack.com/v1.0/1234/flavors/1",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "https://my-openstack.com/v1.0/1234/flavors/1",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "https://my-openstack.com/v1.0/1234/instances/1",
|
||||
"rel": "self"
|
||||
}
|
||||
],
|
||||
"hostname": "e09ad9a3f73309469cf1f43d11e79549caf9acf2.my-openstack.com",
|
||||
"id": "{instanceID}",
|
||||
"name": "json_rack_instance",
|
||||
"status": "BUILD",
|
||||
"updated": "` + timestamp + `",
|
||||
"volume": {
|
||||
"size": 2
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var createReq = `
|
||||
{
|
||||
"instance": {
|
||||
"databases": [
|
||||
{
|
||||
"character_set": "utf8",
|
||||
"collate": "utf8_general_ci",
|
||||
"name": "sampledb"
|
||||
},
|
||||
{
|
||||
"name": "nextround"
|
||||
}
|
||||
],
|
||||
"flavorRef": "1",
|
||||
"name": "json_rack_instance",
|
||||
"users": [
|
||||
{
|
||||
"databases": [
|
||||
{
|
||||
"name": "sampledb"
|
||||
}
|
||||
],
|
||||
"name": "demouser",
|
||||
"password": "demopassword"
|
||||
}
|
||||
],
|
||||
"volume": {
|
||||
"size": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var (
|
||||
instanceID = "{instanceID}"
|
||||
rootURL = "/instances"
|
||||
resURL = rootURL + "/" + instanceID
|
||||
uRootURL = resURL + "/root"
|
||||
aURL = resURL + "/action"
|
||||
)
|
||||
|
||||
var (
|
||||
restartReq = `{"restart": {}}`
|
||||
resizeReq = `{"resize": {"flavorRef": "2"}}`
|
||||
resizeVolReq = `{"resize": {"volume": {"size": 4}}}`
|
||||
)
|
||||
|
||||
var (
|
||||
createResp = fmt.Sprintf(`{"instance": %s}`, instance)
|
||||
listInstancesResp = fmt.Sprintf(`{"instances":[%s]}`, instance)
|
||||
getInstanceResp = createResp
|
||||
enableUserResp = `{"user":{"name":"root","password":"secretsecret"}}`
|
||||
isUserEnabledResp = `{"rootEnabled":true}`
|
||||
)
|
||||
|
||||
var expectedInstance = Instance{
|
||||
Created: timeVal,
|
||||
Updated: timeVal,
|
||||
Flavor: flavors.Flavor{
|
||||
ID: "1",
|
||||
Links: []gophercloud.Link{
|
||||
gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/flavors/1", Rel: "self"},
|
||||
gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/flavors/1", Rel: "bookmark"},
|
||||
},
|
||||
},
|
||||
Hostname: "e09ad9a3f73309469cf1f43d11e79549caf9acf2.my-openstack.com",
|
||||
ID: instanceID,
|
||||
Links: []gophercloud.Link{
|
||||
gophercloud.Link{Href: "https://my-openstack.com/v1.0/1234/instances/1", Rel: "self"},
|
||||
},
|
||||
Name: "json_rack_instance",
|
||||
Status: "BUILD",
|
||||
Volume: Volume{Size: 2},
|
||||
Datastore: datastores.DatastorePartial{
|
||||
Type: "mysql",
|
||||
Version: "5.6",
|
||||
},
|
||||
}
|
||||
|
||||
func HandleCreate(t *testing.T) {
|
||||
fixture.SetupHandler(t, rootURL, "POST", createReq, createResp, 200)
|
||||
}
|
||||
|
||||
func HandleList(t *testing.T) {
|
||||
fixture.SetupHandler(t, rootURL, "GET", "", listInstancesResp, 200)
|
||||
}
|
||||
|
||||
func HandleGet(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL, "GET", "", getInstanceResp, 200)
|
||||
}
|
||||
|
||||
func HandleDelete(t *testing.T) {
|
||||
fixture.SetupHandler(t, resURL, "DELETE", "", "", 202)
|
||||
}
|
||||
|
||||
func HandleEnableRoot(t *testing.T) {
|
||||
fixture.SetupHandler(t, uRootURL, "POST", "", enableUserResp, 200)
|
||||
}
|
||||
|
||||
func HandleIsRootEnabled(t *testing.T) {
|
||||
fixture.SetupHandler(t, uRootURL, "GET", "", isUserEnabledResp, 200)
|
||||
}
|
||||
|
||||
func HandleRestart(t *testing.T) {
|
||||
fixture.SetupHandler(t, aURL, "POST", restartReq, "", 202)
|
||||
}
|
||||
|
||||
func HandleResize(t *testing.T) {
|
||||
fixture.SetupHandler(t, aURL, "POST", resizeReq, "", 202)
|
||||
}
|
||||
|
||||
func HandleResizeVol(t *testing.T) {
|
||||
fixture.SetupHandler(t, aURL, "POST", resizeVolReq, "", 202)
|
||||
}
|
238
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/requests.go
generated
vendored
Normal file
238
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/requests.go
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/users"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsBuilder is the top-level interface for create options.
|
||||
type CreateOptsBuilder interface {
|
||||
ToInstanceCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// DatastoreOpts represents the configuration for how an instance stores data.
|
||||
type DatastoreOpts struct {
|
||||
Version string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (opts DatastoreOpts) ToMap() (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"version": opts.Version,
|
||||
"type": opts.Type,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateOpts is the struct responsible for configuring a new database instance.
|
||||
type CreateOpts struct {
|
||||
// Either the integer UUID (in string form) of the flavor, or its URI
|
||||
// reference as specified in the response from the List() call. Required.
|
||||
FlavorRef string
|
||||
|
||||
// Specifies the volume size in gigabytes (GB). The value must be between 1
|
||||
// and 300. Required.
|
||||
Size int
|
||||
|
||||
// Name of the instance to create. The length of the name is limited to
|
||||
// 255 characters and any characters are permitted. Optional.
|
||||
Name string
|
||||
|
||||
// A slice of database information options.
|
||||
Databases db.CreateOptsBuilder
|
||||
|
||||
// A slice of user information options.
|
||||
Users users.CreateOptsBuilder
|
||||
|
||||
// Options to configure the type of datastore the instance will use. This is
|
||||
// optional, and if excluded will default to MySQL.
|
||||
Datastore *DatastoreOpts
|
||||
}
|
||||
|
||||
// ToInstanceCreateMap will render a JSON map.
|
||||
func (opts CreateOpts) ToInstanceCreateMap() (map[string]interface{}, error) {
|
||||
if opts.Size > 300 || opts.Size < 1 {
|
||||
return nil, fmt.Errorf("Size (GB) must be between 1-300")
|
||||
}
|
||||
if opts.FlavorRef == "" {
|
||||
return nil, fmt.Errorf("FlavorRef is a required field")
|
||||
}
|
||||
|
||||
instance := map[string]interface{}{
|
||||
"volume": map[string]int{"size": opts.Size},
|
||||
"flavorRef": opts.FlavorRef,
|
||||
}
|
||||
|
||||
if opts.Name != "" {
|
||||
instance["name"] = opts.Name
|
||||
}
|
||||
if opts.Databases != nil {
|
||||
dbs, err := opts.Databases.ToDBCreateMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance["databases"] = dbs["databases"]
|
||||
}
|
||||
if opts.Users != nil {
|
||||
users, err := opts.Users.ToUserCreateMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance["users"] = users["users"]
|
||||
}
|
||||
|
||||
return map[string]interface{}{"instance": instance}, nil
|
||||
}
|
||||
|
||||
// Create asynchronously provisions a new database instance. It requires the
|
||||
// user to specify a flavor and a volume size. The API service then provisions
|
||||
// the instance with the requested flavor and sets up a volume of the specified
|
||||
// size, which is the storage for the database instance.
|
||||
//
|
||||
// Although this call only allows the creation of 1 instance per request, you
|
||||
// can create an instance with multiple databases and users. The default
|
||||
// binding for a MySQL instance is port 3306.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToInstanceCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("POST", baseURL(client), gophercloud.RequestOpts{
|
||||
JSONBody: &reqBody,
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// List retrieves the status and information for all database instances.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return InstancePage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, baseURL(client), createPageFn)
|
||||
}
|
||||
|
||||
// Get retrieves the status and information for a specified database instance.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var res GetResult
|
||||
|
||||
_, res.Err = client.Request("GET", resourceURL(client, id), gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete permanently destroys the database instance.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
var res DeleteResult
|
||||
|
||||
_, res.Err = client.Request("DELETE", resourceURL(client, id), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// EnableRootUser enables the login from any host for the root user and
|
||||
// provides the user with a generated root password.
|
||||
func EnableRootUser(client *gophercloud.ServiceClient, id string) UserRootResult {
|
||||
var res UserRootResult
|
||||
|
||||
_, res.Err = client.Request("POST", userRootURL(client, id), gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// IsRootEnabled checks an instance to see if root access is enabled. It returns
|
||||
// True if root user is enabled for the specified database instance or False
|
||||
// otherwise.
|
||||
func IsRootEnabled(client *gophercloud.ServiceClient, id string) (bool, error) {
|
||||
var res gophercloud.Result
|
||||
|
||||
_, err := client.Request("GET", userRootURL(client, id), gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return res.Body.(map[string]interface{})["rootEnabled"] == true, err
|
||||
}
|
||||
|
||||
// Restart will restart only the MySQL Instance. Restarting MySQL will
|
||||
// erase any dynamic configuration settings that you have made within MySQL.
|
||||
// The MySQL service will be unavailable until the instance restarts.
|
||||
func Restart(client *gophercloud.ServiceClient, id string) ActionResult {
|
||||
var res ActionResult
|
||||
|
||||
_, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{
|
||||
JSONBody: map[string]interface{}{"restart": struct{}{}},
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Resize changes the memory size of the instance, assuming a valid
|
||||
// flavorRef is provided. It will also restart the MySQL service.
|
||||
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) ActionResult {
|
||||
var res ActionResult
|
||||
|
||||
type resize struct {
|
||||
FlavorRef string `json:"flavorRef"`
|
||||
}
|
||||
|
||||
type req struct {
|
||||
Resize resize `json:"resize"`
|
||||
}
|
||||
|
||||
reqBody := req{Resize: resize{FlavorRef: flavorRef}}
|
||||
|
||||
_, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{
|
||||
JSONBody: reqBody,
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ResizeVolume will resize the attached volume for an instance. It supports
|
||||
// only increasing the volume size and does not support decreasing the size.
|
||||
// The volume size is in gigabytes (GB) and must be an integer.
|
||||
func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) ActionResult {
|
||||
var res ActionResult
|
||||
|
||||
type volume struct {
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type resize struct {
|
||||
Volume volume `json:"volume"`
|
||||
}
|
||||
|
||||
type req struct {
|
||||
Resize resize `json:"resize"`
|
||||
}
|
||||
|
||||
reqBody := req{Resize: resize{Volume: volume{Size: size}}}
|
||||
|
||||
_, res.Err = client.Request("POST", actionURL(client, id), gophercloud.RequestOpts{
|
||||
JSONBody: reqBody,
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
213
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/results.go
generated
vendored
Normal file
213
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/results.go
generated
vendored
Normal file
@ -0,0 +1,213 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/flavors"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/users"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Volume represents information about an attached volume for a database instance.
|
||||
type Volume struct {
|
||||
// The size in GB of the volume
|
||||
Size int
|
||||
|
||||
Used float64
|
||||
}
|
||||
|
||||
// Instance represents a remote MySQL instance.
|
||||
type Instance struct {
|
||||
// Indicates the datetime that the instance was created
|
||||
Created time.Time `mapstructure:"-"`
|
||||
|
||||
// Indicates the most recent datetime that the instance was updated.
|
||||
Updated time.Time `mapstructure:"-"`
|
||||
|
||||
// Indicates the hardware flavor the instance uses.
|
||||
Flavor flavors.Flavor
|
||||
|
||||
// A DNS-resolvable hostname associated with the database instance (rather
|
||||
// than an IPv4 address). Since the hostname always resolves to the correct
|
||||
// IP address of the database instance, this relieves the user from the task
|
||||
// of maintaining the mapping. Note that although the IP address may likely
|
||||
// change on resizing, migrating, and so forth, the hostname always resolves
|
||||
// to the correct database instance.
|
||||
Hostname string
|
||||
|
||||
// Indicates the unique identifier for the instance resource.
|
||||
ID string
|
||||
|
||||
// Exposes various links that reference the instance resource.
|
||||
Links []gophercloud.Link
|
||||
|
||||
// The human-readable name of the instance.
|
||||
Name string
|
||||
|
||||
// The build status of the instance.
|
||||
Status string
|
||||
|
||||
// Information about the attached volume of the instance.
|
||||
Volume Volume
|
||||
|
||||
// Indicates how the instance stores data.
|
||||
Datastore datastores.DatastorePartial
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a Create operation.
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of a Delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// Extract will extract an Instance from various result structs.
|
||||
func (r commonResult) Extract() (*Instance, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Instance Instance `mapstructure:"instance"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
val := r.Body.(map[string]interface{})["instance"].(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Instance, err
|
||||
}
|
||||
response.Instance.Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Instance, err
|
||||
}
|
||||
response.Instance.Updated = updatedTime
|
||||
}
|
||||
|
||||
return &response.Instance, err
|
||||
}
|
||||
|
||||
// InstancePage represents a single page of a paginated instance collection.
|
||||
type InstancePage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty checks to see whether the collection is empty.
|
||||
func (page InstancePage) IsEmpty() (bool, error) {
|
||||
instances, err := ExtractInstances(page)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(instances) == 0, nil
|
||||
}
|
||||
|
||||
// NextPageURL will retrieve the next page URL.
|
||||
func (page InstancePage) NextPageURL() (string, error) {
|
||||
type resp struct {
|
||||
Links []gophercloud.Link `mapstructure:"instances_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
}
|
||||
|
||||
// ExtractInstances will convert a generic pagination struct into a more
|
||||
// relevant slice of Instance structs.
|
||||
func ExtractInstances(page pagination.Page) ([]Instance, error) {
|
||||
casted := page.(InstancePage).Body
|
||||
|
||||
var resp struct {
|
||||
Instances []Instance `mapstructure:"instances"`
|
||||
}
|
||||
|
||||
if err := mapstructure.Decode(casted, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vals []interface{}
|
||||
switch casted.(type) {
|
||||
case map[string]interface{}:
|
||||
vals = casted.(map[string]interface{})["instances"].([]interface{})
|
||||
case map[string][]interface{}:
|
||||
vals = casted.(map[string][]interface{})["instances"]
|
||||
default:
|
||||
return resp.Instances, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
|
||||
}
|
||||
|
||||
for i, v := range vals {
|
||||
val := v.(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Instances, err
|
||||
}
|
||||
resp.Instances[i].Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Instances, err
|
||||
}
|
||||
resp.Instances[i].Updated = updatedTime
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Instances, nil
|
||||
}
|
||||
|
||||
// UserRootResult represents the result of an operation to enable the root user.
|
||||
type UserRootResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract will extract root user information from a UserRootResult.
|
||||
func (r UserRootResult) Extract() (*users.User, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
User users.User `mapstructure:"user"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
|
||||
return &response.User, err
|
||||
}
|
||||
|
||||
// ActionResult represents the result of action requests, such as: restarting
|
||||
// an instance service, resizing its memory allocation, and resizing its
|
||||
// attached volume size.
|
||||
type ActionResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/urls.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/instances/urls.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package instances
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("instances")
|
||||
}
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("instances", id)
|
||||
}
|
||||
|
||||
func userRootURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("instances", id, "root")
|
||||
}
|
||||
|
||||
func actionURL(c *gophercloud.ServiceClient, id string) string {
|
||||
return c.ServiceURL("instances", id, "action")
|
||||
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package users provides information and interaction with the user API
|
||||
// resource in the OpenStack Database service.
|
||||
package users
|
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/fixtures.go
generated
vendored
Normal file
37
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rackspace/gophercloud/testhelper/fixture"
|
||||
)
|
||||
|
||||
const user1 = `
|
||||
{"databases": [{"name": "databaseA"}],"name": "dbuser3"%s}
|
||||
`
|
||||
|
||||
const user2 = `
|
||||
{"databases": [{"name": "databaseB"},{"name": "databaseC"}],"name": "dbuser4"%s}
|
||||
`
|
||||
|
||||
var (
|
||||
instanceID = "{instanceID}"
|
||||
_rootURL = "/instances/" + instanceID + "/users"
|
||||
pUser1 = fmt.Sprintf(user1, `,"password":"secretsecret"`)
|
||||
pUser2 = fmt.Sprintf(user2, `,"password":"secretsecret"`)
|
||||
createReq = fmt.Sprintf(`{"users":[%s, %s]}`, pUser1, pUser2)
|
||||
listResp = fmt.Sprintf(`{"users":[%s, %s]}`, fmt.Sprintf(user1, ""), fmt.Sprintf(user2, ""))
|
||||
)
|
||||
|
||||
func HandleCreate(t *testing.T) {
|
||||
fixture.SetupHandler(t, _rootURL, "POST", createReq, "", 202)
|
||||
}
|
||||
|
||||
func HandleList(t *testing.T) {
|
||||
fixture.SetupHandler(t, _rootURL, "GET", "", listResp, 200)
|
||||
}
|
||||
|
||||
func HandleDelete(t *testing.T) {
|
||||
fixture.SetupHandler(t, _rootURL+"/{userName}", "DELETE", "", "", 202)
|
||||
}
|
132
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/requests.go
generated
vendored
Normal file
132
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/requests.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsBuilder is the top-level interface for creating JSON maps.
|
||||
type CreateOptsBuilder interface {
|
||||
ToUserCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateOpts is the struct responsible for configuring a new user; often in the
|
||||
// context of an instance.
|
||||
type CreateOpts struct {
|
||||
// [REQUIRED] Specifies a name for the user. Valid names can be composed
|
||||
// of the following characters: letters (either case); numbers; these
|
||||
// characters '@', '?', '#', ' ' but NEVER beginning a name string; '_' is
|
||||
// permitted anywhere. Prohibited characters that are forbidden include:
|
||||
// single quotes, double quotes, back quotes, semicolons, commas, backslashes,
|
||||
// and forward slashes. Spaces at the front or end of a user name are also
|
||||
// not permitted.
|
||||
Name string
|
||||
|
||||
// [REQUIRED] Specifies a password for the user.
|
||||
Password string
|
||||
|
||||
// [OPTIONAL] An array of databases that this user will connect to. The
|
||||
// "name" field is the only requirement for each option.
|
||||
Databases db.BatchCreateOpts
|
||||
|
||||
// [OPTIONAL] Specifies the host from which a user is allowed to connect to
|
||||
// the database. Possible values are a string containing an IPv4 address or
|
||||
// "%" to allow connecting from any host. Optional; the default is "%".
|
||||
Host string
|
||||
}
|
||||
|
||||
// ToMap is a convenience function for creating sub-maps for individual users.
|
||||
func (opts CreateOpts) ToMap() (map[string]interface{}, error) {
|
||||
|
||||
if opts.Name == "root" {
|
||||
return nil, errors.New("root is a reserved user name and cannot be used")
|
||||
}
|
||||
if opts.Name == "" {
|
||||
return nil, errors.New("Name is a required field")
|
||||
}
|
||||
if opts.Password == "" {
|
||||
return nil, errors.New("Password is a required field")
|
||||
}
|
||||
|
||||
user := map[string]interface{}{
|
||||
"name": opts.Name,
|
||||
"password": opts.Password,
|
||||
}
|
||||
|
||||
if opts.Host != "" {
|
||||
user["host"] = opts.Host
|
||||
}
|
||||
|
||||
dbs := make([]map[string]string, len(opts.Databases))
|
||||
for i, db := range opts.Databases {
|
||||
dbs[i] = map[string]string{"name": db.Name}
|
||||
}
|
||||
|
||||
if len(dbs) > 0 {
|
||||
user["databases"] = dbs
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// BatchCreateOpts allows multiple users to be created at once.
|
||||
type BatchCreateOpts []CreateOpts
|
||||
|
||||
// ToUserCreateMap will generate a JSON map.
|
||||
func (opts BatchCreateOpts) ToUserCreateMap() (map[string]interface{}, error) {
|
||||
users := make([]map[string]interface{}, len(opts))
|
||||
for i, opt := range opts {
|
||||
user, err := opt.ToMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users[i] = user
|
||||
}
|
||||
return map[string]interface{}{"users": users}, nil
|
||||
}
|
||||
|
||||
// Create asynchronously provisions a new user for the specified database
|
||||
// instance based on the configuration defined in CreateOpts. If databases are
|
||||
// assigned for a particular user, the user will be granted all privileges
|
||||
// for those specified databases. "root" is a reserved name and cannot be used.
|
||||
func Create(client *gophercloud.ServiceClient, instanceID string, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToUserCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("POST", baseURL(client, instanceID), gophercloud.RequestOpts{
|
||||
JSONBody: &reqBody,
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// List will list all the users associated with a specified database instance,
|
||||
// along with their associated databases. This operation will not return any
|
||||
// system users or administrators for a database.
|
||||
func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
|
||||
createPageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return UserPage{pagination.LinkedPageBase{PageResult: r}}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, baseURL(client, instanceID), createPageFn)
|
||||
}
|
||||
|
||||
// Delete will permanently delete a user from a specified database instance.
|
||||
func Delete(client *gophercloud.ServiceClient, instanceID, userName string) DeleteResult {
|
||||
var res DeleteResult
|
||||
|
||||
_, res.Err = client.Request("DELETE", userURL(client, instanceID, userName), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
73
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/results.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/results.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
db "github.com/rackspace/gophercloud/openstack/db/v1/databases"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// User represents a database user
|
||||
type User struct {
|
||||
// The user name
|
||||
Name string
|
||||
|
||||
// The user password
|
||||
Password string
|
||||
|
||||
// The databases associated with this user
|
||||
Databases []db.Database
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a create operation.
|
||||
type CreateResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of a delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
// UserPage represents a single page of a paginated user collection.
|
||||
type UserPage struct {
|
||||
pagination.LinkedPageBase
|
||||
}
|
||||
|
||||
// IsEmpty checks to see whether the collection is empty.
|
||||
func (page UserPage) IsEmpty() (bool, error) {
|
||||
users, err := ExtractUsers(page)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(users) == 0, nil
|
||||
}
|
||||
|
||||
// NextPageURL will retrieve the next page URL.
|
||||
func (page UserPage) NextPageURL() (string, error) {
|
||||
type resp struct {
|
||||
Links []gophercloud.Link `mapstructure:"users_links"`
|
||||
}
|
||||
|
||||
var r resp
|
||||
err := mapstructure.Decode(page.Body, &r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gophercloud.ExtractNextURL(r.Links)
|
||||
}
|
||||
|
||||
// ExtractUsers will convert a generic pagination struct into a more
|
||||
// relevant slice of User structs.
|
||||
func ExtractUsers(page pagination.Page) ([]User, error) {
|
||||
casted := page.(UserPage).Body
|
||||
|
||||
var response struct {
|
||||
Users []User `mapstructure:"users"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(casted, &response)
|
||||
|
||||
return response.Users, err
|
||||
}
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/urls.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/db/v1/users/urls.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package users
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient, instanceID string) string {
|
||||
return c.ServiceURL("instances", instanceID, "users")
|
||||
}
|
||||
|
||||
func userURL(c *gophercloud.ServiceClient, instanceID, userName string) string {
|
||||
return c.ServiceURL("instances", instanceID, "users", userName)
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/rackspace/gophercloud/openstack/identity/v2/tenants"
|
||||
th "github.com/rackspace/gophercloud/testhelper"
|
||||
thclient "github.com/rackspace/gophercloud/testhelper/client"
|
||||
)
|
||||
|
||||
// ExpectedToken is the token that should be parsed from TokenCreationResponse.
|
||||
@ -54,6 +55,14 @@ var ExpectedServiceCatalog = &ServiceCatalog{
|
||||
},
|
||||
}
|
||||
|
||||
// ExpectedUser is the token that should be parsed from TokenGetResponse.
|
||||
var ExpectedUser = &User{
|
||||
ID: "a530fefc3d594c4ba2693a4ecd6be74e",
|
||||
Name: "apiserver",
|
||||
Roles: []Role{{"member"}, {"service"}},
|
||||
UserName: "apiserver",
|
||||
}
|
||||
|
||||
// TokenCreationResponse is a JSON response that contains ExpectedToken and ExpectedServiceCatalog.
|
||||
const TokenCreationResponse = `
|
||||
{
|
||||
@ -99,6 +108,39 @@ const TokenCreationResponse = `
|
||||
}
|
||||
`
|
||||
|
||||
// TokenGetResponse is a JSON response that contains ExpectedToken and ExpectedUser.
|
||||
const TokenGetResponse = `
|
||||
{
|
||||
"access": {
|
||||
"token": {
|
||||
"issued_at": "2014-01-30T15:30:58.000000Z",
|
||||
"expires": "2014-01-31T15:30:58Z",
|
||||
"id": "aaaabbbbccccdddd",
|
||||
"tenant": {
|
||||
"description": "There are many tenants. This one is yours.",
|
||||
"enabled": true,
|
||||
"id": "fc394f2ab2df4114bde39905f800dc57",
|
||||
"name": "test"
|
||||
}
|
||||
},
|
||||
"serviceCatalog": [],
|
||||
"user": {
|
||||
"id": "a530fefc3d594c4ba2693a4ecd6be74e",
|
||||
"name": "apiserver",
|
||||
"roles": [
|
||||
{
|
||||
"name": "member"
|
||||
},
|
||||
{
|
||||
"name": "service"
|
||||
}
|
||||
],
|
||||
"roles_links": [],
|
||||
"username": "apiserver"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// HandleTokenPost expects a POST against a /tokens handler, ensures that the request body has been
|
||||
// constructed properly given certain auth options, and returns the result.
|
||||
func HandleTokenPost(t *testing.T, requestJSON string) {
|
||||
@ -115,6 +157,19 @@ func HandleTokenPost(t *testing.T, requestJSON string) {
|
||||
})
|
||||
}
|
||||
|
||||
// HandleTokenGet expects a Get against a /tokens handler, ensures that the request body has been
|
||||
// constructed properly given certain auth options, and returns the result.
|
||||
func HandleTokenGet(t *testing.T, token string) {
|
||||
th.Mux.HandleFunc("/tokens/"+token, func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "GET")
|
||||
th.TestHeader(t, r, "Accept", "application/json")
|
||||
th.TestHeader(t, r, "X-Auth-Token", thclient.TokenID)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, TokenGetResponse)
|
||||
})
|
||||
}
|
||||
|
||||
// IsSuccessful ensures that a CreateResult was successful and contains the correct token and
|
||||
// service catalog.
|
||||
func IsSuccessful(t *testing.T, result CreateResult) {
|
||||
@ -126,3 +181,15 @@ func IsSuccessful(t *testing.T, result CreateResult) {
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, ExpectedServiceCatalog, serviceCatalog)
|
||||
}
|
||||
|
||||
// GetIsSuccessful ensures that a GetResult was successful and contains the correct token and
|
||||
// User Info.
|
||||
func GetIsSuccessful(t *testing.T, result GetResult) {
|
||||
token, err := result.ExtractToken()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, ExpectedToken, token)
|
||||
|
||||
user, err := result.ExtractUser()
|
||||
th.AssertNoErr(t, err)
|
||||
th.CheckDeepEquals(t, ExpectedUser, user)
|
||||
}
|
||||
|
@ -88,3 +88,12 @@ func Create(client *gophercloud.ServiceClient, auth AuthOptionsBuilder) CreateRe
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// Validates and retrieves information for user's token.
|
||||
func Get(client *gophercloud.ServiceClient, token string) GetResult {
|
||||
var result GetResult
|
||||
_, result.Err = client.Get(GetURL(client, token), &result.Body, &gophercloud.RequestOpts{
|
||||
OkCodes: []int{200, 203},
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
@ -25,6 +25,17 @@ type Token struct {
|
||||
Tenant tenants.Tenant
|
||||
}
|
||||
|
||||
// Authorization need user info which can get from token authentication's response
|
||||
type Role struct {
|
||||
Name string `mapstructure:"name"`
|
||||
}
|
||||
type User struct {
|
||||
ID string `mapstructure:"id"`
|
||||
Name string `mapstructure:"name"`
|
||||
UserName string `mapstructure:"username"`
|
||||
Roles []Role `mapstructure:"roles"`
|
||||
}
|
||||
|
||||
// Endpoint represents a single API endpoint offered by a service.
|
||||
// It provides the public and internal URLs, if supported, along with a region specifier, again if provided.
|
||||
// The significance of the Region field will depend upon your provider.
|
||||
@ -74,6 +85,12 @@ type CreateResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// GetResult is the deferred response from a Get call, which is the same with a Created token.
|
||||
// Use ExtractUser() to interpret it as a User.
|
||||
type GetResult struct {
|
||||
CreateResult
|
||||
}
|
||||
|
||||
// ExtractToken returns the just-created Token from a CreateResult.
|
||||
func (result CreateResult) ExtractToken() (*Token, error) {
|
||||
if result.Err != nil {
|
||||
@ -131,3 +148,23 @@ func (result CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
|
||||
func createErr(err error) CreateResult {
|
||||
return CreateResult{gophercloud.Result{Err: err}}
|
||||
}
|
||||
|
||||
// ExtractUser returns the User from a GetResult.
|
||||
func (result GetResult) ExtractUser() (*User, error) {
|
||||
if result.Err != nil {
|
||||
return nil, result.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Access struct {
|
||||
User User `mapstructure:"user"`
|
||||
} `mapstructure:"access"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(result.Body, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.Access.User, nil
|
||||
}
|
||||
|
@ -6,3 +6,8 @@ import "github.com/rackspace/gophercloud"
|
||||
func CreateURL(client *gophercloud.ServiceClient) string {
|
||||
return client.ServiceURL("tokens")
|
||||
}
|
||||
|
||||
// GetURL generates the URL used to Validate Tokens.
|
||||
func GetURL(client *gophercloud.ServiceClient, token string) string {
|
||||
return client.ServiceURL("tokens", token)
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ type Scope struct {
|
||||
}
|
||||
|
||||
func subjectTokenHeaders(c *gophercloud.ServiceClient, subjectToken string) map[string]string {
|
||||
h := c.AuthenticatedHeaders()
|
||||
h["X-Subject-Token"] = subjectToken
|
||||
return h
|
||||
return map[string]string{
|
||||
"X-Subject-Token": subjectToken,
|
||||
}
|
||||
}
|
||||
|
||||
// Create authenticates and either generates a new token, or changes the Scope of an existing token.
|
||||
|
@ -102,6 +102,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
// Populate request body
|
||||
reqBody := request{FloatingIP: floatingIP{
|
||||
FloatingNetworkID: opts.FloatingNetworkID,
|
||||
FloatingIP: opts.FloatingIP,
|
||||
PortID: opts.PortID,
|
||||
FixedIP: opts.FixedIP,
|
||||
TenantID: opts.TenantID,
|
||||
|
@ -16,6 +16,7 @@ type ListOpts struct {
|
||||
ID string `q:"id"`
|
||||
Name string `q:"name"`
|
||||
AdminStateUp *bool `q:"admin_state_up"`
|
||||
Distributed *bool `q:"distributed"`
|
||||
Status string `q:"status"`
|
||||
TenantID string `q:"tenant_id"`
|
||||
Limit int `q:"limit"`
|
||||
@ -46,6 +47,7 @@ func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
|
||||
type CreateOpts struct {
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
Distributed *bool
|
||||
TenantID string
|
||||
GatewayInfo *GatewayInfo
|
||||
}
|
||||
@ -62,6 +64,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
type router struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
AdminStateUp *bool `json:"admin_state_up,omitempty"`
|
||||
Distributed *bool `json:"distributed,omitempty"`
|
||||
TenantID *string `json:"tenant_id,omitempty"`
|
||||
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
|
||||
}
|
||||
@ -73,6 +76,7 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
reqBody := request{Router: router{
|
||||
Name: gophercloud.MaybeString(opts.Name),
|
||||
AdminStateUp: opts.AdminStateUp,
|
||||
Distributed: opts.Distributed,
|
||||
TenantID: gophercloud.MaybeString(opts.TenantID),
|
||||
}}
|
||||
|
||||
@ -96,7 +100,9 @@ func Get(c *gophercloud.ServiceClient, id string) GetResult {
|
||||
type UpdateOpts struct {
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
Distributed *bool
|
||||
GatewayInfo *GatewayInfo
|
||||
Routes []Route
|
||||
}
|
||||
|
||||
// Update allows routers to be updated. You can update the name, administrative
|
||||
@ -108,7 +114,9 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu
|
||||
type router struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
AdminStateUp *bool `json:"admin_state_up,omitempty"`
|
||||
Distributed *bool `json:"distributed,omitempty"`
|
||||
GatewayInfo *GatewayInfo `json:"external_gateway_info,omitempty"`
|
||||
Routes []Route `json:"routes"`
|
||||
}
|
||||
|
||||
type request struct {
|
||||
@ -118,12 +126,17 @@ func Update(c *gophercloud.ServiceClient, id string, opts UpdateOpts) UpdateResu
|
||||
reqBody := request{Router: router{
|
||||
Name: gophercloud.MaybeString(opts.Name),
|
||||
AdminStateUp: opts.AdminStateUp,
|
||||
Distributed: opts.Distributed,
|
||||
}}
|
||||
|
||||
if opts.GatewayInfo != nil {
|
||||
reqBody.Router.GatewayInfo = opts.GatewayInfo
|
||||
}
|
||||
|
||||
if opts.Routes != nil {
|
||||
reqBody.Router.Routes = opts.Routes
|
||||
}
|
||||
|
||||
// Send request to API
|
||||
var res UpdateResult
|
||||
_, res.Err = c.Put(resourceURL(c, id), reqBody, &res.Body, &gophercloud.RequestOpts{
|
||||
|
@ -12,6 +12,11 @@ type GatewayInfo struct {
|
||||
NetworkID string `json:"network_id" mapstructure:"network_id"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
NextHop string `mapstructure:"nexthop" json:"nexthop"`
|
||||
DestinationCIDR string `mapstructure:"destination" json:"destination"`
|
||||
}
|
||||
|
||||
// Router represents a Neutron router. A router is a logical entity that
|
||||
// forwards packets across internal subnets and NATs (network address
|
||||
// translation) them on external networks through an appropriate gateway.
|
||||
@ -30,6 +35,9 @@ type Router struct {
|
||||
// Administrative state of the router.
|
||||
AdminStateUp bool `json:"admin_state_up" mapstructure:"admin_state_up"`
|
||||
|
||||
// Whether router is disitrubted or not..
|
||||
Distributed bool `json:"distributed" mapstructure:"distributed"`
|
||||
|
||||
// Human readable name for the router. Does not have to be unique.
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
|
||||
@ -39,6 +47,8 @@ type Router struct {
|
||||
// Owner of the router. Only admin users can specify a tenant identifier
|
||||
// other than its own.
|
||||
TenantID string `json:"tenant_id" mapstructure:"tenant_id"`
|
||||
|
||||
Routes []Route `json:"routes" mapstructure:"routes"`
|
||||
}
|
||||
|
||||
// RouterPage is the page returned by a pager when traversing over a
|
||||
|
@ -104,8 +104,8 @@ type CreateOpts struct {
|
||||
TenantID string
|
||||
}
|
||||
|
||||
// Create is an operation which provisions a new security group with default
|
||||
// security group rules for the IPv4 and IPv6 ether types.
|
||||
// Create is an operation which adds a new security group rule and associates it
|
||||
// with an existing security group (whose ID is specified in CreateOpts).
|
||||
func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
@ -159,14 +159,14 @@ func Create(c *gophercloud.ServiceClient, opts CreateOpts) CreateResult {
|
||||
return res
|
||||
}
|
||||
|
||||
// Get retrieves a particular security group based on its unique ID.
|
||||
// Get retrieves a particular security group rule based on its unique ID.
|
||||
func Get(c *gophercloud.ServiceClient, id string) GetResult {
|
||||
var res GetResult
|
||||
_, res.Err = c.Get(resourceURL(c, id), &res.Body, nil)
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete will permanently delete a particular security group based on its unique ID.
|
||||
// Delete will permanently delete a particular security group rule based on its unique ID.
|
||||
func Delete(c *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
var res DeleteResult
|
||||
_, res.Err = c.Delete(resourceURL(c, id), nil)
|
||||
|
@ -95,15 +95,16 @@ type CreateOptsBuilder interface {
|
||||
|
||||
// CreateOpts represents the attributes used when creating a new port.
|
||||
type CreateOpts struct {
|
||||
NetworkID string
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
MACAddress string
|
||||
FixedIPs interface{}
|
||||
DeviceID string
|
||||
DeviceOwner string
|
||||
TenantID string
|
||||
SecurityGroups []string
|
||||
NetworkID string
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
MACAddress string
|
||||
FixedIPs interface{}
|
||||
DeviceID string
|
||||
DeviceOwner string
|
||||
TenantID string
|
||||
SecurityGroups []string
|
||||
AllowedAddressPairs []AddressPair
|
||||
}
|
||||
|
||||
// ToPortCreateMap casts a CreateOpts struct to a map.
|
||||
@ -139,6 +140,9 @@ func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
|
||||
if opts.MACAddress != "" {
|
||||
p["mac_address"] = opts.MACAddress
|
||||
}
|
||||
if opts.AllowedAddressPairs != nil {
|
||||
p["allowed_address_pairs"] = opts.AllowedAddressPairs
|
||||
}
|
||||
|
||||
return map[string]interface{}{"port": p}, nil
|
||||
}
|
||||
@ -168,12 +172,13 @@ type UpdateOptsBuilder interface {
|
||||
|
||||
// UpdateOpts represents the attributes used when updating an existing port.
|
||||
type UpdateOpts struct {
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
FixedIPs interface{}
|
||||
DeviceID string
|
||||
DeviceOwner string
|
||||
SecurityGroups []string
|
||||
Name string
|
||||
AdminStateUp *bool
|
||||
FixedIPs interface{}
|
||||
DeviceID string
|
||||
DeviceOwner string
|
||||
SecurityGroups []string
|
||||
AllowedAddressPairs []AddressPair
|
||||
}
|
||||
|
||||
// ToPortUpdateMap casts an UpdateOpts struct to a map.
|
||||
@ -198,6 +203,9 @@ func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
|
||||
if opts.Name != "" {
|
||||
p["name"] = opts.Name
|
||||
}
|
||||
if opts.AllowedAddressPairs != nil {
|
||||
p["allowed_address_pairs"] = opts.AllowedAddressPairs
|
||||
}
|
||||
|
||||
return map[string]interface{}{"port": p}, nil
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ func (r commonResult) Extract() (*Port, error) {
|
||||
var res struct {
|
||||
Port *Port `json:"port"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &res)
|
||||
|
||||
return res.Port, err
|
||||
@ -51,6 +50,11 @@ type IP struct {
|
||||
IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
type AddressPair struct {
|
||||
IPAddress string `mapstructure:"ip_address" json:"ip_address,omitempty"`
|
||||
MACAddress string `mapstructure:"mac_address" json:"mac_address,omitempty"`
|
||||
}
|
||||
|
||||
// Port represents a Neutron port. See package documentation for a top-level
|
||||
// description of what this is.
|
||||
type Port struct {
|
||||
@ -78,6 +82,8 @@ type Port struct {
|
||||
SecurityGroups []string `mapstructure:"security_groups" json:"security_groups"`
|
||||
// Identifies the device (e.g., virtual server) using this port.
|
||||
DeviceID string `mapstructure:"device_id" json:"device_id"`
|
||||
// Identifies the list of IP addresses the port will recognize/accept
|
||||
AllowedAddressPairs []AddressPair `mapstructure:"allowed_address_pairs" json:"allowed_address_pairs"`
|
||||
}
|
||||
|
||||
// PortPage is the page returned by a pager when traversing over a collection
|
||||
|
@ -1,12 +1,13 @@
|
||||
package objects
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"bufio"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -167,7 +168,7 @@ type CreateOpts struct {
|
||||
ObjectManifest string `h:"X-Object-Manifest"`
|
||||
TransferEncoding string `h:"Transfer-Encoding"`
|
||||
Expires string `q:"expires"`
|
||||
MultipartManifest string `q:"multiple-manifest"`
|
||||
MultipartManifest string `q:"multipart-manifest"`
|
||||
Signature string `q:"signature"`
|
||||
}
|
||||
|
||||
@ -213,19 +214,20 @@ func Create(c *gophercloud.ServiceClient, containerName, objectName string, cont
|
||||
}
|
||||
|
||||
hash := md5.New()
|
||||
bufioReader := bufio.NewReader(io.TeeReader(content, hash))
|
||||
io.Copy(ioutil.Discard, bufioReader)
|
||||
localChecksum := hash.Sum(nil)
|
||||
|
||||
contentBuffer := bytes.NewBuffer([]byte{})
|
||||
_, err := io.Copy(contentBuffer, io.TeeReader(content, hash))
|
||||
h["ETag"] = fmt.Sprintf("%x", localChecksum)
|
||||
|
||||
_, err := content.Seek(0, 0)
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
localChecksum := hash.Sum(nil)
|
||||
h["ETag"] = fmt.Sprintf("%x", localChecksum)
|
||||
|
||||
ropts := gophercloud.RequestOpts{
|
||||
RawBody: strings.NewReader(contentBuffer.String()),
|
||||
RawBody: content,
|
||||
MoreHeaders: h,
|
||||
}
|
||||
|
||||
|
@ -163,8 +163,8 @@ type ListResourceEventsOpts struct {
|
||||
SortDir SortDir `q:"sort_dir"`
|
||||
}
|
||||
|
||||
// ToResourceEventsListQuery formats a ListOpts into a query string.
|
||||
func (opts ListOpts) ToResourceEventsListQuery() (string, error) {
|
||||
// ToResourceEventListQuery formats a ListResourceEventsOpts into a query string.
|
||||
func (opts ListResourceEventsOpts) ToResourceEventListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -28,10 +28,13 @@ var FindExpected = []Resource{
|
||||
LogicalID: "hello_world",
|
||||
StatusReason: "state changed",
|
||||
UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
|
||||
CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC),
|
||||
RequiredBy: []interface{}{},
|
||||
Status: "CREATE_IN_PROGRESS",
|
||||
PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
Type: "OS::Nova::Server",
|
||||
Attributes: map[string]interface{}{"SXSW": "atx"},
|
||||
Description: "Some resource",
|
||||
},
|
||||
}
|
||||
|
||||
@ -40,6 +43,8 @@ const FindOutput = `
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"description": "Some resource",
|
||||
"attributes": {"SXSW": "atx"},
|
||||
"resource_name": "hello_world",
|
||||
"links": [
|
||||
{
|
||||
@ -54,6 +59,7 @@ const FindOutput = `
|
||||
"logical_resource_id": "hello_world",
|
||||
"resource_status_reason": "state changed",
|
||||
"updated_time": "2015-02-05T21:33:11",
|
||||
"creation_time": "2015-02-05T21:33:10",
|
||||
"required_by": [],
|
||||
"resource_status": "CREATE_IN_PROGRESS",
|
||||
"physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
@ -93,10 +99,13 @@ var ListExpected = []Resource{
|
||||
LogicalID: "hello_world",
|
||||
StatusReason: "state changed",
|
||||
UpdatedTime: time.Date(2015, 2, 5, 21, 33, 11, 0, time.UTC),
|
||||
CreationTime: time.Date(2015, 2, 5, 21, 33, 10, 0, time.UTC),
|
||||
RequiredBy: []interface{}{},
|
||||
Status: "CREATE_IN_PROGRESS",
|
||||
PhysicalID: "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
Type: "OS::Nova::Server",
|
||||
Attributes: map[string]interface{}{"SXSW": "atx"},
|
||||
Description: "Some resource",
|
||||
},
|
||||
}
|
||||
|
||||
@ -121,7 +130,10 @@ const ListOutput = `{
|
||||
"required_by": [],
|
||||
"resource_status": "CREATE_IN_PROGRESS",
|
||||
"physical_resource_id": "49181cd6-169a-4130-9455-31185bbfc5bf",
|
||||
"resource_type": "OS::Nova::Server"
|
||||
"creation_time": "2015-02-05T21:33:10",
|
||||
"resource_type": "OS::Nova::Server",
|
||||
"attributes": {"SXSW": "atx"},
|
||||
"description": "Some resource"
|
||||
}
|
||||
]
|
||||
}`
|
||||
@ -162,6 +174,7 @@ var GetExpected = &Resource{
|
||||
},
|
||||
},
|
||||
LogicalID: "wordpress_instance",
|
||||
Attributes: map[string]interface{}{"SXSW": "atx"},
|
||||
StatusReason: "state changed",
|
||||
UpdatedTime: time.Date(2014, 12, 10, 18, 34, 35, 0, time.UTC),
|
||||
RequiredBy: []interface{}{},
|
||||
@ -174,6 +187,8 @@ var GetExpected = &Resource{
|
||||
const GetOutput = `
|
||||
{
|
||||
"resource": {
|
||||
"description": "Some resource",
|
||||
"attributes": {"SXSW": "atx"},
|
||||
"resource_name": "wordpress_instance",
|
||||
"description": "",
|
||||
"links": [
|
||||
@ -240,7 +255,7 @@ func HandleMetadataSuccessfully(t *testing.T, output string) {
|
||||
}
|
||||
|
||||
// ListTypesExpected represents the expected object from a ListTypes request.
|
||||
var ListTypesExpected = []string{
|
||||
var ListTypesExpected = ResourceTypes{
|
||||
"OS::Nova::Server",
|
||||
"OS::Heat::RandomString",
|
||||
"OS::Swift::Container",
|
||||
@ -251,6 +266,18 @@ var ListTypesExpected = []string{
|
||||
"OS::Nova::KeyPair",
|
||||
}
|
||||
|
||||
// same as above, but sorted
|
||||
var SortedListTypesExpected = ResourceTypes{
|
||||
"OS::Cinder::VolumeAttachment",
|
||||
"OS::Heat::RandomString",
|
||||
"OS::Nova::FloatingIP",
|
||||
"OS::Nova::FloatingIPAssociation",
|
||||
"OS::Nova::KeyPair",
|
||||
"OS::Nova::Server",
|
||||
"OS::Swift::Container",
|
||||
"OS::Trove::Instance",
|
||||
}
|
||||
|
||||
// ListTypesOutput represents the response body from a ListTypes request.
|
||||
const ListTypesOutput = `
|
||||
{
|
||||
@ -296,6 +323,11 @@ var GetSchemaExpected = &TypeSchema{
|
||||
},
|
||||
},
|
||||
ResourceType: "OS::Heat::AResourceName",
|
||||
SupportStatus: map[string]interface{}{
|
||||
"message": "A status message",
|
||||
"status": "SUPPORTED",
|
||||
"version": "2014.1",
|
||||
},
|
||||
}
|
||||
|
||||
// GetSchemaOutput represents the response body from a Schema request.
|
||||
@ -314,7 +346,12 @@ const GetSchemaOutput = `
|
||||
"description": "A resource description."
|
||||
}
|
||||
},
|
||||
"resource_type": "OS::Heat::AResourceName"
|
||||
"resource_type": "OS::Heat::AResourceName",
|
||||
"support_status": {
|
||||
"message": "A status message",
|
||||
"status": "SUPPORTED",
|
||||
"version": "2014.1"
|
||||
}
|
||||
}`
|
||||
|
||||
// HandleGetSchemaSuccessfully creates an HTTP handler at `/resource_types/OS::Heat::AResourceName`
|
||||
@ -332,56 +369,7 @@ func HandleGetSchemaSuccessfully(t *testing.T, output string) {
|
||||
}
|
||||
|
||||
// GetTemplateExpected represents the expected object from a Template request.
|
||||
var GetTemplateExpected = &TypeTemplate{
|
||||
HeatTemplateFormatVersion: "2012-12-12",
|
||||
Outputs: map[string]interface{}{
|
||||
"private_key": map[string]interface{}{
|
||||
"Description": "The private key if it has been saved.",
|
||||
"Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"private_key\"]}",
|
||||
},
|
||||
"public_key": map[string]interface{}{
|
||||
"Description": "The public key.",
|
||||
"Value": "{\"Fn::GetAtt\": [\"KeyPair\", \"public_key\"]}",
|
||||
},
|
||||
},
|
||||
Parameters: map[string]interface{}{
|
||||
"name": map[string]interface{}{
|
||||
"Description": "The name of the key pair.",
|
||||
"Type": "String",
|
||||
},
|
||||
"public_key": map[string]interface{}{
|
||||
"Description": "The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.",
|
||||
"Type": "String",
|
||||
},
|
||||
"save_private_key": map[string]interface{}{
|
||||
"AllowedValues": []string{
|
||||
"True",
|
||||
"true",
|
||||
"False",
|
||||
"false",
|
||||
},
|
||||
"Default": false,
|
||||
"Description": "True if the system should remember a generated private key; False otherwise.",
|
||||
"Type": "String",
|
||||
},
|
||||
},
|
||||
Resources: map[string]interface{}{
|
||||
"KeyPair": map[string]interface{}{
|
||||
"Properties": map[string]interface{}{
|
||||
"name": map[string]interface{}{
|
||||
"Ref": "name",
|
||||
},
|
||||
"public_key": map[string]interface{}{
|
||||
"Ref": "public_key",
|
||||
},
|
||||
"save_private_key": map[string]interface{}{
|
||||
"Ref": "save_private_key",
|
||||
},
|
||||
},
|
||||
"Type": "OS::Nova::KeyPair",
|
||||
},
|
||||
},
|
||||
}
|
||||
var GetTemplateExpected = "{\n \"HeatTemplateFormatVersion\": \"2012-12-12\",\n \"Outputs\": {\n \"private_key\": {\n \"Description\": \"The private key if it has been saved.\",\n \"Value\": \"{\\\"Fn::GetAtt\\\": [\\\"KeyPair\\\", \\\"private_key\\\"]}\"\n },\n \"public_key\": {\n \"Description\": \"The public key.\",\n \"Value\": \"{\\\"Fn::GetAtt\\\": [\\\"KeyPair\\\", \\\"public_key\\\"]}\"\n }\n },\n \"Parameters\": {\n \"name\": {\n \"Description\": \"The name of the key pair.\",\n \"Type\": \"String\"\n },\n \"public_key\": {\n \"Description\": \"The optional public key. This allows users to supply the public key from a pre-existing key pair. If not supplied, a new key pair will be generated.\",\n \"Type\": \"String\"\n },\n \"save_private_key\": {\n \"AllowedValues\": [\n \"True\",\n \"true\",\n \"False\",\n \"false\"\n ],\n \"Default\": false,\n \"Description\": \"True if the system should remember a generated private key; False otherwise.\",\n \"Type\": \"String\"\n }\n },\n \"Resources\": {\n \"KeyPair\": {\n \"Properties\": {\n \"name\": {\n \"Ref\": \"name\"\n },\n \"public_key\": {\n \"Ref\": \"public_key\"\n },\n \"save_private_key\": {\n \"Ref\": \"save_private_key\"\n }\n },\n \"Type\": \"OS::Nova::KeyPair\"\n }\n }\n}"
|
||||
|
||||
// GetTemplateOutput represents the response body from a Template request.
|
||||
const GetTemplateOutput = `
|
||||
|
@ -1,6 +1,7 @@
|
||||
package stackresources
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
@ -12,15 +13,18 @@ import (
|
||||
|
||||
// Resource represents a stack resource.
|
||||
type Resource struct {
|
||||
Links []gophercloud.Link `mapstructure:"links"`
|
||||
LogicalID string `mapstructure:"logical_resource_id"`
|
||||
Name string `mapstructure:"resource_name"`
|
||||
PhysicalID string `mapstructure:"physical_resource_id"`
|
||||
RequiredBy []interface{} `mapstructure:"required_by"`
|
||||
Status string `mapstructure:"resource_status"`
|
||||
StatusReason string `mapstructure:"resource_status_reason"`
|
||||
Type string `mapstructure:"resource_type"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
Attributes map[string]interface{} `mapstructure:"attributes"`
|
||||
CreationTime time.Time `mapstructure:"-"`
|
||||
Description string `mapstructure:"description"`
|
||||
Links []gophercloud.Link `mapstructure:"links"`
|
||||
LogicalID string `mapstructure:"logical_resource_id"`
|
||||
Name string `mapstructure:"resource_name"`
|
||||
PhysicalID string `mapstructure:"physical_resource_id"`
|
||||
RequiredBy []interface{} `mapstructure:"required_by"`
|
||||
Status string `mapstructure:"resource_status"`
|
||||
StatusReason string `mapstructure:"resource_status_reason"`
|
||||
Type string `mapstructure:"resource_type"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
}
|
||||
|
||||
// FindResult represents the result of a Find operation.
|
||||
@ -54,6 +58,13 @@ func (r FindResult) Extract() ([]Resource, error) {
|
||||
}
|
||||
res.Res[i].UpdatedTime = t
|
||||
}
|
||||
if date, ok := resource["creation_time"]; ok && date != nil {
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Res[i].CreationTime = t
|
||||
}
|
||||
}
|
||||
|
||||
return res.Res, nil
|
||||
@ -75,18 +86,6 @@ func (r ResourcePage) IsEmpty() (bool, error) {
|
||||
return len(resources) == 0, nil
|
||||
}
|
||||
|
||||
// LastMarker returns the last container name in a ListResult.
|
||||
func (r ResourcePage) LastMarker() (string, error) {
|
||||
resources, err := ExtractResources(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return resources[len(resources)-1].PhysicalID, nil
|
||||
}
|
||||
|
||||
// ExtractResources interprets the results of a single page from a List() call, producing a slice of Resource entities.
|
||||
func ExtractResources(page pagination.Page) ([]Resource, error) {
|
||||
casted := page.(ResourcePage).Body
|
||||
@ -94,8 +93,9 @@ func ExtractResources(page pagination.Page) ([]Resource, error) {
|
||||
var response struct {
|
||||
Resources []Resource `mapstructure:"resources"`
|
||||
}
|
||||
err := mapstructure.Decode(casted, &response)
|
||||
|
||||
if err := mapstructure.Decode(casted, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resources []interface{}
|
||||
switch casted.(type) {
|
||||
case map[string]interface{}:
|
||||
@ -115,9 +115,16 @@ func ExtractResources(page pagination.Page) ([]Resource, error) {
|
||||
}
|
||||
response.Resources[i].UpdatedTime = t
|
||||
}
|
||||
if date, ok := resource["creation_time"]; ok && date != nil {
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.Resources[i].CreationTime = t
|
||||
}
|
||||
}
|
||||
|
||||
return response.Resources, err
|
||||
return response.Resources, nil
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation.
|
||||
@ -149,6 +156,13 @@ func (r GetResult) Extract() (*Resource, error) {
|
||||
}
|
||||
res.Res.UpdatedTime = t
|
||||
}
|
||||
if date, ok := resource["creation_time"]; ok && date != nil {
|
||||
t, err := time.Parse(gophercloud.STACK_TIME_FMT, date.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Res.CreationTime = t
|
||||
}
|
||||
|
||||
return res.Res, nil
|
||||
}
|
||||
@ -192,21 +206,42 @@ func (r ResourceTypePage) IsEmpty() (bool, error) {
|
||||
return len(rts) == 0, nil
|
||||
}
|
||||
|
||||
// ResourceTypes represents the type that holds the result of ExtractResourceTypes.
|
||||
// We define methods on this type to sort it before output
|
||||
type ResourceTypes []string
|
||||
|
||||
func (r ResourceTypes) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r ResourceTypes) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r ResourceTypes) Less(i, j int) bool {
|
||||
return r[i] < r[j]
|
||||
}
|
||||
|
||||
// ExtractResourceTypes extracts and returns resource types.
|
||||
func ExtractResourceTypes(page pagination.Page) ([]string, error) {
|
||||
func ExtractResourceTypes(page pagination.Page) (ResourceTypes, error) {
|
||||
casted := page.(ResourceTypePage).Body
|
||||
|
||||
var response struct {
|
||||
ResourceTypes []string `mapstructure:"resource_types"`
|
||||
ResourceTypes ResourceTypes `mapstructure:"resource_types"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(ResourceTypePage).Body, &response)
|
||||
return response.ResourceTypes, err
|
||||
if err := mapstructure.Decode(casted, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.ResourceTypes, nil
|
||||
}
|
||||
|
||||
// TypeSchema represents a stack resource schema.
|
||||
type TypeSchema struct {
|
||||
Attributes map[string]interface{} `mapstructure:"attributes"`
|
||||
Properties map[string]interface{} `mapstrucutre:"properties"`
|
||||
ResourceType string `mapstructure:"resource_type"`
|
||||
Attributes map[string]interface{} `mapstructure:"attributes"`
|
||||
Properties map[string]interface{} `mapstrucutre:"properties"`
|
||||
ResourceType string `mapstructure:"resource_type"`
|
||||
SupportStatus map[string]interface{} `mapstructure:"support_status"`
|
||||
}
|
||||
|
||||
// SchemaResult represents the result of a Schema operation.
|
||||
@ -230,31 +265,20 @@ func (r SchemaResult) Extract() (*TypeSchema, error) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// TypeTemplate represents a stack resource template.
|
||||
type TypeTemplate struct {
|
||||
HeatTemplateFormatVersion string
|
||||
Outputs map[string]interface{}
|
||||
Parameters map[string]interface{}
|
||||
Resources map[string]interface{}
|
||||
}
|
||||
|
||||
// TemplateResult represents the result of a Template operation.
|
||||
type TemplateResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract returns a pointer to a TypeTemplate object and is called after a
|
||||
// Extract returns the template and is called after a
|
||||
// Template operation.
|
||||
func (r TemplateResult) Extract() (*TypeTemplate, error) {
|
||||
func (r TemplateResult) Extract() ([]byte, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var res TypeTemplate
|
||||
|
||||
if err := mapstructure.Decode(r.Body, &res); err != nil {
|
||||
template, err := json.MarshalIndent(r.Body, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return template, nil
|
||||
}
|
||||
|
137
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/environment.go
generated
vendored
Normal file
137
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/environment.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Environment is a structure that represents stack environments
|
||||
type Environment struct {
|
||||
TE
|
||||
}
|
||||
|
||||
// EnvironmentSections is a map containing allowed sections in a stack environment file
|
||||
var EnvironmentSections = map[string]bool{
|
||||
"parameters": true,
|
||||
"parameter_defaults": true,
|
||||
"resource_registry": true,
|
||||
}
|
||||
|
||||
// Validate validates the contents of the Environment
|
||||
func (e *Environment) Validate() error {
|
||||
if e.Parsed == nil {
|
||||
if err := e.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for key := range e.Parsed {
|
||||
if _, ok := EnvironmentSections[key]; !ok {
|
||||
return fmt.Errorf("Environment has wrong section: %s", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse environment file to resolve the URL's of the resources. This is done by
|
||||
// reading from the `Resource Registry` section, which is why the function is
|
||||
// named GetRRFileContents.
|
||||
func (e *Environment) getRRFileContents(ignoreIf igFunc) error {
|
||||
// initialize environment if empty
|
||||
if e.Files == nil {
|
||||
e.Files = make(map[string]string)
|
||||
}
|
||||
if e.fileMaps == nil {
|
||||
e.fileMaps = make(map[string]string)
|
||||
}
|
||||
|
||||
// get the resource registry
|
||||
rr := e.Parsed["resource_registry"]
|
||||
|
||||
// search the resource registry for URLs
|
||||
switch rr.(type) {
|
||||
// process further only if the resource registry is a map
|
||||
case map[string]interface{}, map[interface{}]interface{}:
|
||||
rrMap, err := toStringKeys(rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// the resource registry might contain a base URL for the resource. If
|
||||
// such a field is present, use it. Otherwise, use the default base URL.
|
||||
var baseURL string
|
||||
if val, ok := rrMap["base_url"]; ok {
|
||||
baseURL = val.(string)
|
||||
} else {
|
||||
baseURL = e.baseURL
|
||||
}
|
||||
|
||||
// The contents of the resource may be located in a remote file, which
|
||||
// will be a template. Instantiate a temporary template to manage the
|
||||
// contents.
|
||||
tempTemplate := new(Template)
|
||||
tempTemplate.baseURL = baseURL
|
||||
tempTemplate.client = e.client
|
||||
|
||||
// Fetch the contents of remote resource URL's
|
||||
if err = tempTemplate.getFileContents(rr, ignoreIf, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// check the `resources` section (if it exists) for more URL's. Note that
|
||||
// the previous call to GetFileContents was (deliberately) not recursive
|
||||
// as we want more control over where to look for URL's
|
||||
if val, ok := rrMap["resources"]; ok {
|
||||
switch val.(type) {
|
||||
// process further only if the contents are a map
|
||||
case map[string]interface{}, map[interface{}]interface{}:
|
||||
resourcesMap, err := toStringKeys(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range resourcesMap {
|
||||
switch v.(type) {
|
||||
case map[string]interface{}, map[interface{}]interface{}:
|
||||
resourceMap, err := toStringKeys(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var resourceBaseURL string
|
||||
// if base_url for the resource type is defined, use it
|
||||
if val, ok := resourceMap["base_url"]; ok {
|
||||
resourceBaseURL = val.(string)
|
||||
} else {
|
||||
resourceBaseURL = baseURL
|
||||
}
|
||||
tempTemplate.baseURL = resourceBaseURL
|
||||
if err := tempTemplate.getFileContents(v, ignoreIf, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the resource registry contained any URL's, store them. This can
|
||||
// then be passed as parameter to api calls to Heat api.
|
||||
e.Files = tempTemplate.Files
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// function to choose keys whose values are other environment files
|
||||
func ignoreIfEnvironment(key string, value interface{}) bool {
|
||||
// base_url and hooks refer to components which cannot have urls
|
||||
if key == "base_url" || key == "hooks" {
|
||||
return true
|
||||
}
|
||||
// if value is not string, it cannot be a URL
|
||||
valueString, ok := value.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
// if value contains `::`, it must be a reference to another resource type
|
||||
// e.g. OS::Nova::Server : Rackspace::Cloud::Server
|
||||
if strings.Contains(valueString, "::") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -63,6 +63,7 @@ var ListExpected = []ListedStack{
|
||||
CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
|
||||
Status: "CREATE_COMPLETE",
|
||||
ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
Tags: []string{"rackspace", "atx"},
|
||||
},
|
||||
ListedStack{
|
||||
Description: "Simple template to test heat commands",
|
||||
@ -78,6 +79,7 @@ var ListExpected = []ListedStack{
|
||||
UpdatedTime: time.Date(2014, 12, 11, 17, 40, 37, 0, time.UTC),
|
||||
Status: "UPDATE_COMPLETE",
|
||||
ID: "db6977b2-27aa-4775-9ae7-6213212d4ada",
|
||||
Tags: []string{"sfo", "satx"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -98,7 +100,8 @@ const FullListOutput = `
|
||||
"creation_time": "2015-02-03T20:07:39",
|
||||
"updated_time": null,
|
||||
"stack_status": "CREATE_COMPLETE",
|
||||
"id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87"
|
||||
"id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
"tags": ["rackspace", "atx"]
|
||||
},
|
||||
{
|
||||
"description": "Simple template to test heat commands",
|
||||
@ -113,7 +116,8 @@ const FullListOutput = `
|
||||
"creation_time": "2014-12-11T17:39:16",
|
||||
"updated_time": "2014-12-11T17:40:37",
|
||||
"stack_status": "UPDATE_COMPLETE",
|
||||
"id": "db6977b2-27aa-4775-9ae7-6213212d4ada"
|
||||
"id": "db6977b2-27aa-4775-9ae7-6213212d4ada",
|
||||
"tags": ["sfo", "satx"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -165,6 +169,7 @@ var GetExpected = &RetrievedStack{
|
||||
Status: "CREATE_COMPLETE",
|
||||
ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
TemplateDescription: "Simple template to test heat commands",
|
||||
Tags: []string{"rackspace", "atx"},
|
||||
}
|
||||
|
||||
// GetOutput represents the response body from a Get request.
|
||||
@ -194,7 +199,8 @@ const GetOutput = `
|
||||
"stack_status": "CREATE_COMPLETE",
|
||||
"updated_time": null,
|
||||
"id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
"template_description": "Simple template to test heat commands"
|
||||
"template_description": "Simple template to test heat commands",
|
||||
"tags": ["rackspace", "atx"]
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -248,7 +254,6 @@ var PreviewExpected = &PreviewedStack{
|
||||
"OS::stack_name": "postman_stack",
|
||||
"OS::stack_id": "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
},
|
||||
StatusReason: "Stack CREATE completed successfully",
|
||||
Name: "postman_stack",
|
||||
CreationTime: time.Date(2015, 2, 3, 20, 7, 39, 0, time.UTC),
|
||||
Links: []gophercloud.Link{
|
||||
@ -259,7 +264,6 @@ var PreviewExpected = &PreviewedStack{
|
||||
},
|
||||
Capabilities: []interface{}{},
|
||||
NotificationTopics: []interface{}{},
|
||||
Status: "CREATE_COMPLETE",
|
||||
ID: "16ef0584-4458-41eb-87c8-0dc8d5f66c87",
|
||||
TemplateDescription: "Simple template to test heat commands",
|
||||
}
|
||||
@ -316,6 +320,20 @@ var AbandonExpected = &AbandonedStack{
|
||||
"type": "OS::Nova::Server",
|
||||
},
|
||||
},
|
||||
Files: map[string]string{
|
||||
"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "heat_template_version: 2014-10-16\nparameters:\n flavor:\n type: string\n description: Flavor for the server to be created\n default: 4353\n hidden: true\nresources:\n test_server:\n type: \"OS::Nova::Server\"\n properties:\n name: test-server\n flavor: 2 GB General Purpose v1\n image: Debian 7 (Wheezy) (PVHVM)\n",
|
||||
},
|
||||
StackUserProjectID: "897686",
|
||||
ProjectID: "897686",
|
||||
Environment: map[string]interface{}{
|
||||
"encrypted_param_names": make([]map[string]interface{}, 0),
|
||||
"parameter_defaults": make(map[string]interface{}),
|
||||
"parameters": make(map[string]interface{}),
|
||||
"resource_registry": map[string]interface{}{
|
||||
"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml",
|
||||
"resources": make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// AbandonOutput represents the response body from an Abandon request.
|
||||
@ -354,21 +372,233 @@ const AbandonOutput = `
|
||||
"name": "hello_world",
|
||||
"resource_id": "8a310d36-46fc-436f-8be4-37a696b8ac63",
|
||||
"action": "CREATE",
|
||||
"type": "OS::Nova::Server",
|
||||
"type": "OS::Nova::Server"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "heat_template_version: 2014-10-16\nparameters:\n flavor:\n type: string\n description: Flavor for the server to be created\n default: 4353\n hidden: true\nresources:\n test_server:\n type: \"OS::Nova::Server\"\n properties:\n name: test-server\n flavor: 2 GB General Purpose v1\n image: Debian 7 (Wheezy) (PVHVM)\n"
|
||||
},
|
||||
"environment": {
|
||||
"encrypted_param_names": [],
|
||||
"parameter_defaults": {},
|
||||
"parameters": {},
|
||||
"resource_registry": {
|
||||
"file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml": "file:///Users/prat8228/go/src/github.com/rackspace/rack/my_nova.yaml",
|
||||
"resources": {}
|
||||
}
|
||||
},
|
||||
"stack_user_project_id": "897686",
|
||||
"project_id": "897686"
|
||||
}`
|
||||
|
||||
// HandleAbandonSuccessfully creates an HTTP handler at `/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon`
|
||||
// on the test handler mux that responds with an `Abandon` response.
|
||||
func HandleAbandonSuccessfully(t *testing.T) {
|
||||
th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c87/abandon", func(w http.ResponseWriter, r *http.Request) {
|
||||
func HandleAbandonSuccessfully(t *testing.T, output string) {
|
||||
th.Mux.HandleFunc("/stacks/postman_stack/16ef0584-4458-41eb-87c8-0dc8d5f66c8/abandon", func(w http.ResponseWriter, r *http.Request) {
|
||||
th.TestMethod(t, r, "DELETE")
|
||||
th.TestHeader(t, r, "X-Auth-Token", fake.TokenID)
|
||||
th.TestHeader(t, r, "Accept", "application/json")
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, AbandonOutput)
|
||||
fmt.Fprintf(w, output)
|
||||
})
|
||||
}
|
||||
|
||||
// ValidJSONTemplate is a valid OpenStack Heat template in JSON format
|
||||
const ValidJSONTemplate = `
|
||||
{
|
||||
"heat_template_version": "2014-10-16",
|
||||
"parameters": {
|
||||
"flavor": {
|
||||
"default": 4353,
|
||||
"description": "Flavor for the server to be created",
|
||||
"hidden": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"test_server": {
|
||||
"properties": {
|
||||
"flavor": "2 GB General Purpose v1",
|
||||
"image": "Debian 7 (Wheezy) (PVHVM)",
|
||||
"name": "test-server"
|
||||
},
|
||||
"type": "OS::Nova::Server"
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// ValidJSONTemplateParsed is the expected parsed version of ValidJSONTemplate
|
||||
var ValidJSONTemplateParsed = map[string]interface{}{
|
||||
"heat_template_version": "2014-10-16",
|
||||
"parameters": map[string]interface{}{
|
||||
"flavor": map[string]interface{}{
|
||||
"default": 4353,
|
||||
"description": "Flavor for the server to be created",
|
||||
"hidden": true,
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"resources": map[string]interface{}{
|
||||
"test_server": map[string]interface{}{
|
||||
"properties": map[string]interface{}{
|
||||
"flavor": "2 GB General Purpose v1",
|
||||
"image": "Debian 7 (Wheezy) (PVHVM)",
|
||||
"name": "test-server",
|
||||
},
|
||||
"type": "OS::Nova::Server",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ValidYAMLTemplate is a valid OpenStack Heat template in YAML format
|
||||
const ValidYAMLTemplate = `
|
||||
heat_template_version: 2014-10-16
|
||||
parameters:
|
||||
flavor:
|
||||
type: string
|
||||
description: Flavor for the server to be created
|
||||
default: 4353
|
||||
hidden: true
|
||||
resources:
|
||||
test_server:
|
||||
type: "OS::Nova::Server"
|
||||
properties:
|
||||
name: test-server
|
||||
flavor: 2 GB General Purpose v1
|
||||
image: Debian 7 (Wheezy) (PVHVM)
|
||||
`
|
||||
|
||||
// InvalidTemplateNoVersion is an invalid template as it has no `version` section
|
||||
const InvalidTemplateNoVersion = `
|
||||
parameters:
|
||||
flavor:
|
||||
type: string
|
||||
description: Flavor for the server to be created
|
||||
default: 4353
|
||||
hidden: true
|
||||
resources:
|
||||
test_server:
|
||||
type: "OS::Nova::Server"
|
||||
properties:
|
||||
name: test-server
|
||||
flavor: 2 GB General Purpose v1
|
||||
image: Debian 7 (Wheezy) (PVHVM)
|
||||
`
|
||||
|
||||
// ValidJSONEnvironment is a valid environment for a stack in JSON format
|
||||
const ValidJSONEnvironment = `
|
||||
{
|
||||
"parameters": {
|
||||
"user_key": "userkey"
|
||||
},
|
||||
"resource_registry": {
|
||||
"My::WP::Server": "file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml",
|
||||
"OS::Quantum*": "OS::Neutron*",
|
||||
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml",
|
||||
"OS::Metering::Alarm": "OS::Ceilometer::Alarm",
|
||||
"AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml",
|
||||
"resources": {
|
||||
"my_db_server": {
|
||||
"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml"
|
||||
},
|
||||
"my_server": {
|
||||
"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
|
||||
"hooks": "pre-create"
|
||||
},
|
||||
"nested_stack": {
|
||||
"nested_resource": {
|
||||
"hooks": "pre-update"
|
||||
},
|
||||
"another_resource": {
|
||||
"hooks": [
|
||||
"pre-create",
|
||||
"pre-update"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// ValidJSONEnvironmentParsed is the expected parsed version of ValidJSONEnvironment
|
||||
var ValidJSONEnvironmentParsed = map[string]interface{}{
|
||||
"parameters": map[string]interface{}{
|
||||
"user_key": "userkey",
|
||||
},
|
||||
"resource_registry": map[string]interface{}{
|
||||
"My::WP::Server": "file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml",
|
||||
"OS::Quantum*": "OS::Neutron*",
|
||||
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml",
|
||||
"OS::Metering::Alarm": "OS::Ceilometer::Alarm",
|
||||
"AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml",
|
||||
"resources": map[string]interface{}{
|
||||
"my_db_server": map[string]interface{}{
|
||||
"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
|
||||
},
|
||||
"my_server": map[string]interface{}{
|
||||
"OS::DBInstance": "file:///home/mine/all_my_cool_templates/db.yaml",
|
||||
"hooks": "pre-create",
|
||||
},
|
||||
"nested_stack": map[string]interface{}{
|
||||
"nested_resource": map[string]interface{}{
|
||||
"hooks": "pre-update",
|
||||
},
|
||||
"another_resource": map[string]interface{}{
|
||||
"hooks": []interface{}{
|
||||
"pre-create",
|
||||
"pre-update",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ValidYAMLEnvironment is a valid environment for a stack in YAML format
|
||||
const ValidYAMLEnvironment = `
|
||||
parameters:
|
||||
user_key: userkey
|
||||
resource_registry:
|
||||
My::WP::Server: file:///home/shardy/git/heat-templates/hot/F18/WordPress_Native.yaml
|
||||
# allow older templates with Quantum in them.
|
||||
"OS::Quantum*": "OS::Neutron*"
|
||||
# Choose your implementation of AWS::CloudWatch::Alarm
|
||||
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
|
||||
#"AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm"
|
||||
"OS::Metering::Alarm": "OS::Ceilometer::Alarm"
|
||||
"AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml"
|
||||
resources:
|
||||
my_db_server:
|
||||
"OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
|
||||
my_server:
|
||||
"OS::DBInstance": file:///home/mine/all_my_cool_templates/db.yaml
|
||||
hooks: pre-create
|
||||
nested_stack:
|
||||
nested_resource:
|
||||
hooks: pre-update
|
||||
another_resource:
|
||||
hooks: [pre-create, pre-update]
|
||||
`
|
||||
|
||||
// InvalidEnvironment is an invalid environment as it has an extra section called `resources`
|
||||
const InvalidEnvironment = `
|
||||
parameters:
|
||||
flavor:
|
||||
type: string
|
||||
description: Flavor for the server to be created
|
||||
default: 4353
|
||||
hidden: true
|
||||
resources:
|
||||
test_server:
|
||||
type: "OS::Nova::Server"
|
||||
properties:
|
||||
name: test-server
|
||||
flavor: 2 GB General Purpose v1
|
||||
image: Debian 7 (Wheezy) (PVHVM)
|
||||
parameter_defaults:
|
||||
KeyName: heat_key
|
||||
`
|
||||
|
@ -2,6 +2,7 @@ package stacks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
@ -32,9 +33,16 @@ type CreateOptsBuilder interface {
|
||||
type CreateOpts struct {
|
||||
// (REQUIRED) The name of the stack. It must start with an alphabetic character.
|
||||
Name string
|
||||
// (REQUIRED) A structure that contains either the template file or url. Call the
|
||||
// associated methods to extract the information relevant to send in a create request.
|
||||
TemplateOpts *Template
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, TemplateURL will be ignored
|
||||
// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
|
||||
// This value is ignored if Template is supplied inline.
|
||||
TemplateURL string
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, Template will be ignored
|
||||
// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
|
||||
// is a stringified version of the JSON/YAML template. Since the template will likely
|
||||
// be located in a file, one way to set this variable is by using ioutil.ReadFile:
|
||||
@ -50,8 +58,14 @@ type CreateOpts struct {
|
||||
// creation fails. Default is true, meaning all resources are not deleted when
|
||||
// stack creation fails.
|
||||
DisableRollback Rollback
|
||||
// (OPTIONAL) A structure that contains details for the environment of the stack.
|
||||
EnvironmentOpts *Environment
|
||||
// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
|
||||
// (OPTIONAL) A stringified JSON environment for the stack.
|
||||
Environment string
|
||||
// (DEPRECATED): Files is automatically determined
|
||||
// by parsing the template and environment passed as TemplateOpts and
|
||||
// EnvironmentOpts respectively.
|
||||
// (OPTIONAL) A map that maps file names to file contents. It can also be used
|
||||
// to pass provider template contents. Example:
|
||||
// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
|
||||
@ -60,6 +74,8 @@ type CreateOpts struct {
|
||||
Parameters map[string]string
|
||||
// (OPTIONAL) The timeout for stack creation in minutes.
|
||||
Timeout int
|
||||
// (OPTIONAL) A list of tags to assosciate with the Stack
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// ToStackCreateMap casts a CreateOpts struct to a map.
|
||||
@ -70,25 +86,60 @@ func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
|
||||
return s, errors.New("Required field 'Name' not provided.")
|
||||
}
|
||||
s["stack_name"] = opts.Name
|
||||
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
Files := make(map[string]string)
|
||||
if opts.TemplateOpts == nil {
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
}
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
if err := opts.TemplateOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.TemplateOpts.fixFileRefs()
|
||||
s["template"] = string(opts.TemplateOpts.Bin)
|
||||
|
||||
for k, v := range opts.TemplateOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
}
|
||||
if opts.DisableRollback != nil {
|
||||
s["disable_rollback"] = &opts.DisableRollback
|
||||
}
|
||||
|
||||
if opts.EnvironmentOpts != nil {
|
||||
if err := opts.EnvironmentOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.EnvironmentOpts.fixFileRefs()
|
||||
for k, v := range opts.EnvironmentOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
s["environment"] = string(opts.EnvironmentOpts.Bin)
|
||||
} else if opts.Environment != "" {
|
||||
s["environment"] = opts.Environment
|
||||
}
|
||||
|
||||
if opts.Files != nil {
|
||||
s["files"] = opts.Files
|
||||
} else {
|
||||
s["files"] = Files
|
||||
}
|
||||
|
||||
if opts.DisableRollback != nil {
|
||||
s["disable_rollback"] = &opts.DisableRollback
|
||||
}
|
||||
|
||||
if opts.Environment != "" {
|
||||
s["environment"] = opts.Environment
|
||||
}
|
||||
if opts.Files != nil {
|
||||
s["files"] = opts.Files
|
||||
}
|
||||
if opts.Parameters != nil {
|
||||
s["parameters"] = opts.Parameters
|
||||
}
|
||||
@ -97,6 +148,9 @@ func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
|
||||
s["timeout_mins"] = opts.Timeout
|
||||
}
|
||||
|
||||
if opts.Tags != nil {
|
||||
s["tags"] = strings.Join(opts.Tags, ",")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@ -133,9 +187,16 @@ type AdoptOpts struct {
|
||||
Name string
|
||||
// (REQUIRED) The timeout for stack creation in minutes.
|
||||
Timeout int
|
||||
// (REQUIRED) A structure that contains either the template file or url. Call the
|
||||
// associated methods to extract the information relevant to send in a create request.
|
||||
TemplateOpts *Template
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, TemplateURL will be ignored
|
||||
// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
|
||||
// This value is ignored if Template is supplied inline.
|
||||
TemplateURL string
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, Template will be ignored
|
||||
// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
|
||||
// is a stringified version of the JSON/YAML template. Since the template will likely
|
||||
// be located in a file, one way to set this variable is by using ioutil.ReadFile:
|
||||
@ -151,8 +212,14 @@ type AdoptOpts struct {
|
||||
// creation fails. Default is true, meaning all resources are not deleted when
|
||||
// stack creation fails.
|
||||
DisableRollback Rollback
|
||||
// (OPTIONAL) A structure that contains details for the environment of the stack.
|
||||
EnvironmentOpts *Environment
|
||||
// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
|
||||
// (OPTIONAL) A stringified JSON environment for the stack.
|
||||
Environment string
|
||||
// (DEPRECATED): Files is automatically determined
|
||||
// by parsing the template and environment passed as TemplateOpts and
|
||||
// EnvironmentOpts respectively.
|
||||
// (OPTIONAL) A map that maps file names to file contents. It can also be used
|
||||
// to pass provider template contents. Example:
|
||||
// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
|
||||
@ -169,40 +236,69 @@ func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) {
|
||||
return s, errors.New("Required field 'Name' not provided.")
|
||||
}
|
||||
s["stack_name"] = opts.Name
|
||||
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
Files := make(map[string]string)
|
||||
if opts.AdoptStackData != "" {
|
||||
s["adopt_stack_data"] = opts.AdoptStackData
|
||||
} else if opts.TemplateOpts == nil {
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
} else {
|
||||
return s, errors.New("One of AdoptStackData, Template, TemplateURL or TemplateOpts must be provided.")
|
||||
}
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
}
|
||||
if err := opts.TemplateOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.AdoptStackData == "" {
|
||||
return s, errors.New("Required field 'AdoptStackData' not provided.")
|
||||
if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.TemplateOpts.fixFileRefs()
|
||||
s["template"] = string(opts.TemplateOpts.Bin)
|
||||
|
||||
for k, v := range opts.TemplateOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
}
|
||||
s["adopt_stack_data"] = opts.AdoptStackData
|
||||
|
||||
if opts.DisableRollback != nil {
|
||||
s["disable_rollback"] = &opts.DisableRollback
|
||||
}
|
||||
|
||||
if opts.Environment != "" {
|
||||
if opts.EnvironmentOpts != nil {
|
||||
if err := opts.EnvironmentOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.EnvironmentOpts.fixFileRefs()
|
||||
for k, v := range opts.EnvironmentOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
s["environment"] = string(opts.EnvironmentOpts.Bin)
|
||||
} else if opts.Environment != "" {
|
||||
s["environment"] = opts.Environment
|
||||
}
|
||||
|
||||
if opts.Files != nil {
|
||||
s["files"] = opts.Files
|
||||
} else {
|
||||
s["files"] = Files
|
||||
}
|
||||
|
||||
if opts.Parameters != nil {
|
||||
s["parameters"] = opts.Parameters
|
||||
}
|
||||
|
||||
if opts.Timeout == 0 {
|
||||
return nil, errors.New("Required field 'Timeout' not provided.")
|
||||
if opts.Timeout != 0 {
|
||||
s["timeout"] = opts.Timeout
|
||||
}
|
||||
s["timeout_mins"] = opts.Timeout
|
||||
|
||||
return map[string]interface{}{"stack": s}, nil
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Adopt accepts an AdoptOpts struct and creates a new stack using the resources
|
||||
@ -305,9 +401,16 @@ type UpdateOptsBuilder interface {
|
||||
// UpdateOpts contains the common options struct used in this package's Update
|
||||
// operation.
|
||||
type UpdateOpts struct {
|
||||
// (REQUIRED) A structure that contains either the template file or url. Call the
|
||||
// associated methods to extract the information relevant to send in a create request.
|
||||
TemplateOpts *Template
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, TemplateURL will be ignored
|
||||
// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
|
||||
// This value is ignored if Template is supplied inline.
|
||||
TemplateURL string
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, Template will be ignored
|
||||
// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
|
||||
// is a stringified version of the JSON/YAML template. Since the template will likely
|
||||
// be located in a file, one way to set this variable is by using ioutil.ReadFile:
|
||||
@ -319,8 +422,14 @@ type UpdateOpts struct {
|
||||
// }
|
||||
// opts.Template = string(b)
|
||||
Template string
|
||||
// (OPTIONAL) A structure that contains details for the environment of the stack.
|
||||
EnvironmentOpts *Environment
|
||||
// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
|
||||
// (OPTIONAL) A stringified JSON environment for the stack.
|
||||
Environment string
|
||||
// (DEPRECATED): Files is automatically determined
|
||||
// by parsing the template and environment passed as TemplateOpts and
|
||||
// EnvironmentOpts respectively.
|
||||
// (OPTIONAL) A map that maps file names to file contents. It can also be used
|
||||
// to pass provider template contents. Example:
|
||||
// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
|
||||
@ -329,26 +438,58 @@ type UpdateOpts struct {
|
||||
Parameters map[string]string
|
||||
// (OPTIONAL) The timeout for stack creation in minutes.
|
||||
Timeout int
|
||||
// (OPTIONAL) A list of tags to assosciate with the Stack
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// ToStackUpdateMap casts a CreateOpts struct to a map.
|
||||
func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
|
||||
s := make(map[string]interface{})
|
||||
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
Files := make(map[string]string)
|
||||
if opts.TemplateOpts == nil {
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
}
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
if err := opts.TemplateOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.TemplateOpts.fixFileRefs()
|
||||
s["template"] = string(opts.TemplateOpts.Bin)
|
||||
|
||||
for k, v := range opts.TemplateOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Environment != "" {
|
||||
if opts.EnvironmentOpts != nil {
|
||||
if err := opts.EnvironmentOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.EnvironmentOpts.fixFileRefs()
|
||||
for k, v := range opts.EnvironmentOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
s["environment"] = string(opts.EnvironmentOpts.Bin)
|
||||
} else if opts.Environment != "" {
|
||||
s["environment"] = opts.Environment
|
||||
}
|
||||
|
||||
if opts.Files != nil {
|
||||
s["files"] = opts.Files
|
||||
} else {
|
||||
s["files"] = Files
|
||||
}
|
||||
|
||||
if opts.Parameters != nil {
|
||||
@ -359,6 +500,10 @@ func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
|
||||
s["timeout_mins"] = opts.Timeout
|
||||
}
|
||||
|
||||
if opts.Tags != nil {
|
||||
s["tags"] = strings.Join(opts.Tags, ",")
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@ -397,9 +542,16 @@ type PreviewOpts struct {
|
||||
Name string
|
||||
// (REQUIRED) The timeout for stack creation in minutes.
|
||||
Timeout int
|
||||
// (REQUIRED) A structure that contains either the template file or url. Call the
|
||||
// associated methods to extract the information relevant to send in a create request.
|
||||
TemplateOpts *Template
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, TemplateURL will be ignored
|
||||
// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
|
||||
// This value is ignored if Template is supplied inline.
|
||||
TemplateURL string
|
||||
// (DEPRECATED): Please use TemplateOpts for providing the template. If
|
||||
// TemplateOpts is provided, Template will be ignored
|
||||
// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
|
||||
// is a stringified version of the JSON/YAML template. Since the template will likely
|
||||
// be located in a file, one way to set this variable is by using ioutil.ReadFile:
|
||||
@ -415,8 +567,14 @@ type PreviewOpts struct {
|
||||
// creation fails. Default is true, meaning all resources are not deleted when
|
||||
// stack creation fails.
|
||||
DisableRollback Rollback
|
||||
// (OPTIONAL) A structure that contains details for the environment of the stack.
|
||||
EnvironmentOpts *Environment
|
||||
// (DEPRECATED): Please use EnvironmentOpts to provide Environment data
|
||||
// (OPTIONAL) A stringified JSON environment for the stack.
|
||||
Environment string
|
||||
// (DEPRECATED): Files is automatically determined
|
||||
// by parsing the template and environment passed as TemplateOpts and
|
||||
// EnvironmentOpts respectively.
|
||||
// (OPTIONAL) A map that maps file names to file contents. It can also be used
|
||||
// to pass provider template contents. Example:
|
||||
// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
|
||||
@ -433,25 +591,56 @@ func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) {
|
||||
return s, errors.New("Required field 'Name' not provided.")
|
||||
}
|
||||
s["stack_name"] = opts.Name
|
||||
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
Files := make(map[string]string)
|
||||
if opts.TemplateOpts == nil {
|
||||
if opts.Template != "" {
|
||||
s["template"] = opts.Template
|
||||
} else if opts.TemplateURL != "" {
|
||||
s["template_url"] = opts.TemplateURL
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
}
|
||||
} else {
|
||||
return s, errors.New("Either Template or TemplateURL must be provided.")
|
||||
}
|
||||
if err := opts.TemplateOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := opts.TemplateOpts.getFileContents(opts.TemplateOpts.Parsed, ignoreIfTemplate, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.TemplateOpts.fixFileRefs()
|
||||
s["template"] = string(opts.TemplateOpts.Bin)
|
||||
|
||||
for k, v := range opts.TemplateOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
}
|
||||
if opts.DisableRollback != nil {
|
||||
s["disable_rollback"] = &opts.DisableRollback
|
||||
}
|
||||
|
||||
if opts.Environment != "" {
|
||||
if opts.EnvironmentOpts != nil {
|
||||
if err := opts.EnvironmentOpts.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := opts.EnvironmentOpts.getRRFileContents(ignoreIfEnvironment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.EnvironmentOpts.fixFileRefs()
|
||||
for k, v := range opts.EnvironmentOpts.Files {
|
||||
Files[k] = v
|
||||
}
|
||||
s["environment"] = string(opts.EnvironmentOpts.Bin)
|
||||
} else if opts.Environment != "" {
|
||||
s["environment"] = opts.Environment
|
||||
}
|
||||
|
||||
if opts.Files != nil {
|
||||
s["files"] = opts.Files
|
||||
} else {
|
||||
s["files"] = Files
|
||||
}
|
||||
|
||||
if opts.Parameters != nil {
|
||||
s["parameters"] = opts.Parameters
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ type ListedStack struct {
|
||||
Name string `mapstructure:"stack_name"`
|
||||
Status string `mapstructure:"stack_status"`
|
||||
StatusReason string `mapstructure:"stack_status_reason"`
|
||||
Tags []string `mapstructure:"tags"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ func ExtractStacks(page pagination.Page) ([]ListedStack, error) {
|
||||
Stacks []ListedStack `mapstructure:"stacks"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(page.(StackPage).Body, &res)
|
||||
err := mapstructure.Decode(casted, &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -133,6 +134,7 @@ type RetrievedStack struct {
|
||||
Name string `mapstructure:"stack_name"`
|
||||
Status string `mapstructure:"stack_status"`
|
||||
StatusReason string `mapstructure:"stack_status_reason"`
|
||||
Tags []string `mapstructure:"tags"`
|
||||
TemplateDescription string `mapstructure:"template_description"`
|
||||
Timeout int `mapstructure:"timeout_mins"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
@ -200,21 +202,19 @@ type DeleteResult struct {
|
||||
|
||||
// PreviewedStack represents the result of a Preview operation.
|
||||
type PreviewedStack struct {
|
||||
Capabilities []interface{} `mapstructure:"capabilities"`
|
||||
CreationTime time.Time `mapstructure:"-"`
|
||||
Description string `mapstructure:"description"`
|
||||
DisableRollback bool `mapstructure:"disable_rollback"`
|
||||
ID string `mapstructure:"id"`
|
||||
Links []gophercloud.Link `mapstructure:"links"`
|
||||
Name string `mapstructure:"stack_name"`
|
||||
NotificationTopics []interface{} `mapstructure:"notification_topics"`
|
||||
Parameters map[string]string `mapstructure:"parameters"`
|
||||
Resources []map[string]interface{} `mapstructure:"resources"`
|
||||
Status string `mapstructure:"stack_status"`
|
||||
StatusReason string `mapstructure:"stack_status_reason"`
|
||||
TemplateDescription string `mapstructure:"template_description"`
|
||||
Timeout int `mapstructure:"timeout_mins"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
Capabilities []interface{} `mapstructure:"capabilities"`
|
||||
CreationTime time.Time `mapstructure:"-"`
|
||||
Description string `mapstructure:"description"`
|
||||
DisableRollback bool `mapstructure:"disable_rollback"`
|
||||
ID string `mapstructure:"id"`
|
||||
Links []gophercloud.Link `mapstructure:"links"`
|
||||
Name string `mapstructure:"stack_name"`
|
||||
NotificationTopics []interface{} `mapstructure:"notification_topics"`
|
||||
Parameters map[string]string `mapstructure:"parameters"`
|
||||
Resources []interface{} `mapstructure:"resources"`
|
||||
TemplateDescription string `mapstructure:"template_description"`
|
||||
Timeout int `mapstructure:"timeout_mins"`
|
||||
UpdatedTime time.Time `mapstructure:"-"`
|
||||
}
|
||||
|
||||
// PreviewResult represents the result of a Preview operation.
|
||||
@ -269,12 +269,16 @@ func (r PreviewResult) Extract() (*PreviewedStack, error) {
|
||||
|
||||
// AbandonedStack represents the result of an Abandon operation.
|
||||
type AbandonedStack struct {
|
||||
Status string `mapstructure:"status"`
|
||||
Name string `mapstructure:"name"`
|
||||
Template map[string]interface{} `mapstructure:"template"`
|
||||
Action string `mapstructure:"action"`
|
||||
ID string `mapstructure:"id"`
|
||||
Resources map[string]interface{} `mapstructure:"resources"`
|
||||
Status string `mapstructure:"status"`
|
||||
Name string `mapstructure:"name"`
|
||||
Template map[string]interface{} `mapstructure:"template"`
|
||||
Action string `mapstructure:"action"`
|
||||
ID string `mapstructure:"id"`
|
||||
Resources map[string]interface{} `mapstructure:"resources"`
|
||||
Files map[string]string `mapstructure:"files"`
|
||||
StackUserProjectID string `mapstructure:"stack_user_project_id"`
|
||||
ProjectID string `mapstructure:"project_id"`
|
||||
Environment map[string]interface{} `mapstructure:"environment"`
|
||||
}
|
||||
|
||||
// AbandonResult represents the result of an Abandon operation.
|
||||
|
139
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/template.go
generated
vendored
Normal file
139
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/template.go
generated
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Template is a structure that represents OpenStack Heat templates
|
||||
type Template struct {
|
||||
TE
|
||||
}
|
||||
|
||||
// TemplateFormatVersions is a map containing allowed variations of the template format version
|
||||
// Note that this contains the permitted variations of the _keys_ not the values.
|
||||
var TemplateFormatVersions = map[string]bool{
|
||||
"HeatTemplateFormatVersion": true,
|
||||
"heat_template_version": true,
|
||||
"AWSTemplateFormatVersion": true,
|
||||
}
|
||||
|
||||
// Validate validates the contents of the Template
|
||||
func (t *Template) Validate() error {
|
||||
if t.Parsed == nil {
|
||||
if err := t.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for key := range t.Parsed {
|
||||
if _, ok := TemplateFormatVersions[key]; ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Template format version not found.")
|
||||
}
|
||||
|
||||
// GetFileContents recursively parses a template to search for urls. These urls
|
||||
// are assumed to point to other templates (known in OpenStack Heat as child
|
||||
// templates). The contents of these urls are fetched and stored in the `Files`
|
||||
// parameter of the template structure. This is the only way that a user can
|
||||
// use child templates that are located in their filesystem; urls located on the
|
||||
// web (e.g. on github or swift) can be fetched directly by Heat engine.
|
||||
func (t *Template) getFileContents(te interface{}, ignoreIf igFunc, recurse bool) error {
|
||||
// initialize template if empty
|
||||
if t.Files == nil {
|
||||
t.Files = make(map[string]string)
|
||||
}
|
||||
if t.fileMaps == nil {
|
||||
t.fileMaps = make(map[string]string)
|
||||
}
|
||||
switch te.(type) {
|
||||
// if te is a map
|
||||
case map[string]interface{}, map[interface{}]interface{}:
|
||||
teMap, err := toStringKeys(te)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range teMap {
|
||||
value, ok := v.(string)
|
||||
if !ok {
|
||||
// if the value is not a string, recursively parse that value
|
||||
if err := t.getFileContents(v, ignoreIf, recurse); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !ignoreIf(k, value) {
|
||||
// at this point, the k, v pair has a reference to an external template.
|
||||
// The assumption of heatclient is that value v is a reference
|
||||
// to a file in the users environment
|
||||
|
||||
// create a new child template
|
||||
childTemplate := new(Template)
|
||||
|
||||
// initialize child template
|
||||
|
||||
// get the base location of the child template
|
||||
baseURL, err := gophercloud.NormalizePathURL(t.baseURL, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
childTemplate.baseURL = baseURL
|
||||
childTemplate.client = t.client
|
||||
|
||||
// fetch the contents of the child template
|
||||
if err := childTemplate.Parse(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// process child template recursively if required. This is
|
||||
// required if the child template itself contains references to
|
||||
// other templates
|
||||
if recurse {
|
||||
if err := childTemplate.getFileContents(childTemplate.Parsed, ignoreIf, recurse); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// update parent template with current child templates' content.
|
||||
// At this point, the child template has been parsed recursively.
|
||||
t.fileMaps[value] = childTemplate.URL
|
||||
t.Files[childTemplate.URL] = string(childTemplate.Bin)
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// if te is a slice, call the function on each element of the slice.
|
||||
case []interface{}:
|
||||
teSlice := te.([]interface{})
|
||||
for i := range teSlice {
|
||||
if err := t.getFileContents(teSlice[i], ignoreIf, recurse); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// if te is anything else, return
|
||||
case string, bool, float64, nil, int:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%v: Unrecognized type", reflect.TypeOf(te))
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// function to choose keys whose values are other template files
|
||||
func ignoreIfTemplate(key string, value interface{}) bool {
|
||||
// key must be either `get_file` or `type` for value to be a URL
|
||||
if key != "get_file" && key != "type" {
|
||||
return true
|
||||
}
|
||||
// value must be a string
|
||||
valueString, ok := value.(string)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
// `.template` and `.yaml` are allowed suffixes for template URLs when referred to by `type`
|
||||
if key == "type" && !(strings.HasSuffix(valueString, ".template") || strings.HasSuffix(valueString, ".yaml")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
161
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/utils.go
generated
vendored
Normal file
161
Godeps/_workspace/src/github.com/rackspace/gophercloud/openstack/orchestration/v1/stacks/utils.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Client is an interface that expects a Get method similar to http.Get. This
|
||||
// is needed for unit testing, since we can mock an http client. Thus, the
|
||||
// client will usually be an http.Client EXCEPT in unit tests.
|
||||
type Client interface {
|
||||
Get(string) (*http.Response, error)
|
||||
}
|
||||
|
||||
// TE is a base structure for both Template and Environment
|
||||
type TE struct {
|
||||
// Bin stores the contents of the template or environment.
|
||||
Bin []byte
|
||||
// URL stores the URL of the template. This is allowed to be a 'file://'
|
||||
// for local files.
|
||||
URL string
|
||||
// Parsed contains a parsed version of Bin. Since there are 2 different
|
||||
// fields referring to the same value, you must be careful when accessing
|
||||
// this filed.
|
||||
Parsed map[string]interface{}
|
||||
// Files contains a mapping between the urls in templates to their contents.
|
||||
Files map[string]string
|
||||
// fileMaps is a map used internally when determining Files.
|
||||
fileMaps map[string]string
|
||||
// baseURL represents the location of the template or environment file.
|
||||
baseURL string
|
||||
// client is an interface which allows TE to fetch contents from URLS
|
||||
client Client
|
||||
}
|
||||
|
||||
// Fetch fetches the contents of a TE from its URL. Once a TE structure has a
|
||||
// URL, call the fetch method to fetch the contents.
|
||||
func (t *TE) Fetch() error {
|
||||
// if the baseURL is not provided, use the current directors as the base URL
|
||||
if t.baseURL == "" {
|
||||
u, err := getBasePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.baseURL = u
|
||||
}
|
||||
|
||||
// if the contents are already present, do nothing.
|
||||
if t.Bin != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get a fqdn from the URL using the baseURL of the TE. For local files,
|
||||
// the URL's will have the `file` scheme.
|
||||
u, err := gophercloud.NormalizePathURL(t.baseURL, t.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.URL = u
|
||||
|
||||
// get an HTTP client if none present
|
||||
if t.client == nil {
|
||||
t.client = getHTTPClient()
|
||||
}
|
||||
|
||||
// use the client to fetch the contents of the TE
|
||||
resp, err := t.client.Get(t.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Bin = body
|
||||
return nil
|
||||
}
|
||||
|
||||
// get the basepath of the TE
|
||||
func getBasePath() (string, error) {
|
||||
basePath, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u, err := gophercloud.NormalizePathURL("", basePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// get a an HTTP client to retrieve URL's. This client allows the use of `file`
|
||||
// scheme since we may need to fetch files from users filesystem
|
||||
func getHTTPClient() Client {
|
||||
transport := &http.Transport{}
|
||||
transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
|
||||
return &http.Client{Transport: transport}
|
||||
}
|
||||
|
||||
// Parse will parse the contents and then validate. The contents MUST be either JSON or YAML.
|
||||
func (t *TE) Parse() error {
|
||||
if err := t.Fetch(); err != nil {
|
||||
return err
|
||||
}
|
||||
if jerr := json.Unmarshal(t.Bin, &t.Parsed); jerr != nil {
|
||||
if yerr := yaml.Unmarshal(t.Bin, &t.Parsed); yerr != nil {
|
||||
return fmt.Errorf("Data in neither json nor yaml format.")
|
||||
}
|
||||
}
|
||||
return t.Validate()
|
||||
}
|
||||
|
||||
// Validate validates the contents of TE
|
||||
func (t *TE) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// igfunc is a parameter used by GetFileContents and GetRRFileContents to check
|
||||
// for valid URL's.
|
||||
type igFunc func(string, interface{}) bool
|
||||
|
||||
// convert map[interface{}]interface{} to map[string]interface{}
|
||||
func toStringKeys(m interface{}) (map[string]interface{}, error) {
|
||||
switch m.(type) {
|
||||
case map[string]interface{}, map[interface{}]interface{}:
|
||||
typedMap := make(map[string]interface{})
|
||||
if _, ok := m.(map[interface{}]interface{}); ok {
|
||||
for k, v := range m.(map[interface{}]interface{}) {
|
||||
typedMap[k.(string)] = v
|
||||
}
|
||||
} else {
|
||||
typedMap = m.(map[string]interface{})
|
||||
}
|
||||
return typedMap, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("Expected a map of type map[string]interface{} or map[interface{}]interface{}, actual type: %v", reflect.TypeOf(m))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// fix the reference to files by replacing relative URL's by absolute
|
||||
// URL's
|
||||
func (t *TE) fixFileRefs() {
|
||||
tStr := string(t.Bin)
|
||||
if t.fileMaps == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range t.fileMaps {
|
||||
tStr = strings.Replace(tStr, k, v, -1)
|
||||
}
|
||||
t.Bin = []byte(tStr)
|
||||
}
|
@ -10,29 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// GetExpected represents the expected object from a Get request.
|
||||
var GetExpected = &Template{
|
||||
Description: "Simple template to test heat commands",
|
||||
HeatTemplateVersion: "2013-05-23",
|
||||
Parameters: map[string]interface{}{
|
||||
"flavor": map[string]interface{}{
|
||||
"default": "m1.tiny",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Resources: map[string]interface{}{
|
||||
"hello_world": map[string]interface{}{
|
||||
"type": "OS::Nova::Server",
|
||||
"properties": map[string]interface{}{
|
||||
"key_name": "heat_key",
|
||||
"flavor": map[string]interface{}{
|
||||
"get_param": "flavor",
|
||||
},
|
||||
"image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
|
||||
"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
var GetExpected = "{\n \"description\": \"Simple template to test heat commands\",\n \"heat_template_version\": \"2013-05-23\",\n \"parameters\": {\n \"flavor\": {\n \"default\": \"m1.tiny\",\n \"type\": \"string\"\n }\n },\n \"resources\": {\n \"hello_world\": {\n \"properties\": {\n \"flavor\": {\n \"get_param\": \"flavor\"\n },\n \"image\": \"ad091b52-742f-469e-8f3c-fd81cadf0743\",\n \"key_name\": \"heat_key\"\n },\n \"type\": \"OS::Nova::Server\"\n }\n }\n}"
|
||||
|
||||
// GetOutput represents the response body from a Get request.
|
||||
const GetOutput = `
|
||||
@ -53,8 +31,7 @@ const GetOutput = `
|
||||
"flavor": {
|
||||
"get_param": "flavor"
|
||||
},
|
||||
"image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
|
||||
"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
|
||||
"image": "ad091b52-742f-469e-8f3c-fd81cadf0743"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ type ValidateOptsBuilder interface {
|
||||
|
||||
// ValidateOpts specifies the template validation parameters.
|
||||
type ValidateOpts struct {
|
||||
Template map[string]interface{}
|
||||
Template string
|
||||
TemplateURL string
|
||||
}
|
||||
|
||||
// ToStackTemplateValidateMap assembles a request body based on the contents of a ValidateOpts.
|
||||
func (opts ValidateOpts) ToStackTemplateValidateMap() (map[string]interface{}, error) {
|
||||
vo := make(map[string]interface{})
|
||||
if opts.Template != nil {
|
||||
if opts.Template != "" {
|
||||
vo["template"] = opts.Template
|
||||
return vo, nil
|
||||
}
|
||||
|
@ -1,42 +1,33 @@
|
||||
package stacktemplates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
)
|
||||
|
||||
// Template represents a stack template.
|
||||
type Template struct {
|
||||
Description string `mapstructure:"description"`
|
||||
HeatTemplateVersion string `mapstructure:"heat_template_version"`
|
||||
Parameters map[string]interface{} `mapstructure:"parameters"`
|
||||
Resources map[string]interface{} `mapstructure:"resources"`
|
||||
}
|
||||
|
||||
// GetResult represents the result of a Get operation.
|
||||
type GetResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract returns a pointer to a Template object and is called after a
|
||||
// Get operation.
|
||||
func (r GetResult) Extract() (*Template, error) {
|
||||
// Extract returns the JSON template and is called after a Get operation.
|
||||
func (r GetResult) Extract() ([]byte, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var res Template
|
||||
if err := mapstructure.Decode(r.Body, &res); err != nil {
|
||||
template, err := json.MarshalIndent(r.Body, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// ValidatedTemplate represents the parsed object returned from a Validate request.
|
||||
type ValidatedTemplate struct {
|
||||
Description string
|
||||
Parameters map[string]interface{}
|
||||
Description string `mapstructure:"Description"`
|
||||
Parameters map[string]interface{} `mapstructure:"Parameters"`
|
||||
ParameterGroups map[string]interface{} `mapstructure:"ParameterGroups"`
|
||||
}
|
||||
|
||||
// ValidateResult represents the result of a Validate operation.
|
||||
|
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go
generated
vendored
26
Godeps/_workspace/src/github.com/rackspace/gophercloud/provider_client.go
generated
vendored
@ -177,6 +177,9 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
||||
}
|
||||
}
|
||||
|
||||
// Set connection parameter to close the connection immediately when we've got the response
|
||||
req.Close = true
|
||||
|
||||
// Issue the request.
|
||||
resp, err := client.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
@ -192,10 +195,13 @@ func (client *ProviderClient) Request(method, url string, options RequestOpts) (
|
||||
if options.RawBody != nil {
|
||||
options.RawBody.Seek(0, 0)
|
||||
}
|
||||
resp.Body.Close()
|
||||
resp, err = client.Request(method, url, options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Successfully re-authenticated, but got error executing request: %s", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,6 +249,8 @@ func defaultOkCodes(method string) []int {
|
||||
return []int{201, 202}
|
||||
case method == "PUT":
|
||||
return []int{201, 202}
|
||||
case method == "PATCH":
|
||||
return []int{200, 204}
|
||||
case method == "DELETE":
|
||||
return []int{202, 204}
|
||||
}
|
||||
@ -296,6 +304,24 @@ func (client *ProviderClient) Put(url string, JSONBody interface{}, JSONResponse
|
||||
return client.Request("PUT", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Patch(url string, JSONBody interface{}, JSONResponse *interface{}, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
}
|
||||
|
||||
if v, ok := (JSONBody).(io.ReadSeeker); ok {
|
||||
opts.RawBody = v
|
||||
} else if JSONBody != nil {
|
||||
opts.JSONBody = JSONBody
|
||||
}
|
||||
|
||||
if JSONResponse != nil {
|
||||
opts.JSONResponse = JSONResponse
|
||||
}
|
||||
|
||||
return client.Request("PATCH", url, *opts)
|
||||
}
|
||||
|
||||
func (client *ProviderClient) Delete(url string, opts *RequestOpts) (*http.Response, error) {
|
||||
if opts == nil {
|
||||
opts = &RequestOpts{}
|
||||
|
10
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go
generated
vendored
10
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/client.go
generated
vendored
@ -212,3 +212,13 @@ func NewRackConnectV3(client *gophercloud.ProviderClient, eo gophercloud.Endpoin
|
||||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
||||
// NewDBV1 creates a ServiceClient that may be used to access the v1 DB service.
|
||||
func NewDBV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
|
||||
eo.ApplyDefaults("rax:database")
|
||||
url, err := client.EndpointLocator(eo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
|
||||
}
|
||||
|
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/doc.go
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
// Package backups provides information and interaction with the backup API
|
||||
// resource in the Rackspace Database service.
|
||||
//
|
||||
// A backup is a copy of a database instance that can be used to restore it to
|
||||
// some defined point in history.
|
||||
package backups
|
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/fixtures.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package backups
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
timestamp = "2015-11-12T14:22:42Z"
|
||||
timeVal, _ = time.Parse(time.RFC3339, timestamp)
|
||||
)
|
||||
|
||||
var getResp = `
|
||||
{
|
||||
"backup": {
|
||||
"created": "` + timestamp + `",
|
||||
"description": "My Backup",
|
||||
"id": "61f12fef-edb1-4561-8122-e7c00ef26a82",
|
||||
"instance_id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
|
||||
"locationRef": null,
|
||||
"name": "snapshot",
|
||||
"parent_id": null,
|
||||
"size": 100,
|
||||
"status": "NEW",
|
||||
"datastore": {
|
||||
"version": "5.1",
|
||||
"type": "MySQL",
|
||||
"version_id": "20000000-0000-0000-0000-000000000002"
|
||||
},
|
||||
"updated": "` + timestamp + `"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var createReq = `
|
||||
{
|
||||
"backup": {
|
||||
"description": "My Backup",
|
||||
"instance": "d4603f69-ec7e-4e9b-803f-600b9205576f",
|
||||
"name": "snapshot"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var createResp = getResp
|
||||
|
||||
var listResp = `
|
||||
{
|
||||
"backups": [
|
||||
{
|
||||
"status": "COMPLETED",
|
||||
"updated": "` + timestamp + `",
|
||||
"description": "Backup from Restored Instance",
|
||||
"datastore": {
|
||||
"version": "5.1",
|
||||
"type": "MySQL",
|
||||
"version_id": "20000000-0000-0000-0000-000000000002"
|
||||
},
|
||||
"id": "87972694-4be2-40f5-83f8-501656e0032a",
|
||||
"size": 0.141026,
|
||||
"name": "restored_backup",
|
||||
"created": "` + timestamp + `",
|
||||
"instance_id": "29af2cd9-0674-48ab-b87a-b160f00208e6",
|
||||
"parent_id": null,
|
||||
"locationRef": "http://localhost/path/to/backup"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
138
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/requests.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/requests.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
package backups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// CreateOptsBuilder is the top-level interface for creating JSON maps.
|
||||
type CreateOptsBuilder interface {
|
||||
ToBackupCreateMap() (map[string]interface{}, error)
|
||||
}
|
||||
|
||||
// CreateOpts is responsible for configuring newly provisioned backups.
|
||||
type CreateOpts struct {
|
||||
// [REQUIRED] The name of the backup. The only restriction is the name must
|
||||
// be less than 64 characters long.
|
||||
Name string
|
||||
|
||||
// [REQUIRED] The ID of the instance being backed up.
|
||||
InstanceID string
|
||||
|
||||
// [OPTIONAL] A human-readable explanation of the backup.
|
||||
Description string
|
||||
}
|
||||
|
||||
// ToBackupCreateMap will create a JSON map for the Create operation.
|
||||
func (opts CreateOpts) ToBackupCreateMap() (map[string]interface{}, error) {
|
||||
if opts.Name == "" {
|
||||
return nil, errors.New("Name is a required field")
|
||||
}
|
||||
if opts.InstanceID == "" {
|
||||
return nil, errors.New("InstanceID is a required field")
|
||||
}
|
||||
|
||||
backup := map[string]interface{}{
|
||||
"name": opts.Name,
|
||||
"instance": opts.InstanceID,
|
||||
}
|
||||
|
||||
if opts.Description != "" {
|
||||
backup["description"] = opts.Description
|
||||
}
|
||||
|
||||
return map[string]interface{}{"backup": backup}, nil
|
||||
}
|
||||
|
||||
// Create asynchronously creates a new backup for a specified database instance.
|
||||
// During the backup process, write access on MyISAM databases will be
|
||||
// temporarily disabled; innoDB databases will be unaffected. During this time,
|
||||
// you will not be able to add or delete databases or users; nor delete, stop
|
||||
// or reboot the instance itself. Only one backup is permitted at once.
|
||||
//
|
||||
// Backups are not deleted when database instances are deleted; you must
|
||||
// manually delete any backups created using Delete(). Backups are saved to your
|
||||
// Cloud Files account in a new container called z_CLOUDDB_BACKUPS. It is
|
||||
// strongly recommended you do not alter this container or its contents; usual
|
||||
// storage costs apply.
|
||||
func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
|
||||
var res CreateResult
|
||||
|
||||
reqBody, err := opts.ToBackupCreateMap()
|
||||
if err != nil {
|
||||
res.Err = err
|
||||
return res
|
||||
}
|
||||
|
||||
_, res.Err = client.Request("POST", baseURL(client), gophercloud.RequestOpts{
|
||||
JSONBody: &reqBody,
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// ListOptsBuilder is the top-level interface for creating query strings.
|
||||
type ListOptsBuilder interface {
|
||||
ToBackupListQuery() (string, error)
|
||||
}
|
||||
|
||||
// ListOpts allows you to refine a list search by certain parameters.
|
||||
type ListOpts struct {
|
||||
// The type of datastore by which to filter.
|
||||
Datastore string `q:"datastore"`
|
||||
}
|
||||
|
||||
// ToBackupListQuery converts a ListOpts struct into a query string.
|
||||
func (opts ListOpts) ToBackupListQuery() (string, error) {
|
||||
q, err := gophercloud.BuildQueryString(opts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return q.String(), nil
|
||||
}
|
||||
|
||||
// List will list all the saved backups for all database instances.
|
||||
func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
|
||||
url := baseURL(client)
|
||||
|
||||
if opts != nil {
|
||||
query, err := opts.ToBackupListQuery()
|
||||
if err != nil {
|
||||
return pagination.Pager{Err: err}
|
||||
}
|
||||
url += query
|
||||
}
|
||||
|
||||
pageFn := func(r pagination.PageResult) pagination.Page {
|
||||
return BackupPage{pagination.SinglePageBase(r)}
|
||||
}
|
||||
|
||||
return pagination.NewPager(client, url, pageFn)
|
||||
}
|
||||
|
||||
// Get will retrieve details for a particular backup based on its unique ID.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
var res GetResult
|
||||
|
||||
_, res.Err = client.Request("GET", resourceURL(client, id), gophercloud.RequestOpts{
|
||||
JSONResponse: &res.Body,
|
||||
OkCodes: []int{200},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Delete will permanently delete a backup.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) DeleteResult {
|
||||
var res DeleteResult
|
||||
|
||||
_, res.Err = client.Request("DELETE", resourceURL(client, id), gophercloud.RequestOpts{
|
||||
OkCodes: []int{202},
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
149
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/results.go
generated
vendored
Normal file
149
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/results.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
package backups
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack/db/v1/datastores"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// Status represents the various states a Backup can be in.
|
||||
type Status string
|
||||
|
||||
// Enum types for the status.
|
||||
const (
|
||||
StatusNew Status = "NEW"
|
||||
StatusBuilding Status = "BUILDING"
|
||||
StatusCompleted Status = "COMPLETED"
|
||||
StatusFailed Status = "FAILED"
|
||||
StatusDeleteFailed Status = "DELETE_FAILED"
|
||||
)
|
||||
|
||||
// Backup represents a Backup API resource.
|
||||
type Backup struct {
|
||||
Description string
|
||||
ID string
|
||||
InstanceID string `json:"instance_id" mapstructure:"instance_id"`
|
||||
LocationRef string
|
||||
Name string
|
||||
ParentID string `json:"parent_id" mapstructure:"parent_id"`
|
||||
Size float64
|
||||
Status Status
|
||||
Created time.Time `mapstructure:"-"`
|
||||
Updated time.Time `mapstructure:"-"`
|
||||
Datastore datastores.DatastorePartial
|
||||
}
|
||||
|
||||
// CreateResult represents the result of a create operation.
|
||||
type CreateResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// GetResult represents the result of a get operation.
|
||||
type GetResult struct {
|
||||
commonResult
|
||||
}
|
||||
|
||||
// DeleteResult represents the result of a delete operation.
|
||||
type DeleteResult struct {
|
||||
gophercloud.ErrResult
|
||||
}
|
||||
|
||||
type commonResult struct {
|
||||
gophercloud.Result
|
||||
}
|
||||
|
||||
// Extract will retrieve a Backup struct from an operation's result.
|
||||
func (r commonResult) Extract() (*Backup, error) {
|
||||
if r.Err != nil {
|
||||
return nil, r.Err
|
||||
}
|
||||
|
||||
var response struct {
|
||||
Backup Backup `mapstructure:"backup"`
|
||||
}
|
||||
|
||||
err := mapstructure.Decode(r.Body, &response)
|
||||
val := r.Body.(map[string]interface{})["backup"].(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Backup, err
|
||||
}
|
||||
response.Backup.Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return &response.Backup, err
|
||||
}
|
||||
response.Backup.Updated = updatedTime
|
||||
}
|
||||
|
||||
return &response.Backup, err
|
||||
}
|
||||
|
||||
// BackupPage represents a page of backups.
|
||||
type BackupPage struct {
|
||||
pagination.SinglePageBase
|
||||
}
|
||||
|
||||
// IsEmpty checks whether an BackupPage struct is empty.
|
||||
func (r BackupPage) IsEmpty() (bool, error) {
|
||||
is, err := ExtractBackups(r)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return len(is) == 0, nil
|
||||
}
|
||||
|
||||
// ExtractBackups will retrieve a slice of Backup structs from a paginated collection.
|
||||
func ExtractBackups(page pagination.Page) ([]Backup, error) {
|
||||
casted := page.(BackupPage).Body
|
||||
|
||||
var resp struct {
|
||||
Backups []Backup `mapstructure:"backups" json:"backups"`
|
||||
}
|
||||
|
||||
if err := mapstructure.Decode(casted, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vals []interface{}
|
||||
switch casted.(type) {
|
||||
case map[string]interface{}:
|
||||
vals = casted.(map[string]interface{})["backups"].([]interface{})
|
||||
case map[string][]interface{}:
|
||||
vals = casted.(map[string][]interface{})["backups"]
|
||||
default:
|
||||
return resp.Backups, fmt.Errorf("Unknown type: %v", reflect.TypeOf(casted))
|
||||
}
|
||||
|
||||
for i, v := range vals {
|
||||
val := v.(map[string]interface{})
|
||||
|
||||
if t, ok := val["created"].(string); ok && t != "" {
|
||||
creationTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Backups, err
|
||||
}
|
||||
resp.Backups[i].Created = creationTime
|
||||
}
|
||||
|
||||
if t, ok := val["updated"].(string); ok && t != "" {
|
||||
updatedTime, err := time.Parse(time.RFC3339, t)
|
||||
if err != nil {
|
||||
return resp.Backups, err
|
||||
}
|
||||
resp.Backups[i].Updated = updatedTime
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Backups, nil
|
||||
}
|
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/urls.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/backups/urls.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package backups
|
||||
|
||||
import "github.com/rackspace/gophercloud"
|
||||
|
||||
func baseURL(c *gophercloud.ServiceClient) string {
|
||||
return c.ServiceURL("backups")
|
||||
}
|
||||
|
||||
func resourceURL(c *gophercloud.ServiceClient, backupID string) string {
|
||||
return c.ServiceURL("backups", backupID)
|
||||
}
|
79
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/delegate.go
generated
vendored
Normal file
79
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/delegate.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package configurations
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/configurations"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all of the available configurations.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return os.List(client)
|
||||
}
|
||||
|
||||
// Create will create a new configuration group.
|
||||
func Create(client *gophercloud.ServiceClient, opts os.CreateOptsBuilder) os.CreateResult {
|
||||
return os.Create(client, opts)
|
||||
}
|
||||
|
||||
// Get will retrieve the details for a specified configuration group.
|
||||
func Get(client *gophercloud.ServiceClient, configID string) os.GetResult {
|
||||
return os.Get(client, configID)
|
||||
}
|
||||
|
||||
// Update will modify an existing configuration group by performing a merge
|
||||
// between new and existing values. If the key already exists, the new value
|
||||
// will overwrite. All other keys will remain unaffected.
|
||||
func Update(client *gophercloud.ServiceClient, configID string, opts os.UpdateOptsBuilder) os.UpdateResult {
|
||||
return os.Update(client, configID, opts)
|
||||
}
|
||||
|
||||
// Replace will modify an existing configuration group by overwriting the
|
||||
// entire parameter group with the new values provided. Any existing keys not
|
||||
// included in UpdateOptsBuilder will be deleted.
|
||||
func Replace(client *gophercloud.ServiceClient, configID string, opts os.UpdateOptsBuilder) os.ReplaceResult {
|
||||
return os.Replace(client, configID, opts)
|
||||
}
|
||||
|
||||
// Delete will permanently delete a configuration group. Please note that
|
||||
// config groups cannot be deleted whilst still attached to running instances -
|
||||
// you must detach and then delete them.
|
||||
func Delete(client *gophercloud.ServiceClient, configID string) os.DeleteResult {
|
||||
return os.Delete(client, configID)
|
||||
}
|
||||
|
||||
// ListInstances will list all the instances associated with a particular
|
||||
// configuration group.
|
||||
func ListInstances(client *gophercloud.ServiceClient, configID string) pagination.Pager {
|
||||
return os.ListInstances(client, configID)
|
||||
}
|
||||
|
||||
// ListDatastoreParams will list all the available and supported parameters
|
||||
// that can be used for a particular datastore ID and a particular version.
|
||||
// For example, if you are wondering how you can configure a MySQL 5.6 instance,
|
||||
// you can use this operation (you will need to retrieve the MySQL datastore ID
|
||||
// by using the datastores API).
|
||||
func ListDatastoreParams(client *gophercloud.ServiceClient, datastoreID, versionID string) pagination.Pager {
|
||||
return os.ListDatastoreParams(client, datastoreID, versionID)
|
||||
}
|
||||
|
||||
// GetDatastoreParam will retrieve information about a specific configuration
|
||||
// parameter. For example, you can use this operation to understand more about
|
||||
// "innodb_file_per_table" configuration param for MySQL datastores. You will
|
||||
// need the param's ID first, which can be attained by using the ListDatastoreParams
|
||||
// operation.
|
||||
func GetDatastoreParam(client *gophercloud.ServiceClient, datastoreID, versionID, paramID string) os.ParamResult {
|
||||
return os.GetDatastoreParam(client, datastoreID, versionID, paramID)
|
||||
}
|
||||
|
||||
// ListGlobalParams is similar to ListDatastoreParams but does not require a
|
||||
// DatastoreID.
|
||||
func ListGlobalParams(client *gophercloud.ServiceClient, versionID string) pagination.Pager {
|
||||
return os.ListGlobalParams(client, versionID)
|
||||
}
|
||||
|
||||
// GetGlobalParam is similar to GetDatastoreParam but does not require a
|
||||
// DatastoreID.
|
||||
func GetGlobalParam(client *gophercloud.ServiceClient, versionID, paramID string) os.ParamResult {
|
||||
return os.GetGlobalParam(client, versionID, paramID)
|
||||
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/doc.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/doc.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package configurations
|
159
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/fixtures.go
generated
vendored
Normal file
159
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/configurations/fixtures.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
package configurations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/configurations"
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp = "2015-11-12T14:22:42Z"
|
||||
timeVal, _ = time.Parse(time.RFC3339, timestamp)
|
||||
)
|
||||
|
||||
var singleConfigJSON = `
|
||||
{
|
||||
"created": "` + timestamp + `",
|
||||
"datastore_name": "mysql",
|
||||
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"datastore_version_name": "5.6",
|
||||
"description": "example_description",
|
||||
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
"name": "example-configuration-name",
|
||||
"updated": "` + timestamp + `"
|
||||
}
|
||||
`
|
||||
|
||||
var singleConfigWithValuesJSON = `
|
||||
{
|
||||
"created": "` + timestamp + `",
|
||||
"datastore_name": "mysql",
|
||||
"datastore_version_id": "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
"datastore_version_name": "5.6",
|
||||
"description": "example description",
|
||||
"id": "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
"instance_count": 0,
|
||||
"name": "example-configuration-name",
|
||||
"updated": "` + timestamp + `",
|
||||
"values": {
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var (
|
||||
listConfigsJSON = fmt.Sprintf(`{"configurations": [%s]}`, singleConfigJSON)
|
||||
getConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigJSON)
|
||||
createConfigJSON = fmt.Sprintf(`{"configuration": %s}`, singleConfigWithValuesJSON)
|
||||
)
|
||||
|
||||
var createReq = `
|
||||
{
|
||||
"configuration": {
|
||||
"datastore": {
|
||||
"type": "a00000a0-00a0-0a00-00a0-000a000000aa",
|
||||
"version": "b00000b0-00b0-0b00-00b0-000b000000bb"
|
||||
},
|
||||
"description": "example description",
|
||||
"name": "example-configuration-name",
|
||||
"values": {
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var updateReq = `
|
||||
{
|
||||
"configuration": {
|
||||
"values": {
|
||||
"connect_timeout": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
var listInstancesJSON = `
|
||||
{
|
||||
"instances": [
|
||||
{
|
||||
"id": "d4603f69-ec7e-4e9b-803f-600b9205576f",
|
||||
"name": "json_rack_instance"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var listParamsJSON = `
|
||||
{
|
||||
"configuration-parameters": [
|
||||
{
|
||||
"max": 1,
|
||||
"min": 0,
|
||||
"name": "innodb_file_per_table",
|
||||
"restart_required": true,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 4294967296,
|
||||
"min": 0,
|
||||
"name": "key_buffer_size",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 65535,
|
||||
"min": 2,
|
||||
"name": "connect_timeout",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"max": 4294967296,
|
||||
"min": 0,
|
||||
"name": "join_buffer_size",
|
||||
"restart_required": false,
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
var getParamJSON = `
|
||||
{
|
||||
"max": 1,
|
||||
"min": 0,
|
||||
"name": "innodb_file_per_table",
|
||||
"restart_required": true,
|
||||
"type": "integer"
|
||||
}
|
||||
`
|
||||
|
||||
var exampleConfig = os.Config{
|
||||
Created: timeVal,
|
||||
DatastoreName: "mysql",
|
||||
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
DatastoreVersionName: "5.6",
|
||||
Description: "example_description",
|
||||
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
Name: "example-configuration-name",
|
||||
Updated: timeVal,
|
||||
}
|
||||
|
||||
var exampleConfigWithValues = os.Config{
|
||||
Created: timeVal,
|
||||
DatastoreName: "mysql",
|
||||
DatastoreVersionID: "b00000b0-00b0-0b00-00b0-000b000000bb",
|
||||
DatastoreVersionName: "5.6",
|
||||
Description: "example description",
|
||||
ID: "005a8bb7-a8df-40ee-b0b7-fc144641abc2",
|
||||
Name: "example-configuration-name",
|
||||
Updated: timeVal,
|
||||
Values: map[string]interface{}{
|
||||
"collation_server": "latin1_swedish_ci",
|
||||
"connect_timeout": 120,
|
||||
},
|
||||
}
|
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/delegate.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/delegate.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package databases
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/databases"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
func Create(client *gophercloud.ServiceClient, instanceID string, opts os.CreateOptsBuilder) os.CreateResult {
|
||||
return os.Create(client, instanceID, opts)
|
||||
}
|
||||
|
||||
func List(client *gophercloud.ServiceClient, instanceID string) pagination.Pager {
|
||||
return os.List(client, instanceID)
|
||||
}
|
||||
|
||||
func Delete(client *gophercloud.ServiceClient, instanceID, dbName string) os.DeleteResult {
|
||||
return os.Delete(client, instanceID, dbName)
|
||||
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package databases provides information and interaction with the database API
|
||||
// resource in the Rackspace Database service.
|
||||
package databases
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/urls.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/databases/urls.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package databases
|
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/datastores/delegate.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/datastores/delegate.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package datastores
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/datastores"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all available flavors.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return os.List(client)
|
||||
}
|
||||
|
||||
// Get retrieves the details for a particular flavor.
|
||||
func Get(client *gophercloud.ServiceClient, flavorID string) os.GetResult {
|
||||
return os.Get(client, flavorID)
|
||||
}
|
||||
|
||||
// ListVersions will list all of the available versions for a specified
|
||||
// datastore type.
|
||||
func ListVersions(client *gophercloud.ServiceClient, datastoreID string) pagination.Pager {
|
||||
return os.ListVersions(client, datastoreID)
|
||||
}
|
||||
|
||||
// GetVersion will retrieve the details of a specified datastore version.
|
||||
func GetVersion(client *gophercloud.ServiceClient, datastoreID, versionID string) os.GetVersionResult {
|
||||
return os.GetVersion(client, datastoreID, versionID)
|
||||
}
|
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/datastores/doc.go
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/datastores/doc.go
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
package datastores
|
17
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/flavors/delegate.go
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/flavors/delegate.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package flavors
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/flavors"
|
||||
"github.com/rackspace/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// List will list all available flavors.
|
||||
func List(client *gophercloud.ServiceClient) pagination.Pager {
|
||||
return os.List(client)
|
||||
}
|
||||
|
||||
// Get retrieves the details for a particular flavor.
|
||||
func Get(client *gophercloud.ServiceClient, flavorID string) os.GetResult {
|
||||
return os.Get(client, flavorID)
|
||||
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/flavors/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/flavors/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package flavors provides information and interaction with the flavor API
|
||||
// resource in the Rackspace Database service.
|
||||
package flavors
|
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/instances/delegate.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/instances/delegate.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/rackspace/gophercloud"
|
||||
os "github.com/rackspace/gophercloud/openstack/db/v1/instances"
|
||||
)
|
||||
|
||||
// Get retrieves the status and information for a specified database instance.
|
||||
func Get(client *gophercloud.ServiceClient, id string) GetResult {
|
||||
return GetResult{os.Get(client, id)}
|
||||
}
|
||||
|
||||
// Delete permanently destroys the database instance.
|
||||
func Delete(client *gophercloud.ServiceClient, id string) os.DeleteResult {
|
||||
return os.Delete(client, id)
|
||||
}
|
||||
|
||||
// EnableRootUser enables the login from any host for the root user and
|
||||
// provides the user with a generated root password.
|
||||
func EnableRootUser(client *gophercloud.ServiceClient, id string) os.UserRootResult {
|
||||
return os.EnableRootUser(client, id)
|
||||
}
|
||||
|
||||
// IsRootEnabled checks an instance to see if root access is enabled. It returns
|
||||
// True if root user is enabled for the specified database instance or False
|
||||
// otherwise.
|
||||
func IsRootEnabled(client *gophercloud.ServiceClient, id string) (bool, error) {
|
||||
return os.IsRootEnabled(client, id)
|
||||
}
|
||||
|
||||
// Restart will restart only the MySQL Instance. Restarting MySQL will
|
||||
// erase any dynamic configuration settings that you have made within MySQL.
|
||||
// The MySQL service will be unavailable until the instance restarts.
|
||||
func Restart(client *gophercloud.ServiceClient, id string) os.ActionResult {
|
||||
return os.Restart(client, id)
|
||||
}
|
||||
|
||||
// Resize changes the memory size of the instance, assuming a valid
|
||||
// flavorRef is provided. It will also restart the MySQL service.
|
||||
func Resize(client *gophercloud.ServiceClient, id, flavorRef string) os.ActionResult {
|
||||
return os.Resize(client, id, flavorRef)
|
||||
}
|
||||
|
||||
// ResizeVolume will resize the attached volume for an instance. It supports
|
||||
// only increasing the volume size and does not support decreasing the size.
|
||||
// The volume size is in gigabytes (GB) and must be an integer.
|
||||
func ResizeVolume(client *gophercloud.ServiceClient, id string, size int) os.ActionResult {
|
||||
return os.ResizeVolume(client, id, size)
|
||||
}
|
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/instances/doc.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/rackspace/gophercloud/rackspace/db/v1/instances/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
// Package instances provides information and interaction with the instance API
|
||||
// resource in the Rackspace Database service.
|
||||
package instances
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user