-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
helper/schema: Custom diff logic support for providers #14887
Changes from all commits
b99c615
1e8cfc5
aeb793f
f5f4e03
64cc408
6a4f7b0
a5fc664
22220fd
196d7e6
8126ee8
8af9610
ee76918
fa1fc2c
f7e4272
c6647a3
6f422d8
7d5f9ed
8a7c9a6
ad98471
931b0e1
36aa63b
3444549
f0aafe4
9625830
09e2109
529d7e6
5d5a670
3ac0cdf
f5bdbc0
12378b4
5031767
2c541e8
0c0ae3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package test | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
func testResourceCustomDiff() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: testResourceCustomDiffCreate, | ||
Read: testResourceCustomDiffRead, | ||
CustomizeDiff: testResourceCustomDiffCustomizeDiff, | ||
Update: testResourceCustomDiffUpdate, | ||
Delete: testResourceCustomDiffDelete, | ||
Schema: map[string]*schema.Schema{ | ||
"required": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"computed": { | ||
Type: schema.TypeInt, | ||
Computed: true, | ||
}, | ||
"index": { | ||
Type: schema.TypeInt, | ||
Computed: true, | ||
}, | ||
"veto": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
}, | ||
"list": { | ||
Type: schema.TypeList, | ||
Computed: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
type listDiffCases struct { | ||
Type string | ||
Value string | ||
} | ||
|
||
func testListDiffCases(index int) []listDiffCases { | ||
switch index { | ||
case 0: | ||
return []listDiffCases{ | ||
{ | ||
Type: "add", | ||
Value: "dc1", | ||
}, | ||
} | ||
case 1: | ||
return []listDiffCases{ | ||
{ | ||
Type: "remove", | ||
Value: "dc1", | ||
}, | ||
{ | ||
Type: "add", | ||
Value: "dc2", | ||
}, | ||
{ | ||
Type: "add", | ||
Value: "dc3", | ||
}, | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func testListDiffCasesReadResult(index int) []interface{} { | ||
switch index { | ||
case 1: | ||
return []interface{}{"dc1"} | ||
default: | ||
return []interface{}{"dc2", "dc3"} | ||
} | ||
} | ||
|
||
func testResourceCustomDiffCreate(d *schema.ResourceData, meta interface{}) error { | ||
d.SetId("testId") | ||
|
||
// Required must make it through to Create | ||
if _, ok := d.GetOk("required"); !ok { | ||
return fmt.Errorf("missing attribute 'required', but it's required") | ||
} | ||
|
||
_, new := d.GetChange("computed") | ||
expected := new.(int) - 1 | ||
actual := d.Get("index").(int) | ||
if expected != actual { | ||
return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual) | ||
} | ||
d.Set("index", new) | ||
|
||
return testResourceCustomDiffRead(d, meta) | ||
} | ||
|
||
func testResourceCustomDiffRead(d *schema.ResourceData, meta interface{}) error { | ||
if err := d.Set("list", testListDiffCasesReadResult(d.Get("index").(int))); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func testResourceCustomDiffCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error { | ||
if d.Get("veto").(bool) == true { | ||
return fmt.Errorf("veto is true, diff vetoed") | ||
} | ||
// Note that this gets put into state after the update, regardless of whether | ||
// or not anything is acted upon in the diff. | ||
d.SetNew("computed", d.Get("computed").(int)+1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any ill-effects if the At the very least, we'd need to document that providers shouldn't do that if so, but ideally we'd find a way to make it not arise in the first place since those errors tend to be pretty annoying to track down when the do arise in the wild. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From what I understand, I don't think there should be any ill effects. What would happen is any values that got set in with The final step of a The good old "diffs didn't match during apply" error was something I was worried about too, but I don't think we are at risk for that, mainly because I think this could be properly vetoed - the drift could be detected via checking the current real world value and then comparing it to the data from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm kind of wondering if we can possibly build some safety in here so that this can be guarded against before it hits |
||
|
||
// This tests a diffed list, based off of the value of index | ||
dcs := testListDiffCases(d.Get("index").(int)) | ||
s := d.Get("list").([]interface{}) | ||
for _, dc := range dcs { | ||
switch dc.Type { | ||
case "add": | ||
s = append(s, dc.Value) | ||
case "remove": | ||
for i := range s { | ||
if s[i].(string) == dc.Value { | ||
copy(s[i:], s[i+1:]) | ||
s = s[:len(s)-1] | ||
break | ||
} | ||
} | ||
} | ||
} | ||
d.SetNew("list", s) | ||
|
||
return nil | ||
} | ||
|
||
func testResourceCustomDiffUpdate(d *schema.ResourceData, meta interface{}) error { | ||
_, new := d.GetChange("computed") | ||
expected := new.(int) - 1 | ||
actual := d.Get("index").(int) | ||
if expected != actual { | ||
return fmt.Errorf("expected computed to be 1 ahead of index, got computed: %d, index: %d", expected, actual) | ||
} | ||
d.Set("index", new) | ||
return testResourceCustomDiffRead(d, meta) | ||
} | ||
|
||
func testResourceCustomDiffDelete(d *schema.ResourceData, meta interface{}) error { | ||
d.SetId("") | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package test | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/helper/resource" | ||
) | ||
|
||
// TestResourceWithCustomDiff test custom diff behaviour. | ||
func TestResourceWithCustomDiff(t *testing.T) { | ||
resource.UnitTest(t, resource.TestCase{ | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: resourceWithCustomDiffConfig(false), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "1"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "1"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "1"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc1"), | ||
), | ||
ExpectNonEmptyPlan: true, | ||
}, | ||
{ | ||
Config: resourceWithCustomDiffConfig(false), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "computed", "2"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "index", "2"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.#", "2"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.0", "dc2"), | ||
resource.TestCheckResourceAttr("test_resource_with_custom_diff.foo", "list.1", "dc3"), | ||
resource.TestCheckNoResourceAttr("test_resource_with_custom_diff.foo", "list.2"), | ||
), | ||
ExpectNonEmptyPlan: true, | ||
}, | ||
{ | ||
Config: resourceWithCustomDiffConfig(true), | ||
ExpectError: regexp.MustCompile("veto is true, diff vetoed"), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func resourceWithCustomDiffConfig(veto bool) string { | ||
return fmt.Sprintf(` | ||
resource "test_resource_with_custom_diff" "foo" { | ||
required = "yep" | ||
veto = %t | ||
} | ||
`, veto) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor cosmetic nit: visually most of the existing
Resource
attributes are short, so they line up nicely when formatted in a struct literal like this. Perhaps we could preserve that here by just calling thisDiff
. I think that's consistent with the other usage here, since all of these operations are in some sense "customizing" the default behavior thathelper/schema
naturally does.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense - I kind of was wondering this myself and I guess I didn't want a developer thinking that Terraform wasn't doing any diffing behaviour if this was left out now, but I do like the more concise
Diff
for sure. Will adjust!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, so it turns out that this can't necessarily function because it collides with the already existing
resource.Diff
, of course... doh!Trying to think of something else that still keeps the spirit of what we are trying to do here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about
Review
orRevise
? That is kind of what is happening here... the diff is being reviewed, and possibly being vetoed or rejected. :)Other names I could think of were either too close to CRUD-like things that already exist (like
Patch
) or just didn't really roll off the tongue like the current set of functions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes, of course. I forgot that there are actually methods on this struct. 😀
I think part of why this is hard to name is that this function kinda serves two purposes:
So
AlterDiff
seems promising if we consider the checking to be an implicit side-effect, but that's still longer than all of the other verbs here, so doesn't really make a great deal of difference.So with all of that said, maybe let's just stick with
CustomizeDiff
and accept the weird visual texture it creates... too minor a detail to have sweated this much over it. 😀There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I did go forward with changing it to
Review
last night - check 6cbfe22 for the commit and how it looks cosmetically. I can roll it back toCustomizeDiff
if you still think it's too ambiguous.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to bike-shed it too much, but to me "reviewing" a diff sounds like something a human would do rather than something code would do, and feels a little vague.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No sweat :) will roll this back. I think after this the only thing left to review will be the deposed stuff.