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:
parent
1817c8ac3c
commit
6b5ca49578
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/helper/validation"
|
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -198,14 +197,12 @@ var dataprocClusterSchema = map[string]*schema.Schema{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
ValidateFunc: validation.IntAtLeast(10),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"boot_disk_type": {
|
"boot_disk_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false),
|
|
||||||
Default: "pd-standard",
|
Default: "pd-standard",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -271,14 +268,12 @@ var dataprocClusterSchema = map[string]*schema.Schema{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
ValidateFunc: validation.IntAtLeast(10),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"boot_disk_type": {
|
"boot_disk_type": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false),
|
|
||||||
Default: "pd-standard",
|
Default: "pd-standard",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# Helper Libraries
|
# Legacy Helper Libraries
|
||||||
|
|
||||||
This folder contains helper libraries for Terraform plugins. A running
|
The packages in this directory are all legacy code. Some of them are legacy
|
||||||
joke is that this is "Terraform standard library" for plugins. The goal
|
because they are now maintained in
|
||||||
of the packages in this directory are to provide high-level helpers to
|
[the Terraform SDK](https://github.com/hashicorp/terraform-plugin-sdk),
|
||||||
make it easier to implement the various aspects of writing a plugin for
|
while others are just obsolete codepaths that we intend to migrate away
|
||||||
Terraform.
|
from over time.
|
||||||
|
|
||||||
|
Avoid using functions from packages under `helper/` in new projects.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
|
@ -3556,14 +3556,14 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
|
||||||
|
|
||||||
"Conflicting attributes cannot be required": {
|
"Conflicting attributes cannot be required": {
|
||||||
map[string]*Schema{
|
map[string]*Schema{
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeBool,
|
Type: TypeBool,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeBool,
|
Type: TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"blacklist"},
|
ConflictsWith: []string{"a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
@ -3571,10 +3571,10 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
|
||||||
|
|
||||||
"Attribute with conflicts cannot be required": {
|
"Attribute with conflicts cannot be required": {
|
||||||
map[string]*Schema{
|
map[string]*Schema{
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeBool,
|
Type: TypeBool,
|
||||||
Required: true,
|
Required: true,
|
||||||
ConflictsWith: []string{"blacklist"},
|
ConflictsWith: []string{"a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
@ -3582,14 +3582,14 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
|
||||||
|
|
||||||
"ConflictsWith cannot be used w/ ComputedWhen": {
|
"ConflictsWith cannot be used w/ ComputedWhen": {
|
||||||
map[string]*Schema{
|
map[string]*Schema{
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeBool,
|
Type: TypeBool,
|
||||||
ComputedWhen: []string{"foor"},
|
ComputedWhen: []string{"foor"},
|
||||||
},
|
},
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeBool,
|
Type: TypeBool,
|
||||||
Required: true,
|
Required: true,
|
||||||
ConflictsWith: []string{"blacklist"},
|
ConflictsWith: []string{"a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
|
@ -4811,44 +4811,44 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
|
||||||
"Conflicting attributes generate error": {
|
"Conflicting attributes generate error": {
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"whitelist"},
|
ConflictsWith: []string{"b"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"whitelist": "white-val",
|
"b": "b-val",
|
||||||
"blacklist": "black-val",
|
"a": "a-val",
|
||||||
},
|
},
|
||||||
|
|
||||||
Err: true,
|
Err: true,
|
||||||
Errors: []error{
|
Errors: []error{
|
||||||
fmt.Errorf("\"blacklist\": conflicts with whitelist"),
|
fmt.Errorf("\"a\": conflicts with b"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
"Conflicting attributes okay when unknown 1": {
|
"Conflicting attributes okay when unknown 1": {
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"whitelist"},
|
ConflictsWith: []string{"b"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"whitelist": "white-val",
|
"b": "b-val",
|
||||||
"blacklist": hcl2shim.UnknownVariableValue,
|
"a": hcl2shim.UnknownVariableValue,
|
||||||
},
|
},
|
||||||
|
|
||||||
Err: false,
|
Err: false,
|
||||||
|
@ -4856,20 +4856,20 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
|
||||||
"Conflicting attributes okay when unknown 2": {
|
"Conflicting attributes okay when unknown 2": {
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"whitelist"},
|
ConflictsWith: []string{"b"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"whitelist": hcl2shim.UnknownVariableValue,
|
"b": hcl2shim.UnknownVariableValue,
|
||||||
"blacklist": "black-val",
|
"a": "a-val",
|
||||||
},
|
},
|
||||||
|
|
||||||
Err: false,
|
Err: false,
|
||||||
|
@ -4877,33 +4877,33 @@ func TestSchemaMap_Validate(t *testing.T) {
|
||||||
|
|
||||||
"Conflicting attributes generate error even if one is unknown": {
|
"Conflicting attributes generate error even if one is unknown": {
|
||||||
Schema: map[string]*Schema{
|
Schema: map[string]*Schema{
|
||||||
"whitelist": &Schema{
|
"b": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"blacklist", "greenlist"},
|
ConflictsWith: []string{"a", "c"},
|
||||||
},
|
},
|
||||||
"blacklist": &Schema{
|
"a": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"whitelist", "greenlist"},
|
ConflictsWith: []string{"b", "c"},
|
||||||
},
|
},
|
||||||
"greenlist": &Schema{
|
"c": &Schema{
|
||||||
Type: TypeString,
|
Type: TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"whitelist", "blacklist"},
|
ConflictsWith: []string{"b", "a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"whitelist": hcl2shim.UnknownVariableValue,
|
"b": hcl2shim.UnknownVariableValue,
|
||||||
"blacklist": "black-val",
|
"a": "a-val",
|
||||||
"greenlist": "green-val",
|
"c": "c-val",
|
||||||
},
|
},
|
||||||
|
|
||||||
Err: true,
|
Err: true,
|
||||||
Errors: []error{
|
Errors: []error{
|
||||||
fmt.Errorf("\"blacklist\": conflicts with greenlist"),
|
fmt.Errorf("\"a\": conflicts with c"),
|
||||||
fmt.Errorf("\"greenlist\": conflicts with blacklist"),
|
fmt.Errorf("\"c\": conflicts with a"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +1,12 @@
|
||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"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
|
// IntBetween returns a SchemaValidateFunc which tests if the provided value
|
||||||
// is of type int and is between min and max (inclusive)
|
// is of type int and is between min and max (inclusive)
|
||||||
func IntBetween(min, max int) schema.SchemaValidateFunc {
|
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
|
// 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
|
// 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
|
// will test with in lower case if ignoreCase is true
|
||||||
|
@ -145,197 +47,3 @@ func StringInSlice(valid []string, ignoreCase bool) schema.SchemaValidateFunc {
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,69 +13,6 @@ type testCase struct {
|
||||||
expectedErr *regexp.Regexp
|
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) {
|
func TestValidationIntBetween(t *testing.T) {
|
||||||
runTestCases(t, []testCase{
|
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) {
|
func TestValidationStringInSlice(t *testing.T) {
|
||||||
runTestCases(t, []testCase{
|
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) {
|
func runTestCases(t *testing.T, cases []testCase) {
|
||||||
matchErr := func(errs []error, r *regexp.Regexp) bool {
|
matchErr := func(errs []error, r *regexp.Regexp) bool {
|
||||||
// err must match one provided
|
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package variables provides functions and types for working with
|
|
||||||
// Terraform variables provided as input.
|
|
||||||
package variables
|
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue