Skip to content

Commit

Permalink
resource/aws_cloudfront_distribution: Migrate flatmap package interna…
Browse files Browse the repository at this point in the history
…lly for now, add test covering existing active_trusted_signers behavior

Reference: #8902
Reference: #10013
Reference: #10093

Temporarily keep the existing behavior for the only provider attribute using the legacy package so we can migrate to the standalone Terraform Plugin SDK from the github.com/hashicorp/terraform dependency in the future. Switching this attribute to not use the flatmap package will require state migration or deprecation.

Output from acceptance testing:

```
--- PASS: TestAccAWSCloudFrontDistribution_DefaultCacheBehavior_TrustedSigners (1351.69s)
```
  • Loading branch information
bflad committed Sep 25, 2019
1 parent 401984e commit 7a804af
Show file tree
Hide file tree
Showing 7 changed files with 441 additions and 1 deletion.
2 changes: 1 addition & 1 deletion aws/cloudfront_distribution_configuration_structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/flatmap"
)

// cloudFrontRoute53ZoneID defines the route 53 zone ID for CloudFront. This
Expand Down
3 changes: 3 additions & 0 deletions aws/internal/flatmap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# flatmap

This package contains legacy code from `github.com/hashicorp/terraform/flatmap@v0.12.9`. The types and functions within this package should not be used in any future implementations.
71 changes: 71 additions & 0 deletions aws/internal/flatmap/flatten.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package flatmap

import (
"fmt"
"reflect"
)

// Flatten takes a structure and turns into a flat map[string]string.
//
// Within the "thing" parameter, only primitive values are allowed. Structs are
// not supported. Therefore, it can only be slices, maps, primitives, and
// any combination of those together.
//
// See the tests for examples of what inputs are turned into.
func Flatten(thing map[string]interface{}) Map {
result := make(map[string]string)

for k, raw := range thing {
flatten(result, k, reflect.ValueOf(raw))
}

return Map(result)
}

func flatten(result map[string]string, prefix string, v reflect.Value) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}

switch v.Kind() {
case reflect.Bool:
if v.Bool() {
result[prefix] = "true"
} else {
result[prefix] = "false"
}
case reflect.Int:
result[prefix] = fmt.Sprintf("%d", v.Int())
case reflect.Map:
flattenMap(result, prefix, v)
case reflect.Slice:
flattenSlice(result, prefix, v)
case reflect.String:
result[prefix] = v.String()
default:
panic(fmt.Sprintf("Unknown: %s", v))
}
}

func flattenMap(result map[string]string, prefix string, v reflect.Value) {
for _, k := range v.MapKeys() {
if k.Kind() == reflect.Interface {
k = k.Elem()
}

if k.Kind() != reflect.String {
panic(fmt.Sprintf("%s: map key is not string: %s", prefix, k))
}

flatten(result, fmt.Sprintf("%s.%s", prefix, k.String()), v.MapIndex(k))
}
}

func flattenSlice(result map[string]string, prefix string, v reflect.Value) {
prefix = prefix + "."

result[prefix+"#"] = fmt.Sprintf("%d", v.Len())
for i := 0; i < v.Len(); i++ {
flatten(result, fmt.Sprintf("%s%d", prefix, i), v.Index(i))
}
}
88 changes: 88 additions & 0 deletions aws/internal/flatmap/flatten_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package flatmap

import (
"reflect"
"testing"
)

func TestFlatten(t *testing.T) {
cases := []struct {
Input map[string]interface{}
Output map[string]string
}{
{
Input: map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
Output: map[string]string{
"foo": "bar",
"bar": "baz",
},
},

{
Input: map[string]interface{}{
"foo": []string{
"one",
"two",
},
},
Output: map[string]string{
"foo.#": "2",
"foo.0": "one",
"foo.1": "two",
},
},

{
Input: map[string]interface{}{
"foo": []map[interface{}]interface{}{
map[interface{}]interface{}{
"name": "bar",
"port": 3000,
"enabled": true,
},
},
},
Output: map[string]string{
"foo.#": "1",
"foo.0.name": "bar",
"foo.0.port": "3000",
"foo.0.enabled": "true",
},
},

{
Input: map[string]interface{}{
"foo": []map[interface{}]interface{}{
map[interface{}]interface{}{
"name": "bar",
"ports": []string{
"1",
"2",
},
},
},
},
Output: map[string]string{
"foo.#": "1",
"foo.0.name": "bar",
"foo.0.ports.#": "2",
"foo.0.ports.0": "1",
"foo.0.ports.1": "2",
},
},
}

for _, tc := range cases {
actual := Flatten(tc.Input)
if !reflect.DeepEqual(actual, Map(tc.Output)) {
t.Fatalf(
"Input:\n\n%#v\n\nOutput:\n\n%#v\n\nExpected:\n\n%#v\n",
tc.Input,
actual,
tc.Output)
}
}
}
82 changes: 82 additions & 0 deletions aws/internal/flatmap/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package flatmap

