Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
rucciva committed Nov 12, 2020
1 parent b37971c commit 1054d10
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 9 deletions.
106 changes: 97 additions & 9 deletions shell/resource_shell_script.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func resourceShellScript() *schema.Resource {
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
CustomizeDiff: resourceShellScriptCustomizeDiff,
Schema: map[string]*schema.Schema{
"lifecycle_commands": {
Type: schema.TypeList,
Expand Down Expand Up @@ -82,6 +83,11 @@ func resourceShellScript() *schema.Resource {
Optional: true,
Default: false,
},
"read_error": {
Type: schema.TypeString,
Optional: true,
Default: "",
},
},
}
}
Expand All @@ -92,7 +98,16 @@ func resourceShellScriptCreate(d *schema.ResourceData, meta interface{}) error {
}

func resourceShellScriptRead(d *schema.ResourceData, meta interface{}) error {
return read(d, meta, []Action{ActionRead})
err := read(d, meta, []Action{ActionRead})
if err != nil {
_ = d.Set("read_error", err.Error())
} else {
_ = d.Set("read_error", "")
}

// Error could be caused by bugs in the script.
// Give user chance to fix the script or continue to recreate the resource
return nil
}

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

func resourceShellScriptCustomizeDiff(d *schema.ResourceDiff, i interface{}) (err error) {
if d.Id() == "" {
return
}

if d.HasChange("lifecycle_commands") || d.HasChange("interpreter") {
return
}

if v, _ := d.GetChange("read_error"); v != nil {
if e, _ := v.(string); e != "" {
_ = d.ForceNew("read_error") // read error, force recreation
return
}
}

if _, ok := d.GetOk("lifecycle_commands.0.update"); ok {
return // updateable
}

// all the other arguments
for _, k := range []string{
"environment",
"sensitive_environment",
"working_directory",
"dirty",
} {
if !d.HasChange(k) {
continue
}
err = d.ForceNew(k)
if err != nil {
return
}
}
return
}

