Move deps from _workspace/ to vendor/

godep restore
pushd $GOPATH/src/github.com/appc/spec
git co master
popd
go get go4.org/errorutil
rm -rf Godeps
godep save ./...
git add vendor
git add -f $(git ls-files --other vendor/)
git co -- Godeps/LICENSES Godeps/.license_file_state Godeps/OWNERS
This commit is contained in:
Tim Hockin
2016-05-08 20:30:21 -07:00
parent 899f9b4e31
commit 3c0c5ed4e0
4400 changed files with 16739 additions and 376 deletions

View 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

View 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,
},
}

View 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
}

View 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, &param)
return &param, err
}

View 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)
}

View 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

View 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)
}

View 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
}

View 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
}

View 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)
}

View File

@@ -0,0 +1,3 @@
// Package datastores provides information and interaction with the datastore
// API resource in the Rackspace Database service.
package datastores

View 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,
}

View 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
}

View 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
}

View 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)
}

View 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

View 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)
}

View 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
}

View 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
}

View 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")
}

View 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

View 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)
}

View 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
}

View 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
}

View 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")
}

View File

@@ -0,0 +1,3 @@
// Package users provides information and interaction with the user API
// resource in the OpenStack Database service.
package users

View 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)
}

View 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
}

View 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
}

View 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)
}