Merge pull request #19286 from hashicorp/radeksimko/b-timeouts-parsing-fix

helper/schema: Fix timeout parsing during Provider.Diff
This commit is contained in:
Radek Simko 2018-11-06 11:18:31 +00:00 committed by GitHub
commit 7eae051a16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 160 additions and 75 deletions

View File

@ -3,6 +3,7 @@ package schema
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"strings"
"testing" "testing"
"time" "time"
@ -277,6 +278,100 @@ func TestProviderValidate(t *testing.T) {
} }
} }
func TestProviderDiff_timeoutInvalidType(t *testing.T) {
p := &Provider{
ResourcesMap: map[string]*Resource{
"blah": &Resource{
Schema: map[string]*Schema{
"foo": {
Type: TypeInt,
Optional: true,
},
},
Timeouts: &ResourceTimeout{
Create: DefaultTimeout(10 * time.Minute),
},
},
},
}
invalidCfg := map[string]interface{}{
"foo": 42,
"timeouts": []map[string]interface{}{
map[string]interface{}{
"create": "40m",
},
},
}
ic, err := config.NewRawConfig(invalidCfg)
if err != nil {
t.Fatalf("err: %s", err)
}
_, err = p.Diff(
&terraform.InstanceInfo{
Type: "blah",
},
nil,
terraform.NewResourceConfig(ic),
)
if err == nil {
t.Fatal("Expected provider.Diff to fail with invalid timeout type")
}
expectedErrMsg := "Invalid Timeout structure"
if !strings.Contains(err.Error(), expectedErrMsg) {
t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
err.Error(),
expectedErrMsg)
}
}
func TestProviderDiff_timeoutInvalidValue(t *testing.T) {
p := &Provider{
ResourcesMap: map[string]*Resource{
"blah": &Resource{
Schema: map[string]*Schema{
"foo": {
Type: TypeInt,
Optional: true,
},
},
Timeouts: &ResourceTimeout{
Create: DefaultTimeout(10 * time.Minute),
},
},
},
}
invalidCfg := map[string]interface{}{
"foo": 42,
"timeouts": map[string]interface{}{
"create": "invalid",
},
}
ic, err := config.NewRawConfig(invalidCfg)
if err != nil {
t.Fatalf("err: %s", err)
}
_, err = p.Diff(
&terraform.InstanceInfo{
Type: "blah",
},
nil,
terraform.NewResourceConfig(ic),
)
if err == nil {
t.Fatal("Expected provider.Diff to fail with invalid timeout value")
}
expectedErrMsg := "time: invalid duration invalid"
if !strings.Contains(err.Error(), expectedErrMsg) {
t.Fatalf("Unexpected error message: %q\nExpected message to contain %q",
err.Error(),
expectedErrMsg)
}
}
func TestProviderValidateResource(t *testing.T) { func TestProviderValidateResource(t *testing.T) {
cases := []struct { cases := []struct {
P *Provider P *Provider

View File

@ -220,10 +220,9 @@ func TestResourceDiff_Timeout_diff(t *testing.T) {
raw, err := config.NewRawConfig( raw, err := config.NewRawConfig(
map[string]interface{}{ map[string]interface{}{
"foo": 42, "foo": 42,
"timeouts": []map[string]interface{}{ TimeoutsConfigKey: map[string]interface{}{
map[string]interface{}{ "create": "2h",
"create": "2h", },
}},
}) })
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
@ -256,7 +255,7 @@ func TestResourceDiff_Timeout_diff(t *testing.T) {
} }
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta) t.Fatalf("Not equal Meta in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
} }
} }

View File

@ -62,55 +62,52 @@ func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig)
} }
if raw, ok := c.Config[TimeoutsConfigKey]; ok { if raw, ok := c.Config[TimeoutsConfigKey]; ok {
if configTimeouts, ok := raw.([]map[string]interface{}); ok { if timeoutValues, ok := raw.(map[string]interface{}); ok {
for _, timeoutValues := range configTimeouts { for timeKey, timeValue := range timeoutValues {
// loop through each Timeout given in the configuration and validate they // validate that we're dealing with the normal CRUD actions
// the Timeout defined in the resource var found bool
for timeKey, timeValue := range timeoutValues { for _, key := range timeoutKeys() {
// validate that we're dealing with the normal CRUD actions if timeKey == key {
var found bool found = true
for _, key := range timeoutKeys() { break
if timeKey == key {
found = true
break
}
} }
if !found {
return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey)
}
// Get timeout
rt, err := time.ParseDuration(timeValue.(string))
if err != nil {
return fmt.Errorf("Error parsing Timeout for (%s): %s", timeKey, err)
}
var timeout *time.Duration
switch timeKey {
case TimeoutCreate:
timeout = t.Create
case TimeoutUpdate:
timeout = t.Update
case TimeoutRead:
timeout = t.Read
case TimeoutDelete:
timeout = t.Delete
case TimeoutDefault:
timeout = t.Default
}
// If the resource has not delcared this in the definition, then error
// with an unsupported message
if timeout == nil {
return unsupportedTimeoutKeyError(timeKey)
}
*timeout = rt
} }
if !found {
return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey)
}
// Get timeout
rt, err := time.ParseDuration(timeValue.(string))
if err != nil {
return fmt.Errorf("Error parsing %q timeout: %s", timeKey, err)
}
var timeout *time.Duration
switch timeKey {
case TimeoutCreate:
timeout = t.Create
case TimeoutUpdate:
timeout = t.Update
case TimeoutRead:
timeout = t.Read
case TimeoutDelete:
timeout = t.Delete
case TimeoutDefault:
timeout = t.Default
}
// If the resource has not delcared this in the definition, then error
// with an unsupported message
if timeout == nil {
return unsupportedTimeoutKeyError(timeKey)
}
*timeout = rt
} }
} else { } else {
log.Printf("[WARN] Invalid Timeout structure found, skipping timeouts") log.Printf("[ERROR] Invalid timeout structure: %T", raw)
return fmt.Errorf("Invalid Timeout structure found")
} }
} }

