helper: remove a bunch of unused packages, types and functions

With the SDK moving out into its own repository, a lot of the packages
under "helper/" are no longer needed. We still need to keep around just
enough legacy SDK to support the "test" provider and some little bits and
bobs in the backends and provisioners, but a lot of this is now just dead
code.

One of the test provider tests was depending on some validation functions
only because the schema in there was originally copied from a "real"
provider. The validation rules are not actually important for the test,
so I removed them. Otherwise, this removes only dead code.
This commit is contained in:
Martin Atkins 2020-10-01 16:36:22 -07:00
parent 1817c8ac3c
commit 6b5ca49578
59 changed files with 59 additions and 5841 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/hashicorp/terraform/terraform"
)
@ -194,19 +193,17 @@ var dataprocClusterSchema = map[string]*schema.Schema{
},
"boot_disk_size_gb": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.IntAtLeast(10),
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"boot_disk_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false),
Default: "pd-standard",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "pd-standard",
},
},
},
@ -267,19 +264,17 @@ var dataprocClusterSchema = map[string]*schema.Schema{
},
"boot_disk_size_gb": {
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.IntAtLeast(10),
Type: schema.TypeInt,
Optional: true,
Computed: true,
ForceNew: true,
},
"boot_disk_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false),
Default: "pd-standard",
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "pd-standard",
},
},
},

View File

@ -1,7 +1,9 @@
# Helper Libraries
# Legacy Helper Libraries
This folder contains helper libraries for Terraform plugins. A running
joke is that this is "Terraform standard library" for plugins. The goal
of the packages in this directory are to provide high-level helpers to
make it easier to implement the various aspects of writing a plugin for
Terraform.
The packages in this directory are all legacy code. Some of them are legacy
because they are now maintained in
[the Terraform SDK](https://github.com/hashicorp/terraform-plugin-sdk),
while others are just obsolete codepaths that we intend to migrate away
from over time.
Avoid using functions from packages under `helper/` in new projects.

View File

@ -1,28 +0,0 @@
package config
import (
"github.com/mitchellh/mapstructure"
)
func Decode(target interface{}, raws ...interface{}) (*mapstructure.Metadata, error) {
var md mapstructure.Metadata
decoderConfig := &mapstructure.DecoderConfig{
Metadata: &md,
Result: target,
WeaklyTypedInput: true,
}
decoder, err := mapstructure.NewDecoder(decoderConfig)
if err != nil {
return nil, err
}
for _, raw := range raws {
err := decoder.Decode(raw)
if err != nil {
return nil, err
}
}
return &md, nil
}

View File

@ -1,214 +0,0 @@
package config
import (
"fmt"
"strconv"
"strings"
"github.com/hashicorp/terraform/flatmap"
"github.com/hashicorp/terraform/terraform"
)
// Validator is a helper that helps you validate the configuration
// of your resource, resource provider, etc.
//
// At the most basic level, set the Required and Optional lists to be
// specifiers of keys that are required or optional. If a key shows up
// that isn't in one of these two lists, then an error is generated.
//
// The "specifiers" allowed in this is a fairly rich syntax to help
// describe the format of your configuration:
//
// * Basic keys are just strings. For example: "foo" will match the
// "foo" key.
//
// * Nested structure keys can be matched by doing
// "listener.*.foo". This will verify that there is at least one
// listener element that has the "foo" key set.
//
// * The existence of a nested structure can be checked by simply
// doing "listener.*" which will verify that there is at least
// one element in the "listener" structure. This is NOT
// validating that "listener" is an array. It is validating
// that it is a nested structure in the configuration.
//
type Validator struct {
Required []string
Optional []string
}
func (v *Validator) Validate(
c *terraform.ResourceConfig) (ws []string, es []error) {
// Flatten the configuration so it is easier to reason about
flat := flatmap.Flatten(c.Raw)
keySet := make(map[string]validatorKey)
for i, vs := range [][]string{v.Required, v.Optional} {
req := i == 0
for _, k := range vs {
vk, err := newValidatorKey(k, req)
if err != nil {
es = append(es, err)
continue
}
keySet[k] = vk
}
}
purged := make([]string, 0)
for _, kv := range keySet {
p, w, e := kv.Validate(flat)
if len(w) > 0 {
ws = append(ws, w...)
}
if len(e) > 0 {
es = append(es, e...)
}
purged = append(purged, p...)
}
// Delete all the keys we processed in order to find
// the unknown keys.
for _, p := range purged {
delete(flat, p)
}
// The rest are unknown
for k, _ := range flat {
es = append(es, fmt.Errorf("Unknown configuration: %s", k))
}
return
}
type validatorKey interface {
// Validate validates the given configuration and returns viewed keys,
// warnings, and errors.
Validate(map[string]string) ([]string, []string, []error)
}
func newValidatorKey(k string, req bool) (validatorKey, error) {
var result validatorKey
parts := strings.Split(k, ".")
if len(parts) > 1 && parts[1] == "*" {
result = &nestedValidatorKey{
Parts: parts,
Required: req,
}
} else {
result = &basicValidatorKey{
Key: k,
Required: req,
}
}
return result, nil
}
// basicValidatorKey validates keys that are basic such as "foo"
type basicValidatorKey struct {
Key string
Required bool
}
func (v *basicValidatorKey) Validate(
m map[string]string) ([]string, []string, []error) {
for k, _ := range m {
// If we have the exact key its a match
if k == v.Key {
return []string{k}, nil, nil
}
}
if !v.Required {
return nil, nil, nil
}
return nil, nil, []error{fmt.Errorf(
"Key not found: %s", v.Key)}
}
type nestedValidatorKey struct {
Parts []string
Required bool
}
func (v *nestedValidatorKey) validate(
m map[string]string,
prefix string,
offset int) ([]string, []string, []error) {
if offset >= len(v.Parts) {
// We're at the end. Look for a specific key.
v2 := &basicValidatorKey{Key: prefix, Required: v.Required}
return v2.Validate(m)
}
current := v.Parts[offset]
// If we're at offset 0, special case to start at the next one.
if offset == 0 {
return v.validate(m, current, offset+1)
}
// Determine if we're doing a "for all" or a specific key
if current != "*" {
// We're looking at a specific key, continue on.
return v.validate(m, prefix+"."+current, offset+1)
}
// We're doing a "for all", so we loop over.
countStr, ok := m[prefix+".#"]
if !ok {
if !v.Required {
// It wasn't required, so its no problem.
return nil, nil, nil
}
return nil, nil, []error{fmt.Errorf(
"Key not found: %s", prefix)}
}
count, err := strconv.ParseInt(countStr, 0, 0)
if err != nil {
// This shouldn't happen if flatmap works properly
panic("invalid flatmap array")
}
var e []error
var w []string
u := make([]string, 1, count+1)
u[0] = prefix + ".#"
for i := 0; i < int(count); i++ {
prefix := fmt.Sprintf("%s.%d", prefix, i)
// Mark that we saw this specific key
u = append(u, prefix)
// Mark all prefixes of this
for k, _ := range m {
if !strings.HasPrefix(k, prefix+".") {
continue
}
u = append(u, k)
}
// If we have more parts, then validate deeper
if offset+1 < len(v.Parts) {
u2, w2, e2 := v.validate(m, prefix, offset+1)
u = append(u, u2...)
w = append(w, w2...)
e = append(e, e2...)
}
}
return u, w, e
}
func (v *nestedValidatorKey) Validate(
m map[string]string) ([]string, []string, []error) {
return v.validate(m, "", 0)
}

View File

@ -1,192 +0,0 @@
package config
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestValidator(t *testing.T) {
v := &Validator{
Required: []string{"foo"},
Optional: []string{"bar"},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"foo": "bar",
})
testValid(v, c)
// Valid + optional
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"bar": "baz",
})
testValid(v, c)
// Missing required
c = testConfig(t, map[string]interface{}{
"bar": "baz",
})
testInvalid(v, c)
// Unknown key
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"what": "what",
})
testInvalid(v, c)
}
func TestValidator_array(t *testing.T) {
v := &Validator{
Required: []string{
"foo",
"nested.*",
},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": []interface{}{"foo", "bar"},
})
testValid(v, c)
// Not a nested structure
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": "baa",
})
testInvalid(v, c)
}
func TestValidator_complex(t *testing.T) {
v := &Validator{
Required: []string{
"foo",
"nested.*",
},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": []interface{}{
map[string]interface{}{"foo": "bar"},
},
})
testValid(v, c)
// Not a nested structure
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": "baa",
})
testInvalid(v, c)
}
func TestValidator_complexNested(t *testing.T) {
v := &Validator{
Required: []string{
"ingress.*",
"ingress.*.from_port",
},
Optional: []string{
"ingress.*.cidr_blocks.*",
},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"ingress": []interface{}{
map[string]interface{}{
"from_port": "80",
},
},
})
testValid(v, c)
// Valid
c = testConfig(t, map[string]interface{}{
"ingress": []interface{}{
map[string]interface{}{
"from_port": "80",
"cidr_blocks": []interface{}{"foo"},
},
},
})
testValid(v, c)
}
func TestValidator_complexDeepRequired(t *testing.T) {
v := &Validator{
Required: []string{
"foo",
"nested.*.foo",
},
}
var c *terraform.ResourceConfig
// Valid
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": []interface{}{
map[string]interface{}{"foo": "bar"},
},
})
testValid(v, c)
// Valid
c = testConfig(t, map[string]interface{}{
"foo": "bar",
})
testInvalid(v, c)
// Not a nested structure
c = testConfig(t, map[string]interface{}{
"foo": "bar",
"nested": "baa",
})
testInvalid(v, c)
}
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
return terraform.NewResourceConfigRaw(c)
}
func testInvalid(v *Validator, c *terraform.ResourceConfig) {
ws, es := v.Validate(c)
if len(ws) > 0 {
panic(fmt.Sprintf("bad: %#v", ws))
}
if len(es) == 0 {
panic(fmt.Sprintf("bad: %#v", es))
}
}
func testValid(v *Validator, c *terraform.ResourceConfig) {
ws, es := v.Validate(c)
if len(ws) > 0 {
panic(fmt.Sprintf("bad: %#v", ws))
}
if len(es) > 0 {
estrs := make([]string, len(es))
for i, e := range es {
estrs[i] = e.Error()
}
panic(fmt.Sprintf("bad: %#v", estrs))
}
}

View File

@ -1,72 +0,0 @@
package customdiff
import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/schema"
)
// All returns a CustomizeDiffFunc that runs all of the given
// CustomizeDiffFuncs and returns all of the errors produced.
//
// If one function produces an error, functions after it are still run.
// If this is not desirable, use function Sequence instead.
//
// If multiple functions returns errors, the result is a multierror.
//
// For example:
//
// &schema.Resource{
// // ...
// CustomizeDiff: customdiff.All(
// customdiff.ValidateChange("size", func (old, new, meta interface{}) error {
// // If we are increasing "size" then the new value must be
// // a multiple of the old value.
// if new.(int) <= old.(int) {
// return nil
// }
// if (new.(int) % old.(int)) != 0 {
// return fmt.Errorf("new size value must be an integer multiple of old value %d", old.(int))
// }
// return nil
// }),
// customdiff.ForceNewIfChange("size", func (old, new, meta interface{}) bool {
// // "size" can only increase in-place, so we must create a new resource
// // if it is decreased.
// return new.(int) < old.(int)
// }),
// customdiff.ComputedIf("version_id", func (d *schema.ResourceDiff, meta interface{}) bool {
// // Any change to "content" causes a new "version_id" to be allocated.
// return d.HasChange("content")
// }),
// ),
// }
//
func All(funcs ...schema.CustomizeDiffFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
var err error
for _, f := range funcs {
thisErr := f(d, meta)
if thisErr != nil {
err = multierror.Append(err, thisErr)
}
}
return err
}
}
// Sequence returns a CustomizeDiffFunc that runs all of the given
// CustomizeDiffFuncs in sequence, stopping at the first one that returns
// an error and returning that error.
//
// If all functions succeed, the combined function also succeeds.
func Sequence(funcs ...schema.CustomizeDiffFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
for _, f := range funcs {
err := f(d, meta)
if err != nil {
return err
}
}
return nil
}
}

View File

