package openstack import ( "fmt" "net/http" "reflect" "sort" "strconv" "strings" "time" "github.com/Unknwon/com" "github.com/gophercloud/gophercloud" "github.com/hashicorp/terraform/flatmap" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) // BuildRequest takes an opts struct and builds a request body for // Gophercloud to execute func BuildRequest(opts interface{}, parent string) (map[string]interface{}, error) { b, err := gophercloud.BuildRequestBody(opts, "") if err != nil { return nil, err } b = AddValueSpecs(b) return map[string]interface{}{parent: b}, nil } // CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, // sets the resource ID to the empty string instead of throwing an error. func CheckDeleted(d *schema.ResourceData, err error, msg string) error { if _, ok := err.(gophercloud.ErrDefault404); ok { d.SetId("") return nil } return fmt.Errorf("%s %s: %s", msg, d.Id(), err) } // GetRegion returns the region that was specified in the resource. If a // region was not set, the provider-level region is checked. The provider-level // region can either be set by the region argument or by OS_REGION_NAME. func GetRegion(d *schema.ResourceData, config *Config) string { if v, ok := d.GetOk("region"); ok { return v.(string) } return config.Region } // AddValueSpecs expands the 'value_specs' object and removes 'value_specs' // from the reqeust body. func AddValueSpecs(body map[string]interface{}) map[string]interface{} { if body["value_specs"] != nil { for k, v := range body["value_specs"].(map[string]interface{}) { body[k] = v } delete(body, "value_specs") } return body } // MapValueSpecs converts ResourceData into a map func MapValueSpecs(d *schema.ResourceData) map[string]string { m := make(map[string]string) for key, val := range d.Get("value_specs").(map[string]interface{}) { m[key] = val.(string) } return m } // List of headers that need to be redacted var REDACT_HEADERS = []string{"x-auth-token", "x-auth-key", "x-service-token", "x-storage-token", "x-account-meta-temp-url-key", "x-account-meta-temp-url-key-2", "x-container-meta-temp-url-key", "x-container-meta-temp-url-key-2", "set-cookie", "x-subject-token"} // RedactHeaders processes a headers object, returning a redacted list func RedactHeaders(headers http.Header) (processedHeaders []string) { for name, header := range headers { for _, v := range header { if com.IsSliceContainsStr(REDACT_HEADERS, name) { processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, "***")) } else { processedHeaders = append(processedHeaders, fmt.Sprintf("%v: %v", name, v)) } } } return } // FormatHeaders processes a headers object plus a deliminator, returning a string func FormatHeaders(headers http.Header, seperator string) string { redactedHeaders := RedactHeaders(headers) sort.Strings(redactedHeaders) return strings.Join(redactedHeaders, seperator) } func checkForRetryableError(err error) *resource.RetryError { switch errCode := err.(type) { case gophercloud.ErrDefault500: return resource.RetryableError(err) case gophercloud.ErrUnexpectedResponseCode: switch errCode.Actual { case 409, 503: return resource.RetryableError(err) default: return resource.NonRetryableError(err) } default: return resource.NonRetryableError(err) } } func suppressEquivilentTimeDiffs(k, old, new string, d *schema.ResourceData) bool { oldTime, err := time.Parse(time.RFC3339, old) if err != nil { return false } newTime, err := time.Parse(time.RFC3339, new) if err != nil { return false } return oldTime.Equal(newTime) } func validateSubnetV2IPv6Mode(v interface{}, k string) (ws []string, errors []error) { value := v.(string) if value != "slaac" && value != "dhcpv6-stateful" && value != "dhcpv6-stateless" { err := fmt.Errorf("%s must be one of slaac, dhcpv6-stateful or dhcpv6-stateless", k) errors = append(errors, err) } return } func resourceNetworkingAvailabilityZoneHintsV2(d *schema.ResourceData) []string { rawAZH := d.Get("availability_zone_hints").([]interface{}) azh := make([]string, len(rawAZH)) for i, raw := range rawAZH { azh[i] = raw.(string) } return azh } func expandVendorOptions(vendOptsRaw []interface{}) map[string]interface{} { vendorOptions := make(map[string]interface{}) for _, option := range vendOptsRaw { for optKey, optValue := range option.(map[string]interface{}) { vendorOptions[optKey] = optValue } } return vendorOptions } func networkV2ReadAttributesTags(d *schema.ResourceData, tags []string) { d.Set("all_tags", tags) allTags := d.Get("all_tags").(*schema.Set) desiredTags := d.Get("tags").(*schema.Set) actualTags := allTags.Intersection(desiredTags) if !actualTags.Equal(desiredTags) { d.Set("tags", expandToStringSlice(actualTags.List())) } } func networkV2UpdateAttributesTags(d *schema.ResourceData) (tags []string) { allTags := d.Get("all_tags").(*schema.Set) oldTagsRaw, newTagsRaw := d.GetChange("tags") oldTags, newTags := oldTagsRaw.(*schema.Set), newTagsRaw.(*schema.Set) allWithoutOld := allTags.Difference(oldTags) return expandToStringSlice(allWithoutOld.Union(newTags).List()) } func networkV2AttributesTags(d *schema.ResourceData) (tags []string) { rawTags := d.Get("tags").(*schema.Set).List() tags = make([]string, len(rawTags)) for i, raw := range rawTags { tags[i] = raw.(string) } return } func testAccCheckNetworkingV2Tags(name string, tags []string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("resource not found: %s", name) } var tagLen int64 var err error if count, ok := rs.Primary.Attributes["tags.#"]; !ok { return fmt.Errorf("resource tags not found: %s.tags", name) } else { tagLen, err = strconv.ParseInt(count, 10, 64) if err != nil { return fmt.Errorf("Failed to parse tag amount: %s", err) } } rtags := make([]string, tagLen) itags := flatmap.Expand(rs.Primary.Attributes, "tags").([]interface{}) for i, val := range itags { rtags[i] = val.(string) } sort.Strings(rtags) sort.Strings(tags) if !reflect.DeepEqual(rtags, tags) { return fmt.Errorf( "%s.tags: expected: %#v, got %#v", name, tags, rtags) } return nil } } func expandToMapStringString(v map[string]interface{}) map[string]string { m := make(map[string]string) for key, val := range v { if strVal, ok := val.(string); ok { m[key] = strVal } } return m } func expandToStringSlice(v []interface{}) []string { s := make([]string, len(v)) for i, val := range v { if strVal, ok := val.(string); ok { s[i] = strVal } } return s } // strSliceContains checks if a given string is contained in a slice // When anybody asks why Go needs generics, here you go. func strSliceContains(haystack []string, needle string) bool { for _, s := range haystack { if s == needle { return true } } return false } func sliceUnion(a, b []string) []string { var res []string for _, i := range a { if !strSliceContains(res, i) { res = append(res, i) } } for _, k := range b { if !strSliceContains(res, k) { res = append(res, k) } } return res }