Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bd7aa5b
initial implementation
austinvalle Oct 30, 2025
7025357
update the config directory to work running manually
austinvalle Oct 30, 2025
232b910
add count
austinvalle Oct 30, 2025
bbe4230
add test
austinvalle Oct 31, 2025
07aa729
add tests
austinvalle Oct 31, 2025
2653d24
don't encode null arguments
austinvalle Nov 3, 2025
b1ca955
add copyright headers
austinvalle Nov 3, 2025
9b226e4
add documentation and example
austinvalle Nov 3, 2025
67c13fe
remove the tests temporarily for the CI
austinvalle Nov 3, 2025
f65ffa7
lint
austinvalle Nov 3, 2025
9640fa9
use rc1 for generating docs
austinvalle Nov 3, 2025
d87fd02
add changelog and fix tests for windows
austinvalle Nov 3, 2025
2b98309
add vscode gitignore
austinvalle Nov 3, 2025
7e68f1a
refactor
austinvalle Nov 4, 2025
3103be9
first draft of data source
austinvalle Nov 4, 2025
945e12d
comments
austinvalle Nov 4, 2025
bcc4334
add stdout tests
austinvalle Nov 5, 2025
b9c3f88
refactor
austinvalle Nov 6, 2025
c7eb0ec
refactor + additional tests
austinvalle Nov 6, 2025
483a428
add another test
austinvalle Nov 6, 2025
e207dfd
remove comment (not needed)
austinvalle Nov 6, 2025
691c1f1
update docs on schema
austinvalle Nov 6, 2025
21dd9c5
add example to docs
austinvalle Nov 6, 2025
3c497b9
add changelog
austinvalle Nov 6, 2025
85affd3
add doc comment
austinvalle Nov 10, 2025
0bb679b
add invalid working directory test
austinvalle Nov 10, 2025
112917d
Merge branch 'main' into av/local-exec-ds
austinvalle Nov 13, 2025
cc983c4
adjust OS specific tests
austinvalle Nov 13, 2025
b117e90
explicitly use absolute path, since windows is weird lol
austinvalle Nov 13, 2025
c5a258e
reverting
austinvalle Nov 13, 2025
b75aba5
Fix TF 0.13 errors
austinvalle Nov 13, 2025
e3f1139
use pwd command
austinvalle Nov 13, 2025
7a61031
fix windows CI
austinvalle Nov 13, 2025
c54c7da
more WSL fixes
austinvalle Nov 13, 2025
93d0089
skip for consistency
austinvalle Nov 13, 2025
68f3a77
reformat comment
austinvalle Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20251103-153952.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'action/local_command: New action that invokes an executable on the local machine.'
time: 2025-11-03T15:39:52.741799-05:00
custom:
Issue: "450"
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20251106-180154.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'data/local_command: New data source that runs an executable on the local machine and returns the exit code, standard output data, and standard error data.'
time: 2025-11-06T18:01:54.341138-05:00
custom:
Issue: "452"
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
- name: Set up Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
# TODO: Remove once Terraform 1.14 GA is released
terraform_version: 1.14.0-rc1
terraform_wrapper: false

- name: Generate
Expand Down Expand Up @@ -66,6 +68,10 @@ jobs:
terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }}

steps:
# https://github.com/actions/runner-images/issues/7443
- name: Install yq (windows only)
if: "matrix.os == 'windows-latest'"
run: choco install yq

- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ website/vendor
# Test exclusions
!command/test-fixtures/**/*.tfstate
!command/test-fixtures/**/.terraform/

.vscode
79 changes: 79 additions & 0 deletions docs/actions/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
page_title: "local_command Action - terraform-provider-local"
subcategory: ""
description: |-
Invokes an executable on the local machine. All environment variables visible to the Terraform process are passed through to the child process. After the child process successfully executes, the stdout will be returned for Terraform to display to the user.
Any non-zero exit code will be treated as an error and will return a diagnostic to Terraform containing the stderr message if available.
---

# local_command (Action)

Invokes an executable on the local machine. All environment variables visible to the Terraform process are passed through to the child process. After the child process successfully executes, the `stdout` will be returned for Terraform to display to the user.

Any non-zero exit code will be treated as an error and will return a diagnostic to Terraform containing the `stderr` message if available.

## Example Usage