@ -1,110 +0,0 @@
package customdiff
import (
"errors"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestAll(t *testing.T) {
var aCalled, bCalled, cCalled bool
provider := testProvider(
map[string]*schema.Schema{},
All(
func(d *schema.ResourceDiff, meta interface{}) error {
aCalled = true
return errors.New("A bad")
},
func(d *schema.ResourceDiff, meta interface{}) error {
bCalled = true
return nil
},
func(d *schema.ResourceDiff, meta interface{}) error {
cCalled = true
return errors.New("C bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatal("Diff succeeded; want error")
}
if s, sub := err.Error(), "* A bad"; !strings.Contains(s, sub) {
t.Errorf("Missing substring %q in error message %q", sub, s)
}
if s, sub := err.Error(), "* C bad"; !strings.Contains(s, sub) {
t.Errorf("Missing substring %q in error message %q", sub, s)
}
if !aCalled {
t.Error("customize callback A was not called")
}
if !bCalled {
t.Error("customize callback B was not called")
}
if !cCalled {
t.Error("customize callback C was not called")
}
}
func TestSequence(t *testing.T) {
var aCalled, bCalled, cCalled bool
provider := testProvider(
map[string]*schema.Schema{},
Sequence(
func(d *schema.ResourceDiff, meta interface{}) error {
aCalled = true
return nil
},
func(d *schema.ResourceDiff, meta interface{}) error {
bCalled = true
return errors.New("B bad")
},
func(d *schema.ResourceDiff, meta interface{}) error {
cCalled = true
return errors.New("C bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatal("Diff succeeded; want error")
}
if got, want := err.Error(), "B bad"; got != want {
t.Errorf("Wrong error message %q; want %q", got, want)
}
if !aCalled {
t.Error("customize callback A was not called")
}
if !bCalled {
t.Error("customize callback B was not called")
}
if cCalled {
t.Error("customize callback C was called (should not have been)")
}
}

View File

@ -1,16 +0,0 @@
package customdiff
import (
"github.com/hashicorp/terraform/helper/schema"
)
// ComputedIf returns a CustomizeDiffFunc that sets the given key's new value
// as computed if the given condition function returns true.
func ComputedIf(key string, f ResourceConditionFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
if f(d, meta) {
d.SetNewComputed(key)
}
return nil
}
}

View File

@ -1,126 +0,0 @@
package customdiff
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestComputedIf(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
"comp": {
Type: schema.TypeString,
Computed: true,
},
},
ComputedIf("comp", func(d *schema.ResourceDiff, meta interface{}) bool {
// When we set "ForceNew", our CustomizeDiff function is actually
// called a second time to construct the "create" portion of
// the replace diff. On the second call, the old value is masked
// as "" to suggest that the object is being created rather than
// updated.
condCalls++
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return true
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
"comp": "old",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if !diff.Attributes["comp"].NewComputed {
t.Error("Attribute 'comp' is not marked as NewComputed")
}
})
t.Run("false", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
"comp": {
Type: schema.TypeString,
Computed: true,
},
},
ComputedIf("comp", func(d *schema.ResourceDiff, meta interface{}) bool {
condCalls++
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return false
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
"comp": "old",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if diff.Attributes["comp"] != nil && diff.Attributes["comp"].NewComputed {
t.Error("Attribute 'foo' is marked as NewComputed, but should not be")
}
})
}

View File

@ -1,60 +0,0 @@
package customdiff
import (
"github.com/hashicorp/terraform/helper/schema"
)
// ResourceConditionFunc is a function type that makes a boolean decision based
// on an entire resource diff.
type ResourceConditionFunc func(d *schema.ResourceDiff, meta interface{}) bool
// ValueChangeConditionFunc is a function type that makes a boolean decision
// by comparing two values.
type ValueChangeConditionFunc func(old, new, meta interface{}) bool
// ValueConditionFunc is a function type that makes a boolean decision based
// on a given value.
type ValueConditionFunc func(value, meta interface{}) bool
// If returns a CustomizeDiffFunc that calls the given condition
// function and then calls the given CustomizeDiffFunc only if the condition
// function returns true.
//
// This can be used to include conditional customizations when composing
// customizations using All and Sequence, but should generally be used only in
// simple scenarios. Prefer directly writing a CustomizeDiffFunc containing
// a conditional branch if the given CustomizeDiffFunc is already a
// locally-defined function, since this avoids obscuring the control flow.
func If(cond ResourceConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
if cond(d, meta) {
return f(d, meta)
}
return nil
}
}
// IfValueChange returns a CustomizeDiffFunc that calls the given condition
// function with the old and new values of the given key and then calls the
// given CustomizeDiffFunc only if the condition function returns true.
func IfValueChange(key string, cond ValueChangeConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
old, new := d.GetChange(key)
if cond(old, new, meta) {
return f(d, meta)
}
return nil
}
}
// IfValue returns a CustomizeDiffFunc that calls the given condition
// function with the new values of the given key and then calls the
// given CustomizeDiffFunc only if the condition function returns true.
func IfValue(key string, cond ValueConditionFunc, f schema.CustomizeDiffFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
if cond(d.Get(key), meta) {
return f(d, meta)
}
return nil
}
}

View File

@ -1,348 +0,0 @@
package customdiff
import (
"errors"
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestIf(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalled, customCalled bool
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
If(
func(d *schema.ResourceDiff, meta interface{}) bool {
condCalled = true
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return true
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatal("Diff succeeded; want error")
}
if got, want := err.Error(), "bad"; got != want {
t.Fatalf("wrong error message %q; want %q", got, want)
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q; want %q", got, want)
}
}
if !customCalled {
t.Error("customize callback was not called")
}
})
t.Run("false", func(t *testing.T) {
var condCalled, customCalled bool
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
If(
func(d *schema.ResourceDiff, meta interface{}) bool {
condCalled = true
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return false
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff error %q; want success", err.Error())
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q; want %q", got, want)
}
}
if customCalled {
t.Error("customize callback was called (should not have been)")
}
})
}
func TestIfValueChange(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalled, customCalled bool
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
IfValueChange(
"foo",
func(old, new, meta interface{}) bool {
condCalled = true
gotOld = old.(string)
gotNew = new.(string)
return true
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatal("Diff succeeded; want error")
}
if got, want := err.Error(), "bad"; got != want {
t.Fatalf("wrong error message %q; want %q", got, want)
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q; want %q", got, want)
}
}
if !customCalled {
t.Error("customize callback was not called")
}
})
t.Run("false", func(t *testing.T) {
var condCalled, customCalled bool
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
IfValueChange(
"foo",
func(old, new, meta interface{}) bool {
condCalled = true
gotOld = old.(string)
gotNew = new.(string)
return false
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff error %q; want success", err.Error())
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q; want %q", got, want)
}
}
if customCalled {
t.Error("customize callback was called (should not have been)")
}
})
}
func TestIfValue(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalled, customCalled bool
var gotValue string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
IfValue(
"foo",
func(value, meta interface{}) bool {
condCalled = true
gotValue = value.(string)
return true
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatal("Diff succeeded; want error")
}
if got, want := err.Error(), "bad"; got != want {
t.Fatalf("wrong error message %q; want %q", got, want)
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotValue, "baz"; got != want {
t.Errorf("wrong value %q; want %q", got, want)
}
}
if !customCalled {
t.Error("customize callback was not called")
}
})
t.Run("false", func(t *testing.T) {
var condCalled, customCalled bool
var gotValue string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
IfValue(
"foo",
func(value, meta interface{}) bool {
condCalled = true
gotValue = value.(string)
return false
},
func(d *schema.ResourceDiff, meta interface{}) error {
customCalled = true
return errors.New("bad")
},
),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff error %q; want success", err.Error())
}
if !condCalled {
t.Error("condition callback was not called")
} else {
if got, want := gotValue, "baz"; got != want {
t.Errorf("wrong value %q; want %q", got, want)
}
}
if customCalled {
t.Error("customize callback was called (should not have been)")
}
})
}

View File

@ -1,11 +0,0 @@
// Package customdiff provides a set of reusable and composable functions
// to enable more "declarative" use of the CustomizeDiff mechanism available
// for resources in package helper/schema.
//
// The intent of these helpers is to make the intent of a set of diff
// customizations easier to see, rather than lost in a sea of Go function
// boilerplate. They should _not_ be used in situations where they _obscure_
// intent, e.g. by over-using the composition functions where a single
// function containing normal Go control flow statements would be more
// straightforward.
package customdiff

View File

@ -1,40 +0,0 @@
package customdiff
import (
"github.com/hashicorp/terraform/helper/schema"
)
// ForceNewIf returns a CustomizeDiffFunc that flags the given key as
// requiring a new resource if the given condition function returns true.
//
// The return value of the condition function is ignored if the old and new
// values of the field compare equal, since no attribute diff is generated in
// that case.
func ForceNewIf(key string, f ResourceConditionFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
if f(d, meta) {
d.ForceNew(key)
}
return nil
}
}
// ForceNewIfChange returns a CustomizeDiffFunc that flags the given key as
// requiring a new resource if the given condition function returns true.
//
// The return value of the condition function is ignored if the old and new
// values compare equal, since no attribute diff is generated in that case.
//
// This function is similar to ForceNewIf but provides the condition function
// only the old and new values of the given key, which leads to more compact
// and explicit code in the common case where the decision can be made with
// only the specific field value.
func ForceNewIfChange(key string, f ValueChangeConditionFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
old, new := d.GetChange(key)
if f(old, new, meta) {
d.ForceNew(key)
}
return nil
}
}

View File

@ -1,249 +0,0 @@
package customdiff
import (
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestForceNewIf(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalls int
var gotOld1, gotNew1, gotOld2, gotNew2 string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIf("foo", func(d *schema.ResourceDiff, meta interface{}) bool {
// When we set "ForceNew", our CustomizeDiff function is actually
// called a second time to construct the "create" portion of
// the replace diff. On the second call, the old value is masked
// as "" to suggest that the object is being created rather than
// updated.
condCalls++
old, new := d.GetChange("foo")
switch condCalls {
case 1:
gotOld1 = old.(string)
gotNew1 = new.(string)
case 2:
gotOld2 = old.(string)
gotNew2 = new.(string)
}
return true
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 2 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 2)
} else {
if got, want := gotOld1, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew1, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
if got, want := gotOld2, ""; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew2, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if !diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is not marked as RequiresNew")
}
})
t.Run("false", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIf("foo", func(d *schema.ResourceDiff, meta interface{}) bool {
condCalls++
old, new := d.GetChange("foo")
gotOld = old.(string)
gotNew = new.(string)
return false
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is marked as RequiresNew, but should not be")
}
})
}
func TestForceNewIfChange(t *testing.T) {
t.Run("true", func(t *testing.T) {
var condCalls int
var gotOld1, gotNew1, gotOld2, gotNew2 string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIfChange("foo", func(old, new, meta interface{}) bool {
// When we set "ForceNew", our CustomizeDiff function is actually
// called a second time to construct the "create" portion of
// the replace diff. On the second call, the old value is masked
// as "" to suggest that the object is being created rather than
// updated.
condCalls++
switch condCalls {
case 1:
gotOld1 = old.(string)
gotNew1 = new.(string)
case 2:
gotOld2 = old.(string)
gotNew2 = new.(string)
}
return true
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 2 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 2)
} else {
if got, want := gotOld1, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew1, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
if got, want := gotOld2, ""; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew2, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if !diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is not marked as RequiresNew")
}
})
t.Run("false", func(t *testing.T) {
var condCalls int
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ForceNewIfChange("foo", func(old, new, meta interface{}) bool {
condCalls++
gotOld = old.(string)
gotNew = new.(string)
return false
}),
)
diff, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err != nil {
t.Fatalf("Diff failed with error: %s", err)
}
if condCalls != 1 {
t.Fatalf("Wrong number of conditional callback calls %d; want %d", condCalls, 1)
} else {
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q on first call; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q on first call; want %q", got, want)
}
}
if diff.Attributes["foo"].RequiresNew {
t.Error("Attribute 'foo' is marked as RequiresNew, but should not be")
}
})
}

View File

@ -1,38 +0,0 @@
package customdiff
import (
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
func testProvider(s map[string]*schema.Schema, cd schema.CustomizeDiffFunc) terraform.ResourceProvider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"test": {
Schema: s,
CustomizeDiff: cd,
},
},
}
}
func testDiff(provider terraform.ResourceProvider, old, new map[string]string) (*terraform.InstanceDiff, error) {
newI := make(map[string]interface{}, len(new))
for k, v := range new {
newI[k] = v
}
return provider.Diff(
&terraform.InstanceInfo{
Id: "test",
Type: "test",
ModulePath: []string{},
},
&terraform.InstanceState{
Attributes: old,
},
&terraform.ResourceConfig{
Config: newI,
},
)
}

View File

@ -1,38 +0,0 @@
package customdiff
import (
"github.com/hashicorp/terraform/helper/schema"
)
// ValueChangeValidationFunc is a function type that validates the difference
// (or lack thereof) between two values, returning an error if the change
// is invalid.
type ValueChangeValidationFunc func(old, new, meta interface{}) error
// ValueValidationFunc is a function type that validates a particular value,
// returning an error if the value is invalid.
type ValueValidationFunc func(value, meta interface{}) error
// ValidateChange returns a CustomizeDiffFunc that applies the given validation
// function to the change for the given key, returning any error produced.
func ValidateChange(key string, f ValueChangeValidationFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
old, new := d.GetChange(key)
return f(old, new, meta)
}
}
// ValidateValue returns a CustomizeDiffFunc that applies the given validation
// function to value of the given key, returning any error produced.
//
// This should generally not be used since it is functionally equivalent to
// a validation function applied directly to the schema attribute in question,
// but is provided for situations where composing multiple CustomizeDiffFuncs
// together makes intent clearer than spreading that validation across the
// schema.
func ValidateValue(key string, f ValueValidationFunc) schema.CustomizeDiffFunc {
return func(d *schema.ResourceDiff, meta interface{}) error {
val := d.Get(key)
return f(val, meta)
}
}

View File

@ -1,98 +0,0 @@
package customdiff
import (
"errors"
"testing"
"github.com/hashicorp/terraform/helper/schema"
)
func TestValidateChange(t *testing.T) {
var called bool
var gotOld, gotNew string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ValidateChange("foo", func(old, new, meta interface{}) error {
called = true
gotOld = old.(string)
gotNew = new.(string)
return errors.New("bad")
}),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatalf("Diff succeeded; want error")
}
if got, want := err.Error(), "bad"; got != want {
t.Fatalf("wrong error message %q; want %q", got, want)
}
if !called {
t.Fatal("ValidateChange callback was not called")
}
if got, want := gotOld, "bar"; got != want {
t.Errorf("wrong old value %q; want %q", got, want)
}
if got, want := gotNew, "baz"; got != want {
t.Errorf("wrong new value %q; want %q", got, want)
}
}
func TestValidateValue(t *testing.T) {
var called bool
var gotValue string
provider := testProvider(
map[string]*schema.Schema{
"foo": {
Type: schema.TypeString,
Optional: true,
},
},
ValidateValue("foo", func(value, meta interface{}) error {
called = true
gotValue = value.(string)
return errors.New("bad")
}),
)
_, err := testDiff(
provider,
map[string]string{
"foo": "bar",
},
map[string]string{
"foo": "baz",
},
)
if err == nil {
t.Fatalf("Diff succeeded; want error")
}
if got, want := err.Error(), "bad"; got != want {
t.Fatalf("wrong error message %q; want %q", got, want)
}
if !called {
t.Fatal("ValidateValue callback was not called")
}
if got, want := gotValue, "baz"; got != want {
t.Errorf("wrong value %q; want %q", got, want)
}
}

View File