func create(d *schema.ResourceData, meta interface{}, stack []Action) error {
log.Printf("[DEBUG] Creating shell script resource...")
printStackTrace(stack)
Expand Down Expand Up @@ -212,22 +265,52 @@ func read(d *schema.ResourceData, meta interface{}, stack []Action) error {
return nil
}

func restoreOldResourceData(rd *schema.ResourceData, except ...string) (err error) {
exceptMap := map[string]bool{}
for _, k := range except {
exceptMap[k] = true
}
for _, k := range []string{
"lifecycle_commands",
"triggers",

"environment",
"sensitive_environment",
"interpreter",
"working_directory",
"output",

"dirty",
"read_error",
} {

if exceptMap[k] {
continue
}

o, _ := rd.GetChange(k)
err = rd.Set(k, o)
if err != nil {
return
}
}

return
}

func update(d *schema.ResourceData, meta interface{}, stack []Action) error {
if d.HasChanges("lifecycle_commands", "interpreter") {
_ = restoreOldResourceData(d, "lifecycle_commands", "interpreter", "dirty", "read_error")
return nil
}

log.Printf("[DEBUG] Updating shell script resource...")
d.Set("dirty", false)
printStackTrace(stack)
l := d.Get("lifecycle_commands").([]interface{})
c := l[0].(map[string]interface{})
command := c["update"].(string)

//if update is not set, then treat it simply as a tainted resource - delete then recreate
if len(command) == 0 {
stack = append(stack, ActionDelete)
delete(d, meta, stack)
stack = append(stack, ActionCreate)
return create(d, meta, stack)
}

client := meta.(*Client)
envVariables := getEnvironmentVariables(client, d)
environment := formatEnvironmentVariables(envVariables)
Expand All @@ -250,6 +333,7 @@ func update(d *schema.ResourceData, meta interface{}, stack []Action) error {
}
output, err := runCommand(commandConfig)
if err != nil {
_ = restoreOldResourceData(d)
return err
}

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

func delete(d *schema.ResourceData, meta interface{}, stack []Action) error {
if e, _ := d.Get("read_error").(string); e != "" {
return nil
}

log.Printf("[DEBUG] Deleting shell script resource...")
printStackTrace(stack)
l := d.Get("lifecycle_commands").([]interface{})
Expand Down
173 changes: 173 additions & 0 deletions shell/resource_shell_script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,176 @@ EOF
}
`
}

func TestAccShellShellScript_failedUpdate(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccShellScriptConfig_failedUpdate("value1"),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "environment.VALUE", "value1"),
},
{
Config: testAccShellScriptConfig_failedUpdate("value2"),
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile("Error occured during shell execution"),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "environment.VALUE", "value1"),
},
},
})
}

func testAccShellScriptConfig_failedUpdate(value string) string {
return fmt.Sprintf(`
resource "shell_script" "shell_script" {
lifecycle_commands {
create = "echo"
read = <<-EOF
echo -n '{"test": true}'
EOF
update = "exit 1"
delete = "echo"
}
environment = {
VALUE = "%s"
}
}
`, value)
}

func testAccCheckNoFiles(files ...string) func(t *terraform.State) error {
return func(t *terraform.State) error {
for _, f := range files {
if _, err := os.Stat(f); err == nil {
return fmt.Errorf("'%s' should no longer exist", f)
}
}
return nil
}
}

func TestAccShellShellScript_recreate(t *testing.T) {
file1, file2 := "/tmp/some-file-"+acctest.RandString(16), "/tmp/some-file-"+acctest.RandString(16)
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: testAccCheckNoFiles(file1, file2),
Steps: []resource.TestStep{
{
Config: testAccShellShellScriptConfig_recreate(file1),
},
{
Config: testAccShellShellScriptConfig_recreate(file2),
},
},
})
}
func testAccShellShellScriptConfig_recreate(filename string) string {
return fmt.Sprintf(`
resource "shell_script" "shell_script" {
lifecycle_commands {
create = <<-EOF
echo -n '{"test": true}' > "$FILE"
EOF
read = <<-EOF
cat "$FILE"
EOF
delete = <<-EOF
rm "$FILE"
EOF
}
environment = {
FILE = "%s"
}
}
`, filename)
}

func TestAccShellShellScript_readFailed(t *testing.T) {
file := "/tmp/test-file-" + acctest.RandString(16)
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: testAccCheckNoFiles(file),
Steps: []resource.TestStep{
{
Config: testAccShellShellScriptConfig_readFailed(file, true),
ExpectNonEmptyPlan: true,
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.test", "true"),
},
{
Config: testAccShellShellScriptConfig_readFailed(file, false),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.test", "true"),
},
},
})
}
func testAccShellShellScriptConfig_readFailed(filename string, bug bool) string {
return fmt.Sprintf(`
resource "shell_script" "shell_script" {
lifecycle_commands {
create = <<-EOF
echo -n '{"test": true}' > "$FILE"
EOF
read = <<-EOF
{ cat "$FILE"; [ "$BUG" == "true" ] && rm "$FILE" || true ;}
EOF
delete = <<-EOF
rm "$FILE"
EOF
}
environment = {
FILE = "%s"
BUG = "%t"
}
}
`, filename, bug)
}

func TestAccShellShellScript_updateCommands(t *testing.T) {
file := "/tmp/test-file-" + acctest.RandString(16)
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccShellShellScriptConfig_updateCommands(file, true),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
ExpectNonEmptyPlan: true,
},
{
Config: testAccShellShellScriptConfig_updateCommands(file, false),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
},
{
Config: testAccShellShellScriptConfig_updateCommands(file, false),
Check: resource.TestCheckResourceAttr("shell_script.shell_script", "output.bug", "false"),
},
},
})
}
func testAccShellShellScriptConfig_updateCommands(filename string, bug bool) string {
var read = `cat "$FILE"`
if bug {
read = `[ -f "$FILE.bug" ] && cat "$FILE.bug" || { cat "$FILE" ; echo -n '{}' > "$FILE.bug" ;}`
}

return fmt.Sprintf(`
resource "shell_script" "shell_script" {
lifecycle_commands {
create = <<-EOF
echo -n '{"bug": false}' > "$FILE"
EOF
read = <<-EOF
%s
EOF
update = <<-EOF
echo -n '{"bug": true}' > "$FILE"
EOF
delete = <<-EOF
rm "$FILE"
EOF
}
environment = {
FILE = "%s"
}
}
`, read, filename)
}

0 comments on commit 1054d10

Please sign in to comment.