For the following bash script (`example_script.sh`):
```bash
#!/bin/bash

DATA=$(</dev/stdin)
echo "stdin: $DATA, args: $@"
```

Here is an example configuration that will run the script after a resource is created:

```terraform
resource "terraform_data" "test" {
lifecycle {
action_trigger {
events = [after_create]
actions = [action.local_command.bash_example]
}
}
}

action "local_command" "bash_example" {
config {
command = "bash"
arguments = ["example_script.sh", "arg1", "arg2"]
stdin = jsonencode({
"key1" : "value1"
"key2" : "value2"
})
}
}
```

The `stdout` will be displayed when the action is invoked:
```bash
$ terraform apply -auto-approve

# .. Terraform CLI output truncated for example purposes

Plan: 1 to add, 0 to change, 0 to destroy. Actions: 1 to invoke.
terraform_data.test: Creating...
terraform_data.test: Creation complete after 0s [id=4b41b541-5550-590a-9949-657e91baa346]
Action started: action.local_command.bash_example (triggered by terraform_data.test)
Action action.local_command.bash_example (triggered by terraform_data.test):

stdin: {"key1":"value1","key2":"value2"}, args: arg1 arg2


Action complete: action.local_command.bash_example (triggered by terraform_data.test)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

<!-- action schema generated by tfplugindocs -->
## Schema

### Required

- `command` (String) Executable name to be discovered on the PATH or absolute path to executable.

### Optional

- `arguments` (List of String) Arguments to be passed to the given command. Any `null` arguments will be removed from the list.
- `stdin` (String) Data to be passed to the given command's standard input.
- `working_directory` (String) The directory where the command should be executed. Defaults to the Terraform working directory.
63 changes: 63 additions & 0 deletions docs/data-sources/command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "local_command Data Source - terraform-provider-local"
subcategory: ""
description: |-
Runs an executable on the local machine and returns the exit code, standard output data (stdout), and standard error data (stderr). All environment variables visible to the Terraform process are passed through to the child process. Both stdout and stderr returned by this data source are UTF-8 strings, which can be decoded into Terraform values https://developer.hashicorp.com/terraform/language/expressions/types for use elsewhere in the Terraform configuration. There are built-in decoding functions such as jsondecode https://developer.hashicorp.com/terraform/language/functions/jsondecode or yamldecode https://developer.hashicorp.com/terraform/language/functions/yamldecode, and more specialized decoding functions https://developer.hashicorp.com/terraform/plugin/framework/functions/concepts can be built with a Terraform provider.
Any non-zero exit code returned by the command will be treated as an error and will return a diagnostic to Terraform containing the stderr message if available. If a non-zero exit code is expected by the command, set allow_non_zero_exit_code to true.
~> Warning This mechanism is provided as an "escape hatch" for exceptional situations where a first-class Terraform provider is not more appropriate. Its capabilities are limited in comparison to a true data source, and implementing a data source via a local executable is likely to hurt the portability of your Terraform configuration by creating dependencies on external programs and libraries that may not be available (or may need to be used differently) on different operating systems.
~> Warning HCP Terraform and Terraform Enterprise do not guarantee availability of any particular language runtimes or external programs beyond standard shell utilities, so it is not recommended to use this data source within configurations that are applied within either.
---

# local_command (Data Source)

Runs an executable on the local machine and returns the exit code, standard output data (`stdout`), and standard error data (`stderr`). All environment variables visible to the Terraform process are passed through to the child process. Both `stdout` and `stderr` returned by this data source are UTF-8 strings, which can be decoded into [Terraform values](https://developer.hashicorp.com/terraform/language/expressions/types) for use elsewhere in the Terraform configuration. There are built-in decoding functions such as [`jsondecode`](https://developer.hashicorp.com/terraform/language/functions/jsondecode) or [`yamldecode`](https://developer.hashicorp.com/terraform/language/functions/yamldecode), and more specialized [decoding functions](https://developer.hashicorp.com/terraform/plugin/framework/functions/concepts) can be built with a Terraform provider.

Any non-zero exit code returned by the command will be treated as an error and will return a diagnostic to Terraform containing the `stderr` message if available. If a non-zero exit code is expected by the command, set `allow_non_zero_exit_code` to `true`.

~> **Warning** This mechanism is provided as an "escape hatch" for exceptional situations where a first-class Terraform provider is not more appropriate. Its capabilities are limited in comparison to a true data source, and implementing a data source via a local executable is likely to hurt the portability of your Terraform configuration by creating dependencies on external programs and libraries that may not be available (or may need to be used differently) on different operating systems.

~> **Warning** HCP Terraform and Terraform Enterprise do not guarantee availability of any particular language runtimes or external programs beyond standard shell utilities, so it is not recommended to use this data source within configurations that are applied within either.

## Example Usage

```terraform
// A toy example using the JSON utility `jq` to process Terraform data
// https://jqlang.org/
data "local_command" "filter_fruit" {
command = "jq"
stdin = jsonencode([{ name = "apple" }, { name = "lemon" }, { name = "apricot" }])
arguments = [".[:2] | [.[].name]"] # Grab the first two fruit names from the list
}

