278 lines
7.2 KiB
Go
278 lines
7.2 KiB
Go
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
|
|
}
|