@ -1,40 +0,0 @@
package encryption
import (
"encoding/base64"
"fmt"
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/pgpkeys"
)
// RetrieveGPGKey returns the PGP key specified as the pgpKey parameter, or queries
// the public key from the keybase service if the parameter is a keybase username
// prefixed with the phrase "keybase:"
func RetrieveGPGKey(pgpKey string) (string, error) {
const keybasePrefix = "keybase:"
encryptionKey := pgpKey
if strings.HasPrefix(pgpKey, keybasePrefix) {
publicKeys, err := pgpkeys.FetchKeybasePubkeys([]string{pgpKey})
if err != nil {
return "", errwrap.Wrapf(fmt.Sprintf("Error retrieving Public Key for %s: {{err}}", pgpKey), err)
}
encryptionKey = publicKeys[pgpKey]
}
return encryptionKey, nil
}
// EncryptValue encrypts the given value with the given encryption key. Description
// should be set such that errors return a meaningful user-facing response.
func EncryptValue(encryptionKey, value, description string) (string, string, error) {
fingerprints, encryptedValue, err :=
pgpkeys.EncryptShares([][]byte{[]byte(value)}, []string{encryptionKey})
if err != nil {
return "", "", errwrap.Wrapf(fmt.Sprintf("Error encrypting %s: {{err}}", description), err)
}
return fingerprints[0], base64.StdEncoding.EncodeToString(encryptedValue[0]), nil
}

View File

@ -1,41 +0,0 @@
package hilmapstructure
import (
"fmt"
"reflect"
"github.com/mitchellh/mapstructure"
)
var hilMapstructureDecodeHookEmptySlice []interface{}
var hilMapstructureDecodeHookStringSlice []string
var hilMapstructureDecodeHookEmptyMap map[string]interface{}
// WeakDecode behaves in the same way as mapstructure.WeakDecode but has a
// DecodeHook which defeats the backward compatibility mode of mapstructure
// which WeakDecodes []interface{}{} into an empty map[string]interface{}. This
// allows us to use WeakDecode (desirable), but not fail on empty lists.
func WeakDecode(m interface{}, rawVal interface{}) error {
config := &mapstructure.DecoderConfig{
DecodeHook: func(source reflect.Type, target reflect.Type, val interface{}) (interface{}, error) {
sliceType := reflect.TypeOf(hilMapstructureDecodeHookEmptySlice)
stringSliceType := reflect.TypeOf(hilMapstructureDecodeHookStringSlice)
mapType := reflect.TypeOf(hilMapstructureDecodeHookEmptyMap)
if (source == sliceType || source == stringSliceType) && target == mapType {
return nil, fmt.Errorf("Cannot convert a []interface{} into a map[string]interface{}")
}
return val, nil
},
WeaklyTypedInput: true,
Result: rawVal,
}
decoder, err := mapstructure.NewDecoder(config)
if err != nil {
return err
}
return decoder.Decode(m)
}

View File

@ -1,51 +0,0 @@
package mutexkv
import (
"log"
"sync"
)
// MutexKV is a simple key/value store for arbitrary mutexes. It can be used to
// serialize changes across arbitrary collaborators that share knowledge of the
// keys they must serialize on.
//
// The initial use case is to let aws_security_group_rule resources serialize
// their access to individual security groups based on SG ID.
type MutexKV struct {
lock sync.Mutex
store map[string]*sync.Mutex
}
// Locks the mutex for the given key. Caller is responsible for calling Unlock
// for the same key
func (m *MutexKV) Lock(key string) {
log.Printf("[DEBUG] Locking %q", key)
m.get(key).Lock()
log.Printf("[DEBUG] Locked %q", key)
}
// Unlock the mutex for the given key. Caller must have called Lock for the same key first
func (m *MutexKV) Unlock(key string) {
log.Printf("[DEBUG] Unlocking %q", key)
m.get(key).Unlock()
log.Printf("[DEBUG] Unlocked %q", key)
}
// Returns a mutex for the given key, no guarantee of its lock status
func (m *MutexKV) get(key string) *sync.Mutex {
m.lock.Lock()
defer m.lock.Unlock()
mutex, ok := m.store[key]
if !ok {
mutex = &sync.Mutex{}
m.store[key] = mutex
}
return mutex
}
// Returns a properly initalized MutexKV
func NewMutexKV() *MutexKV {
return &MutexKV{
store: make(map[string]*sync.Mutex),
}
}

View File

@ -1,67 +0,0 @@
package mutexkv
import (
"testing"
"time"
)
func TestMutexKVLock(t *testing.T) {
mkv := NewMutexKV()
mkv.Lock("foo")
doneCh := make(chan struct{})
go func() {
mkv.Lock("foo")
close(doneCh)
}()
select {
case <-doneCh:
t.Fatal("Second lock was able to be taken. This shouldn't happen.")
case <-time.After(50 * time.Millisecond):
// pass
}
}
func TestMutexKVUnlock(t *testing.T) {
mkv := NewMutexKV()
mkv.Lock("foo")
mkv.Unlock("foo")
doneCh := make(chan struct{})
go func() {
mkv.Lock("foo")
close(doneCh)
}()
select {
case <-doneCh:
// pass
case <-time.After(50 * time.Millisecond):
t.Fatal("Second lock blocked after unlock. This shouldn't happen.")
}
}
func TestMutexKVDifferentKeys(t *testing.T) {
mkv := NewMutexKV()
mkv.Lock("foo")
doneCh := make(chan struct{})
go func() {
mkv.Lock("bar")
close(doneCh)
}()
select {
case <-doneCh:
// pass
case <-time.After(50 * time.Millisecond):
t.Fatal("Second lock on a different key blocked. This shouldn't happen.")
}
}

View File

@ -1,140 +0,0 @@
package resource
import (
"fmt"
"sort"
"github.com/hashicorp/terraform/terraform"
)
// Map is a map of resources that are supported, and provides helpers for
// more easily implementing a ResourceProvider.
type Map struct {
Mapping map[string]Resource
}
func (m *Map) Validate(
t string, c *terraform.ResourceConfig) ([]string, []error) {
r, ok := m.Mapping[t]
if !ok {
return nil, []error{fmt.Errorf("Unknown resource type: %s", t)}
}
// If there is no validator set, then it is valid
if r.ConfigValidator == nil {
return nil, nil
}
return r.ConfigValidator.Validate(c)
}
// Apply performs a create or update depending on the diff, and calls
// the proper function on the matching Resource.
func (m *Map) Apply(
info *terraform.InstanceInfo,
s *terraform.InstanceState,
d *terraform.InstanceDiff,
meta interface{}) (*terraform.InstanceState, error) {
r, ok := m.Mapping[info.Type]
if !ok {
return nil, fmt.Errorf("Unknown resource type: %s", info.Type)
}
if d.Destroy || d.RequiresNew() {
if s.ID != "" {
// Destroy the resource if it is created
err := r.Destroy(s, meta)
if err != nil {
return s, err
}
s.ID = ""
}
// If we're only destroying, and not creating, then return now.
// Otherwise, we continue so that we can create a new resource.
if !d.RequiresNew() {
return nil, nil
}
}
var result *terraform.InstanceState
var err error
if s.ID == "" {
result, err = r.Create(s, d, meta)
} else {
if r.Update == nil {
return s, fmt.Errorf(
"Resource type '%s' doesn't support update",
info.Type)
}
result, err = r.Update(s, d, meta)
}
if result != nil {
if result.Attributes == nil {
result.Attributes = make(map[string]string)
}
result.Attributes["id"] = result.ID
}
return result, err
}
// Diff performs a diff on the proper resource type.
func (m *Map) Diff(
info *terraform.InstanceInfo,
s *terraform.InstanceState,
c *terraform.ResourceConfig,
meta interface{}) (*terraform.InstanceDiff, error) {
r, ok := m.Mapping[info.Type]
if !ok {
return nil, fmt.Errorf("Unknown resource type: %s", info.Type)
}
return r.Diff(s, c, meta)
}
// Refresh performs a Refresh on the proper resource type.
//
// Refresh on the Resource won't be called if the state represents a
// non-created resource (ID is blank).
//
// An error is returned if the resource isn't registered.
func (m *Map) Refresh(
info *terraform.InstanceInfo,
s *terraform.InstanceState,
meta interface{}) (*terraform.InstanceState, error) {
// If the resource isn't created, don't refresh.
if s.ID == "" {
return s, nil
}
r, ok := m.Mapping[info.Type]
if !ok {
return nil, fmt.Errorf("Unknown resource type: %s", info.Type)
}
return r.Refresh(s, meta)
}
// Resources returns all the resources that are supported by this
// resource map and can be used to satisfy the Resources method of
// a ResourceProvider.
func (m *Map) Resources() []terraform.ResourceType {
ks := make([]string, 0, len(m.Mapping))
for k, _ := range m.Mapping {
ks = append(ks, k)
}
sort.Strings(ks)
rs := make([]terraform.ResourceType, 0, len(m.Mapping))
for _, k := range ks {
rs = append(rs, terraform.ResourceType{
Name: k,
})
}
return rs
}

View File

@ -1,73 +0,0 @@
package resource
import (
"reflect"
"testing"
"github.com/hashicorp/terraform/helper/config"
"github.com/hashicorp/terraform/terraform"
)
func TestMapResources(t *testing.T) {
m := &Map{
Mapping: map[string]Resource{
"aws_elb": Resource{},
"aws_instance": Resource{},
},
}
rts := m.Resources()
expected := []terraform.ResourceType{
terraform.ResourceType{
Name: "aws_elb",
},
terraform.ResourceType{
Name: "aws_instance",
},
}
if !reflect.DeepEqual(rts, expected) {
t.Fatalf("bad: %#v", rts)
}
}
func TestMapValidate(t *testing.T) {
m := &Map{
Mapping: map[string]Resource{
"aws_elb": Resource{
ConfigValidator: &config.Validator{
Required: []string{"foo"},
},
},
},
}
var c *terraform.ResourceConfig
var ws []string
var es []error
// Valid
c = testConfigForMap(t, map[string]interface{}{"foo": "bar"})
ws, es = m.Validate("aws_elb", c)
if len(ws) > 0 {
t.Fatalf("bad: %#v", ws)
}
if len(es) > 0 {
t.Fatalf("bad: %#v", es)
}
// Invalid
c = testConfigForMap(t, map[string]interface{}{})
ws, es = m.Validate("aws_elb", c)
if len(ws) > 0 {
t.Fatalf("bad: %#v", ws)
}
if len(es) == 0 {
t.Fatalf("bad: %#v", es)
}
}
func testConfigForMap(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
return terraform.NewResourceConfigRaw(c)
}

View File

@ -1,49 +0,0 @@
package resource
import (
"github.com/hashicorp/terraform/helper/config"
"github.com/hashicorp/terraform/terraform"
)
type Resource struct {
ConfigValidator *config.Validator
Create CreateFunc
Destroy DestroyFunc
Diff DiffFunc
Refresh RefreshFunc
Update UpdateFunc
}
// CreateFunc is a function that creates a resource that didn't previously
// exist.
type CreateFunc func(
*terraform.InstanceState,
*terraform.InstanceDiff,
interface{}) (*terraform.InstanceState, error)
// DestroyFunc is a function that destroys a resource that previously
// exists using the state.
type DestroyFunc func(
*terraform.InstanceState,
interface{}) error
// DiffFunc is a function that performs a diff of a resource.
type DiffFunc func(
*terraform.InstanceState,
*terraform.ResourceConfig,
interface{}) (*terraform.InstanceDiff, error)
// RefreshFunc is a function that performs a refresh of a specific type
// of resource.
type RefreshFunc func(
*terraform.InstanceState,
interface{}) (*terraform.InstanceState, error)
// UpdateFunc is a function that is called to update a resource that
// previously existed. The difference between this and CreateFunc is that
// the diff is guaranteed to only contain attributes that don't require
// a new resource.
type UpdateFunc func(
*terraform.InstanceState,
*terraform.InstanceDiff,
interface{}) (*terraform.InstanceState, error)

View File

