diff --git a/huaweicloud/config.go b/huaweicloud/config.go index 4ab5b5f36a..176f6c0297 100644 --- a/huaweicloud/config.go +++ b/huaweicloud/config.go @@ -500,3 +500,10 @@ func (c *Config) getHwEndpointType() golangsdk.Availability { } return golangsdk.AvailabilityPublic } + +func (c *Config) orchestrationV1Client(region string) (*golangsdk.ServiceClient, error) { + return huaweisdk.NewOrchestrationV1(c.HwClient, golangsdk.EndpointOpts{ + Region: c.determineRegion(region), + Availability: c.getHwEndpointType(), + }) +} diff --git a/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1.go b/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1.go new file mode 100644 index 0000000000..76c2b8047d --- /dev/null +++ b/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1.go @@ -0,0 +1,95 @@ +package huaweicloud + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources" +) + +func dataSourceRTSStackResourcesV1() *schema.Resource { + return &schema.Resource{ + Read: dataSourceRTSStackResourcesV1Read, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + "stack_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "resource_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "logical_resource_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "required_by": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + "resource_status": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "resource_status_reason": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "physical_resource_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "resource_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func dataSourceRTSStackResourcesV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + orchestrationClient, err := config.orchestrationV1Client(GetRegion(d, config)) + + listOpts := stackresources.ListOpts{ + Name: d.Get("resource_name").(string), + PhysicalID: d.Get("physical_resource_id").(string), + Type: d.Get("resource_type").(string), + } + + refinedResources, err := stackresources.List(orchestrationClient, d.Get("stack_name").(string), listOpts) + if err != nil { + return fmt.Errorf("Unable to retrieve Stack Resources: %s", err) + } + + if len(refinedResources) < 1 { + return fmt.Errorf("No matching resource found. " + + "Please change your search criteria and try again.") + } + + if len(refinedResources) > 1 { + return fmt.Errorf("Multiple resources matched; use additional constraints to reduce matches to a single resource") + } + + stackResource := refinedResources[0] + d.SetId(stackResource.PhysicalID) + + d.Set("resource_name", stackResource.Name) + d.Set("resource_status", stackResource.Status) + d.Set("logical_resource_id", stackResource.LogicalID) + d.Set("physical_resource_id", stackResource.PhysicalID) + d.Set("required_by", stackResource.RequiredBy) + d.Set("resource_status_reason", stackResource.StatusReason) + d.Set("resource_type", stackResource.Type) + d.Set("region", GetRegion(d, config)) + return nil +} diff --git a/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1_test.go b/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1_test.go new file mode 100644 index 0000000000..0035d581e7 --- /dev/null +++ b/huaweicloud/data_source_huaweicloud_rts_stack_resource_v1_test.go @@ -0,0 +1,89 @@ +package huaweicloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccRTSStackResourcesV1DataSource_basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceRTSStackResourcesV1Config, + Check: resource.ComposeTestCheckFunc( + testAccCheckRTSStackResourcesV1DataSourceID("data.huaweicloud_rts_stack_resource_v1.resource_1"), + resource.TestCheckResourceAttr( + "data.huaweicloud_rts_stack_resource_v1.resource_1", "resource_name", "random"), + resource.TestCheckResourceAttr( + "data.huaweicloud_rts_stack_resource_v1.resource_1", "resource_type", "OS::Heat::RandomString"), + resource.TestCheckResourceAttr( + "data.huaweicloud_rts_stack_resource_v1.resource_1", "resource_status", "CREATE_COMPLETE"), + ), + }, + }, + }) +} + +func testAccCheckRTSStackResourcesV1DataSourceID(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Can't find stack resource data source: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("stack resource data source ID not set") + } + + return nil + } +} + +const testAccDataSourceRTSStackResourcesV1Config = ` + +resource "huaweicloud_rts_stack_v1" "stack_1" { + name = "huaweicloud_rts_stack" + disable_rollback= true + timeout_mins=60 + template_body = < 64 { + errors = append(errors, fmt.Errorf( + "%q cannot be longer than 64 characters: %q", k, value)) + } + + pattern := `^[\.\-_A-Za-z0-9]+$` + if !regexp.MustCompile(pattern).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q doesn't comply with restrictions (%q): %q", + k, pattern, value)) + } + + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/doc.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/doc.go new file mode 100644 index 0000000000..1deebd08de --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/doc.go @@ -0,0 +1,2 @@ +// Package stackresources provides operations for working with stack resources. +package stackresources diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/requests.go new file mode 100644 index 0000000000..1bd95461ef --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/requests.go @@ -0,0 +1,104 @@ +package stackresources + +import ( + "reflect" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// ListOptsBuilder allows extensions to add additional parameters to the +// List request. +type ListOptsBuilder interface { + ToStackResourceListQuery() (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 rts attributes you want to see returned. +type ListOpts struct { + //Specifies the logical resource ID of the resource. + LogicalID string `q:"logical_resource_id"` + + //Name is the human readable name for the Resource. + Name string `q:"resource_name"` + + //Specifies the Physical resource ID of the resource. + PhysicalID string `q:"physical_resource_id"` + + //Status indicates whether or not a subnet is currently operational. + Status string `q:"resource_status"` + + //Specifies the resource type that are defined in the template. + Type string `q:"resource_type"` +} + +// List returns collection of +// resources. It accepts a ListOpts struct, which allows you to filter and sort +// the returned collection for greater efficiency. +// +// Default policy settings return only those resources that are owned by the +// tenant who submits the request, unless an admin user submits the request. +func List(client *golangsdk.ServiceClient, stackName string, opts ListOpts) ([]Resource, error) { + u := listURL(client, stackName) + pages, err := pagination.NewPager(client, u, func(r pagination.PageResult) pagination.Page { + return ResourcePage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allResources, err := ExtractResources(pages) + if err != nil { + return nil, err + } + + return FilterResources(allResources, opts) +} + +func FilterResources(resources []Resource, opts ListOpts) ([]Resource, error) { + + var refinedResources []Resource + var matched bool + m := map[string]interface{}{} + + if opts.LogicalID != "" { + m["LogicalID"] = opts.LogicalID + } + if opts.Name != "" { + m["Name"] = opts.Name + } + if opts.PhysicalID != "" { + m["PhysicalID"] = opts.PhysicalID + } + if opts.Status != "" { + m["Status"] = opts.Status + } + if opts.Type != "" { + m["Type"] = opts.Type + } + + if len(m) > 0 && len(resources) > 0 { + for _, resource := range resources { + matched = true + + for key, value := range m { + if sVal := getStructField(&resource, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedResources = append(refinedResources, resource) + } + } + + } else { + refinedResources = resources + } + + return refinedResources, nil +} + +func getStructField(v *Resource, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/results.go new file mode 100644 index 0000000000..a7dc82e16a --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/results.go @@ -0,0 +1,65 @@ +package stackresources + +import ( + "encoding/json" + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// Resource represents a stack resource. +type Resource struct { + CreationTime time.Time `json:"-"` + Links []golangsdk.Link `json:"links"` + LogicalID string `json:"logical_resource_id"` + Name string `json:"resource_name"` + PhysicalID string `json:"physical_resource_id"` + RequiredBy []string `json:"required_by"` + Status string `json:"resource_status"` + StatusReason string `json:"resource_status_reason"` + Type string `json:"resource_type"` + UpdatedTime time.Time `json:"-"` +} + +// ResourcePage is the page returned by a pager when traversing over a +// collection of resources. +type ResourcePage struct { + pagination.LinkedPageBase +} + +func (r *Resource) UnmarshalJSON(b []byte) error { + type tmp Resource + var s struct { + tmp + CreationTime golangsdk.JSONRFC3339NoZ `json:"creation_time"` + UpdatedTime golangsdk.JSONRFC3339NoZ `json:"updated_time"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = Resource(s.tmp) + + r.CreationTime = time.Time(s.CreationTime) + r.UpdatedTime = time.Time(s.UpdatedTime) + + return nil +} + +// IsEmpty returns true if a page contains no Server results. +func (r ResourcePage) IsEmpty() (bool, error) { + resources, err := ExtractResources(r) + return len(resources) == 0, err +} + +// ExtractResources accepts a Page struct, specifically a ResourcePage struct, +// and extracts the elements into a slice of Resource structs. In other words, +// a generic collection is mapped into a relevant slice. +func ExtractResources(r pagination.Page) ([]Resource, error) { + var s struct { + Resources []Resource `json:"resources"` + } + err := (r.(ResourcePage)).ExtractInto(&s) + return s.Resources, err +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/urls.go new file mode 100644 index 0000000000..8c893fb53e --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources/urls.go @@ -0,0 +1,7 @@ +package stackresources + +import "github.com/huaweicloud/golangsdk" + +func listURL(c *golangsdk.ServiceClient, stackName string) string { + return c.ServiceURL("stacks", stackName, "resources") +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/doc.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/doc.go new file mode 100644 index 0000000000..e4cbdababc --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/doc.go @@ -0,0 +1,9 @@ +// Package stacks provides operation for working with Heat stacks. A stack is a +// group of resources (servers, load balancers, databases, and so forth) +// combined to fulfill a useful purpose. Based on a template, Heat orchestration +// engine creates an instantiated set of resources (a stack) to run the +// application framework or component specified (in the template). A stack is a +// running instance of a template. The result of creating a stack is a deployment +// of the application framework or component. + +package stacks diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/environment.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/environment.go new file mode 100644 index 0000000000..86989186fa --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/environment.go @@ -0,0 +1,134 @@ +package stacks + +import "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 ErrInvalidEnvironment{Section: 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 +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/errors.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/errors.go new file mode 100644 index 0000000000..20cf158e09 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/errors.go @@ -0,0 +1,33 @@ +package stacks + +import ( + "fmt" + + "github.com/huaweicloud/golangsdk" +) + +type ErrInvalidEnvironment struct { + golangsdk.BaseError + Section string +} + +func (e ErrInvalidEnvironment) Error() string { + return fmt.Sprintf("Environment has wrong section: %s", e.Section) +} + +type ErrInvalidDataFormat struct { + golangsdk.BaseError +} + +func (e ErrInvalidDataFormat) Error() string { + return fmt.Sprintf("Data in neither json nor yaml format.") +} + +type ErrInvalidTemplateFormatVersion struct { + golangsdk.BaseError + Version string +} + +func (e ErrInvalidTemplateFormatVersion) Error() string { + return fmt.Sprintf("Template format version not found.") +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/fixtures.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/fixtures.go new file mode 100644 index 0000000000..58987d4bfd --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/fixtures.go @@ -0,0 +1,199 @@ +package stacks + +// ValidJSONTemplate is a valid OpenStack Heat template in JSON format +const ValidJSONTemplate = ` +{ + "heat_template_version": "2014-10-16", + "parameters": { + "flavor": { + "default": "debian2G", + "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" + } + } +} +` + +// 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: debian2G + 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: debian2G + 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" + ] + } + } + } + } +} +` + +// 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: debian2G + 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 +` + +// 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", + }, + }, + }, + }, + }, +} + +// 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": "debian2G", + "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", + }, + }, +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/requests.go new file mode 100644 index 0000000000..e68bdef3d2 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/requests.go @@ -0,0 +1,303 @@ +package stacks + +import ( + "reflect" + "strings" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/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 { + // The name of the stack. It must start with an alphabetic character. + Name string `json:"stack_name" required:"true"` + // 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 `json:"-" required:"true"` + // Enables or disables deletion of all stack resources when a stack + // creation fails. Default is true, meaning all resources are not deleted when + // stack creation fails. + DisableRollback *bool `json:"disable_rollback,omitempty"` + // A structure that contains details for the environment of the stack. + EnvironmentOpts *Environment `json:"-"` + // User-defined parameters to pass to the template. + Parameters map[string]string `json:"parameters,omitempty"` + // The timeout for stack creation in minutes. + Timeout int `json:"timeout_mins,omitempty"` + // A list of tags to assosciate with the Stack + Tags []string `json:"-"` +} + +// ToStackCreateMap casts a CreateOpts struct to a map. +func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + 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() + b["template"] = string(opts.TemplateOpts.Bin) + + files := make(map[string]string) + for k, v := range opts.TemplateOpts.Files { + files[k] = v + } + + 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 + } + b["environment"] = string(opts.EnvironmentOpts.Bin) + } + + if len(files) > 0 { + b["files"] = files + } + + if opts.Tags != nil { + b["tags"] = strings.Join(opts.Tags, ",") + } + + return b, nil +} + +// Create accepts a CreateOpts struct and creates a new stack using the values +// provided. +func Create(c *golangsdk.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { + b, err := opts.ToStackCreateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Post(createURL(c), b, &r.Body, nil) + return +} + +// 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 rts 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 { + ID string `q:"id"` + 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 := golangsdk.BuildQueryString(opts) + if err != nil { + return "", err + } + return q.String(), nil +} + +func List(c *golangsdk.ServiceClient, opts ListOpts) ([]ListedStack, error) { + u := listURL(c) + pages, err := pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page { + return StackPage{pagination.LinkedPageBase{PageResult: r}} + }).AllPages() + + allStacks, err := ExtractStacks(pages) + if err != nil { + return nil, err + } + + return FilterStacks(allStacks, opts) +} + +func FilterStacks(stacks []ListedStack, opts ListOpts) ([]ListedStack, error) { + + var refinedStacks []ListedStack + var matched bool + m := map[string]interface{}{} + + if opts.ID != "" { + m["ID"] = opts.ID + } + if opts.Name != "" { + m["Name"] = opts.Name + } + if opts.Status != "" { + m["Status"] = opts.Status + } + + if len(m) > 0 && len(stacks) > 0 { + for _, stack := range stacks { + matched = true + + for key, value := range m { + if sVal := getStructField(&stack, key); !(sVal == value) { + matched = false + } + } + + if matched { + refinedStacks = append(refinedStacks, stack) + } + } + + } else { + refinedStacks = stacks + } + + return refinedStacks, nil +} + +func getStructField(v *ListedStack, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + return string(f.String()) +} + +func Get(c *golangsdk.ServiceClient, stackName string) (r GetResult) { + _, r.Err = c.Get(getURL(c, stackName), &r.Body, nil) + return +} + +// 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 { + // 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 `json:"-" required:"true"` + // A structure that contains details for the environment of the stack. + EnvironmentOpts *Environment `json:"-"` + // User-defined parameters to pass to the template. + Parameters map[string]string `json:"parameters,omitempty"` + // The timeout for stack creation in minutes. + Timeout int `json:"timeout_mins,omitempty"` + // Enables or disables deletion of all stack resources when a stack + // creation fails. Default is true, meaning all resources are not deleted when + // stack creation fails. + DisableRollback *bool `json:"disable_rollback,omitempty"` + // A list of tags to assosciate with the Stack + Tags []string `json:"-"` +} + +// ToStackUpdateMap casts a CreateOpts struct to a map. +func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) { + b, err := golangsdk.BuildRequestBody(opts, "") + if err != nil { + return nil, err + } + + 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() + b["template"] = string(opts.TemplateOpts.Bin) + + files := make(map[string]string) + for k, v := range opts.TemplateOpts.Files { + files[k] = v + } + + 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 + } + b["environment"] = string(opts.EnvironmentOpts.Bin) + } + + if len(files) > 0 { + b["files"] = files + } + + if opts.Tags != nil { + b["tags"] = strings.Join(opts.Tags, ",") + } + + return b, nil +} + +// Update accepts an UpdateOpts struct and updates an existing stack using the values +// provided. +func Update(c *golangsdk.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) (r UpdateResult) { + b, err := opts.ToStackUpdateMap() + if err != nil { + r.Err = err + return + } + _, r.Err = c.Put(updateURL(c, stackName, stackID), b, nil, nil) + return +} + +// Delete deletes a stack based on the stack name and stack ID. +func Delete(c *golangsdk.ServiceClient, stackName, stackID string) (r DeleteResult) { + _, r.Err = c.Delete(deleteURL(c, stackName, stackID), nil) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/results.go new file mode 100644 index 0000000000..d3bc773318 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/results.go @@ -0,0 +1,303 @@ +package stacks + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + "time" + + "github.com/huaweicloud/golangsdk" + "github.com/huaweicloud/golangsdk/pagination" +) + +// CreatedStack represents the object extracted from a Create operation. +type CreatedStack struct { + ID string `json:"id"` + Links []golangsdk.Link `json:"links"` +} + +// CreateResult represents the result of a Create operation. +type CreateResult struct { + golangsdk.Result +} + +// Extract returns a pointer to a CreatedStack object and is called after a +// Create operation. +func (r CreateResult) Extract() (*CreatedStack, error) { + var s struct { + CreatedStack *CreatedStack `json:"stack"` + } + err := r.ExtractInto(&s) + return s.CreatedStack, err +} + +// StackPage is a pagination.Pager that is returned from a call to the List function. +type StackPage struct { + pagination.LinkedPageBase +} + +// IsEmpty returns true if a ListResult contains no Stacks. +func (r StackPage) IsEmpty() (bool, error) { + stacks, err := ExtractStacks(r) + return len(stacks) == 0, err +} + +// ListedStack represents an element in the slice extracted from a List operation. +type ListedStack struct { + CreationTime time.Time `json:"-"` + Description string `json:"description"` + ID string `json:"id"` + Links []golangsdk.Link `json:"links"` + Name string `json:"stack_name"` + Status string `json:"stack_status"` + StatusReason string `json:"stack_status_reason"` + UpdatedTime time.Time `json:"-"` +} + +func (r *ListedStack) UnmarshalJSON(b []byte) error { + type tmp ListedStack + var s struct { + tmp + CreationTime golangsdk.JSONRFC3339NoZ `json:"creation_time"` + UpdatedTime golangsdk.JSONRFC3339NoZ `json:"updated_time"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = ListedStack(s.tmp) + + r.CreationTime = time.Time(s.CreationTime) + r.UpdatedTime = time.Time(s.UpdatedTime) + + return nil +} + +// ExtractStacks extracts and returns a slice of ListedStack. It is used while iterating +// over a stacks.List call. +func ExtractStacks(r pagination.Page) ([]ListedStack, error) { + var s struct { + ListedStacks []ListedStack `json:"stacks"` + } + err := (r.(StackPage)).ExtractInto(&s) + return s.ListedStacks, err +} + +// RetrievedStack represents the object extracted from a Get operation. +// RetrievedStack represents the object extracted from a Get operation. +type RetrievedStack struct { + Capabilities []interface{} `json:"capabilities"` + CreationTime time.Time `json:"-"` + Description string `json:"description"` + DisableRollback bool `json:"disable_rollback"` + ID string `json:"id"` + TenantId string `json:"tenant_id"` + Links []golangsdk.Link `json:"links"` + NotificationTopics []interface{} `json:"notification_topics"` + Outputs []*Output `json:"outputs"` + Parameters map[string]string `json:"parameters"` + Name string `json:"stack_name"` + Status string `json:"stack_status"` + StatusReason string `json:"stack_status_reason"` + Tags []string `json:"tags"` + TemplateDescription string `json:"template_description"` + Timeout int `json:"timeout_mins"` + UpdatedTime time.Time `json:"-"` +} + +// The Output data type. +type Output struct { + _ struct{} `type:"structure"` + + // User defined description associated with the output. + Description string `json:"description"` + + // The name of the export associated with the output. + //ExportName *string `json:"name"` + + // The key associated with the output. + OutputKey *string `json:"output_key"` + + // The value associated with the output. + OutputValue *string `json:"output_value"` +} + +// String returns the string representation +func (s Output) String() string { + return Prettify(s) +} + +// GoString returns the string representation +func (s Output) GoString() string { + return s.String() +} + +// SetDescription sets the Description field's value. +func (s *Output) SetDescription(v string) *Output { + s.Description = v + return s +} + +// SetOutputKey sets the OutputKey field's value. +func (s *Output) SetOutputKey(v string) *Output { + s.OutputKey = &v + return s +} + +// SetOutputValue sets the OutputValue field's value. +func (s *Output) SetOutputValue(v string) *Output { + s.OutputValue = &v + return s +} + +// RetrievedStack represents the object extracted from a Get operation. +func (r *RetrievedStack) UnmarshalJSON(b []byte) error { + type tmp RetrievedStack + var s struct { + tmp + CreationTime golangsdk.JSONRFC3339NoZ `json:"creation_time"` + UpdatedTime golangsdk.JSONRFC3339NoZ `json:"updated_time"` + } + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + *r = RetrievedStack(s.tmp) + + r.CreationTime = time.Time(s.CreationTime) + r.UpdatedTime = time.Time(s.UpdatedTime) + + return nil +} + +// GetResult represents the result of a Get operation. +type GetResult struct { + golangsdk.Result +} + +// Extract returns a pointer to a CreatedStack object and is called after a +// Create operation. +func (r GetResult) Extract() (*RetrievedStack, error) { + var s struct { + Stack *RetrievedStack `json:"stack"` + } + err := r.ExtractInto(&s) + return s.Stack, err +} + +// UpdateResult represents the result of a Update operation. +type UpdateResult struct { + golangsdk.ErrResult +} + +// DeleteResult represents the result of a Delete operation. +type DeleteResult struct { + golangsdk.ErrResult +} + +// Prettify returns the string representation of a value. +func Prettify(i interface{}) string { + var buf bytes.Buffer + prettify(reflect.ValueOf(i), 0, &buf) + return buf.String() +} + +// prettify will recursively walk value v to build a textual +// representation of the value. +func prettify(v reflect.Value, indent int, buf *bytes.Buffer) { + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Struct: + strtype := v.Type().String() + if strtype == "time.Time" { + fmt.Fprintf(buf, "%s", v.Interface()) + break + } else if strings.HasPrefix(strtype, "io.") { + buf.WriteString("") + break + } + + buf.WriteString("{\n") + + names := []string{} + for i := 0; i < v.Type().NumField(); i++ { + name := v.Type().Field(i).Name + f := v.Field(i) + if name[0:1] == strings.ToLower(name[0:1]) { + continue // ignore unexported fields + } + if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice || f.Kind() == reflect.Map) && f.IsNil() { + continue // ignore unset fields + } + names = append(names, name) + } + + for i, n := range names { + val := v.FieldByName(n) + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString(n + ": ") + prettify(val, indent+2, buf) + + if i < len(names)-1 { + buf.WriteString(",\n") + } + } + + buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") + case reflect.Slice: + strtype := v.Type().String() + if strtype == "[]uint8" { + fmt.Fprintf(buf, " len %d", v.Len()) + break + } + + nl, id, id2 := "", "", "" + if v.Len() > 3 { + nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2) + } + buf.WriteString("[" + nl) + for i := 0; i < v.Len(); i++ { + buf.WriteString(id2) + prettify(v.Index(i), indent+2, buf) + + if i < v.Len()-1 { + buf.WriteString("," + nl) + } + } + + buf.WriteString(nl + id + "]") + case reflect.Map: + buf.WriteString("{\n") + + for i, k := range v.MapKeys() { + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString(k.String() + ": ") + prettify(v.MapIndex(k), indent+2, buf) + + if i < v.Len()-1 { + buf.WriteString(",\n") + } + } + + buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") + default: + if !v.IsValid() { + fmt.Fprint(buf, "") + return + } + format := "%v" + switch v.Interface().(type) { + case string: + format = "%q" + case io.ReadSeeker, io.Reader: + format = "buffer(%p)" + } + fmt.Fprintf(buf, format, v.Interface()) + } +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/template.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/template.go new file mode 100644 index 0000000000..3431b7a0bb --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/template.go @@ -0,0 +1,141 @@ +package stacks + +import ( + "fmt" + "reflect" + "strings" + + "github.com/huaweicloud/golangsdk" +) + +// 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 + } + } + var invalid string + for key := range t.Parsed { + if _, ok := TemplateFormatVersions[key]; ok { + return nil + } + invalid = key + } + return ErrInvalidTemplateFormatVersion{Version: invalid} +} + +// 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 := golangsdk.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 golangsdk.ErrUnexpectedType{Actual: fmt.Sprintf("%v", 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 +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/urls.go new file mode 100644 index 0000000000..c59eacc9b8 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/urls.go @@ -0,0 +1,23 @@ +package stacks + +import ( + "github.com/huaweicloud/golangsdk" +) + +func createURL(c *golangsdk.ServiceClient) string { + return c.ServiceURL("stacks") +} +func listURL(c *golangsdk.ServiceClient) string { + return createURL(c) +} + +func getURL(c *golangsdk.ServiceClient, name string) string { + return c.ServiceURL("stacks", name) +} +func updateURL(c *golangsdk.ServiceClient, name, id string) string { + return c.ServiceURL("stacks", name, id) +} + +func deleteURL(c *golangsdk.ServiceClient, name, id string) string { + return updateURL(c, name, id) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/utils.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/utils.go new file mode 100644 index 0000000000..53a2584922 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks/utils.go @@ -0,0 +1,160 @@ +package stacks + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "reflect" + "strings" + + "github.com/huaweicloud/golangsdk" + "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 := golangsdk.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 := golangsdk.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 ErrInvalidDataFormat{} + } + } + 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, golangsdk.ErrUnexpectedType{Expected: "map[string]interface{}/map[interface{}]interface{}", Actual: fmt.Sprintf("%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) +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/doc.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/doc.go new file mode 100644 index 0000000000..40e3d73470 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/doc.go @@ -0,0 +1,2 @@ +// Package stacktemplates provides operations for working with Heat templates. +package stacktemplates diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/requests.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/requests.go new file mode 100644 index 0000000000..9e3036b513 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/requests.go @@ -0,0 +1,9 @@ +package stacktemplates + +import "github.com/huaweicloud/golangsdk" + +// Get retreives data for the given stack template. +func Get(c *golangsdk.ServiceClient, stackName, stackID string) (r GetResult) { + _, r.Err = c.Get(getURL(c, stackName, stackID), &r.Body, nil) + return +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/results.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/results.go new file mode 100644 index 0000000000..6184bd7f73 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/results.go @@ -0,0 +1,24 @@ +package stacktemplates + +import ( + "encoding/json" + + "github.com/huaweicloud/golangsdk" +) + +// GetResult represents the result of a Get operation. +type GetResult struct { + golangsdk.Result +} + +// 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 + } + template, err := json.MarshalIndent(r.Body, "", " ") + if err != nil { + return nil, err + } + return template, nil +} diff --git a/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/urls.go b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/urls.go new file mode 100644 index 0000000000..27db437e01 --- /dev/null +++ b/vendor/github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates/urls.go @@ -0,0 +1,7 @@ +package stacktemplates + +import "github.com/huaweicloud/golangsdk" + +func getURL(c *golangsdk.ServiceClient, stackName, stackID string) string { + return c.ServiceURL("stacks", stackName, stackID, "template") +} diff --git a/vendor/vendor.json b/vendor/vendor.json index a6b6a2d55b..ec02e43d4e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -782,6 +782,7 @@ "revisionTime": "2016-07-20T23:31:40Z" }, { + "checksumSHA1": "XOIIgkGo+ASyGm2ggTUkbV5FUXg=", "checksumSHA1": "cZlMp+J4uHxr97+lBhMMQavprwk=", "path": "github.com/huaweicloud/golangsdk", "revision": "ffc7e20dd56d293082449363b897dac040bed9e4", @@ -907,6 +908,24 @@ "revision": "888f77744ab7c65bb4d448d5b5313edba29e76c7", "revisionTime": "2018-02-24T07:23:49Z" }, + { + "checksumSHA1": "f0sDyNlCmV7tTWrKeQZ7NUqNRyE=", + "path": "github.com/huaweicloud/golangsdk/openstack/rts/v1/stackresources", + "revision": "aef01041fa9cf1fa8844a490cac4ded3fceec9a7", + "revisionTime": "2018-07-17T08:53:40Z" + }, + { + "checksumSHA1": "WQHNIyaeZnFaEk64tp87kSmSs9I=", + "path": "github.com/huaweicloud/golangsdk/openstack/rts/v1/stacks", + "revision": "aef01041fa9cf1fa8844a490cac4ded3fceec9a7", + "revisionTime": "2018-07-17T08:53:40Z" + }, + { + "checksumSHA1": "6GfFqMtL9GUvZQotCF8p1vp2bpc=", + "path": "github.com/huaweicloud/golangsdk/openstack/rts/v1/stacktemplates", + "revision": "aef01041fa9cf1fa8844a490cac4ded3fceec9a7", + "revisionTime": "2018-07-17T08:53:40Z" + }, { "checksumSHA1": "RV9GKwWK04J4e9L2kbfZnyO+0+U=", "path": "github.com/huaweicloud/golangsdk/openstack/smn/v2/subscriptions", diff --git a/website/docs/d/rts_stack_resource_v1.html.md b/website/docs/d/rts_stack_resource_v1.html.md new file mode 100644 index 0000000000..d8efd55edf --- /dev/null +++ b/website/docs/d/rts_stack_resource_v1.html.md @@ -0,0 +1,51 @@ +--- +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_rts_stack_resource_v1" +sidebar_current: "docs-huaweicloud-datasource-rts-stack-resource-v1" +description: |- + Provides metadata of an RTS stack resource +--- + +# Data Source: huaweicloud_rts_stack_resource_v1 + +The HuaweiCloud RTS Stack Resource data source allows access to stack resource metadata. + +## Example Usage + +```hcl +variable "stack_name" { } +variable "resource_name" { } + +data "huaweicloud_rts_stack_resource_v1" "stackresource" { + stack_name = "${var.stack_name}" + resource_name = "${var.resource_name}" + +} +``` + +## Argument Reference +The following arguments are supported: + +* `stack_name` - (Required) The unique stack name. + +* `resource_name` - (Optional) The name of a resource in the stack. + +* `physical_resource_id` - (Optional) The physical resource ID. + +* `resource_type` - (Optional) The resource type. + + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `logical_resource_id` - The logical resource ID. + +* `resource_status` - The status of the resource. + +* `resource_status_reason` - The resource operation reason. + +* `required_by` - Specifies the resource dependency. + + + diff --git a/website/docs/d/rts_stack_v1.html.md b/website/docs/d/rts_stack_v1.html.md new file mode 100644 index 0000000000..0c9be97231 --- /dev/null +++ b/website/docs/d/rts_stack_v1.html.md @@ -0,0 +1,54 @@ +--- +layout: "huaweicloud" +page_title: "HuaweiCloud: huaweicloud_rts_stack_v1" +sidebar_current: "docs-huaweicloud-datasource-rts-stack-v1" +description: |- + Get information on an HuaweiCloud RTS. +--- + +# huaweicloud_rts_stack_v1 + +The Huaweicloud `Resource Template Service` Stack data source allows access to stack outputs and other useful data including the template body. + +## Example Usage + +The following example shows how one might accept a VPC id as a variable and use this data source to obtain the data necessary to create a subnet within it. + +```hcl +variable "stack_name" { } + +data "huaweicloud_rts_stack_v1" "stacks" { + name = "${var.stack_name}" +} +``` + +## Argument Reference +The following arguments are supported: + +* `name` - (Required) The name of the stack. + +## Attributes Reference + +The following attributes are exported: + +* `capabilities` - List of stack capabilities for stack. + +* `description` - Describes the stack. + +* `disable_rollback` - Specifies whether to perform a rollback if the update fails. + +* `outputs` - A list of stack outputs. + +* `parameters` - Specifies the stack parameters. + +* `template_body` - Structure containing the template body. + +* `timeout_mins` - Specifies the timeout duration. + +* `status` - Specifies the stack status. + +* `name` - Specifies the stack name. + +* `status_reason` - Specifies the description of the stack operation. + +* `notification_topics` - List of notification topics for stack. diff --git a/website/docs/r/rts_stack_v1.html.md b/website/docs/r/rts_stack_v1.html.md new file mode 100644 index 0000000000..4cdbd393bc --- /dev/null +++ b/website/docs/r/rts_stack_v1.html.md @@ -0,0 +1,130 @@ +--- +layout: "huaweicloud" +page_title: "huaweicloud: huaweicloud_rts_stack_v1" +sidebar_current: "docs-huaweicloud-resource-rts-stack-v1" +description: |- + Provides an Huawei cloud RTS Stack resource. +--- + +# huaweicloud_rts_stack_v1_ + +Provides an Huawei Cloud Stack resource. + +## Example Usage + + ```hcl + variable "name" { } + variable "network_id" { } + variable "instance_type" { } +variable "image_id" { } + +resource "huaweicloud_rts_stack_v1" "stack" { + name = "${var.name}" + disable_rollback = true + timeout_mins=60 + parameters = { + "network_id" = "${var.network_id}" + "instance_type" = "${var.instance_type}" + "image_id" = "${var.image_id}" + } + template_body = < +## Timeouts + +`huaweicloud_rts_stack_v1` provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - (Default `30 minutes`) Used for Creating Stacks +- `update` - (Default `30 minutes`) Used for Stack modifications +- `delete` - (Default `30 minutes`) Used for destroying stacks. diff --git a/website/huaweicloud.erb b/website/huaweicloud.erb index 1a8f48c431..3263b05b3a 100644 --- a/website/huaweicloud.erb +++ b/website/huaweicloud.erb @@ -34,6 +34,12 @@ > huaweicloud_s3_bucket_object + > + huaweicloud_rts_stack_v1 + + > + huaweicloud_rts_stack_resource_v1 + @@ -238,6 +244,15 @@ + > + RTS Resources + + + <% end %>