output "fruit_tf" {
value = jsondecode(data.local_command.filter_fruit.stdout)
}

# Outputs:
#
# fruit_tf = [
# "apple",
# "lemon",
# ]
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `command` (String) Executable name to be discovered on the PATH or absolute path to executable.

### Optional

- `allow_non_zero_exit_code` (Boolean) Indicates that the command returning a non-zero exit code should be treated as a successful execution. Further assertions can be made of the `exit_code` value with the [`check` block](https://developer.hashicorp.com/terraform/language/block/check). Defaults to false.
- `arguments` (List of String) Arguments to be passed to the given command. Any `null` arguments will be removed from the list.
- `stdin` (String) Data to be passed to the given command's standard input as a UTF-8 string. [Terraform values](https://developer.hashicorp.com/terraform/language/expressions/types) can be encoded by any Terraform encode function, for example, [`jsonencode`](https://developer.hashicorp.com/terraform/language/functions/jsonencode).
- `working_directory` (String) The directory path where the command should be executed, either an absolute path or relative to the Terraform working directory. If not provided, defaults to the Terraform working directory.

### Read-Only

- `exit_code` (Number) The exit code returned by the command. By default, if the exit code is non-zero, the data source will return a diagnostic to Terraform. If a non-zero exit code is expected by the command, set `allow_non_zero_exit_code` to `true`.
- `stderr` (String) Data returned from the command's standard error stream. The data is returned directly from the command as a UTF-8 string and will be populated regardless of the exit code returned.
- `stdout` (String) Data returned from the command's standard output stream. The data is returned directly from the command as a UTF-8 string, which can then be decoded by any Terraform decode function, for example, [`jsondecode`](https://developer.hashicorp.com/terraform/language/functions/jsondecode).
19 changes: 19 additions & 0 deletions examples/actions/local_command/action.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
resource "terraform_data" "test" {
lifecycle {
action_trigger {
events = [after_create]
actions = [action.local_command.bash_example]
}
}
}

action "local_command" "bash_example" {
config {
command = "bash"
arguments = ["example_script.sh", "arg1", "arg2"]
stdin = jsonencode({
"key1" : "value1"
"key2" : "value2"
})
}
}
4 changes: 4 additions & 0 deletions examples/actions/local_command/example_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

DATA=$(</dev/stdin)
echo "stdin: $DATA, args: $@"
18 changes: 18 additions & 0 deletions examples/data-sources/local_command/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// A toy example using the JSON utility `jq` to process Terraform data
// https://jqlang.org/
data "local_command" "filter_fruit" {
command = "jq"
stdin = jsonencode([{ name = "apple" }, { name = "lemon" }, { name = "apricot" }])
arguments = [".[:2] | [.[].name]"] # Grab the first two fruit names from the list
}

output "fruit_tf" {
value = jsondecode(data.local_command.filter_fruit.stdout)
}

# Outputs:
#
# fruit_tf = [
# "apple",
# "lemon",
# ]
32 changes: 16 additions & 16 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ require (
github.com/hashicorp/terraform-plugin-framework v1.16.1
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-go v0.29.0
github.com/hashicorp/terraform-plugin-testing v1.13.3
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.14.0-beta.1.0.20251029152858-203e6cc410a0
)

require (
Expand All @@ -28,35 +29,34 @@ require (
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.23.0 // indirect
github.com/hashicorp/terraform-json v0.25.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 // indirect
github.com/hashicorp/terraform-exec v0.24.0 // indirect
github.com/hashicorp/terraform-json v0.27.2 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.35.0 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/grpc v1.75.1 // indirect
Expand Down
Loading