stacks
diff --git a/openstack/client.go b/openstack/client.go
index 9c12dca..5fce3d6 100644
--- a/openstack/client.go
+++ b/openstack/client.go
@@ -214,3 +214,13 @@
}
return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
+
+// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
+func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
+ eo.ApplyDefaults("orchestration")
+ url, err := client.EndpointLocator(eo)
+ if err != nil {
+ return nil, err
+ }
+ return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
+}
diff --git a/openstack/orchestration/v1/stacks/requests.go b/openstack/orchestration/v1/stacks/requests.go
new file mode 100644
index 0000000..50b900d
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/requests.go
@@ -0,0 +1,429 @@
+package stacks
+
+import (
+ "errors"
+
+ "github.com/racker/perigee"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+// CreateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the main Create operation in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type CreateOptsBuilder interface {
+ ToStackCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts is the common options struct used in this package's Create
+// operation.
+type CreateOpts struct {
+ DisableRollback *bool
+ Environment string
+ Files map[string]interface{}
+ Name string
+ Parameters map[string]string
+ Template string
+ TemplateURL string
+ Timeout int
+}
+
+// ToStackCreateMap casts a CreateOpts struct to a map.
+func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
+
+ if opts.Name == "" {
+ 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
+ } else {
+ return s, errors.New("Either Template or TemplateURL must be provided.")
+ }
+
+ 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
+ }
+
+ if opts.Timeout != 0 {
+ s["timeout_mins"] = opts.Timeout
+ }
+
+ return s, nil
+}
+
+// Create accepts a CreateOpts struct and creates a new stack using the values
+// provided.
+func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
+ var res CreateResult
+
+ reqBody, err := opts.ToStackCreateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ // Send request to API
+ _, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{201},
+ })
+ return res
+}
+
+// AdoptOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Adopt function in this package. Since many
+// extensions decorate or modify the common logic, it is useful for them to
+// satisfy a basic interface in order for them to be used.
+type AdoptOptsBuilder interface {
+ ToStackAdoptMap() (map[string]interface{}, error)
+}
+
+// AdoptOpts is the common options struct used in this package's Adopt
+// operation.
+type AdoptOpts struct {
+ AdoptStackData string
+ DisableRollback *bool
+ Environment string
+ Files map[string]interface{}
+ Name string
+ Parameters map[string]string
+ Template string
+ TemplateURL string
+ Timeout int
+}
+
+// ToStackAdoptMap casts a CreateOpts struct to a map.
+func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
+
+ if opts.Name == "" {
+ 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
+ } else {
+ return s, errors.New("Either Template or TemplateURL must be provided.")
+ }
+
+ if opts.AdoptStackData == "" {
+ return s, errors.New("Required field 'AdoptStackData' not provided.")
+ }
+ s["adopt_stack_data"] = opts.AdoptStackData
+
+ 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
+ }
+
+ if opts.Timeout != 0 {
+ s["timeout_mins"] = opts.Timeout
+ }
+
+ return map[string]interface{}{"stack": s}, nil
+}
+
+// Adopt accepts an AdoptOpts struct and creates a new stack using the resources
+// from another stack.
+func Adopt(c *gophercloud.ServiceClient, opts AdoptOptsBuilder) CreateResult {
+ var res CreateResult
+
+ reqBody, err := opts.ToStackAdoptMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ // Send request to API
+ _, res.Err = perigee.Request("POST", adoptURL(c), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{201},
+ })
+ return res
+}
+
+// SortDir is a type for specifying in which direction to sort a list of stacks.
+type SortDir string
+
+// SortKey is a type for specifying by which key to sort a list of stacks.
+type SortKey string
+
+var (
+ // SortAsc is used to sort a list of stacks in ascending order.
+ SortAsc SortDir = "asc"
+ // SortDesc is used to sort a list of stacks in descending order.
+ SortDesc SortDir = "desc"
+ // SortName is used to sort a list of stacks by name.
+ SortName SortKey = "name"
+ // SortStatus is used to sort a list of stacks by status.
+ SortStatus SortKey = "status"
+ // SortCreatedAt is used to sort a list of stacks by date created.
+ SortCreatedAt SortKey = "created_at"
+ // SortUpdatedAt is used to sort a list of stacks by date updated.
+ SortUpdatedAt SortKey = "updated_at"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+ ToStackListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the network attributes you want to see returned. SortKey allows you to sort
+// by a particular network attribute. SortDir sets the direction, and is either
+// `asc' or `desc'. Marker and Limit are used for pagination.
+type ListOpts struct {
+ Status string `q:"status"`
+ Name string `q:"name"`
+ Marker string `q:"marker"`
+ Limit int `q:"limit"`
+ SortKey SortKey `q:"sort_keys"`
+ SortDir SortDir `q:"sort_dir"`
+}
+
+// ToStackListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToStackListQuery() (string, error) {
+ q, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return "", err
+ }
+ return q.String(), nil
+}
+
+// List returns a Pager which allows you to iterate over a collection of
+// stacks. It accepts a ListOpts struct, which allows you to filter and sort
+// the returned collection for greater efficiency.
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+ url := listURL(c)
+ if opts != nil {
+ query, err := opts.ToStackListQuery()
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
+ url += query
+ }
+
+ createPage := func(r pagination.PageResult) pagination.Page {
+ return StackPage{pagination.SinglePageBase(r)}
+ }
+ return pagination.NewPager(c, url, createPage)
+}
+
+// Get retreives a stack based on the stack name and stack ID.
+func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
+ var res GetResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
+
+// UpdateOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Update operation in this package.
+type UpdateOptsBuilder interface {
+ ToStackUpdateMap() (map[string]interface{}, error)
+}
+
+// UpdateOpts contains the common options struct used in this package's Update
+// operation.
+type UpdateOpts struct {
+ Environment string
+ Files map[string]interface{}
+ Parameters map[string]string
+ Template string
+ TemplateURL string
+ Timeout int
+}
+
+// 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
+ } else {
+ return s, errors.New("Either Template or TemplateURL must be provided.")
+ }
+
+ if opts.Environment != "" {
+ s["environment"] = opts.Environment
+ }
+
+ if opts.Files != nil {
+ s["files"] = opts.Files
+ }
+
+ if opts.Parameters != nil {
+ s["parameters"] = opts.Parameters
+ }
+
+ if opts.Timeout != 0 {
+ s["timeout_mins"] = opts.Timeout
+ }
+
+ return s, nil
+}
+
+// Update accepts an UpdateOpts struct and updates an existing stack using the values
+// provided.
+func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) UpdateResult {
+ var res UpdateResult
+
+ reqBody, err := opts.ToStackUpdateMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ // Send request to API
+ _, res.Err = perigee.Request("PUT", updateURL(c, stackName, stackID), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ OkCodes: []int{202},
+ })
+ return res
+}
+
+// Delete deletes a stack based on the stack name and stack ID.
+func Delete(c *gophercloud.ServiceClient, stackName, stackID string) DeleteResult {
+ var res DeleteResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("DELETE", deleteURL(c, stackName, stackID), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ OkCodes: []int{204},
+ })
+ return res
+}
+
+// PreviewOptsBuilder is the interface options structs have to satisfy in order
+// to be used in the Preview operation in this package.
+type PreviewOptsBuilder interface {
+ ToStackPreviewMap() (map[string]interface{}, error)
+}
+
+// PreviewOpts contains the common options struct used in this package's Preview
+// operation.
+type PreviewOpts struct {
+ DisableRollback *bool
+ Environment string
+ Files map[string]interface{}
+ Name string
+ Parameters map[string]string
+ Template string
+ TemplateURL string
+ Timeout int
+}
+
+// ToStackPreviewMap casts a PreviewOpts struct to a map.
+func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) {
+ s := make(map[string]interface{})
+
+ if opts.Name == "" {
+ 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
+ } else {
+ return s, errors.New("Either Template or TemplateURL must be provided.")
+ }
+
+ 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
+ }
+
+ if opts.Timeout != 0 {
+ s["timeout_mins"] = opts.Timeout
+ }
+
+ return s, nil
+}
+
+// Preview accepts a PreviewOptsBuilder interface and creates a preview of a stack using the values
+// provided.
+func Preview(c *gophercloud.ServiceClient, opts PreviewOptsBuilder) PreviewResult {
+ var res PreviewResult
+
+ reqBody, err := opts.ToStackPreviewMap()
+ if err != nil {
+ res.Err = err
+ return res
+ }
+
+ // Send request to API
+ _, res.Err = perigee.Request("POST", previewURL(c), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ ReqBody: &reqBody,
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
+
+// Abandon deletes the stack with the provided stackName and stackID, but leaves its
+// resources intact, and returns data describing the stack and its resources.
+func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) AbandonResult {
+ var res AbandonResult
+
+ // Send request to API
+ _, res.Err = perigee.Request("POST", previewURL(c), perigee.Options{
+ MoreHeaders: c.AuthenticatedHeaders(),
+ Results: &res.Body,
+ OkCodes: []int{200},
+ })
+ return res
+}
diff --git a/openstack/orchestration/v1/stacks/results.go b/openstack/orchestration/v1/stacks/results.go
new file mode 100644
index 0000000..9e11f82
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/results.go
@@ -0,0 +1,219 @@
+package stacks
+
+import (
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+)
+
+type CreateStack struct {
+ ID string `mapstructure:"id"`
+ Links []gophercloud.Link `mapstructure:"links"`
+}
+
+type CreateResult struct {
+ gophercloud.Result
+}
+
+func (r CreateResult) Extract() (*CreateStack, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Stack *CreateStack `json:"stack"`
+ }
+
+ if err := mapstructure.Decode(r.Body, &res); err != nil {
+ return nil, err
+ }
+
+ return res.Stack, nil
+}
+
+type AdoptResult struct {
+ gophercloud.Result
+}
+
+// StackPage is a pagination.Pager that is returned from a call to the List function.
+type StackPage struct {
+ pagination.SinglePageBase
+}
+
+// IsEmpty returns true if a ListResult contains no Stacks.
+func (r StackPage) IsEmpty() (bool, error) {
+ stacks, err := ExtractStacks(r)
+ if err != nil {
+ return true, err
+ }
+ return len(stacks) == 0, nil
+}
+
+type ListStack struct {
+ CreationTime time.Time `mapstructure:"-"`
+ Description string `mapstructure:"description"`
+ ID string `mapstructure:"id"`
+ Links []gophercloud.Link `mapstructure:"links"`
+ Name string `mapstructure:"stack_name"`
+ Status string `mapstructure:"stack_status"`
+ StausReason string `mapstructure:"stack_status_reason"`
+ UpdatedTime time.Time `mapstructure:"-"`
+}
+
+// ExtractStacks extracts and returns a slice of Stacks. It is used while iterating
+// over a stacks.List call.
+func ExtractStacks(page pagination.Page) ([]ListStack, error) {
+ var res struct {
+ Stacks []ListStack `json:"stacks"`
+ }
+
+ err := mapstructure.Decode(page.(StackPage).Body, &res)
+ return res.Stacks, err
+}
+
+type GetStack 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"`
+ NotificationTopics []interface{} `mapstructure:"notification_topics"`
+ Outputs []map[string]string `mapstructure:"outputs"`
+ Parameters map[string]string `mapstructure:"parameters"`
+ Name string `mapstructure:"stack_name"`
+ Status string `mapstructure:"stack_status"`
+ StausReason string `mapstructure:"stack_status_reason"`
+ TemplateDescription string `mapstructure:"template_description"`
+ Timeout int `mapstructure:"timeout_mins"`
+ UpdatedTime time.Time `mapstructure:"-"`
+}
+
+type GetResult struct {
+ gophercloud.Result
+}
+
+func (r GetResult) Extract() (*GetStack, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Stack *GetStack `json:"stack"`
+ }
+
+ config := &mapstructure.DecoderConfig{
+ Result: &res,
+ WeaklyTypedInput: true,
+ }
+ decoder, err := mapstructure.NewDecoder(config)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := decoder.Decode(r.Body); err != nil {
+ return nil, err
+ }
+
+ b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
+
+ if date, ok := b["creation_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Stack.CreationTime = t
+ }
+
+ if date, ok := b["updated_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Stack.UpdatedTime = t
+ }
+
+ return res.Stack, err
+}
+
+type UpdateResult struct {
+ gophercloud.ErrResult
+}
+
+type DeleteResult struct {
+ gophercloud.ErrResult
+}
+
+type PreviewStack 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]string `mapstructure:"resources"`
+ Status string `mapstructure:"stack_status"`
+ StausReason string `mapstructure:"stack_status_reason"`
+ TemplateDescription string `mapstructure:"template_description"`
+ Timeout int `mapstructure:"timeout_mins"`
+ UpdatedTime time.Time `mapstructure:"-"`
+}
+
+type PreviewResult struct {
+ gophercloud.Result
+}
+
+func (r PreviewResult) Extract() (*PreviewStack, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Stack *PreviewStack `json:"stack"`
+ }
+
+ config := &mapstructure.DecoderConfig{
+ Result: &res,
+ WeaklyTypedInput: true,
+ }
+ decoder, err := mapstructure.NewDecoder(config)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := decoder.Decode(r.Body); err != nil {
+ return nil, err
+ }
+
+ b := r.Body.(map[string]interface{})["stack"].(map[string]interface{})
+
+ if date, ok := b["creation_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Stack.CreationTime = t
+ }
+
+ if date, ok := b["updated_time"]; ok && date != nil {
+ t, err := time.Parse(time.RFC3339, date.(string))
+ if err != nil {
+ return nil, err
+ }
+ res.Stack.UpdatedTime = t
+ }
+
+ return res.Stack, err
+}
+
+type AbandonStack struct {
+}
+
+type AbandonResult struct {
+ gophercloud.Result
+}
diff --git a/openstack/orchestration/v1/stacks/urls.go b/openstack/orchestration/v1/stacks/urls.go
new file mode 100644
index 0000000..385af48
--- /dev/null
+++ b/openstack/orchestration/v1/stacks/urls.go
@@ -0,0 +1,55 @@
+package stacks
+
+import "github.com/rackspace/gophercloud"
+
+func createURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("stacks")
+}
+
+func adoptURL(c *gophercloud.ServiceClient) string {
+ return createURL(c)
+}
+
+func listURL(c *gophercloud.ServiceClient) string {
+ return createURL(c)
+}
+
+func getURL(c *gophercloud.ServiceClient, name, id string) string {
+ return c.ServiceURL("stacks", name, id)
+}
+
+func updateURL(c *gophercloud.ServiceClient, name, id string) string {
+ return getURL(c, name, id)
+}
+
+func deleteURL(c *gophercloud.ServiceClient, name, id string) string {
+ return getURL(c, name, id)
+}
+
+func previewURL(c *gophercloud.ServiceClient) string {
+ return c.ServiceURL("stacks", "preview")
+}
+
+func abandonURL(c *gophercloud.ServiceClient, name, id string) string {
+ return c.ServiceURL("stacks", name, id, "abandon")
+}
+
+func createSnapshotURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "snapshots")
+}
+
+func listSnapshotsURL(c *gophercloud.ServiceClient, stackName, stackID string) string {
+ return createSnapshotURL(c, stackName, stackID)
+}
+
+func getSnapshotURL(c *gophercloud.ServiceClient, stackName, stackID, snapshotID string) string {
+ return c.ServiceURL("stacks", stackName, stackID, "snapshots", snapshotID)
+}
+
+func restoreSnapshotURL(c *gophercloud.ServiceClient, stackName, stackID, snapshotID string) string {
+ return getSnapshotURL(c, stackName, stackID, snapshotID)
+}
+
+func deleteSnapshotURL(c *gophercloud.ServiceClient, stackName, stackID, snapshotID string) string {
+ return getSnapshotURL(c, stackName, stackID, snapshotID)
+}