scottwinkler / terraform-provider-shell

Terraform provider for executing shell commands and saving output to state file
Mozilla Public License 2.0
279 stars 60 forks source link

Suggestion: Use CustomizeDiff and ForceNew if Update is empty #78

Closed rucciva closed 3 years ago

rucciva commented 3 years ago

Calling Delete -> Create when Update is empty will result in Delete operation done using new state, instead of old state. For example in the following script, Delete will run rm /tmp/shell/folder/test1 instead of rm /tmp/shell/folder/test (which surprisingly does not produce error even though the script return error due to file not exist) when updating environment.File.

locals {
  path = "/tmp/shell/folder"  # change this
}

resource "null_resource" "directory"{
  triggers = {
    path = local.path
  }
  provisioner "local-exec" {
    command = "mkdir -p ${self.triggers.path}"
  }
  provisioner "local-exec" {
    when = destroy
    command = "rm -rf ${self.triggers.path}"
  }
}

resource "shell_script" "github_repository" {
  lifecycle_commands {
    create = "touch $FILE"
    read   = "echo '{\"name\":\"'\"$FILE\"'\"}'"
    delete = "rm $FILE"
  }

  environment = {
    FILE = "${null_resource.directory.triggers["path"]}/test1"
  }

}
$ ls /tmp/shell
ls: /tmp/shell: No such file or directory
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # null_resource.directory will be created
  + resource "null_resource" "directory" {
      + id       = (known after apply)
      + triggers = {
          + "path" = "/tmp/shell/folder"
        }
    }

  # shell_script.github_repository will be created
  + resource "shell_script" "github_repository" {
      + dirty             = false
      + environment       = {
          + "FILE" = "/tmp/shell/folder/test"
        }
      + id                = (known after apply)
      + output            = (known after apply)
      + working_directory = "."

      + lifecycle_commands {
          + create = "touch $FILE"
          + delete = "rm $FILE"
          + read   = "echo '{\"name\":\"'\"$FILE\"'\"}'"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

null_resource.directory: Creating...
null_resource.directory: Provisioning with 'local-exec'...
null_resource.directory (local-exec): Executing: ["/bin/sh" "-c" "mkdir -p /tmp/shell/folder"]
null_resource.directory: Creation complete after 0s [id=8706134334622958825]
shell_script.github_repository: Creating...
shell_script.github_repository: Creation complete after 0s [id=buchmouvvhfkg3qaorj0]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
$ ls  /tmp/shell/folder/
test
$ terraform apply
null_resource.directory: Refreshing state... [id=8706134334622958825]
shell_script.github_repository: Refreshing state... [id=buchmouvvhfkg3qaorj0]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # shell_script.github_repository will be updated in-place
  ~ resource "shell_script" "github_repository" {
        dirty             = false
      ~ environment       = {
          ~ "FILE" = "/tmp/shell/folder/test" -> "/tmp/shell/folder/test1"
        }
        id                = "buchmouvvhfkg3qaorj0"
        output            = {
            "name" = "/tmp/shell/folder/test"
        }
        working_directory = "."

        lifecycle_commands {
            create = "touch $FILE"
            delete = "rm $FILE"
            read   = "echo '{\"name\":\"'\"$FILE\"'\"}'"
        }
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

shell_script.github_repository: Modifying... [id=buchmouvvhfkg3qaorj0]
shell_script.github_repository: Modifications complete after 0s [id=buchmv6vvhfkgjj4kq20]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
$ ls  /tmp/shell/folder/
test    test1

i suggest using CustomizeDiffFunc as such

                CustomizeDiff: func(c context.Context, rd *schema.ResourceDiff, i interface{}) (err error) {
            if rd.Id() == "" {
                return
            }
            if _, ok := rd.GetOk("lifecycle_commands.0.update"); ok {
                return
            }
            for _, key := range rd.GetChangedKeysPrefix("") {
                                 if strings.HasPrefix(key, "triggers") {
                    continue // already force new
                }

                // need to remove index from map and list
                switch {
                case strings.HasPrefix(key, "environment"):
                    key = strings.Split(key, ".")[0]
                case strings.HasPrefix(key, "sensitive_environment"):
                    key = strings.Split(key, ".")[0]
                case strings.HasPrefix(key, "interpreter"):
                    key = strings.Split(key, ".")[0]
                }

                err = rd.ForceNew(key)
                if err != nil {
                    return
                }
            }
            return
        },