@ -3556,14 +3556,14 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
"Conflicting attributes cannot be required": {
map[string]*Schema{
"blacklist": &Schema{
"a": &Schema{
Type: TypeBool,
Required: true,
},
"whitelist": &Schema{
"b": &Schema{
Type: TypeBool,
Optional: true,
ConflictsWith: []string{"blacklist"},
ConflictsWith: []string{"a"},
},
},
true,
@ -3571,10 +3571,10 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
"Attribute with conflicts cannot be required": {
map[string]*Schema{
"whitelist": &Schema{
"b": &Schema{
Type: TypeBool,
Required: true,
ConflictsWith: []string{"blacklist"},
ConflictsWith: []string{"a"},
},
},
true,
@ -3582,14 +3582,14 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
"ConflictsWith cannot be used w/ ComputedWhen": {
map[string]*Schema{
"blacklist": &Schema{
"a": &Schema{
Type: TypeBool,
ComputedWhen: []string{"foor"},
},
"whitelist": &Schema{
"b": &Schema{
Type: TypeBool,
Required: true,
ConflictsWith: []string{"blacklist"},
ConflictsWith: []string{"a"},
},
},
true,
@ -4811,44 +4811,44 @@ func TestSchemaMap_Validate(t *testing.T) {
"Conflicting attributes generate error": {
Schema: map[string]*Schema{
"whitelist": &Schema{
"b": &Schema{
Type: TypeString,
Optional: true,
},
"blacklist": &Schema{
"a": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"whitelist"},
ConflictsWith: []string{"b"},
},
},
Config: map[string]interface{}{
"whitelist": "white-val",
"blacklist": "black-val",
"b": "b-val",
"a": "a-val",
},
Err: true,
Errors: []error{
fmt.Errorf("\"blacklist\": conflicts with whitelist"),
fmt.Errorf("\"a\": conflicts with b"),
},
},
"Conflicting attributes okay when unknown 1": {
Schema: map[string]*Schema{
"whitelist": &Schema{
"b": &Schema{
Type: TypeString,
Optional: true,
},
"blacklist": &Schema{
"a": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"whitelist"},
ConflictsWith: []string{"b"},
},
},
Config: map[string]interface{}{
"whitelist": "white-val",
"blacklist": hcl2shim.UnknownVariableValue,
"b": "b-val",
"a": hcl2shim.UnknownVariableValue,
},
Err: false,
@ -4856,20 +4856,20 @@ func TestSchemaMap_Validate(t *testing.T) {
"Conflicting attributes okay when unknown 2": {
Schema: map[string]*Schema{
"whitelist": &Schema{
"b": &Schema{
Type: TypeString,
Optional: true,
},
"blacklist": &Schema{
"a": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"whitelist"},
ConflictsWith: []string{"b"},
},
},
Config: map[string]interface{}{
"whitelist": hcl2shim.UnknownVariableValue,
"blacklist": "black-val",
"b": hcl2shim.UnknownVariableValue,
"a": "a-val",
},
Err: false,
@ -4877,33 +4877,33 @@ func TestSchemaMap_Validate(t *testing.T) {
"Conflicting attributes generate error even if one is unknown": {
Schema: map[string]*Schema{
"whitelist": &Schema{
"b": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"blacklist", "greenlist"},
ConflictsWith: []string{"a", "c"},
},
"blacklist": &Schema{
"a": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"whitelist", "greenlist"},
ConflictsWith: []string{"b", "c"},
},
"greenlist": &Schema{
"c": &Schema{
Type: TypeString,
Optional: true,
ConflictsWith: []string{"whitelist", "blacklist"},
ConflictsWith: []string{"b", "a"},
},
},
Config: map[string]interface{}{
"whitelist": hcl2shim.UnknownVariableValue,
"blacklist": "black-val",
"greenlist": "green-val",
"b": hcl2shim.UnknownVariableValue,
"a": "a-val",
"c": "c-val",
},
Err: true,
Errors: []error{
fmt.Errorf("\"blacklist\": conflicts with greenlist"),
fmt.Errorf("\"greenlist\": conflicts with blacklist"),
fmt.Errorf("\"a\": conflicts with c"),
fmt.Errorf("\"c\": conflicts with a"),
},
},

View File

@ -1,83 +0,0 @@
package shadow
import (
"fmt"
"io"
"reflect"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/reflectwalk"
)
// Close will close all shadow values within the given structure.
//
// This uses reflection to walk the structure, find all shadow elements,
// and close them. Currently this will only find struct fields that are
// shadow values, and not slice elements, etc.
func Close(v interface{}) error {
// We require a pointer so we can address the internal fields
val := reflect.ValueOf(v)
if val.Kind() != reflect.Ptr {
return fmt.Errorf("value must be a pointer")
}
// Walk and close
var w closeWalker
if err := reflectwalk.Walk(v, &w); err != nil {
return err
}
return w.Err
}
type closeWalker struct {
Err error
}
func (w *closeWalker) Struct(reflect.Value) error {
// Do nothing. We implement this for reflectwalk.StructWalker
return nil
}
var closerType = reflect.TypeOf((*io.Closer)(nil)).Elem()
func (w *closeWalker) StructField(f reflect.StructField, v reflect.Value) error {
// Not sure why this would be but lets avoid some panics
if !v.IsValid() {
return nil
}
// Empty for exported, so don't check unexported fields
if f.PkgPath != "" {
return nil
}
// Verify the io.Closer is in this package
typ := v.Type()
if typ.PkgPath() != "github.com/hashicorp/terraform/helper/shadow" {
return nil
}
var closer io.Closer
if v.Type().Implements(closerType) {
closer = v.Interface().(io.Closer)
} else if v.CanAddr() {
// The Close method may require a pointer receiver, but we only have a value.
v := v.Addr()
if v.Type().Implements(closerType) {
closer = v.Interface().(io.Closer)
}
}
if closer == nil {
return reflectwalk.SkipEntry
}
// Close it
if err := closer.Close(); err != nil {
w.Err = multierror.Append(w.Err, err)
}
// Don't go into the struct field
return reflectwalk.SkipEntry
}

View File

@ -1,69 +0,0 @@
package shadow
import (
"testing"
"time"
)
func TestClose(t *testing.T) {
var foo struct {
A Value
B KeyedValue
}
if err := Close(&foo); err != nil {
t.Fatalf("err: %s", err)
}
if v := foo.A.Value(); v != ErrClosed {
t.Fatalf("bad: %#v", v)
}
if v := foo.B.Value("foo"); v != ErrClosed {
t.Fatalf("bad: %#v", v)
}
}
func TestClose_nonPtr(t *testing.T) {
var foo struct{}
if err := Close(foo); err == nil {
t.Fatal("should error")
}
}
func TestClose_unexported(t *testing.T) {
var foo struct {
A Value
b Value
}
if err := Close(&foo); err != nil {
t.Fatalf("err: %s", err)
}
if v := foo.A.Value(); v != ErrClosed {
t.Fatalf("bad: %#v", v)
}
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- foo.b.Value()
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
foo.b.Close()
val := <-valueCh
// Verify
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}

View File

@ -1,128 +0,0 @@
package shadow
import (
"sync"
)
// ComparedValue is a struct that finds a value by comparing some key
// to the list of stored values. This is useful when there is no easy
// uniquely identifying key that works in a map (for that, use KeyedValue).
//
// ComparedValue is very expensive, relative to other Value types. Try to
// limit the number of values stored in a ComparedValue by potentially
// nesting it within a KeyedValue (a keyed value points to a compared value,
// for example).
type ComparedValue struct {
// Func is a function that is given the lookup key and a single
// stored value. If it matches, it returns true.
Func func(k, v interface{}) bool
lock sync.Mutex
once sync.Once
closed bool
values []interface{}
waiters map[interface{}]*Value
}
// Close closes the value. This can never fail. For a definition of
// "close" see the ErrClosed docs.
func (w *ComparedValue) Close() error {
w.lock.Lock()
defer w.lock.Unlock()
// Set closed to true always
w.closed = true
// For all waiters, complete with ErrClosed
for k, val := range w.waiters {
val.SetValue(ErrClosed)
delete(w.waiters, k)
}
return nil
}
// Value returns the value that was set for the given key, or blocks
// until one is available.
func (w *ComparedValue) Value(k interface{}) interface{} {
v, val := w.valueWaiter(k)
if val == nil {
return v
}
return val.Value()
}
// ValueOk gets the value for the given key, returning immediately if the
// value doesn't exist. The second return argument is true if the value exists.
func (w *ComparedValue) ValueOk(k interface{}) (interface{}, bool) {
v, val := w.valueWaiter(k)
return v, val == nil
}
func (w *ComparedValue) SetValue(v interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
w.once.Do(w.init)
// Check if we already have this exact value (by simply comparing
// with == directly). If we do, then we don't insert it again.
found := false
for _, v2 := range w.values {
if v == v2 {
found = true
break
}
}
if !found {
// Set the value, always
w.values = append(w.values, v)
}
// Go through the waiters
for k, val := range w.waiters {
if w.Func(k, v) {
val.SetValue(v)
delete(w.waiters, k)
}
}
}
func (w *ComparedValue) valueWaiter(k interface{}) (interface{}, *Value) {
w.lock.Lock()
w.once.Do(w.init)
// Look for a pre-existing value
for _, v := range w.values {
if w.Func(k, v) {
w.lock.Unlock()
return v, nil
}
}
// If we're closed, return that
if w.closed {
w.lock.Unlock()
return ErrClosed, nil
}
// Pre-existing value doesn't exist, create a waiter
val := w.waiters[k]
if val == nil {
val = new(Value)
w.waiters[k] = val
}
w.lock.Unlock()
// Return the waiter
return nil, val
}
// Must be called with w.lock held.
func (w *ComparedValue) init() {
w.waiters = make(map[interface{}]*Value)
if w.Func == nil {
w.Func = func(k, v interface{}) bool { return k == v }
}
}

View File

@ -1,178 +0,0 @@
package shadow
import (
"testing"
"time"
)
func TestComparedValue(t *testing.T) {
v := &ComparedValue{
Func: func(k, v interface{}) bool { return k == v },
}
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
v.SetValue("foo")
val := <-valueCh
// Verify
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
// We should get the next value
val = v.Value("foo")
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValue_setFirst(t *testing.T) {
v := &ComparedValue{
Func: func(k, v interface{}) bool { return k == v },
}
// Set the value
v.SetValue("foo")
val := v.Value("foo")
// Verify
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValueOk(t *testing.T) {
v := &ComparedValue{
Func: func(k, v interface{}) bool { return k == v },
}
// Try
val, ok := v.ValueOk("foo")
if ok {
t.Fatal("should not be ok")
}
// Set
v.SetValue("foo")
// Try again
val, ok = v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValueClose(t *testing.T) {
v := &ComparedValue{
Func: func(k, v interface{}) bool { return k == v },
}
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValueClose_blocked(t *testing.T) {
var v ComparedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Close
v.Close()
// Verify
val := <-valueCh
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValueClose_existing(t *testing.T) {
var v ComparedValue
// Set a value
v.SetValue("foo")
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
}
func TestComparedValueClose_existingBlocked(t *testing.T) {
var v ComparedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// Wait
time.Sleep(10 * time.Millisecond)
// Set a value
v.SetValue("foo")
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != "foo" {
t.Fatalf("bad: %#v", val)
}
}

View File

@ -1,151 +0,0 @@
package shadow
import (
"sync"
)
// KeyedValue is a struct that coordinates a value by key. If a value is
// not available for a give key, it'll block until it is available.
type KeyedValue struct {
lock sync.Mutex
once sync.Once
values map[string]interface{}
waiters map[string]*Value
closed bool
}
// Close closes the value. This can never fail. For a definition of
// "close" see the ErrClosed docs.
func (w *KeyedValue) Close() error {
w.lock.Lock()
defer w.lock.Unlock()
// Set closed to true always
w.closed = true
// For all waiters, complete with ErrClosed
for k, val := range w.waiters {
val.SetValue(ErrClosed)
delete(w.waiters, k)
}
return nil
}
// Value returns the value that was set for the given key, or blocks
// until one is available.
func (w *KeyedValue) Value(k string) interface{} {
w.lock.Lock()
v, val := w.valueWaiter(k)
w.lock.Unlock()
// If we have no waiter, then return the value
if val == nil {
return v
}
// We have a waiter, so wait
return val.Value()
}
// WaitForChange waits for the value with the given key to be set again.
// If the key isn't set, it'll wait for an initial value. Note that while
// it is called "WaitForChange", the value isn't guaranteed to _change_;
// this will return when a SetValue is called for the given k.
func (w *KeyedValue) WaitForChange(k string) interface{} {
w.lock.Lock()
w.once.Do(w.init)
// If we're closed, we're closed
if w.closed {
w.lock.Unlock()
return ErrClosed
}
// Check for an active waiter. If there isn't one, make it
val := w.waiters[k]
if val == nil {
val = new(Value)
w.waiters[k] = val
}
w.lock.Unlock()
// And wait
return val.Value()
}
// ValueOk gets the value for the given key, returning immediately if the
// value doesn't exist. The second return argument is true if the value exists.
func (w *KeyedValue) ValueOk(k string) (interface{}, bool) {
w.lock.Lock()
defer w.lock.Unlock()
v, val := w.valueWaiter(k)
return v, val == nil
}
func (w *KeyedValue) SetValue(k string, v interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
w.setValue(k, v)
}
// Init will initialize the key to a given value only if the key has
// not been set before. This is safe to call multiple times and in parallel.
func (w *KeyedValue) Init(k string, v interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
// If we have a waiter, set the value.
_, val := w.valueWaiter(k)
if val != nil {
w.setValue(k, v)
}
}
// Must be called with w.lock held.
func (w *KeyedValue) init() {
w.values = make(map[string]interface{})
w.waiters = make(map[string]*Value)
}
// setValue is like SetValue but assumes the lock is held.
func (w *KeyedValue) setValue(k string, v interface{}) {
w.once.Do(w.init)
// Set the value, always
w.values[k] = v
// If we have a waiter, set it
if val, ok := w.waiters[k]; ok {
val.SetValue(v)
delete(w.waiters, k)
}
}
// valueWaiter gets the value or the Value waiter for a given key.
//
// This must be called with lock held.
func (w *KeyedValue) valueWaiter(k string) (interface{}, *Value) {
w.once.Do(w.init)
// If we have this value already, return it
if v, ok := w.values[k]; ok {
return v, nil
}
// If we're closed, return that
if w.closed {
return ErrClosed, nil
}
// No pending value, check for a waiter
val := w.waiters[k]
if val == nil {
val = new(Value)
w.waiters[k] = val
}
// Return the waiter
return nil, val
}

View File

@ -1,336 +0,0 @@
package shadow
import (
"testing"
"time"
)
func TestKeyedValue(t *testing.T) {
var v KeyedValue
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
v.SetValue("foo", 42)
val := <-valueCh
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should get the next value
val = v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValue_setFirst(t *testing.T) {
var v KeyedValue
// Set the value
v.SetValue("foo", 42)
val := v.Value("foo")
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueOk(t *testing.T) {
var v KeyedValue
// Try
val, ok := v.ValueOk("foo")
if ok {
t.Fatal("should not be ok")
}
// Set
v.SetValue("foo", 42)
// Try again
val, ok = v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueClose(t *testing.T) {
var v KeyedValue
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueClose_blocked(t *testing.T) {
var v KeyedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Close
v.Close()
// Verify
val := <-valueCh
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueClose_existing(t *testing.T) {
var v KeyedValue
// Set a value
v.SetValue("foo", "bar")
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != "bar" {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueClose_existingBlocked(t *testing.T) {
var v KeyedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value("foo")
}()
// Wait
time.Sleep(10 * time.Millisecond)
// Set a value
v.SetValue("foo", "bar")
// Close
v.Close()
// Try again
val, ok := v.ValueOk("foo")
if !ok {
t.Fatal("should be ok")
}
// Verify
if val != "bar" {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueInit(t *testing.T) {
var v KeyedValue
v.Init("foo", 42)
// We should get the value
val := v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should get the value
val = v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// This should do nothing
v.Init("foo", 84)
// We should get the value
val = v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueInit_set(t *testing.T) {
var v KeyedValue
v.SetValue("foo", 42)
// We should get the value
val := v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should get the value
val = v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// This should do nothing
v.Init("foo", 84)
// We should get the value
val = v.Value("foo")
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueWaitForChange(t *testing.T) {
var v KeyedValue
// Set a value
v.SetValue("foo", 42)
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.WaitForChange("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set a new value
v.SetValue("foo", 84)
// Verify
val := <-valueCh
if val != 84 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueWaitForChange_initial(t *testing.T) {
var v KeyedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.WaitForChange("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set a new value
v.SetValue("foo", 84)
// Verify
val := <-valueCh
if val != 84 {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueWaitForChange_closed(t *testing.T) {
var v KeyedValue
// Start reading this should be blocking
valueCh := make(chan interface{})
go func() {
valueCh <- v.WaitForChange("foo")
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Close
v.Close()
// Verify
val := <-valueCh
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
// Set a value
v.SetValue("foo", 42)
// Try again
val = v.WaitForChange("foo")
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestKeyedValueWaitForChange_closedFirst(t *testing.T) {
var v KeyedValue
// Close
v.Close()
// Verify
val := v.WaitForChange("foo")
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
// Set a value
v.SetValue("foo", 42)
// Try again
val = v.WaitForChange("foo")
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}

View File

@ -1,66 +0,0 @@
package shadow
import (
"container/list"
"sync"
)
// OrderedValue is a struct that keeps track of a value in the order
// it is set. Each time Value() is called, it will return the most recent
// calls value then discard it.
//
// This is unlike Value that returns the same value once it is set.
type OrderedValue struct {
lock sync.Mutex
values *list.List
waiters *list.List
}
// Value returns the last value that was set, or blocks until one
// is received.
func (w *OrderedValue) Value() interface{} {
w.lock.Lock()
// If we have a pending value already, use it
if w.values != nil && w.values.Len() > 0 {
front := w.values.Front()
w.values.Remove(front)
w.lock.Unlock()
return front.Value
}
// No pending value, create a waiter
if w.waiters == nil {
w.waiters = list.New()
}
var val Value
w.waiters.PushBack(&val)
w.lock.Unlock()
// Return the value once we have it
return val.Value()
}
// SetValue sets the latest value.
func (w *OrderedValue) SetValue(v interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
// If we have a waiter, notify it
if w.waiters != nil && w.waiters.Len() > 0 {
front := w.waiters.Front()
w.waiters.Remove(front)
val := front.Value.(*Value)
val.SetValue(v)
return
}
// Add it to the list of values
if w.values == nil {
w.values = list.New()
}
w.values.PushBack(v)
}

View File

@ -1,76 +0,0 @@
package shadow
import (
"testing"
"time"
)
func TestOrderedValue(t *testing.T) {
var v OrderedValue
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value()
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
v.SetValue(42)
val := <-valueCh
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should not get the value again
go func() {
valueCh <- v.Value()
}()
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// We should get the next value
v.SetValue(21)
val = <-valueCh
if val != 21 {
t.Fatalf("bad: %#v", val)
}
}
func TestOrderedValue_setFirst(t *testing.T) {
var v OrderedValue
// Set the value
v.SetValue(42)
val := v.Value()
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should not get the value again
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value()
}()
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set a value so the goroutine doesn't hang around
v.SetValue(1)
}

View File

@ -1,87 +0,0 @@
package shadow
import (
"errors"
"sync"
)
// ErrClosed is returned by any closed values.
//
// A "closed value" is when the shadow has been notified that the real
// side is complete and any blocking values will _never_ be satisfied
// in the future. In this case, this error is returned. If a value is already
// available, that is still returned.
var ErrClosed = errors.New("shadow closed")
// Value is a struct that coordinates a value between two
// parallel routines. It is similar to atomic.Value except that when
// Value is called if it isn't set it will wait for it.
//
// The Value can be closed with Close, which will cause any future
// blocking operations to return immediately with ErrClosed.
type Value struct {
lock sync.Mutex
cond *sync.Cond
value interface{}
valueSet bool
}
func (v *Value) Lock() {
v.lock.Lock()
}
func (v *Value) Unlock() {
v.lock.Unlock()
}
// Close closes the value. This can never fail. For a definition of
// "close" see the struct docs.
func (w *Value) Close() error {
w.lock.Lock()
set := w.valueSet
w.lock.Unlock()
// If we haven't set the value, set it
if !set {
w.SetValue(ErrClosed)
}
// Done
return nil
}
// Value returns the value that was set.
func (w *Value) Value() interface{} {
w.lock.Lock()
defer w.lock.Unlock()
// If we already have a value just return
for !w.valueSet {
// No value, setup the condition variable if we have to
if w.cond == nil {
w.cond = sync.NewCond(&w.lock)
}
// Wait on it
w.cond.Wait()
}
// Return the value
return w.value
}
// SetValue sets the value.
func (w *Value) SetValue(v interface{}) {
w.lock.Lock()
defer w.lock.Unlock()
// Set the value
w.valueSet = true
w.value = v
// If we have a condition, clear it
if w.cond != nil {
w.cond.Broadcast()
w.cond = nil
}
}

View File

@ -1,103 +0,0 @@
package shadow
import (
"testing"
"time"
)
func TestValue(t *testing.T) {
var v Value
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value()
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
v.SetValue(42)
val := <-valueCh
// Verify
if val != 42 {
t.Fatalf("bad: %#v", val)
}
// We should be able to ask for the value again immediately
if val := v.Value(); val != 42 {
t.Fatalf("bad: %#v", val)
}
// We can change the value
v.SetValue(84)
if val := v.Value(); val != 84 {
t.Fatalf("bad: %#v", val)
}
}
func TestValueClose(t *testing.T) {
var v Value
// Close
v.Close()
// Verify
val := v.Value()
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestValueClose_blocked(t *testing.T) {
var v Value
// Start trying to get the value
valueCh := make(chan interface{})
go func() {
valueCh <- v.Value()
}()
// We should not get the value
select {
case <-valueCh:
t.Fatal("shouldn't receive value")
case <-time.After(10 * time.Millisecond):
}
// Set the value
v.Close()
val := <-valueCh
// Verify
if val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
// We should be able to ask for the value again immediately
if val := v.Value(); val != ErrClosed {
t.Fatalf("bad: %#v", val)
}
}
func TestValueClose_existing(t *testing.T) {
var v Value
// Set the value
v.SetValue(42)
// Close
v.Close()
// Verify
val := v.Value()
if val != 42 {
t.Fatalf("bad: %#v", val)
}
}

View File

@ -1,182 +0,0 @@
// Package signalwrapper is used to run functions that are sensitive to
// signals that may be received from outside the process. It can also be
// used as just an async function runner that is cancellable and we may
// abstract this further into another package in the future.
package signalwrapper
import (
"log"
"os"
"os/signal"
"sync"
)
// CancellableFunc is a function that cancels if it receives a message
// on the given channel. It must return an error if any occurred. It should
// return no error if it was cancelled successfully since it is assumed
// that this function will probably be called again at some future point
// since it was interrupted.
type CancellableFunc func(<-chan struct{}) error
// Run wraps and runs the given cancellable function and returns the Wrapped
// struct that can be used to listen for events, cancel on other events
// (such as timeouts), etc.
func Run(f CancellableFunc) *Wrapped {
// Determine the signals we're listening to. Prematurely making
// this a slice since I predict a future where we'll add others and
// the complexity in doing so is low.
signals := []os.Signal{os.Interrupt}
// Register a listener for the signals
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, signals...)
// Create the channel we'll use to "cancel"
cancelCh := make(chan struct{})
// This channel keeps track of whether the function we're running
// completed successfully and the errors it may have had. It is
// VERY IMPORTANT that the errCh is buffered to at least 1 so that
// it doesn't block when finishing.
doneCh := make(chan struct{})
errCh := make(chan error, 1)
// Build our wrapped result
wrapped := &Wrapped{
ErrCh: errCh,
errCh: errCh,
cancelCh: cancelCh,
}
// Start the function
go func() {
log.Printf("[DEBUG] signalwrapper: executing wrapped function")
err := f(cancelCh)
// Close the done channel _before_ sending the error in case
// the error channel read blocks (it shouldn't) to avoid interrupts
// doing anything.
close(doneCh)
// Mark completion
log.Printf("[DEBUG] signalwrapper: wrapped function execution ended")
wrapped.done(err)
}()
// Goroutine to track interrupts and make sure we do at-most-once
// delivery of an interrupt since we're using a channel.
go func() {
// Clean up after this since this is the only function that
// reads signals.
defer signal.Stop(sigCh)
select {
case <-doneCh:
// Everything happened naturally
case <-sigCh:
log.Printf("[DEBUG] signalwrapper: signal received, cancelling wrapped function")
// Stop the function. Goroutine since we don't care about
// the result and we'd like to end this goroutine as soon
// as possible to avoid any more signals coming in.
go wrapped.Cancel()
}
}()
return wrapped
}
// Wrapped is the return value of wrapping a function. This has channels
// that can be used to wait for a result as well as functions to help with
// different behaviors.
type Wrapped struct {
// Set and consumed by user
// ErrCh is the channel to listen for real-time events on the wrapped
// function. A nil error sent means the execution completed without error.
// This is an exactly once delivery channel.
ErrCh <-chan error
// Set by creator
errCh chan<- error
cancelCh chan<- struct{}
// Set automatically
once sync.Once
cancelCond *sync.Cond
cancelLock *sync.Mutex
resultErr error
resultSet bool
}
// Cancel stops the running function and blocks until it returns. The
// resulting value is returned.
//
// It is safe to call this multiple times. This will return the resulting
// error value each time.
func (w *Wrapped) Cancel() error {
w.once.Do(w.init)
w.cancelLock.Lock()
// If we have a result set, return that
if w.resultSet {
w.cancelLock.Unlock()
return w.resultErr
}
// If we have a cancel channel, close it to signal and set it to
// nil so we never do that again.
if w.cancelCh != nil {
close(w.cancelCh)
w.cancelCh = nil
}
// Wait for the result to be set
defer w.cancelLock.Unlock()
w.cancelCond.Wait()
return w.resultErr
}
// Wait waits for the completion of the wrapped function and returns the result.
//
// This can be called multiple times safely.
func (w *Wrapped) Wait() error {
w.once.Do(w.init)
w.cancelLock.Lock()
defer w.cancelLock.Unlock()
// If we don't have a result yet, wait for that
if !w.resultSet {
w.cancelCond.Wait()
}
// Return the result
return w.resultErr
}
// done marks this wrapped function as done with the resulting value.
// This must only be called once.
func (w *Wrapped) done(err error) {
w.once.Do(w.init)
w.cancelLock.Lock()
// Set the result
w.resultErr = err
w.resultSet = true
// Notify any waiters
w.cancelCond.Broadcast()
// Unlock since the next call can be blocking
w.cancelLock.Unlock()
// Notify any channel listeners
w.errCh <- err
}
func (w *Wrapped) init() {
// Create the condition variable
var m sync.Mutex
w.cancelCond = sync.NewCond(&m)
w.cancelLock = &m
}

View File

@ -1,94 +0,0 @@
package signalwrapper
import (
"errors"
"testing"
"time"
)
func TestWrapped_goodCh(t *testing.T) {
errVal := errors.New("hi")
f := func(<-chan struct{}) error { return errVal }
err := <-Run(f).ErrCh
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}
func TestWrapped_goodWait(t *testing.T) {
errVal := errors.New("hi")
f := func(<-chan struct{}) error {
time.Sleep(10 * time.Millisecond)
return errVal
}
wrapped := Run(f)
// Once
{
err := wrapped.Wait()
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}
// Again
{
err := wrapped.Wait()
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}
}
func TestWrapped_cancel(t *testing.T) {
errVal := errors.New("hi")
f := func(ch <-chan struct{}) error {
<-ch
return errVal
}
wrapped := Run(f)
// Once
{
err := wrapped.Cancel()
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}
// Again
{
err := wrapped.Cancel()
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}
}
func TestWrapped_waitAndCancel(t *testing.T) {
errVal := errors.New("hi")
readyCh := make(chan struct{})
f := func(ch <-chan struct{}) error {
<-ch
<-readyCh
return errVal
}
wrapped := Run(f)
// Run both cancel and wait and wait some time to hope they're
// scheduled. We could _ensure_ both are scheduled by using some
// more lines of code but this is probably just good enough.
go wrapped.Cancel()
go wrapped.Wait()
close(readyCh)
time.Sleep(10 * time.Millisecond)
// Check it by calling Cancel again
err := wrapped.Cancel()
if err != errVal {
t.Fatalf("bad: %#v", err)
}
}

View File

@ -1,11 +0,0 @@
package structure
import "encoding/json"
func ExpandJsonFromString(jsonString string) (map[string]interface{}, error) {
var result map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &result)
return result, err
}

View File

@ -1,48 +0,0 @@
package structure
import (
"reflect"
"testing"
)
func TestExpandJson_emptyString(t *testing.T) {
_, err := ExpandJsonFromString("")
if err == nil {
t.Fatal("Expected to throw an error while Expanding JSON")
}
}
func TestExpandJson_singleItem(t *testing.T) {
input := `{
"foo": "bar"
}`
expected := make(map[string]interface{}, 1)
expected["foo"] = "bar"
actual, err := ExpandJsonFromString(input)
if err != nil {
t.Fatalf("Expected not to throw an error while Expanding JSON, but got: %s", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Got:\n\n%+v\n\nExpected:\n\n%+v\n", actual, expected)
}
}
func TestExpandJson_multipleItems(t *testing.T) {
input := `{
"foo": "bar",
"hello": "world"
}`
expected := make(map[string]interface{}, 1)
expected["foo"] = "bar"
expected["hello"] = "world"
actual, err := ExpandJsonFromString(input)
if err != nil {
t.Fatalf("Expected not to throw an error while Expanding JSON, but got: %s", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Got:\n\n%+v\n\nExpected:\n\n%+v\n", actual, expected)
}
}

View File

@ -1,16 +0,0 @@
package structure
import "encoding/json"
func FlattenJsonToString(input map[string]interface{}) (string, error) {
if len(input) == 0 {
return "", nil
}
result, err := json.Marshal(input)
if err != nil {
return "", err
}
return string(result), nil
}

View File

@ -1,47 +0,0 @@
package structure
import (
"testing"
)
func TestFlattenJson_empty(t *testing.T) {
input := make(map[string]interface{}, 0)
expected := ""
actual, err := FlattenJsonToString(input)
if err != nil {
t.Fatalf("Expected not to throw an error while Flattening JSON, but got: %s", err)
}
if expected != actual {
t.Fatalf("Got: `%+v`. Expected: `%+v`", actual, expected)
}
}
func TestFlattenJson_singleItem(t *testing.T) {
input := make(map[string]interface{}, 1)
input["foo"] = "bar"
expected := `{"foo":"bar"}`
actual, err := FlattenJsonToString(input)
if err != nil {
t.Fatalf("Expected not to throw an error while Flattening JSON, but got: %s", err)
}
if expected != actual {
t.Fatalf("Got: `%+v`. Expected: `%+v`", actual, expected)
}
}
func TestFlattenJson_multipleItems(t *testing.T) {
input := make(map[string]interface{}, 1)
input["foo"] = "bar"
input["bar"] = "foo"
expected := `{"bar":"foo","foo":"bar"}`
actual, err := FlattenJsonToString(input)
if err != nil {
t.Fatalf("Expected not to throw an error while Flattening JSON, but got: %s", err)
}
if expected != actual {
t.Fatalf("Got: `%+v`. Expected: `%+v`", actual, expected)
}
}

View File

@ -1,24 +0,0 @@
package structure
import "encoding/json"
// Takes a value containing JSON string and passes it through
// the JSON parser to normalize it, returns either a parsing
// error or normalized JSON string.
func NormalizeJsonString(jsonString interface{}) (string, error) {
var j interface{}
if jsonString == nil || jsonString.(string) == "" {
return "", nil
}
s := jsonString.(string)
err := json.Unmarshal([]byte(s), &j)
if err != nil {
return s, err
}
bytes, _ := json.Marshal(j)
return string(bytes[:]), nil
}

View File

@ -1,91 +0,0 @@
package structure
import (
"testing"
)
func TestNormalizeJsonString_valid(t *testing.T) {
// Well formatted and valid.
validJson := `{
"abc": {
"def": 123,
"xyz": [
{
"a": "ホリネズミ"
},
{
"b": "1\\n2"
}
]
}
}`
expected := `{"abc":{"def":123,"xyz":[{"a":"ホリネズミ"},{"b":"1\\n2"}]}}`
actual, err := NormalizeJsonString(validJson)
if err != nil {
t.Fatalf("Expected not to throw an error while parsing JSON, but got: %s", err)
}
if actual != expected {
t.Fatalf("Got:\n\n%s\n\nExpected:\n\n%s\n", actual, expected)
}
// Well formatted but not valid,
// missing closing square bracket.
invalidJson := `{
"abc": {
"def": 123,
"xyz": [
{
"a": "1"
}
}
}
}`
actual, err = NormalizeJsonString(invalidJson)
if err == nil {
t.Fatalf("Expected to throw an error while parsing JSON, but got: %s", err)
}
// We expect the invalid JSON to be shown back to us again.
if actual != invalidJson {
t.Fatalf("Got:\n\n%s\n\nExpected:\n\n%s\n", actual, invalidJson)
}
// Verify that it leaves strings alone
testString := "2016-07-28t04:07:02z\nsomething else"
expected = "2016-07-28t04:07:02z\nsomething else"
actual, err = NormalizeJsonString(testString)
if err == nil {
t.Fatalf("Expected to throw an error while parsing JSON, but got: %s", err)
}
if actual != expected {
t.Fatalf("Got:\n\n%s\n\nExpected:\n\n%s\n", actual, expected)
}
}
func TestNormalizeJsonString_invalid(t *testing.T) {
// Well formatted but not valid,
// missing closing squre bracket.
invalidJson := `{
"abc": {
"def": 123,
"xyz": [
{
"a": "1"
}
}
}
}`
expected := `{"abc":{"def":123,"xyz":[{"a":"ホリネズミ"},{"b":"1\\n2"}]}}`
actual, err := NormalizeJsonString(invalidJson)
if err == nil {
t.Fatalf("Expected to throw an error while parsing JSON, but got: %s", err)
}
// We expect the invalid JSON to be shown back to us again.
if actual != invalidJson {
t.Fatalf("Got:\n\n%s\n\nExpected:\n\n%s\n", expected, invalidJson)
}
}

View File

@ -1,21 +0,0 @@
package structure
import (
"reflect"
"github.com/hashicorp/terraform/helper/schema"
)
func SuppressJsonDiff(k, old, new string, d *schema.ResourceData) bool {
oldMap, err := ExpandJsonFromString(old)
if err != nil {
return false
}
newMap, err := ExpandJsonFromString(new)
if err != nil {
return false
}
return reflect.DeepEqual(oldMap, newMap)
}

View File

@ -1,51 +0,0 @@
package structure
import (
"testing"
)
func TestSuppressJsonDiff_same(t *testing.T) {
original := `{ "enabled": true }`
new := `{ "enabled": true }`
expected := true
actual := SuppressJsonDiff("test", original, new, nil)
if actual != expected {
t.Fatal("[ERROR] Identical JSON values shouldn't cause a diff")
}
}
func TestSuppressJsonDiff_sameWithWhitespace(t *testing.T) {
original := `{
"enabled": true
}`
new := `{ "enabled": true }`
expected := true
actual := SuppressJsonDiff("test", original, new, nil)
if actual != expected {
t.Fatal("[ERROR] Identical JSON values shouldn't cause a diff")
}
}
func TestSuppressJsonDiff_differentValue(t *testing.T) {
original := `{ "enabled": true }`
new := `{ "enabled": false }`
expected := false
actual := SuppressJsonDiff("test", original, new, nil)
if actual != expected {
t.Fatal("[ERROR] Different JSON values should cause a diff")
}
}
func TestSuppressJsonDiff_newValue(t *testing.T) {
original := `{ "enabled": true }`
new := `{ "enabled": false, "world": "round" }`
expected := false
actual := SuppressJsonDiff("test", original, new, nil)
if actual != expected {
t.Fatal("[ERROR] Different JSON values should cause a diff")
}
}

View File

@ -1,51 +1,12 @@
package validation
import (
"bytes"
"fmt"
"net"
"reflect"
"regexp"
"strings"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/structure"
)
// All returns a SchemaValidateFunc which tests if the provided value
// passes all provided SchemaValidateFunc
func All(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc {
return func(i interface{}, k string) ([]string, []error) {
var allErrors []error
var allWarnings []string
for _, validator := range validators {
validatorWarnings, validatorErrors := validator(i, k)
allWarnings = append(allWarnings, validatorWarnings...)
allErrors = append(allErrors, validatorErrors...)
}
return allWarnings, allErrors
}
}
// Any returns a SchemaValidateFunc which tests if the provided value
// passes any of the provided SchemaValidateFunc
func Any(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc {
return func(i interface{}, k string) ([]string, []error) {
var allErrors []error
var allWarnings []string
for _, validator := range validators {
validatorWarnings, validatorErrors := validator(i, k)
if len(validatorWarnings) == 0 && len(validatorErrors) == 0 {
return []string{}, []error{}
}
allWarnings = append(allWarnings, validatorWarnings...)
allErrors = append(allErrors, validatorErrors...)
}
return allWarnings, allErrors
}
}
// IntBetween returns a SchemaValidateFunc which tests if the provided value
// is of type int and is between min and max (inclusive)
func IntBetween(min, max int) schema.SchemaValidateFunc {
@ -65,65 +26,6 @@ func IntBetween(min, max int) schema.SchemaValidateFunc {
}
}
// IntAtLeast returns a SchemaValidateFunc which tests if the provided value
// is of type int and is at least min (inclusive)
func IntAtLeast(min int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(int)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be int", k))
return
}
if v < min {
es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d", k, min, v))
return
}
return
}
}
// IntAtMost returns a SchemaValidateFunc which tests if the provided value
// is of type int and is at most max (inclusive)
func IntAtMost(max int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(int)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be int", k))
return
}
if v > max {
es = append(es, fmt.Errorf("expected %s to be at most (%d), got %d", k, max, v))
return
}
return
}
}
// IntInSlice returns a SchemaValidateFunc which tests if the provided value
// is of type int and matches the value of an element in the valid slice
func IntInSlice(valid []int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(int)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be an integer", k))
return
}
for _, validInt := range valid {
if v == validInt {
return
}
}
es = append(es, fmt.Errorf("expected %s to be one of %v, got %d", k, valid, v))
return
}
}
// StringInSlice returns a SchemaValidateFunc which tests if the provided value
// is of type string and matches the value of an element in the valid slice
// will test with in lower case if ignoreCase is true
@ -145,197 +47,3 @@ func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc {
return
}
}
// StringLenBetween returns a SchemaValidateFunc which tests if the provided value
// is of type string and has length between min and max (inclusive)
func StringLenBetween(min, max int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}
if len(v) < min || len(v) > max {
es = append(es, fmt.Errorf("expected length of %s to be in the range (%d - %d), got %s", k, min, max, v))
}
return
}
}
// StringMatch returns a SchemaValidateFunc which tests if the provided value
// matches a given regexp. Optionally an error message can be provided to
// return something friendlier than "must match some globby regexp".
func StringMatch(r *regexp.Regexp, message string) schema.SchemaValidateFunc {
return func(i interface{}, k string) ([]string, []error) {
v, ok := i.(string)
if !ok {
return nil, []error{fmt.Errorf("expected type of %s to be string", k)}
}
if ok := r.MatchString(v); !ok {
if message != "" {
return nil, []error{fmt.Errorf("invalid value for %s (%s)", k, message)}
}
return nil, []error{fmt.Errorf("expected value of %s to match regular expression %q", k, r)}
}
return nil, nil
}
}
// NoZeroValues is a SchemaValidateFunc which tests if the provided value is
// not a zero value. It's useful in situations where you want to catch
// explicit zero values on things like required fields during validation.
func NoZeroValues(i interface{}, k string) (s []string, es []error) {
if reflect.ValueOf(i).Interface() == reflect.Zero(reflect.TypeOf(i)).Interface() {
switch reflect.TypeOf(i).Kind() {
case reflect.String:
es = append(es, fmt.Errorf("%s must not be empty", k))
case reflect.Int, reflect.Float64:
es = append(es, fmt.Errorf("%s must not be zero", k))
default:
// this validator should only ever be applied to TypeString, TypeInt and TypeFloat
panic(fmt.Errorf("can't use NoZeroValues with %T attribute %s", i, k))
}
}
return
}
// CIDRNetwork returns a SchemaValidateFunc which tests if the provided value
// is of type string, is in valid CIDR network notation, and has significant bits between min and max (inclusive)
func CIDRNetwork(min, max int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}
_, ipnet, err := net.ParseCIDR(v)
if err != nil {
es = append(es, fmt.Errorf(
"expected %s to contain a valid CIDR, got: %s with err: %s", k, v, err))
return
}
if ipnet == nil || v != ipnet.String() {
es = append(es, fmt.Errorf(
"expected %s to contain a valid network CIDR, expected %s, got %s",
k, ipnet, v))
}
sigbits, _ := ipnet.Mask.Size()
if sigbits < min || sigbits > max {
es = append(es, fmt.Errorf(
"expected %q to contain a network CIDR with between %d and %d significant bits, got: %d",
k, min, max, sigbits))
}
return
}
}
// SingleIP returns a SchemaValidateFunc which tests if the provided value
// is of type string, and in valid single IP notation
func SingleIP() schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}
ip := net.ParseIP(v)
if ip == nil {
es = append(es, fmt.Errorf(
"expected %s to contain a valid IP, got: %s", k, v))
}
return
}
}
// IPRange returns a SchemaValidateFunc which tests if the provided value
// is of type string, and in valid IP range notation
func IPRange() schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(string)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be string", k))
return
}
ips := strings.Split(v, "-")
if len(ips) != 2 {
es = append(es, fmt.Errorf(
"expected %s to contain a valid IP range, got: %s", k, v))
return
}
ip1 := net.ParseIP(ips[0])
ip2 := net.ParseIP(ips[1])
if ip1 == nil || ip2 == nil || bytes.Compare(ip1, ip2) > 0 {
es = append(es, fmt.Errorf(
"expected %s to contain a valid IP range, got: %s", k, v))
}
return
}
}
// ValidateJsonString is a SchemaValidateFunc which tests to make sure the
// supplied string is valid JSON.
func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) {
if _, err := structure.NormalizeJsonString(v); err != nil {
errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err))
}
return
}
// ValidateListUniqueStrings is a ValidateFunc that ensures a list has no
// duplicate items in it. It's useful for when a list is needed over a set
// because order matters, yet the items still need to be unique.
func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []error) {
for n1, v1 := range v.([]interface{}) {
for n2, v2 := range v.([]interface{}) {
if v1.(string) == v2.(string) && n1 != n2 {
errors = append(errors, fmt.Errorf("%q: duplicate entry - %s", k, v1.(string)))
}
}
}
return
}
// ValidateRegexp returns a SchemaValidateFunc which tests to make sure the
// supplied string is a valid regular expression.
func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) {
if _, err := regexp.Compile(v.(string)); err != nil {
errors = append(errors, fmt.Errorf("%q: %s", k, err))
}
return
}
// ValidateRFC3339TimeString is a ValidateFunc that ensures a string parses
// as time.RFC3339 format
func ValidateRFC3339TimeString(v interface{}, k string) (ws []string, errors []error) {
if _, err := time.Parse(time.RFC3339, v.(string)); err != nil {
errors = append(errors, fmt.Errorf("%q: invalid RFC3339 timestamp", k))
}
return
}
// FloatBetween returns a SchemaValidateFunc which tests if the provided value
// is of type float64 and is between min and max (inclusive).
func FloatBetween(min, max float64) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {
v, ok := i.(float64)
if !ok {
es = append(es, fmt.Errorf("expected type of %s to be float64", k))
return
}
if v < min || v > max {
es = append(es, fmt.Errorf("expected %s to be in the range (%f - %f), got %f", k, min, max, v))
return
}
return
}
}

View File

@ -13,69 +13,6 @@ type testCase struct {
expectedErr *regexp.Regexp
}
func TestValidationAll(t *testing.T) {
runTestCases(t, []testCase{
{
val: "valid",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
},
{
val: "foo",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
expectedErr: regexp.MustCompile("expected length of [\\w]+ to be in the range \\(5 - 42\\), got foo"),
},
{
val: "!!!!!",
f: All(
StringLenBetween(5, 42),
StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"),
),
expectedErr: regexp.MustCompile("value must be alphanumeric"),
},
})
}
func TestValidationAny(t *testing.T) {
runTestCases(t, []testCase{
{
val: 43,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
},
{
val: 4,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
},
{
val: 7,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(42\\), got 7"),
},
{
val: 7,
f: Any(
IntAtLeast(42),
IntAtMost(5),
),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(5\\), got 7"),
},
})
}
func TestValidationIntBetween(t *testing.T) {
runTestCases(t, []testCase{
{
@ -99,71 +36,6 @@ func TestValidationIntBetween(t *testing.T) {
})
}
func TestValidationIntAtLeast(t *testing.T) {
runTestCases(t, []testCase{
{
val: 1,
f: IntAtLeast(1),
},
{
val: 1,
f: IntAtLeast(0),
},
{
val: 1,
f: IntAtLeast(2),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at least \\(2\\), got 1"),
},
{
val: "1",
f: IntAtLeast(2),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
},
})
}
func TestValidationIntAtMost(t *testing.T) {
runTestCases(t, []testCase{
{
val: 1,
f: IntAtMost(1),
},
{
val: 1,
f: IntAtMost(2),
},
{
val: 1,
f: IntAtMost(0),
expectedErr: regexp.MustCompile("expected [\\w]+ to be at most \\(0\\), got 1"),
},
{
val: "1",
f: IntAtMost(0),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be int"),
},
})
}
func TestValidationIntInSlice(t *testing.T) {
runTestCases(t, []testCase{
{
val: 42,
f: IntInSlice([]int{1, 42}),
},
{
val: 42,
f: IntInSlice([]int{10, 20}),
expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[10 20\\], got 42"),
},
{
val: "InvalidValue",
f: IntInSlice([]int{10, 20}),
expectedErr: regexp.MustCompile("expected type of [\\w]+ to be an integer"),
},
})
}
func TestValidationStringInSlice(t *testing.T) {
runTestCases(t, []testCase{
{
@ -193,240 +65,6 @@ func TestValidationStringInSlice(t *testing.T) {
})
}
func TestValidationStringMatch(t *testing.T) {
runTestCases(t, []testCase{
{
val: "foobar",
f: StringMatch(regexp.MustCompile(".*foo.*"), ""),
},
{
val: "bar",
f: StringMatch(regexp.MustCompile(".*foo.*"), ""),
expectedErr: regexp.MustCompile("expected value of [\\w]+ to match regular expression " + regexp.QuoteMeta(`".*foo.*"`)),
},
{
val: "bar",
f: StringMatch(regexp.MustCompile(".*foo.*"), "value must contain foo"),
expectedErr: regexp.MustCompile("invalid value for [\\w]+ \\(value must contain foo\\)"),
},
})
}
func TestValidationRegexp(t *testing.T) {
runTestCases(t, []testCase{
{
val: ".*foo.*",
f: ValidateRegexp,
},
{
val: "foo(bar",
f: ValidateRegexp,
expectedErr: regexp.MustCompile(regexp.QuoteMeta("error parsing regexp: missing closing ): `foo(bar`")),
},
})
}
func TestValidationSingleIP(t *testing.T) {
runTestCases(t, []testCase{
{
val: "172.10.10.10",
f: SingleIP(),
},
{
val: "1.1.1",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
{
val: "1.1.1.0/20",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
{
val: "256.1.1.1",
f: SingleIP(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP, got:")),
},
})
}
func TestValidationIPRange(t *testing.T) {
runTestCases(t, []testCase{
{
val: "172.10.10.10-172.10.10.12",
f: IPRange(),
},
{
val: "172.10.10.20",
f: IPRange(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
},
{
val: "172.10.10.20-172.10.10.12",
f: IPRange(),
expectedErr: regexp.MustCompile(regexp.QuoteMeta("expected test_property to contain a valid IP range, got:")),
},
})
}
func TestValidateRFC3339TimeString(t *testing.T) {
runTestCases(t, []testCase{
{
val: "2018-03-01T00:00:00Z",
f: ValidateRFC3339TimeString,
},
{
val: "2018-03-01T00:00:00-05:00",
f: ValidateRFC3339TimeString,
},
{
val: "2018-03-01T00:00:00+05:00",
f: ValidateRFC3339TimeString,
},
{
val: "03/01/2018",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "03-01-2018",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00Z05:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
{
val: "2018-03-01T00:00:00Z-05:00",
f: ValidateRFC3339TimeString,
expectedErr: regexp.MustCompile(regexp.QuoteMeta(`invalid RFC3339 timestamp`)),
},
})
}
func TestValidateJsonString(t *testing.T) {
type testCases struct {
Value string
ErrCount int
}
invalidCases := []testCases{
{
Value: `{0:"1"}`,
ErrCount: 1,
},
{
Value: `{'abc':1}`,
ErrCount: 1,
},
{
Value: `{"def":}`,
ErrCount: 1,
},
{
Value: `{"xyz":[}}`,
ErrCount: 1,
},
}
for _, tc := range invalidCases {
_, errors := ValidateJsonString(tc.Value, "json")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
}
}
validCases := []testCases{
{
Value: ``,
ErrCount: 0,
},
{
Value: `{}`,
ErrCount: 0,
},
{
Value: `{"abc":["1","2"]}`,
ErrCount: 0,
},
}
for _, tc := range validCases {
_, errors := ValidateJsonString(tc.Value, "json")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
}
}
}
func TestValidateListUniqueStrings(t *testing.T) {
runTestCases(t, []testCase{
{
val: []interface{}{"foo", "bar"},
f: ValidateListUniqueStrings,
},
{
val: []interface{}{"foo", "bar", "foo"},
f: ValidateListUniqueStrings,
expectedErr: regexp.MustCompile("duplicate entry - foo"),
},
{
val: []interface{}{"foo", "bar", "foo", "baz", "bar"},
f: ValidateListUniqueStrings,
expectedErr: regexp.MustCompile("duplicate entry - (?:foo|bar)"),
},
})
}
func TestValidationNoZeroValues(t *testing.T) {
runTestCases(t, []testCase{
{
val: "foo",
f: NoZeroValues,
},
{
val: 1,
f: NoZeroValues,
},
{
val: float64(1),
f: NoZeroValues,
},
{
val: "",
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be empty"),
},
{
val: 0,
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be zero"),
},
{
val: float64(0),
f: NoZeroValues,
expectedErr: regexp.MustCompile("must not be zero"),
},
})
}
func runTestCases(t *testing.T, cases []testCase) {
matchErr := func(errs []error, r *regexp.Regexp) bool {
// err must match one provided
@ -455,46 +93,3 @@ func runTestCases(t *testing.T, cases []testCase) {
}
}
}
func TestFloatBetween(t *testing.T) {
cases := map[string]struct {
Value interface{}
ValidateFunc schema.SchemaValidateFunc
ExpectValidationErrors bool
}{
"accept valid value": {
Value: 1.5,
ValidateFunc: FloatBetween(1.0, 2.0),
ExpectValidationErrors: false,
},
"accept valid value inclusive upper bound": {
Value: 1.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: false,
},
"accept valid value inclusive lower bound": {
Value: 0.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: false,
},
"reject out of range value": {
Value: -1.0,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: true,
},
"reject incorrectly typed value": {
Value: 1,
ValidateFunc: FloatBetween(0.0, 1.0),
ExpectValidationErrors: true,
},
}
for tn, tc := range cases {
_, errors := tc.ValidateFunc(tc.Value, tn)
if len(errors) > 0 && !tc.ExpectValidationErrors {
t.Errorf("%s: unexpected errors %s", tn, errors)
} else if len(errors) == 0 && tc.ExpectValidationErrors {
t.Errorf("%s: expected errors but got none", tn)
}
}
}

View File

@ -1,38 +0,0 @@
package variables
import (
"fmt"
"strings"
)
// Flag a flag.Value implementation for parsing user variables
// from the command-line in the format of '-var key=value', where value is
// a type intended for use as a Terraform variable.
type Flag map[string]interface{}
func (v *Flag) String() string {
return ""
}
func (v *Flag) Set(raw string) error {
idx := strings.Index(raw, "=")
if idx == -1 {
return fmt.Errorf("No '=' value in arg: %s", raw)
}
key, input := raw[0:idx], raw[idx+1:]
// Trim the whitespace on the key
key = strings.TrimSpace(key)
if key == "" {
return fmt.Errorf("No key to left '=' in arg: %s", raw)
}
value, err := ParseInput(input)
if err != nil {
return err
}
*v = Merge(*v, map[string]interface{}{key: value})
return nil
}

View File

@ -1,25 +0,0 @@
package variables
import (
"strings"
)
// FlagAny is a flag.Value for parsing user variables in the format of
// 'key=value' OR a file path. 'key=value' is assumed if '=' is in the value.
// You cannot use a file path that contains an '='.
type FlagAny map[string]interface{}
func (v *FlagAny) String() string {
return ""
}
func (v *FlagAny) Set(raw string) error {
idx := strings.Index(raw, "=")
if idx >= 0 {
flag := (*Flag)(v)
return flag.Set(raw)
}
flag := (*FlagFile)(v)
return flag.Set(raw)
}

View File

@ -1,299 +0,0 @@
package variables
import (
"flag"
"fmt"
"io/ioutil"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestFlagAny_impl(t *testing.T) {
var _ flag.Value = new(FlagAny)
}
func TestFlagAny(t *testing.T) {
cases := []struct {
Input interface{}
Output map[string]interface{}
Error bool
}{
{
"=value",
nil,
true,
},
{
" =value",
nil,
true,
},
{
"key=value",
map[string]interface{}{"key": "value"},
false,
},
{
"key=",
map[string]interface{}{"key": ""},
false,
},
{
"key=foo=bar",
map[string]interface{}{"key": "foo=bar"},
false,
},
{
"key=false",
map[string]interface{}{"key": "false"},
false,
},
{
"key =value",
map[string]interface{}{"key": "value"},
false,
},
{
"key = value",
map[string]interface{}{"key": " value"},
false,
},
{
`key = "value"`,
map[string]interface{}{"key": "value"},
false,
},
{
"map.key=foo",
map[string]interface{}{"map.key": "foo"},
false,
},
{
"key",
nil,
true,
},
{
`key=["hello", "world"]`,
map[string]interface{}{"key": []interface{}{"hello", "world"}},
false,
},
{
`key={"hello" = "world", "foo" = "bar"}`,
map[string]interface{}{
"key": map[string]interface{}{
"hello": "world",
"foo": "bar",
},
},
false,
},
{
`key={"hello" = "world", "foo" = "bar"}\nkey2="invalid"`,
nil,
true,
},
{
"key=/path",
map[string]interface{}{"key": "/path"},
false,
},
{
"key=1234.dkr.ecr.us-east-1.amazonaws.com/proj:abcdef",
map[string]interface{}{"key": "1234.dkr.ecr.us-east-1.amazonaws.com/proj:abcdef"},
false,
},
// simple values that can parse as numbers should remain strings
{
"key=1",
map[string]interface{}{
"key": "1",
},
false,
},
{
"key=1.0",
map[string]interface{}{
"key": "1.0",
},
false,
},
{
"key=0x10",
map[string]interface{}{
"key": "0x10",
},
false,
},
// Test setting multiple times
{
[]string{
"foo=bar",
"bar=baz",
},
map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
false,
},
// Test map merging
{
[]string{
`foo={ foo = "bar" }`,
`foo={ bar = "baz" }`,
},
map[string]interface{}{
"foo": map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
var input []string
switch v := tc.Input.(type) {
case string:
input = []string{v}
case []string:
input = v
default:
t.Fatalf("bad input type: %T", tc.Input)
}
f := new(FlagAny)
for i, single := range input {
err := f.Set(single)
// Only check for expected errors on the final input
expected := tc.Error && i == len(input)-1
if err != nil != expected {
t.Fatalf("bad error. Input: %#v\n\nError: %s", single, err)
}
}
actual := map[string]interface{}(*f)
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("bad:\nexpected: %s\n\n got: %s\n", spew.Sdump(tc.Output), spew.Sdump(actual))
}
})
}
}
func TestFlagAny_file(t *testing.T) {
inputLibucl := `
foo = "bar"
`
inputMap := `
foo = {
k = "v"
}`
inputJson := `{
"foo": "bar"}`
cases := []struct {
Input interface{}
Output map[string]interface{}
Error bool
}{
{
inputLibucl,
map[string]interface{}{"foo": "bar"},
false,
},
{
inputJson,
map[string]interface{}{"foo": "bar"},
false,
},
{
`map.key = "foo"`,
map[string]interface{}{"map.key": "foo"},
false,
},
{
inputMap,
map[string]interface{}{
"foo": map[string]interface{}{
"k": "v",
},
},
false,
},
{
[]string{
`foo = { "k" = "v"}`,
`foo = { "j" = "v" }`,
},
map[string]interface{}{
"foo": map[string]interface{}{
"k": "v",
"j": "v",
},
},
false,
},
}
path := testTempFile(t)
for i, tc := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
var input []string
switch i := tc.Input.(type) {
case string:
input = []string{i}
case []string:
input = i
default:
t.Fatalf("bad input type: %T", i)
}
f := new(FlagAny)
for _, input := range input {
if err := ioutil.WriteFile(path, []byte(input), 0644); err != nil {
t.Fatalf("err: %s", err)
}
err := f.Set(path)
if err != nil != tc.Error {
t.Fatalf("bad error. Input: %#v, err: %s", input, err)
}
}
actual := map[string]interface{}(*f)
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("bad: %#v", actual)
}
})
}
}

View File

@ -1,65 +0,0 @@
package variables
import (
"fmt"
"io/ioutil"
"github.com/hashicorp/hcl"
"github.com/mitchellh/go-homedir"
)
// FlagFile is a flag.Value implementation for parsing user variables
// from the command line in the form of files. i.e. '-var-file=foo'
type FlagFile map[string]interface{}
func (v *FlagFile) String() string {
return ""
}
func (v *FlagFile) Set(raw string) error {
vs, err := loadKVFile(raw)
if err != nil {
return err
}
*v = Merge(*v, vs)
return nil
}
func loadKVFile(rawPath string) (map[string]interface{}, error) {
path, err := homedir.Expand(rawPath)
if err != nil {
return nil, fmt.Errorf(
"Error expanding path: %s", err)
}
// Read the HCL file and prepare for parsing
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf(
"Error reading %s: %s", path, err)
}
// Parse it
obj, err := hcl.Parse(string(d))
if err != nil {
return nil, fmt.Errorf(
"Error parsing %s: %s", path, err)
}
var result map[string]interface{}
if err := hcl.DecodeObject(&result, obj); err != nil {
return nil, fmt.Errorf(
"Error decoding Terraform vars file: %s\n\n"+
"The vars file should be in the format of `key = \"value\"`.\n"+
"Decoding errors are usually caused by an invalid format.",
err)
}
err = flattenMultiMaps(result)
if err != nil {
return nil, err
}
return result, nil
}

View File

@ -1,107 +0,0 @@
package variables
import (
"flag"
"fmt"
"io/ioutil"
"reflect"
"testing"
)
func TestFlagFile_impl(t *testing.T) {
var _ flag.Value = new(FlagFile)
}
func TestFlagFile(t *testing.T) {
inputLibucl := `
foo = "bar"
`
inputMap := `
foo = {
k = "v"
}`
inputJson := `{
"foo": "bar"}`
cases := []struct {
Input interface{}
Output map[string]interface{}
Error bool
}{
{
inputLibucl,
map[string]interface{}{"foo": "bar"},
false,
},
{
inputJson,
map[string]interface{}{"foo": "bar"},
false,
},
{
`map.key = "foo"`,
map[string]interface{}{"map.key": "foo"},
false,
},
{
inputMap,
map[string]interface{}{
"foo": map[string]interface{}{
"k": "v",
},
},
false,
},
{
[]string{
`foo = { "k" = "v"}`,
`foo = { "j" = "v" }`,
},
map[string]interface{}{
"foo": map[string]interface{}{
"k": "v",
"j": "v",
},
},
false,
},
}
path := testTempFile(t)
for i, tc := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
var input []string
switch i := tc.Input.(type) {
case string:
input = []string{i}
case []string:
input = i
default:
t.Fatalf("bad input type: %T", i)
}
f := new(FlagFile)
for _, input := range input {
if err := ioutil.WriteFile(path, []byte(input), 0644); err != nil {
t.Fatalf("err: %s", err)
}
err := f.Set(path)
if err != nil != tc.Error {
t.Fatalf("bad error. Input: %#v, err: %s", input, err)
}
}
actual := map[string]interface{}(*f)
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("bad: %#v", actual)
}
})
}
}

View File

@ -1,204 +0,0 @@
package variables
import (
"flag"
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
func TestFlag_impl(t *testing.T) {
var _ flag.Value = new(Flag)
}
func TestFlag(t *testing.T) {
cases := []struct {
Input interface{}
Output map[string]interface{}
Error bool
}{
{
"=value",
nil,
true,
},
{
" =value",
nil,
true,
},
{
"key=value",
map[string]interface{}{"key": "value"},
false,
},
{
"key=",
map[string]interface{}{"key": ""},
false,
},
{
"key=foo=bar",
map[string]interface{}{"key": "foo=bar"},
false,
},
{
"key=false",
map[string]interface{}{"key": "false"},
false,
},
{
"key =value",
map[string]interface{}{"key": "value"},
false,
},
{
"key = value",
map[string]interface{}{"key": " value"},
false,
},
{
`key = "value"`,
map[string]interface{}{"key": "value"},
false,
},
{
"map.key=foo",
map[string]interface{}{"map.key": "foo"},
false,
},
{
"key",
nil,
true,
},
{
`key=["hello", "world"]`,
map[string]interface{}{"key": []interface{}{"hello", "world"}},
false,
},
{
`key={"hello" = "world", "foo" = "bar"}`,
map[string]interface{}{
"key": map[string]interface{}{
"hello": "world",
"foo": "bar",
},
},
false,
},
{
`key={"hello" = "world", "foo" = "bar"}\nkey2="invalid"`,
nil,
true,
},
{
"key=/path",
map[string]interface{}{"key": "/path"},
false,
},
{
"key=1234.dkr.ecr.us-east-1.amazonaws.com/proj:abcdef",
map[string]interface{}{"key": "1234.dkr.ecr.us-east-1.amazonaws.com/proj:abcdef"},
false,
},
// simple values that can parse as numbers should remain strings
{
"key=1",
map[string]interface{}{
"key": "1",
},
false,
},
{
"key=1.0",
map[string]interface{}{
"key": "1.0",
},
false,
},
{
"key=0x10",
map[string]interface{}{
"key": "0x10",
},
false,
},
// Test setting multiple times
{
[]string{
"foo=bar",
"bar=baz",
},
map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
false,
},
// Test map merging
{
[]string{
`foo={ foo = "bar" }`,
`foo={ bar = "baz" }`,
},
map[string]interface{}{
"foo": map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Input), func(t *testing.T) {
var input []string
switch v := tc.Input.(type) {
case string:
input = []string{v}
case []string:
input = v
default:
t.Fatalf("bad input type: %T", tc.Input)
}
f := new(Flag)
for i, single := range input {
err := f.Set(single)
// Only check for expected errors on the final input
expected := tc.Error && i == len(input)-1
if err != nil != expected {
t.Fatalf("bad error. Input: %#v\n\nError: %s", single, err)
}
}
actual := map[string]interface{}(*f)
if !reflect.DeepEqual(actual, tc.Output) {
t.Fatalf("bad:\nexpected: %s\n\n got: %s\n", spew.Sdump(tc.Output), spew.Sdump(actual))
}
})
}
}

View File

@ -1,66 +0,0 @@
package variables
// Merge merges raw variable values b into a.
//
// The parameters given here should be the full map of set variables, such
// as those created by Flag and FlagFile.
//
// The merge behavior is to override the top-level key except for map
// types. Map types are merged together by key. Any other types are overwritten:
// primitives and lists.
//
// This returns the resulting map. This merges into a but if a is nil a new
// map will be allocated. A non-nil "a" value is returned regardless.
func Merge(a, b map[string]interface{}) map[string]interface{} {
if a == nil {
a = map[string]interface{}{}
}
for k, raw := range b {
switch v := raw.(type) {
case map[string]interface{}:
// For maps, we do a deep merge. If the value in the original
// map (a) is not a map, we just overwrite. For invalid types
// they're caught later in the validation step in Terraform.
// If there is no value set, just set it
rawA, ok := a[k]
if !ok {
a[k] = v
continue
}
// If the value is not a map, just set it
mapA, ok := rawA.(map[string]interface{})
if !ok {
a[k] = v
continue
}
// Go over the values in the map. If we're setting a raw value,
// then override. If we're setting a nested map, then recurse.
for k, v := range v {
// If the value isn't a map, then there is nothing to merge
// further so we just set it.
mv, ok := v.(map[string]interface{})
if !ok {
mapA[k] = v
continue
}
switch av := mapA[k].(type) {
case map[string]interface{}:
mapA[k] = Merge(av, mv)
default:
// Unset or non-map, just set it
mapA[k] = mv
}
}
default:
// Any other type we just set directly
a[k] = v
}
}
return a
}

View File

@ -1,93 +0,0 @@
package variables
import (
"fmt"
"reflect"
"testing"
)
func TestMerge(t *testing.T) {
cases := []struct {
Name string
A, B map[string]interface{}
Expected map[string]interface{}
}{
{
"basic key/value",
map[string]interface{}{
"foo": "bar",
},
map[string]interface{}{
"bar": "baz",
},
map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
{
"map unset",
map[string]interface{}{
"foo": "bar",
},
map[string]interface{}{
"bar": map[string]interface{}{
"foo": "bar",
},
},
map[string]interface{}{
"foo": "bar",
"bar": map[string]interface{}{
"foo": "bar",
},
},
},
{
"map merge",
map[string]interface{}{
"foo": "bar",
"bar": map[string]interface{}{
"bar": "baz",
},
},
map[string]interface{}{
"bar": map[string]interface{}{
"foo": "bar",
},
},
map[string]interface{}{
"foo": "bar",
"bar": map[string]interface{}{
"foo": "bar",
"bar": "baz",
},
},
},
{
"basic k/v with lists",
map[string]interface{}{
"foo": "bar",
"bar": []interface{}{"foo"},
},
map[string]interface{}{
"bar": []interface{}{"bar"},
},
map[string]interface{}{
"foo": "bar",
"bar": []interface{}{"bar"},
},
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
actual := Merge(tc.A, tc.B)
if !reflect.DeepEqual(tc.Expected, actual) {
t.Fatalf("bad: %#v", actual)
}
})
}
}

View File

@ -1,118 +0,0 @@
package variables
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/hashicorp/hcl"
)
// ParseInput parses a manually inputed variable to a richer value.
//
// This will turn raw input into rich types such as `[]` to a real list or
// `{}` to a real map. This function should be used to parse any manual untyped
// input for variables in order to provide a consistent experience.
func ParseInput(value string) (interface{}, error) {
trimmed := strings.TrimSpace(value)
// If the value is a simple number, don't parse it as hcl because the
// variable type may actually be a string, and HCL will convert it to the
// numberic value. We could check this in the validation later, but the
// conversion may alter the string value.
if _, err := strconv.ParseInt(trimmed, 10, 64); err == nil {
return value, nil
}
if _, err := strconv.ParseFloat(trimmed, 64); err == nil {
return value, nil
}
// HCL will also parse hex as a number
if strings.HasPrefix(trimmed, "0x") {
if _, err := strconv.ParseInt(trimmed[2:], 16, 64); err == nil {
return value, nil
}
}
// If the value is a boolean value, also convert it to a simple string
// since Terraform core doesn't accept primitives as anything other
// than string for now.
if _, err := strconv.ParseBool(trimmed); err == nil {
return value, nil
}
parsed, err := hcl.Parse(fmt.Sprintf("foo=%s", trimmed))
if err != nil {
// If it didn't parse as HCL, we check if it doesn't match our
// whitelist of TF-accepted HCL types for inputs. If not, then
// we let it through as a raw string.
if !varFlagHCLRe.MatchString(trimmed) {
return value, nil
}
// This covers flags of the form `foo=bar` which is not valid HCL
// At this point, probablyName is actually the name, and the remainder
// of the expression after the equals sign is the value.
if regexp.MustCompile(`Unknown token: \d+:\d+ IDENT`).Match([]byte(err.Error())) {
return value, nil
}
return nil, fmt.Errorf(
"Cannot parse value for variable (%q) as valid HCL: %s",
value, err)
}
var decoded map[string]interface{}
if hcl.DecodeObject(&decoded, parsed); err != nil {
return nil, fmt.Errorf(
"Cannot parse value for variable (%q) as valid HCL: %s",
value, err)
}
// Cover cases such as key=
if len(decoded) == 0 {
return "", nil
}
if len(decoded) > 1 {
return nil, fmt.Errorf(
"Cannot parse value for variable (%q) as valid HCL. "+
"Only one value may be specified.",
value)
}
err = flattenMultiMaps(decoded)
if err != nil {
return "", err
}
return decoded["foo"], nil
}
var (
// This regular expression is how we check if a value for a variable
// matches what we'd expect a rich HCL value to be. For example: {
// definitely signals a map. If a value DOESN'T match this, we return
// it as a raw string.
varFlagHCLRe = regexp.MustCompile(`^["\[\{]`)
)
// Variables don't support any type that can be configured via multiple
// declarations of the same HCL map, so any instances of
// []map[string]interface{} are either a single map that can be flattened, or
// are invalid config.
func flattenMultiMaps(m map[string]interface{}) error {
for k, v := range m {
switch v := v.(type) {
case []map[string]interface{}:
switch {
case len(v) > 1:
return fmt.Errorf("multiple map declarations not supported for variables")
case len(v) == 1:
m[k] = v[0]
}
}
}
return nil
}

View File

@ -1,81 +0,0 @@
package variables
import (
"fmt"
"reflect"
"testing"
)
func TestParseInput(t *testing.T) {
cases := []struct {
Name string
Input string
Result interface{}
Error bool
}{
{
"unquoted string",
"foo",
"foo",
false,
},
{
"number",
"1",
"1",
false,
},
{
"float",
"1.2",
"1.2",
false,
},
{
"hex number",
"0x12",
"0x12",
false,
},
{
"bool",
"true",
"true",
false,
},
{
"list",
`["foo"]`,
[]interface{}{"foo"},
false,
},
{
"map",
`{ foo = "bar" }`,
map[string]interface{}{"foo": "bar"},
false,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
actual, err := ParseInput(tc.Input)
if (err != nil) != tc.Error {
t.Fatalf("err: %s", err)
}
if err != nil {
return
}
if !reflect.DeepEqual(actual, tc.Result) {
t.Fatalf("bad: %#v", actual)
}
})
}
}

View File

@ -1,3 +0,0 @@
// Package variables provides functions and types for working with
// Terraform variables provided as input.
package variables

View File

@ -1,20 +0,0 @@
package variables
import (
"io/ioutil"
"path/filepath"
"testing"
)
func testTempFile(t *testing.T) string {
return filepath.Join(testTempDir(t), "temp.dat")
}
func testTempDir(t *testing.T) string {
d, err := ioutil.TempDir("", "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
return d
}