View File

@ -16,7 +16,7 @@ func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) {
// what the resource has defined in source // what the resource has defined in source
ResourceDefaultTimeout *ResourceTimeout ResourceDefaultTimeout *ResourceTimeout
// configuration provider by user in tf file // configuration provider by user in tf file
Config []map[string]interface{} Config map[string]interface{}
// what we expect the parsed ResourceTimeout to be // what we expect the parsed ResourceTimeout to be
Expected *ResourceTimeout Expected *ResourceTimeout
// Should we have an error (key not defined in source) // Should we have an error (key not defined in source)
@ -46,10 +46,9 @@ func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) {
{ {
Name: "Use something besides 'minutes'", Name: "Use something besides 'minutes'",
ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3), ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3),
Config: []map[string]interface{}{ Config: map[string]interface{}{
map[string]interface{}{ "create": "2h",
"create": "2h", },
}},
Expected: timeoutForValues(120, 0, 5, 0, 3), Expected: timeoutForValues(120, 0, 5, 0, 3),
ShouldErr: false, ShouldErr: false,
}, },
@ -87,7 +86,7 @@ func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) {
} }
if !reflect.DeepEqual(c.Expected, timeout) { if !reflect.DeepEqual(c.Expected, timeout) {
t.Fatalf("ConfigDecode match error case (%d), expected:\n%#v\ngot:\n%#v", i, c.Expected, timeout) t.Fatalf("ConfigDecode match error case (%d).\nExpected:\n%#v\nGot:\n%#v", i, c.Expected, timeout)
} }
}) })
} }
@ -104,13 +103,9 @@ func TestResourceTimeout_ConfigDecode(t *testing.T) {
raw, err := config.NewRawConfig( raw, err := config.NewRawConfig(
map[string]interface{}{ map[string]interface{}{
"foo": "bar", "foo": "bar",
TimeoutsConfigKey: []map[string]interface{}{ TimeoutsConfigKey: map[string]interface{}{
map[string]interface{}{ "create": "2m",
"create": "2m", "update": "1m",
},
map[string]interface{}{
"update": "1m",
},
}, },
}) })
if err != nil { if err != nil {
@ -130,7 +125,7 @@ func TestResourceTimeout_ConfigDecode(t *testing.T) {
} }
if !reflect.DeepEqual(timeout, expected) { if !reflect.DeepEqual(timeout, expected) {
t.Fatalf("bad timeout decode, expected (%#v), got (%#v)", expected, timeout) t.Fatalf("bad timeout decode.\nExpected:\n%#v\nGot:\n%#v\n", expected, timeout)
} }
} }
@ -329,24 +324,24 @@ func expectedForValues(create, read, update, del, def int) map[string]interface{
return ex return ex
} }
func expectedConfigForValues(create, read, update, delete, def int) []map[string]interface{} { func expectedConfigForValues(create, read, update, delete, def int) map[string]interface{} {
ex := make([]map[string]interface{}, 0) ex := make(map[string]interface{}, 0)
if create != 0 { if create != 0 {
ex = append(ex, map[string]interface{}{"create": fmt.Sprintf("%dm", create)}) ex["create"] = fmt.Sprintf("%dm", create)
} }
if read != 0 { if read != 0 {
ex = append(ex, map[string]interface{}{"read": fmt.Sprintf("%dm", read)}) ex["read"] = fmt.Sprintf("%dm", read)
} }
if update != 0 { if update != 0 {
ex = append(ex, map[string]interface{}{"update": fmt.Sprintf("%dm", update)}) ex["update"] = fmt.Sprintf("%dm", update)
} }
if delete != 0 { if delete != 0 {
ex = append(ex, map[string]interface{}{"delete": fmt.Sprintf("%dm", delete)}) ex["delete"] = fmt.Sprintf("%dm", delete)
} }
if def != 0 { if def != 0 {
ex = append(ex, map[string]interface{}{"default": fmt.Sprintf("%dm", def)}) ex["default"] = fmt.Sprintf("%dm", def)
} }
return ex return ex
} }

View File

@ -286,10 +286,9 @@ func TestShimResourceDiff_Timeout_diff(t *testing.T) {
raw, err := config.NewRawConfig( raw, err := config.NewRawConfig(
map[string]interface{}{ map[string]interface{}{
"foo": 42, "foo": 42,
"timeouts": []map[string]interface{}{ TimeoutsConfigKey: map[string]interface{}{
map[string]interface{}{ "create": "2h",
"create": "2h", },
}},
}) })
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)