Skip to content

Commit 335cecd

Browse files
authored
Merge pull request #80 from TelkomIndonesia/feature/costumize-diff
costumize diff (#69) (#77) (#78)
2 parents b37971c + 1054d10 commit 335cecd

File tree

2 files changed

+270
-9
lines changed

2 files changed

+270
-9
lines changed

shell/resource_shell_script.go

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func resourceShellScript() *schema.Resource {
1818
Importer: &schema.ResourceImporter{
1919
State: schema.ImportStatePassthrough,
2020
},
21+
CustomizeDiff: resourceShellScriptCustomizeDiff,
2122
Schema: map[string]*schema.Schema{
2223
"lifecycle_commands": {
2324
Type: schema.TypeList,
@@ -82,6 +83,11 @@ func resourceShellScript() *schema.Resource {
8283
Optional: true,
8384
Default: false,
8485
},
86+
"read_error": {
87+
Type: schema.TypeString,
88+
Optional: true,
89+
Default: "",
90+
},
8591
},
8692
}
8793
}
@@ -92,7 +98,16 @@ func resourceShellScriptCreate(d *schema.ResourceData, meta interface{}) error {
9298
}
9399

94100
func resourceShellScriptRead(d *schema.ResourceData, meta interface{}) error {
95-
return read(d, meta, []Action{ActionRead})
101+
err := read(d, meta, []Action{ActionRead})
102+
if err != nil {
103+
_ = d.Set("read_error", err.Error())
104+
} else {
105+
_ = d.Set("read_error", "")
106+
}
107+
108+
// Error could be caused by bugs in the script.
109+
// Give user chance to fix the script or continue to recreate the resource
110+
return nil
96111
}
97112

98113
func resourceShellScriptUpdate(d *schema.ResourceData, meta interface{}) error {
@@ -103,6 +118,44 @@ func resourceShellScriptDelete(d *schema.ResourceData, meta interface{}) error {
103118
return delete(d, meta, []Action{ActionDelete})
104119
}
105120

121+
func resourceShellScriptCustomizeDiff(d *schema.ResourceDiff, i interface{}) (err error) {
122+
if d.Id() == "" {
123+
return
124+
}
125+
126+
if d.HasChange("lifecycle_commands") || d.HasChange("interpreter") {
127+
return
128+
}
129+
130+
if v, _ := d.GetChange("read_error"); v != nil {
131+
if e, _ := v.(string); e != "" {
132+
_ = d.ForceNew("read_error") // read error, force recreation
133+
return
134+
}
135+
}
136+
137+
if _, ok := d.GetOk("lifecycle_commands.0.update"); ok {
138+
return // updateable
139+
}
140+
141+
// all the other arguments
142+
for _, k := range []string{
143+
"environment",
144+
"sensitive_environment",
145+
"working_directory",
146+
"dirty",
147+
} {
148+
if !d.HasChange(k) {
149+
continue
150+
}
151+
err = d.ForceNew(k)
152+
if err != nil {
153+
return
154+
}
155+
}
156+
return
157+
}
158+
106159
func create(d *schema.ResourceData, meta interface{}, stack []Action) error {
107160
log.Printf("[DEBUG] Creating shell script resource...")
108161
printStackTrace(stack)
@@ -212,22 +265,52 @@ func read(d *schema.ResourceData, meta interface{}, stack []Action) error {
212265
return nil
213266
}
214267

268+
func restoreOldResourceData(rd *schema.ResourceData, except ...string) (err error) {
269+
exceptMap := map[string]bool{}
270+
for _, k := range except {
271+
exceptMap[k] = true
272+
}
273+
for _, k := range []string{
274+
"lifecycle_commands",
275+
"triggers",
276+
277+
"environment",
278+
"sensitive_environment",
279+
"interpreter",
280+
"working_directory",
281+
"output",
282+
283+
"dirty",
284+
"read_error",
285+
} {
286+
287+
if exceptMap[k] {
288+
continue
289+
}
290+
291+
o, _ := rd.GetChange(k)
292+
err = rd.Set(k, o)
293+
if err != nil {
294+
return
295+
}
296+
}
297+
298+
return
299+
}
300+
215301
func update(d *schema.ResourceData, meta interface{}, stack []Action) error {
302+
if d.HasChanges("lifecycle_commands", "interpreter") {
303+
_ = restoreOldResourceData(d, "lifecycle_commands", "interpreter", "dirty", "read_error")
304+
return nil
305+
}
306+
216307
log.Printf("[DEBUG] Updating shell script resource...")
217308
d.Set("dirty", false)
218309
printStackTrace(stack)
219310
l := d.Get("lifecycle_commands").([]interface{})
220311
c := l[0].(map[string]interface{})
221312
command := c["update"].(string)
222313

223-
//if update is not set, then treat it simply as a tainted resource - delete then recreate
224-
if len(command) == 0 {
225-
stack = append(stack, ActionDelete)
226-
delete(d, meta, stack)
227-
stack = append(stack, ActionCreate)
228-
return create(d, meta, stack)
229-
}
230-
231314
client := meta.(*Client)
232315
envVariables := getEnvironmentVariables(client, d)
233316
environment := formatEnvironmentVariables(envVariables)
@@ -250,6 +333,7 @@ func update(d *schema.ResourceData, meta interface{}, stack []Action) error {
250333
}
251334
output, err := runCommand(commandConfig)
252335
if err != nil {
336+
_ = restoreOldResourceData(d)
253337
return err
254338
}
255339

@@ -266,6 +350,10 @@ func update(d *schema.ResourceData, meta interface{}, stack []Action) error {
266350
}
267351

268352
func delete(d *schema.ResourceData, meta interface{}, stack []Action) error {
353+
if e, _ := d.Get("read_error").(string); e != "" {
354+
return nil
355+
}
356+
269357
log.Printf("[DEBUG] Deleting shell script resource...")
270358
printStackTrace(stack)
271359
l := d.Get("lifecycle_commands").([]interface{})

shell/resource_shell_script_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,176 @@ EOF
272272
}
273273
`
274274
}
275+
276+
func TestAccShellShellScript_failedUpdate(t *testing.T) {
277+
resource.Test(t, resource.TestCase{
278+
Providers: testAccProviders,
279+
Steps: []resource.TestStep{
280+
{
281+
Config: testAccShellScriptConfig_failedUpdate("value1"),
282+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "environment.VALUE", "value1"),
283+
},
284+
{
285+
Config: testAccShellScriptConfig_failedUpdate("value2"),
286+
ExpectNonEmptyPlan: true,
287+
ExpectError: regexp.MustCompile("Error occured during shell execution"),
288+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "environment.VALUE", "value1"),
289+
},
290+
},
291+
})
292+
}
293+
294+
func testAccShellScriptConfig_failedUpdate(value string) string {
295+
return fmt.Sprintf(`
296+
resource "shell_script" "shell_script" {
297+
lifecycle_commands {
298+
create = "echo"
299+
read = <<-EOF
300+
echo -n '{"test": true}'
301+
EOF
302+
update = "exit 1"
303+
delete = "echo"
304+
}
305+
environment = {
306+
VALUE = "%s"
307+
}
308+
}
309+
`, value)
310+
}
311+
312+
func testAccCheckNoFiles(files ...string) func(t *terraform.State) error {
313+
return func(t *terraform.State) error {
314+
for _, f := range files {
315+
if _, err := os.Stat(f); err == nil {
316+
return fmt.Errorf("'%s' should no longer exist", f)
317+
}
318+
}
319+
return nil
320+
}
321+
}
322+
323+
func TestAccShellShellScript_recreate(t *testing.T) {
324+
file1, file2 := "/tmp/some-file-"+acctest.RandString(16), "/tmp/some-file-"+acctest.RandString(16)
325+
resource.Test(t, resource.TestCase{
326+
Providers: testAccProviders,
327+
CheckDestroy: testAccCheckNoFiles(file1, file2),
328+
Steps: []resource.TestStep{
329+
{
330+
Config: testAccShellShellScriptConfig_recreate(file1),
331+
},
332+
{
333+
Config: testAccShellShellScriptConfig_recreate(file2),
334+
},
335+
},
336+
})
337+
}
338+
func testAccShellShellScriptConfig_recreate(filename string) string {
339+
return fmt.Sprintf(`
340+
resource "shell_script" "shell_script" {
341+
lifecycle_commands {
342+
create = <<-EOF
343+
echo -n '{"test": true}' > "$FILE"
344+
EOF
345+
read = <<-EOF
346+
cat "$FILE"
347+
EOF
348+
delete = <<-EOF
349+
rm "$FILE"
350+
EOF
351+
}
352+
environment = {
353+
FILE = "%s"
354+
}
355+
}
356+
`, filename)
357+
}
358+
359+
func TestAccShellShellScript_readFailed(t *testing.T) {
360+
file := "/tmp/test-file-" + acctest.RandString(16)
361+
resource.Test(t, resource.TestCase{
362+
Providers: testAccProviders,
363+
CheckDestroy: testAccCheckNoFiles(file),
364+
Steps: []resource.TestStep{
365+
{
366+
Config: testAccShellShellScriptConfig_readFailed(file, true),
367+
ExpectNonEmptyPlan: true,
368+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.test", "true"),
369+
},
370+
{
371+
Config: testAccShellShellScriptConfig_readFailed(file, false),
372+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.test", "true"),
373+
},
374+
},
375+
})
376+
}
377+
func testAccShellShellScriptConfig_readFailed(filename string, bug bool) string {
378+
return fmt.Sprintf(`
379+
resource "shell_script" "shell_script" {
380+
lifecycle_commands {
381+
create = <<-EOF
382+
echo -n '{"test": true}' > "$FILE"
383+
EOF
384+
read = <<-EOF
385+
{ cat "$FILE"; [ "$BUG" == "true" ] && rm "$FILE" || true ;}
386+
EOF
387+
delete = <<-EOF
388+
rm "$FILE"
389+
EOF
390+
}
391+
environment = {
392+
FILE = "%s"
393+
BUG = "%t"
394+
}
395+
}
396+
`, filename, bug)
397+
}
398+
399+
func TestAccShellShellScript_updateCommands(t *testing.T) {
400+
file := "/tmp/test-file-" + acctest.RandString(16)
401+
resource.Test(t, resource.TestCase{
402+
Providers: testAccProviders,
403+
Steps: []resource.TestStep{
404+
{
405+
Config: testAccShellShellScriptConfig_updateCommands(file, true),
406+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
407+
ExpectNonEmptyPlan: true,
408+
},
409+
{
410+
Config: testAccShellShellScriptConfig_updateCommands(file, false),
411+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
412+
},
413+
{
414+
Config: testAccShellShellScriptConfig_updateCommands(file, false),
415+
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
416+
},
417+
},
418+
})
419+
}
420+
func testAccShellShellScriptConfig_updateCommands(filename string, bug bool) string {
421+
var read = `cat "$FILE"`
422+
if bug {
423+
read = `[ -f "$FILE.bug" ] && cat "$FILE.bug" || { cat "$FILE" ; echo -n '{}' > "$FILE.bug" ;}`
424+
}
425+
426+
return fmt.Sprintf(`
427+
resource "shell_script" "shell_script" {
428+
lifecycle_commands {
429+
create = <<-EOF
430+
echo -n '{"bug": false}' > "$FILE"
431+
EOF
432+
read = <<-EOF
433+
%s
434+
EOF
435+
update = <<-EOF
436+
echo -n '{"bug": true}' > "$FILE"
437+
EOF
438+
delete = <<-EOF
439+
rm "$FILE"
440+
EOF
441+
}
442+
environment = {
443+
FILE = "%s"
444+
}
445+
}
446+
`, read, filename)
447+
}

0 commit comments

Comments
 (0)