import (
"strings"
)

// Map is a wrapper around map[string]string that provides some helpers
// above it that assume the map is in the format that flatmap expects
// (the result of Flatten).
//
// All modifying functions such as Delete are done in-place unless
// otherwise noted.
type Map map[string]string

// Contains returns true if the map contains the given key.
func (m Map) Contains(key string) bool {
for _, k := range m.Keys() {
if k == key {
return true
}
}

return false
}

// Delete deletes a key out of the map with the given prefix.
func (m Map) Delete(prefix string) {
for k := range m {
match := k == prefix
if !match {
if !strings.HasPrefix(k, prefix) {
continue
}

if k[len(prefix):len(prefix)+1] != "." {
continue
}
}

delete(m, k)
}
}

// Keys returns all of the top-level keys in this map
func (m Map) Keys() []string {
ks := make(map[string]struct{})
for k := range m {
idx := strings.Index(k, ".")
if idx == -1 {
idx = len(k)
}

ks[k[:idx]] = struct{}{}
}

result := make([]string, 0, len(ks))
for k := range ks {
result = append(result, k)
}

return result
}

// Merge merges the contents of the other Map into this one.
//
// This merge is smarter than a simple map iteration because it
// will fully replace arrays and other complex structures that
// are present in this map with the other map's. For example, if
// this map has a 3 element "foo" list, and m2 has a 2 element "foo"
// list, then the result will be that m has a 2 element "foo"
// list.
func (m Map) Merge(m2 Map) {
for _, prefix := range m2.Keys() {
m.Delete(prefix)

for k, v := range m2 {
if strings.HasPrefix(k, prefix) {
m[k] = v
}
}
}
}
120 changes: 120 additions & 0 deletions aws/internal/flatmap/map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package flatmap

import (
"reflect"
"sort"
"testing"
)

func TestMapContains(t *testing.T) {
cases := []struct {
Input map[string]string
Key string
Result bool
}{
{
Input: map[string]string{
"foo": "bar",
"bar": "nope",
},
Key: "foo",
Result: true,
},

{
Input: map[string]string{
"foo": "bar",
"bar": "nope",
},
Key: "baz",
Result: false,
},
}

for i, tc := range cases {
actual := Map(tc.Input).Contains(tc.Key)
if actual != tc.Result {
t.Fatalf("case %d bad: %#v", i, tc.Input)
}
}
}

func TestMapDelete(t *testing.T) {
m := Flatten(map[string]interface{}{
"foo": "bar",
"routes": []map[string]string{
map[string]string{
"foo": "bar",
},
},
})

m.Delete("routes")

expected := Map(map[string]string{"foo": "bar"})
if !reflect.DeepEqual(m, expected) {
t.Fatalf("bad: %#v", m)
}
}

func TestMapKeys(t *testing.T) {
cases := []struct {
Input map[string]string
Output []string
}{
{
Input: map[string]string{
"foo": "bar",
"bar.#": "bar",
"bar.0.foo": "bar",
"bar.0.baz": "bar",
},
Output: []string{
"bar",
"foo",
},
},
}

for _, tc := range cases {
actual := Map(tc.Input).Keys()

// Sort so we have a consistent view of the output
sort.Strings(actual)

if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("input: %#v\n\nbad: %#v", tc.Input, actual)
}
}
}

func TestMapMerge(t *testing.T) {
cases := []struct {
One map[string]string
Two map[string]string
Result map[string]string
}{
{
One: map[string]string{
"foo": "bar",
"bar": "nope",
},
Two: map[string]string{
"bar": "baz",
"baz": "buz",
},
Result: map[string]string{
"foo": "bar",
"bar": "baz",
"baz": "buz",
},
},
}

for i, tc := range cases {
Map(tc.One).Merge(Map(tc.Two))
if !reflect.DeepEqual(tc.One, tc.Result) {
t.Fatalf("case %d bad: %#v", i, tc.One)
}
}
}
Loading

0 comments on commit 7a804af

Please sign in to comment.