diff --git a/docs/resources/script.md b/docs/resources/script.md index 4a17908..3e5b2fb 100644 --- a/docs/resources/script.md +++ b/docs/resources/script.md @@ -1,6 +1,6 @@ # linux_script -Manage arbritrary resource by specifying commands that will be executed remotely. +Manage arbritrary resource by specifying commands that will be uploaded and executed remotely. ## Example Usage @@ -32,16 +32,18 @@ The following arguments are supported: ### lifecycle_commands -Block that contains commands to be remotely executed respective to terraform's **Create**,**Read**,**Update**, and **Delete** phase. For complex commands, use [the file function](https://www.terraform.io/docs/configuration/functions/file.html). The following arguments are supported: +Block that contains commands to be uploaded and remotely executed respective to the terraform's [**Create**, **Read**, **Update**, and **Delete** phase](https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers). For complex commands, use [the file function](https://www.terraform.io/docs/configuration/functions/file.html). The following arguments are supported: - `create` - (Required, string) Commands that will be executed in **Create** phase. -- `read` - (Required, string) Commands that will be executed in **Read** phase and after execution of `create` or `update` commands. Terraform will record the output of these commands inside `output` attributes and trigger update/recreation when it changes (in **Read** phase only). If the result of running these commands produce an error, then it will give a signal for resource recreation. In this scenario, user have three options before applying the changes: (1) do nothing since the resource has indeed become absent, (2) manually modifying the linux machine so no error will be produced in the next run, (3) update the commands. It is recommended that this operations does not do any kind of 'write' operation. -- `update` - (Optional, string) Commands that will be executed in **Update** phase. Previous `output` are accessible from stdin. Omiting this will trigger resource recreation (**Delete** -> **Create**) each time terraform detect changes. +- `read` - (Required, string) Commands that will be executed in **Read** phase and after execution of `create` or `update` commands. Terraform will record the output of these commands inside `output` attributes and trigger update/recreation when it changes (in **Read** phase only). If the result of running these commands instead produce an error, then it will give a signal for resource recreation. In this scenario, user have three options before applying the changes: (1) do nothing and apply the changes since the resource has indeed become absent, (2) manually modifying the linux machine so no error will be produced in the next run, or (3) update the commands. If (1) is choosen then `delete` script will not be executed in **Delete** phases. It is recommended that this operations does not do any kind of 'write' operation or at least safe to be retried. +- `update` - (Optional, string) Commands that will be executed in **Update** phase. The previous `output` are accessible from stdin. Omiting this will trigger resource recreation (**Delete** -> **Create**) each time terraform detect changes. - `delete` - (Required, string) Commands that will be executed in **Delete** phase. -### Resource Update +### Updating Resource -When any of the `lifecycle_commands` and/or `interpreter` are updated, then nothing will be executed (except for the current `read` commands with existing `interpreter` since it will always be executed before changes are detected). This is to mimic the behavior of an updated provider's instructions, where no previous instructions are executed. Changes to these two arguments must not be followed with changes to other arguments at the same time, or else an error will be thrown. +This resource is somewhat differ from regular terraform resource because the state does not only consist of information about the actual resource, but also the instructions to CRUD the resource. Among these arguments, `lifecycle_commands` and `interpreter` are considered as instructions while the rest are considered as the actual data. A special course of actions must be taken when these arguments are updated, or else user would get undesired behavior such as `update` command being executed when updating only the `delete` commands. + +As such, if `lifecycle_commands` and/or `interpreter` are updated, then no commands will be executed--except for the current `read` commands using the existing `interpreter`, where the outcomes will be ignored--. At the same time, no changes to other arguments are allowed, or else an error will be thrown. When successfully updated through `terraform apply`, the next terraform execution will use these new instructions and update to other arguments are allowed. ## Attribute Reference diff --git a/linux/script-resource.go b/linux/script-resource.go index 488f865..8801869 100644 --- a/linux/script-resource.go +++ b/linux/script-resource.go @@ -233,15 +233,15 @@ func (h handlerScriptResource) Create(ctx context.Context, rd *schema.ResourceDa return diag.FromErr(err) } - if err := h.read(ctx, rd, l); err != nil { - return diag.FromErr(err) - } - id, err := uuid.NewRandom() if err != nil { return diag.FromErr(err) } rd.SetId(id.String()) + + if err := h.read(ctx, rd, l); err != nil { + return diag.FromErr(err) + } return } @@ -285,6 +285,9 @@ func (h handlerScriptResource) Update(ctx context.Context, rd *schema.ResourceDa } func (h handlerScriptResource) Delete(ctx context.Context, rd *schema.ResourceData, meta interface{}) (d diag.Diagnostics) { + if cast.ToBool(rd.Get(attrScriptReadFailed)) { + return + } l := meta.(*linux) sc := h.newScript(rd, l, attrScriptLifecycleCommandDelete) if _, err := sc.exec(ctx); err != nil { diff --git a/linux/script-resource_test.go b/linux/script-resource_test.go index a09c441..7a2661e 100644 --- a/linux/script-resource_test.go +++ b/linux/script-resource_test.go @@ -144,23 +144,16 @@ func TestAccLinuxScriptNoUpdate(t *testing.T) { "Version": "1.0.0", }, } - conf2 := tfConf{ - Provider: testAccProvider, - Script: conf1.Script.Copy(), - Extra: conf1.Extra.Copy().With("Version", "2.0.0"), - } - conf3 := tfConf{ - Provider: testAccProvider, - Script: tfScript{ - Environment: conf2.Script.Environment.Copy().With("FILE", fmt.Sprintf(`"/tmp/linux1/%s"`, acctest.RandString(16))), - }, - Extra: conf2.Extra.Copy(), - } - conf4 := tfConf{ - Provider: testAccProvider, - Script: conf2.Script.Copy(), - Extra: conf2.Extra.Copy().With("Taint", `\n`), - } + conf2 := conf1.Copy(func(tc *tfConf) { + tc.Extra.With("Version", "2.0.0") + }) + conf3 := conf2.Copy(func(tc *tfConf) { + tc.Script.Environment. + With("FILE", fmt.Sprintf(`"/tmp/linux1/%s"`, acctest.RandString(16))) + }) + conf4 := conf2.Copy(func(tc *tfConf) { + tc.Extra.With("Taint", `\n`) + }) resource.Test(t, resource.TestCase{ ExternalProviders: map[string]resource.ExternalProvider{"null": {}}, @@ -434,7 +427,7 @@ func TestAccLinuxScriptFailedRead(t *testing.T) { attrScriptLifecycleCommandCreate: `"echo -n $CONTENT > $FILE"`, attrScriptLifecycleCommandRead: `"cat $FILE"`, attrScriptLifecycleCommandUpdate: `"echo -n '\n'$CONTENT >> $FILE"`, - attrScriptLifecycleCommandDelete: `"rm $FILE || true"`, + attrScriptLifecycleCommandDelete: `"rm $FILE"`, }, Environment: tfmap{ "FILE": `"/tmp/linux-test"`, @@ -452,8 +445,7 @@ func TestAccLinuxScriptFailedRead(t *testing.T) { scriptUpdatedWithOtherArguments := createFile.Copy(func(tc *tfConf) { tc.Extra.Without("ShouldDelete") tc.Script.LifecycleCommands. - With(attrScriptLifecycleCommandRead, `"cat $FILE || echo"`). - With(attrScriptLifecycleCommandDelete, `"rm $FILE"`) + With(attrScriptLifecycleCommandRead, `"cat $FILE || echo"`) tc.Script.Environment.With("CONTENT", `"test2"`) }) scriptUpdated := scriptUpdatedWithOtherArguments.Copy(func(tc *tfConf) {