diff --git a/README.md b/README.md
index 4bacc38..9594eff 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,11 @@ Run Ansible playbooks using Terraform.
```terraform
resource "ansible_navigator_run" "webservers_example" {
playbook = <<-EOT
- - hosts: webservers
+ - name: Example
+ hosts: webservers
tasks:
- - ansible.builtin.package:
+ - name: Install nginx
+ ansible.builtin.package:
name: nginx
EOT
inventory = yamlencode({
@@ -22,6 +24,24 @@ resource "ansible_navigator_run" "webservers_example" {
}
})
}
+
+data "ansible_navigator_run" "uptime_example" {
+ playbook = <<-EOT
+ - name: Example
+ hosts: all
+ EOT
+ inventory = yamlencode({})
+ artifact_queries = {
+ "uptimes" = {
+ jq_filter = "[.plays[] | select(.name==\"Example\") | .tasks[] | select(.task==\"Gathering Facts\") | {host: .host, uptime_seconds: .res.ansible_facts.ansible_uptime_seconds }]"
+ }
+ }
+}
+
+output "uptimes" {
+ value = jsondecode(data.ansible_navigator_run.uptime_example.artifact_queries.uptimes.results[0])
+}
+
```
## Features
@@ -29,7 +49,7 @@ resource "ansible_navigator_run" "webservers_example" {
1. Run Ansible playbooks against Terraform managed infrastructure (without the `local-exec` provisioner). Eliminates the need for additional scripting or pipeline steps.
2. Construct Ansible inventories using other data sources and resources. Set Ansible host and group variables to values and secrets from other providers.
3. Utilize Ansible [execution environments](https://ansible.readthedocs.io/en/latest/getting_started_ee/index.html) (containers images) to customize and run the Ansible software stack. Isolate Ansible and its related dependencies (Python/System packages, collections, etc) to simplify pipeline and workstation setup.
-4. Write JSONPath queries against [playbook artifacts](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator). Extract values from the playbook run for use elsewhere in the Terraform configuration. Examples include: Ansible facts, remote file contents, task results -- the possibilities are endless!
+4. Write [`jq`](https://jqlang.github.io/jq/) queries against [playbook artifacts](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator). Extract values from the playbook run for use elsewhere in the Terraform configuration. Examples include: Ansible facts, remote file contents, task results -- the possibilities are endless!
5. Control playbook re-run behavior using several "lifecycle" options, including an attribute for running the playbook on resource destruction. Implement conditional plays/tasks with the environment variable `ANSIBLE_TF_OPERATION`.
6. Connect to hosts securely by specifying SSH private keys and known host entries. No need manage `~/.ssh` files or setup `ssh-agent` in the environment which Terraform runs.
diff --git a/docs/data-sources/navigator_run.md b/docs/data-sources/navigator_run.md
index 1d65fe8..63bafe1 100644
--- a/docs/data-sources/navigator_run.md
+++ b/docs/data-sources/navigator_run.md
@@ -39,23 +39,22 @@ data "ansible_navigator_run" "inline" {
# 2. artifact queries -- get file contents
data "ansible_navigator_run" "artifact_query_file" {
playbook = <<-EOT
- - name: Get file
- hosts: all
+ - name: Example
tasks:
- - name: resolv.conf
+ - name: Get file
ansible.builtin.slurp:
src: /etc/resolv.conf
EOT
- inventory = "..."
+ inventory = yamlencode({})
artifact_queries = {
"resolv_conf" = {
- jsonpath = "$.plays[?(@.__play_name=='Get file')].tasks[?(@.__task=='resolv.conf')].res.content"
+ jq_filter = ".plays[] | select(.name==\"Example\") | .tasks[] | select(.task==\"Get file\") | .res.content"
}
}
}
output "resolv_conf" {
- value = base64decode(data.ansible_navigator_run.artifact_query_file.artifact_queries.resolv_conf.result)
+ value = base64decode(jsondecode(data.ansible_navigator_run.artifact_query_file.artifact_queries.resolv_conf.results[0]))
}
```
@@ -71,7 +70,7 @@ output "resolv_conf" {
- `ansible_navigator_binary` (String) Path to the `ansible-navigator` binary. By default `$PATH` is searched.
- `ansible_options` (Attributes) Ansible [playbook](https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html) run related configuration. (see [below for nested schema](#nestedatt--ansible_options))
-- `artifact_queries` (Attributes Map) Query the playbook artifact with [JSONPath](https://goessner.net/articles/JsonPath/). The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run. (see [below for nested schema](#nestedatt--artifact_queries))
+- `artifact_queries` (Attributes Map) Query the Ansible playbook artifact with [`jq`](https://jqlang.github.io/jq/) syntax. The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run. (see [below for nested schema](#nestedatt--artifact_queries))
- `execution_environment` (Attributes) [Execution environment](https://ansible.readthedocs.io/en/latest/getting_started_ee/index.html) (EE) related configuration. (see [below for nested schema](#nestedatt--execution_environment))
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `timezone` (String) IANA time zone, use `local` for the system time zone. Defaults to `UTC`.
@@ -110,11 +109,11 @@ Required:
Required:
-- `jsonpath` (String) JSONPath expression.
+- `jq_filter` (String) `jq` filter. Example: `.status, .stdout`.
Read-Only:
-- `result` (String) Result of the query. Result may be empty if a field or map key cannot be located.
+- `results` (List of String) Results of the `jq` filter in JSON format.
diff --git a/docs/resources/navigator_run.md b/docs/resources/navigator_run.md
index 6b0d901..7fc6b62 100644
--- a/docs/resources/navigator_run.md
+++ b/docs/resources/navigator_run.md
@@ -38,8 +38,8 @@ resource "ansible_navigator_run" "existing" {
# 3. configure ansible with ansible.cfg placed in working directory (see example below)
resource "ansible_navigator_run" "working_directory" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
working_directory = "some-directory-with-ansible-cfg-file"
}
@@ -54,7 +54,7 @@ resource "ansible_navigator_run" "environment_variables" {
- "{{ lookup('ansible.builtin.env', 'SOME_VAR') }}"
- "{{ lookup('ansible.builtin.env', 'EDITOR') }}"
EOT
- inventory = "..."
+ inventory = yamlencode({})
execution_environment = {
environment_variables_set = {
"SOME_VAR" = "some-value"
@@ -67,8 +67,8 @@ resource "ansible_navigator_run" "environment_variables" {
# 5. ansible playbook options
resource "ansible_navigator_run" "ansible_options" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
ansible_options = {
force_handlers = true # --force-handlers
skip_tags = ["tag1", "tag2"] # --skip-tags tag1,tag2
@@ -89,14 +89,14 @@ resource "ansible_navigator_run" "destroy" {
msg: "resource is being destroyed!"
when: destroy
EOT
- inventory = "..."
+ inventory = yamlencode({})
run_on_destroy = true
}
# 7. triggers and replacement triggers
resource "ansible_navigator_run" "triggers" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
triggers = {
somekey = some_resource.example.id # re-run playbook when id changes
}
@@ -107,17 +107,17 @@ resource "ansible_navigator_run" "triggers" {
# 8. artifact queries -- get playbook stdout
resource "ansible_navigator_run" "artifact_query_stdout" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
artifact_queries = {
"stdout" = {
- jsonpath = "$.stdout"
+ jq_filter = ".stdout"
}
}
}
output "playbook_stdout" {
- value = join("\n", jsondecode(ansible_navigator_run.artifact_query_stdout.artifact_queries.stdout.result))
+ value = join("\n", jsondecode(ansible_navigator_run.artifact_query_stdout.artifact_queries.stdout.results[0]))
}
# 9. ssh private keys
@@ -126,8 +126,8 @@ resource "tls_private_key" "client" {
}
resource "ansible_navigator_run" "private_keys" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
ansible_options = {
private_keys = [
{
@@ -144,7 +144,7 @@ resource "tls_private_key" "server" {
}
resource "ansible_navigator_run" "known_hosts" {
- playbook = "..."
+ playbook = "# example"
inventory = yamlencode({
all = {
vars = {
@@ -195,7 +195,7 @@ pipelining=True
- `ansible_navigator_binary` (String) Path to the `ansible-navigator` binary. By default `$PATH` is searched.
- `ansible_options` (Attributes) Ansible [playbook](https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html) run related configuration. (see [below for nested schema](#nestedatt--ansible_options))
-- `artifact_queries` (Attributes Map) Query the playbook artifact with [JSONPath](https://goessner.net/articles/JsonPath/). The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run. (see [below for nested schema](#nestedatt--artifact_queries))
+- `artifact_queries` (Attributes Map) Query the Ansible playbook artifact with [`jq`](https://jqlang.github.io/jq/) syntax. The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run. (see [below for nested schema](#nestedatt--artifact_queries))
- `execution_environment` (Attributes) [Execution environment](https://ansible.readthedocs.io/en/latest/getting_started_ee/index.html) (EE) related configuration. (see [below for nested schema](#nestedatt--execution_environment))
- `replacement_triggers` (Map of String) Arbitrary map of values that, when changed, will recreate the resource. Similar to `triggers`, but will cause `id` to change. Useful when combined with `run_on_destroy`.
- `run_on_destroy` (Boolean) Run playbook on destroy. The environment variable `ANSIBLE_TF_OPERATION` is set to `delete` during the run to allow for conditional plays, tasks, etc. Defaults to `false`.
@@ -237,11 +237,11 @@ Required:
Required:
-- `jsonpath` (String) JSONPath expression.
+- `jq_filter` (String) `jq` filter. Example: `.status, .stdout`.
Read-Only:
-- `result` (String) Result of the query. Result may be empty if a field or map key cannot be located.
+- `results` (List of String) Results of the `jq` filter in JSON format.
diff --git a/examples/complete/aws/main.tf b/examples/complete/aws/main.tf
index f1410a3..9f1831d 100644
--- a/examples/complete/aws/main.tf
+++ b/examples/complete/aws/main.tf
@@ -168,12 +168,12 @@ resource "ansible_navigator_run" "this" {
}
artifact_queries = {
"stdout" = {
- jsonpath = "$.stdout"
+ jq_filter = ".stdout"
}
}
depends_on = [aws_iam_user_policy.ssh_ssm]
}
output "playbook_stdout" {
- value = join("\n", jsondecode(ansible_navigator_run.this.artifact_queries.stdout.result))
+ value = join("\n", jsondecode(ansible_navigator_run.this.artifact_queries.stdout.results[0]))
}
diff --git a/examples/complete/libvirt/main.tf b/examples/complete/libvirt/main.tf
index f58f289..9908d16 100644
--- a/examples/complete/libvirt/main.tf
+++ b/examples/complete/libvirt/main.tf
@@ -129,11 +129,11 @@ resource "ansible_navigator_run" "this" {
}
artifact_queries = {
"stdout" = {
- jsonpath = "$.stdout"
+ jq_filter = ".stdout"
}
}
}
output "playbook_stdout" {
- value = join("\n", jsondecode(ansible_navigator_run.this.artifact_queries.stdout.result))
+ value = join("\n", jsondecode(ansible_navigator_run.this.artifact_queries.stdout.result[0]))
}
diff --git a/examples/data-sources/ansible_navigator_run/data-source.tf b/examples/data-sources/ansible_navigator_run/data-source.tf
index 6c4a352..02501f6 100644
--- a/examples/data-sources/ansible_navigator_run/data-source.tf
+++ b/examples/data-sources/ansible_navigator_run/data-source.tf
@@ -22,21 +22,20 @@ data "ansible_navigator_run" "inline" {
# 2. artifact queries -- get file contents
data "ansible_navigator_run" "artifact_query_file" {
playbook = <<-EOT
- - name: Get file
- hosts: all
+ - name: Example
tasks:
- - name: resolv.conf
+ - name: Get file
ansible.builtin.slurp:
src: /etc/resolv.conf
EOT
- inventory = "..."
+ inventory = yamlencode({})
artifact_queries = {
"resolv_conf" = {
- jsonpath = "$.plays[?(@.__play_name=='Get file')].tasks[?(@.__task=='resolv.conf')].res.content"
+ jq_filter = ".plays[] | select(.name==\"Example\") | .tasks[] | select(.task==\"Get file\") | .res.content"
}
}
}
output "resolv_conf" {
- value = base64decode(data.ansible_navigator_run.artifact_query_file.artifact_queries.resolv_conf.result)
+ value = base64decode(jsondecode(data.ansible_navigator_run.artifact_query_file.artifact_queries.resolv_conf.results[0]))
}
diff --git a/examples/resources/ansible_navigator_run/resource.tf b/examples/resources/ansible_navigator_run/resource.tf
index df0bdb1..302060f 100644
--- a/examples/resources/ansible_navigator_run/resource.tf
+++ b/examples/resources/ansible_navigator_run/resource.tf
@@ -23,8 +23,8 @@ resource "ansible_navigator_run" "existing" {
# 3. configure ansible with ansible.cfg placed in working directory (see example below)
resource "ansible_navigator_run" "working_directory" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
working_directory = "some-directory-with-ansible-cfg-file"
}
@@ -39,7 +39,7 @@ resource "ansible_navigator_run" "environment_variables" {
- "{{ lookup('ansible.builtin.env', 'SOME_VAR') }}"
- "{{ lookup('ansible.builtin.env', 'EDITOR') }}"
EOT
- inventory = "..."
+ inventory = yamlencode({})
execution_environment = {
environment_variables_set = {
"SOME_VAR" = "some-value"
@@ -52,8 +52,8 @@ resource "ansible_navigator_run" "environment_variables" {
# 5. ansible playbook options
resource "ansible_navigator_run" "ansible_options" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
ansible_options = {
force_handlers = true # --force-handlers
skip_tags = ["tag1", "tag2"] # --skip-tags tag1,tag2
@@ -74,14 +74,14 @@ resource "ansible_navigator_run" "destroy" {
msg: "resource is being destroyed!"
when: destroy
EOT
- inventory = "..."
+ inventory = yamlencode({})
run_on_destroy = true
}
# 7. triggers and replacement triggers
resource "ansible_navigator_run" "triggers" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
triggers = {
somekey = some_resource.example.id # re-run playbook when id changes
}
@@ -92,17 +92,17 @@ resource "ansible_navigator_run" "triggers" {
# 8. artifact queries -- get playbook stdout
resource "ansible_navigator_run" "artifact_query_stdout" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
artifact_queries = {
"stdout" = {
- jsonpath = "$.stdout"
+ jq_filter = ".stdout"
}
}
}
output "playbook_stdout" {
- value = join("\n", jsondecode(ansible_navigator_run.artifact_query_stdout.artifact_queries.stdout.result))
+ value = join("\n", jsondecode(ansible_navigator_run.artifact_query_stdout.artifact_queries.stdout.results[0]))
}
# 9. ssh private keys
@@ -111,8 +111,8 @@ resource "tls_private_key" "client" {
}
resource "ansible_navigator_run" "private_keys" {
- playbook = "..."
- inventory = "..."
+ playbook = "# example"
+ inventory = yamlencode({})
ansible_options = {
private_keys = [
{
@@ -129,7 +129,7 @@ resource "tls_private_key" "server" {
}
resource "ansible_navigator_run" "known_hosts" {
- playbook = "..."
+ playbook = "# example"
inventory = yamlencode({
all = {
vars = {
diff --git a/go.mod b/go.mod
index 36ac7c9..845d0ba 100644
--- a/go.mod
+++ b/go.mod
@@ -11,10 +11,10 @@ require (
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-log v0.9.0
- github.com/hashicorp/terraform-plugin-testing v1.9.0
+ github.com/hashicorp/terraform-plugin-testing v1.10.0
+ github.com/itchyny/gojq v0.12.16
golang.org/x/crypto v0.26.0
gopkg.in/yaml.v3 v3.0.1
- k8s.io/client-go v0.30.3
)
require (
@@ -42,9 +42,10 @@ require (
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
+ github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
- github.com/hashicorp/hc-install v0.7.0 // indirect
+ github.com/hashicorp/hc-install v0.8.0 // indirect
github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
@@ -55,9 +56,10 @@ require (
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.15 // indirect
+ github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.9 // indirect
+ github.com/mattn/go-runewidth v0.0.15 // 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
@@ -65,17 +67,19 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/posener/complete v1.2.3 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
+ github.com/stretchr/testify v1.8.4 // 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/yuin/goldmark v1.7.1 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
- github.com/zclconf/go-cty v1.14.4 // indirect
+ github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
- golang.org/x/mod v0.17.0 // indirect
+ golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
diff --git a/go.sum b/go.sum
index 65697a4..237246b 100644
--- a/go.sum
+++ b/go.sum
@@ -86,13 +86,15 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
+github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
+github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk=
-github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
+github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI=
+github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
@@ -115,8 +117,8 @@ github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9T
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
-github.com/hashicorp/terraform-plugin-testing v1.9.0 h1:xOsQRqqlHKXpFq6etTxih3ubdK3HVDtfE1IY7Rpd37o=
-github.com/hashicorp/terraform-plugin-testing v1.9.0/go.mod h1:fhhVx/8+XNJZTD5o3b4stfZ6+q7z9+lIWigIYdT6/44=
+github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
+github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
@@ -128,6 +130,10 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
+github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
+github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
+github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
+github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
@@ -150,8 +156,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@@ -172,6 +178,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@@ -205,8 +214,8 @@ github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
-github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
-github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
+github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
+github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
@@ -219,8 +228,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -290,5 +299,3 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k=
-k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U=
diff --git a/internal/provider/helpers_test.go b/internal/provider/helpers_test.go
index 0d65a14..0b096fb 100644
--- a/internal/provider/helpers_test.go
+++ b/internal/provider/helpers_test.go
@@ -182,7 +182,7 @@ func testSSHServer(t *testing.T, clientPublicKey string, serverPrivateKey string
}
// https://github.com/hashicorp/terraform-provider-random/blob/main/internal/provider/resource_integer_test.go
-func testExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) resource.TestCheckFunc { //nolint:unparam
+func testExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resourceState, ok := s.RootModule().Resources[resourceName]
diff --git a/internal/provider/navigator_run_common.go b/internal/provider/navigator_run_common.go
index 64bac7c..580d901 100644
--- a/internal/provider/navigator_run_common.go
+++ b/internal/provider/navigator_run_common.go
@@ -37,8 +37,8 @@ type PrivateKeyModel struct {
}
type ArtifactQueryModel struct {
- JSONPath types.String `tfsdk:"jsonpath"`
- Result types.String `tfsdk:"result"`
+ JQFilter types.String `tfsdk:"jq_filter"`
+ Results types.List `tfsdk:"results"`
}
func (ExecutionEnvironmentModel) AttrTypes() map[string]attr.Type {
@@ -162,16 +162,16 @@ func (m PrivateKeyModel) Value(ctx context.Context, key *ansible.PrivateKey) dia
func (ArtifactQueryModel) AttrTypes() map[string]attr.Type {
return map[string]attr.Type{
- "jsonpath": types.StringType,
- "result": types.StringType,
+ "jq_filter": types.StringType,
+ "results": types.ListType{ElemType: types.StringType},
}
}
func (m ArtifactQueryModel) Value(ctx context.Context, query *ansible.ArtifactQuery) diag.Diagnostics {
var diags diag.Diagnostics
- query.JSONPath = m.JSONPath.ValueString()
- query.Result = m.Result.ValueString()
+ query.JQFilter = m.JQFilter.ValueString()
+ query.Results = []string{} // m.Results always unknown when this function is called
return diags
}
@@ -179,8 +179,11 @@ func (m ArtifactQueryModel) Value(ctx context.Context, query *ansible.ArtifactQu
func (m *ArtifactQueryModel) Set(ctx context.Context, query ansible.ArtifactQuery) diag.Diagnostics {
var diags diag.Diagnostics
- m.JSONPath = types.StringValue(query.JSONPath)
- m.Result = types.StringValue(query.Result)
+ m.JQFilter = types.StringValue(query.JQFilter)
+
+ resultsValue, newDiags := types.ListValueFrom(ctx, types.StringType, query.Results)
+ diags.Append(newDiags...)
+ m.Results = resultsValue
return diags
}
diff --git a/internal/provider/navigator_run_data_source.go b/internal/provider/navigator_run_data_source.go
index 7e6926a..0e0434a 100644
--- a/internal/provider/navigator_run_data_source.go
+++ b/internal/provider/navigator_run_data_source.go
@@ -373,21 +373,24 @@ func (d *NavigatorRunDataSource) Schema(ctx context.Context, req datasource.Sche
},
},
"artifact_queries": schema.MapNestedAttribute{
- Description: "Query the playbook artifact with JSONPath. The playbook artifact contains detailed information about every play and task, as well as the stdout from the playbook run.",
- MarkdownDescription: "Query the playbook artifact with [JSONPath](https://goessner.net/articles/JsonPath/). The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run.",
+ Description: "Query the Ansible playbook artifact with 'jq' syntax. The playbook artifact contains detailed information about every play and task, as well as the stdout from the playbook run.",
+ MarkdownDescription: "Query the Ansible playbook artifact with [`jq`](https://jqlang.github.io/jq/) syntax. The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
- "jsonpath": schema.StringAttribute{
- Description: "JSONPath expression.",
- Required: true,
+ "jq_filter": schema.StringAttribute{
+ Description: "'jq' filter. Example: '.status, .stdout'.",
+ MarkdownDescription: "`jq` filter. Example: `.status, .stdout`.",
+ Required: true,
Validators: []validator.String{
- stringIsIsJSONPathExpression(),
+ stringIsIsJQFilter(),
},
},
- "result": schema.StringAttribute{
- Description: "Result of the query. Result may be empty if a field or map key cannot be located.",
- Computed: true,
+ "results": schema.ListAttribute{ // TODO switch to a dynamic attribute when supported as an element in a collection
+ Description: "Results of the 'jq' filter in JSON format.",
+ MarkdownDescription: "Results of the `jq` filter in JSON format.",
+ Computed: true,
+ ElementType: types.StringType,
},
},
},
diff --git a/internal/provider/navigator_run_data_source_test.go b/internal/provider/navigator_run_data_source_test.go
index 0bcbb80..b9a9469 100644
--- a/internal/provider/navigator_run_data_source_test.go
+++ b/internal/provider/navigator_run_data_source_test.go
@@ -35,7 +35,7 @@ func TestAccNavigatorRunDataSource_artifact_queries(t *testing.T) {
"file_contents": config.StringVariable(fileContents),
}),
Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")),
+ resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.results.0", regexp.MustCompile("ok=3")),
testExtractResourceAttr(navigatorRunDataSource, "command", &dataSourceCommand),
),
ConfigStateChecks: []statecheck.StateCheck{
@@ -53,7 +53,7 @@ func TestAccNavigatorRunDataSource_artifact_queries(t *testing.T) {
},
},
Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")),
+ resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.results.0", regexp.MustCompile("ok=3")),
testExtractResourceAttr(navigatorRunDataSource, "command", &dataSourceCommandUpdate),
testCheckAttributeValuesDiffer(&dataSourceCommand, &dataSourceCommandUpdate),
),
diff --git a/internal/provider/navigator_run_resource.go b/internal/provider/navigator_run_resource.go
index b3c0b63..526779a 100644
--- a/internal/provider/navigator_run_resource.go
+++ b/internal/provider/navigator_run_resource.go
@@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
@@ -370,23 +371,26 @@ func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRe
},
},
"artifact_queries": schema.MapNestedAttribute{
- Description: "Query the playbook artifact with JSONPath. The playbook artifact contains detailed information about every play and task, as well as the stdout from the playbook run.",
- MarkdownDescription: "Query the playbook artifact with [JSONPath](https://goessner.net/articles/JsonPath/). The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run.",
+ Description: "Query the Ansible playbook artifact with 'jq' syntax. The playbook artifact contains detailed information about every play and task, as well as the stdout from the playbook run.",
+ MarkdownDescription: "Query the Ansible playbook artifact with [`jq`](https://jqlang.github.io/jq/) syntax. The [playbook artifact](https://access.redhat.com/documentation/en-us/red_hat_ansible_automation_platform/2.0-ea/html/ansible_navigator_creator_guide/assembly-troubleshooting-navigator_ansible-navigator#proc-review-artifact_troubleshooting-navigator) contains detailed information about every play and task, as well as the stdout from the playbook run.",
Optional: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
- "jsonpath": schema.StringAttribute{
- Description: "JSONPath expression.",
- Required: true,
+ "jq_filter": schema.StringAttribute{
+ Description: "'jq' filter. Example: '.status, .stdout'.",
+ MarkdownDescription: "`jq` filter. Example: `.status, .stdout`.",
+ Required: true,
Validators: []validator.String{
- stringIsIsJSONPathExpression(),
+ stringIsIsJQFilter(),
},
},
- "result": schema.StringAttribute{
- Description: "Result of the query. Result may be empty if a field or map key cannot be located.",
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
+ "results": schema.ListAttribute{ // TODO switch to a dynamic attribute when supported as an element in a collection
+ Description: "Results of the 'jq' filter in JSON format.",
+ MarkdownDescription: "Results of the `jq` filter in JSON format.",
+ Computed: true,
+ ElementType: types.StringType,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
},
},
},
@@ -482,7 +486,7 @@ func (r *NavigatorRunResource) ModifyPlan(ctx context.Context, req resource.Modi
resp.Diagnostics.Append(data.ArtifactQueries.ElementsAs(ctx, &artifactQueriesPlanModel, false)...)
for name, model := range artifactQueriesPlanModel {
- model.Result = types.StringUnknown()
+ model.Results = types.ListUnknown(types.StringType)
artifactQueriesPlanModel[name] = model
}
diff --git a/internal/provider/navigator_run_resource_errors_test.go b/internal/provider/navigator_run_resource_errors_test.go
index 66d941f..38ab15f 100644
--- a/internal/provider/navigator_run_resource_errors_test.go
+++ b/internal/provider/navigator_run_resource_errors_test.go
@@ -19,7 +19,7 @@ func TestAccNavigatorRunResource_errors(t *testing.T) {
}{
{
name: "artifact_query",
- expected: regexp.MustCompile("Not a valid JSONPath expression"),
+ expected: regexp.MustCompile("Not a valid JQ filter"),
},
{
name: "env_var_name_empty",
diff --git a/internal/provider/navigator_run_resource_test.go b/internal/provider/navigator_run_resource_test.go
index 18e9c8e..a9e82d6 100644
--- a/internal/provider/navigator_run_resource_test.go
+++ b/internal/provider/navigator_run_resource_test.go
@@ -55,7 +55,7 @@ func TestAccNavigatorRunResource_artifact_queries(t *testing.T) {
"file_contents": config.StringVariable(fileContents),
}),
Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")),
+ resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.stdout.results.0", regexp.MustCompile("ok=3")),
testExtractResourceAttr(navigatorRunResource, "command", &resourceCommand),
),
ConfigStateChecks: []statecheck.StateCheck{
@@ -70,12 +70,12 @@ func TestAccNavigatorRunResource_artifact_queries(t *testing.T) {
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectNonEmptyPlan(),
- plancheck.ExpectUnknownValue(navigatorRunResource, tfjsonpath.New("artifact_queries").AtMapKey("file_contents").AtMapKey("result")),
+ plancheck.ExpectUnknownValue(navigatorRunResource, tfjsonpath.New("artifact_queries").AtMapKey("file_contents").AtMapKey("results")),
plancheck.ExpectUnknownValue(navigatorRunResource, tfjsonpath.New("command")),
},
},
Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")),
+ resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.stdout.results.0", regexp.MustCompile("ok=3")),
testExtractResourceAttr(navigatorRunResource, "command", &resourceCommandUpdate),
testCheckAttributeValuesDiffer(&resourceCommand, &resourceCommandUpdate),
),
@@ -276,10 +276,10 @@ func TestAccNavigatorRunResource_pull_args(t *testing.T) {
{
Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "pull_args")),
ConfigVariables: testConfigVariables(t, config.Variables{
- "pull_arguments": config.ListVariable(config.StringVariable(arg)),
+ "pull_args": config.ListVariable(config.StringVariable(arg)),
}),
Check: resource.ComposeAggregateTestCheckFunc(
- resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.pull_args.result", regexp.MustCompile(arg)),
+ resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.pull_args.results.0", regexp.MustCompile(arg)),
),
},
},
@@ -333,6 +333,7 @@ func TestAccNavigatorRunResource_skip_run(t *testing.T) {
t.Parallel()
var resourceCommand, resourceCommandUpdate string
+ var queryResult, queryResultUpdate string
resource.Test(t, resource.TestCase{
PreCheck: func() { testPreCheck(t) },
@@ -344,7 +345,9 @@ func TestAccNavigatorRunResource_skip_run(t *testing.T) {
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(navigatorRunResource, "id"),
resource.TestCheckResourceAttrSet(navigatorRunResource, "command"),
+ resource.TestCheckResourceAttrSet(navigatorRunResource, "artifact_queries.test.results.0"),
testExtractResourceAttr(navigatorRunResource, "command", &resourceCommand),
+ testExtractResourceAttr(navigatorRunResource, "artifact_queries.test.results.0", &queryResult),
),
},
{
@@ -360,8 +363,11 @@ func TestAccNavigatorRunResource_skip_run(t *testing.T) {
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(navigatorRunResource, "id"),
resource.TestCheckResourceAttrSet(navigatorRunResource, "command"),
+ resource.TestCheckResourceAttrSet(navigatorRunResource, "artifact_queries.test.results.0"),
testExtractResourceAttr(navigatorRunResource, "command", &resourceCommandUpdate),
testCheckAttributeValuesEqual(&resourceCommand, &resourceCommandUpdate),
+ testExtractResourceAttr(navigatorRunResource, "artifact_queries.test.results.0", &queryResultUpdate),
+ testCheckAttributeValuesEqual(&queryResult, &queryResultUpdate),
),
},
},
diff --git a/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf b/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf
index e14d4d8..1a28bd4 100644
--- a/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf
+++ b/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf
@@ -5,27 +5,27 @@ data "ansible_navigator_run" "test" {
hosts: localhost
become: false
tasks:
- - name: write file
+ - name: Write file
ansible.builtin.copy:
dest: /tmp/test
content: ${var.file_contents}
- - name: get file
+ - name: Get file
ansible.builtin.slurp:
src: /tmp/test
EOT
inventory = "# localhost"
artifact_queries = {
- stdout = {
- jsonpath = "$.stdout"
+ "stdout" = {
+ jq_filter = ".stdout"
}
- file_contents = {
- jsonpath = "$.plays[?(@.__play_name=='Test')].tasks[?(@.__task=='get file')].res.content"
+ "file_contents" = {
+ jq_filter = ".plays[] | select(.name==\"Test\") | .tasks[] | select(.task==\"Get file\") | .res.content"
}
}
}
output "file_contents" {
- value = base64decode(data.ansible_navigator_run.test.artifact_queries.file_contents.result)
+ value = base64decode(jsondecode((data.ansible_navigator_run.test.artifact_queries.file_contents.results[0])))
}
variable "ansible_navigator_binary" {
diff --git a/internal/provider/testdata/navigator_run_resource/artifact_queries.tf b/internal/provider/testdata/navigator_run_resource/artifact_queries.tf
index be7b551..b70e017 100644
--- a/internal/provider/testdata/navigator_run_resource/artifact_queries.tf
+++ b/internal/provider/testdata/navigator_run_resource/artifact_queries.tf
@@ -5,27 +5,27 @@ resource "ansible_navigator_run" "test" {
hosts: localhost
become: false
tasks:
- - name: write file
+ - name: Write file
ansible.builtin.copy:
dest: /tmp/test
content: ${var.file_contents}
- - name: get file
+ - name: Get file
ansible.builtin.slurp:
src: /tmp/test
EOT
inventory = "# localhost"
artifact_queries = {
- stdout = {
- jsonpath = "$.stdout"
+ "stdout" = {
+ jq_filter = ".stdout"
}
- file_contents = {
- jsonpath = "$.plays[?(@.__play_name=='Test')].tasks[?(@.__task=='get file')].res.content"
+ "file_contents" = {
+ jq_filter = ".plays[] | select(.name==\"Test\") | .tasks[] | select(.task==\"Get file\") | .res.content"
}
}
}
output "file_contents" {
- value = base64decode(ansible_navigator_run.test.artifact_queries.file_contents.result)
+ value = base64decode(jsondecode(ansible_navigator_run.test.artifact_queries.file_contents.results[0]))
}
variable "ansible_navigator_binary" {
diff --git a/internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf b/internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf
index da441d7..cc84bc4 100644
--- a/internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf
+++ b/internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf
@@ -7,7 +7,7 @@ resource "ansible_navigator_run" "test" {
inventory = "# localhost"
artifact_queries = {
"test" = {
- jsonpath = "!"
+ jq_filter = "!"
}
}
}
diff --git a/internal/provider/testdata/navigator_run_resource/pull_args.tf b/internal/provider/testdata/navigator_run_resource/pull_args.tf
index 1f819bf..f23ead6 100644
--- a/internal/provider/testdata/navigator_run_resource/pull_args.tf
+++ b/internal/provider/testdata/navigator_run_resource/pull_args.tf
@@ -6,11 +6,11 @@ resource "ansible_navigator_run" "test" {
EOT
inventory = "# localhost"
execution_environment = {
- pull_arguments = var.pull_arguments
+ pull_arguments = var.pull_args
}
artifact_queries = {
- pull_args = {
- jsonpath = "$.settings_entries.ansible-navigator.execution-environment.pull.arguments"
+ "pull_args" = {
+ jq_filter = ".settings_entries.\"ansible-navigator\".\"execution-environment\".pull.arguments"
}
}
}
@@ -20,7 +20,7 @@ variable "ansible_navigator_binary" {
nullable = false
}
-variable "pull_arguments" {
+variable "pull_args" {
type = list(string)
nullable = false
}
diff --git a/internal/provider/testdata/navigator_run_resource/skip_run.tf b/internal/provider/testdata/navigator_run_resource/skip_run.tf
index ac4ab38..11cb239 100644
--- a/internal/provider/testdata/navigator_run_resource/skip_run.tf
+++ b/internal/provider/testdata/navigator_run_resource/skip_run.tf
@@ -5,7 +5,12 @@ resource "ansible_navigator_run" "test" {
become: false
EOT
inventory = "# localhost"
- run_on_destroy = true
+ artifact_queries = {
+ "test" = {
+ jq_filter = "now"
+ }
+ }
+ run_on_destroy = true
timeouts = {
create = "60m"
update = "60m"
diff --git a/internal/provider/testdata/navigator_run_resource/skip_run_update.tf b/internal/provider/testdata/navigator_run_resource/skip_run_update.tf
index b32a92b..bb5b496 100644
--- a/internal/provider/testdata/navigator_run_resource/skip_run_update.tf
+++ b/internal/provider/testdata/navigator_run_resource/skip_run_update.tf
@@ -5,7 +5,12 @@ resource "ansible_navigator_run" "test" {
become: false
EOT
inventory = "# localhost"
- run_on_destroy = false
+ artifact_queries = {
+ "test" = {
+ jq_filter = "now"
+ }
+ }
+ run_on_destroy = false
timeouts = {
create = "90m"
update = "90m"
diff --git a/internal/provider/validators.go b/internal/provider/validators.go
index 955f9f6..400e2bf 100644
--- a/internal/provider/validators.go
+++ b/internal/provider/validators.go
@@ -153,7 +153,7 @@ func (v stringIsYAMLValidator) ValidateString(ctx context.Context, req validator
return
}
- var output interface{}
+ var output any
err := yaml.Unmarshal([]byte(req.ConfigValue.ValueString()), &output)
if addPathError(&resp.Diagnostics, req.Path, "Not valid YAML", err) {
return
@@ -204,25 +204,25 @@ func stringIsIANATimezone() stringIsIANATimezoneValidator {
return stringIsIANATimezoneValidator{}
}
-type stringIsIsJSONPathExpressionValidator struct{}
+type stringIsIsJQFilterValidator struct{}
-func (v stringIsIsJSONPathExpressionValidator) Description(ctx context.Context) string {
- return "string must be a JSONPath expression"
+func (v stringIsIsJQFilterValidator) Description(ctx context.Context) string {
+ return "string must be a JQ filter"
}
-func (v stringIsIsJSONPathExpressionValidator) MarkdownDescription(ctx context.Context) string {
+func (v stringIsIsJQFilterValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}
-func (v stringIsIsJSONPathExpressionValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
+func (v stringIsIsJQFilterValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
return
}
- err := ansible.ValidateJSONPathExpression(req.ConfigValue.ValueString())
- addPathError(&resp.Diagnostics, req.Path, "Not a valid JSONPath expression", err)
+ err := ansible.ValidateJQFilter(req.ConfigValue.ValueString())
+ addPathError(&resp.Diagnostics, req.Path, "Not a valid JQ filter", err)
}
-func stringIsIsJSONPathExpression() stringIsIsJSONPathExpressionValidator {
- return stringIsIsJSONPathExpressionValidator{}
+func stringIsIsJQFilter() stringIsIsJQFilterValidator {
+ return stringIsIsJQFilterValidator{}
}
diff --git a/pkg/ansible/navigator_query.go b/pkg/ansible/navigator_query.go
index 8e778a0..4182699 100644
--- a/pkg/ansible/navigator_query.go
+++ b/pkg/ansible/navigator_query.go
@@ -4,11 +4,13 @@ import (
"fmt"
"os"
"path/filepath"
+
+ jq "github.com/itchyny/gojq"
)
type ArtifactQuery struct {
- JSONPath string
- Result string
+ JQFilter string
+ Results []string
}
func QueryPlaybookArtifact(dir string, queries map[string]ArtifactQuery) error {
@@ -20,20 +22,20 @@ func QueryPlaybookArtifact(dir string, queries map[string]ArtifactQuery) error {
}
for name, query := range queries {
- result, err := jsonPath(contents, query.JSONPath)
+ results, err := jqJSON(contents, query.JQFilter)
if err != nil {
- return fmt.Errorf("failed to query playbook artifact with JSONPath, %w", err)
+ return fmt.Errorf("failed to query playbook artifact, %w", err)
}
- query.Result = result
+ query.Results = results
queries[name] = query
}
return nil
}
-func ValidateJSONPathExpression(expression string) error {
- _, err := jsonPathParse(expression)
+func ValidateJQFilter(filter string) error {
+ _, err := jq.Parse(filter)
return err
}
diff --git a/pkg/ansible/utils.go b/pkg/ansible/utils.go
index 986b018..37edc2d 100644
--- a/pkg/ansible/utils.go
+++ b/pkg/ansible/utils.go
@@ -1,13 +1,12 @@
package ansible
import (
- "bytes"
"encoding/json"
- "fmt"
+ "errors"
"os"
"os/exec"
- "k8s.io/client-go/util/jsonpath"
+ jq "github.com/itchyny/gojq"
)
func programExistsOnPath(program string) error {
@@ -22,33 +21,42 @@ func writeFile(path string, contents string) error {
return os.WriteFile(path, []byte(contents), 0o600) //nolint:gomnd,mnd
}
-func jsonPathParse(expression string) (*jsonpath.JSONPath, error) {
- jsonPath := jsonpath.New(expression)
- jsonPath.AllowMissingKeys(true)
+func jqJSON(data []byte, filter string) ([]string, error) {
+ var blob any
+ if err := json.Unmarshal(data, &blob); err != nil {
+ return nil, err
+ }
- err := jsonPath.Parse(fmt.Sprintf("{%s}", expression))
+ query, err := jq.Parse(filter)
if err != nil {
return nil, err
}
- return jsonPath, nil
-}
+ var results []string
-func jsonPath(data []byte, expression string) (string, error) {
- var blob interface{}
- if err := json.Unmarshal(data, &blob); err != nil {
- return "", err
- }
+ iter := query.Run(blob)
+ for {
+ value, ok := iter.Next()
+ if !ok {
+ break
+ }
- jsonPath, err := jsonPathParse(expression)
- if err != nil {
- return "", err
- }
+ if err, ok := value.(error); ok {
+ var haltErr *jq.HaltError
+ if errors.As(err, &haltErr) && haltErr.Value() == nil {
+ break
+ }
+
+ return nil, err
+ }
+
+ result, err := jq.Marshal(value)
+ if err != nil {
+ return nil, err
+ }
- output := new(bytes.Buffer)
- if err := jsonPath.Execute(output, blob); err != nil {
- return "", err
+ results = append(results, string(result))
}
- return output.String(), nil
+ return results, nil
}