diff --git a/.golangci.yml b/.golangci.yml index e6c5e4a..ff28e33 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,4 +22,6 @@ linters: linters-settings: varnamelen: + ignore-type-assert-ok: true ignore-map-index-ok: true + ignore-chan-recv-ok: true diff --git a/docs/data-sources/navigator_run.md b/docs/data-sources/navigator_run.md new file mode 100644 index 0000000..4e9d802 --- /dev/null +++ b/docs/data-sources/navigator_run.md @@ -0,0 +1,92 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ansible_navigator_run Data Source - terraform-provider-ansible" +subcategory: "" +description: |- + Run an Ansible playbook. Recommended to only run playbooks without observable side-effects. Requires ansible-navigator and a container engine to run within an execution environment (EE). +--- + +# ansible_navigator_run (Data Source) + +Run an Ansible playbook. Recommended to only run playbooks without observable side-effects. Requires `ansible-navigator` and a container engine to run within an execution environment (EE). + + + + +## Schema + +### Required + +- `inventory` (String) Ansible [inventory](https://docs.ansible.com/ansible/latest/getting_started/get_started_inventory.html) contents. +- `playbook` (String) Ansible [playbook](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html) contents. + +### Optional + +- `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)) +- `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`. +- `working_directory` (String) Directory which `ansible-navigator` is run from. Recommended to be the root Ansible [content directory](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html#sample-directory-layout) (sometimes called the project directory), which is likely to contain `ansible.cfg`, `roles/`, etc. + +### Read-Only + +- `command` (String) Generated `ansible-navigator` run command. Useful for troubleshooting. +- `id` (String) UUID. + + +### Nested Schema for `ansible_options` + +Optional: + +- `force_handlers` (Boolean) Run handlers even if a task fails. +- `limit` (List of String) Further limit selected hosts to an additional pattern. +- `private_keys` (Attributes List) SSH private keys used for authentication in addition to the [automatically mounted](https://ansible.readthedocs.io/projects/navigator/faq/#how-do-i-use-my-ssh-keys-with-an-execution-environment) default named keys and SSH agent socket path. (see [below for nested schema](#nestedatt--ansible_options--private_keys)) +- `skip_tags` (List of String) Only run plays and tasks whose tags do not match these values. +- `start_at_task` (String) Start the playbook at the task matching this name. +- `tags` (List of String) Only run plays and tasks tagged with these values. + + +### Nested Schema for `ansible_options.private_keys` + +Required: + +- `data` (String, Sensitive) Key data. +- `name` (String) Key name. + + + + +### Nested Schema for `artifact_queries` + +Required: + +- `jsonpath` (String) JSONPath expression. + +Read-Only: + +- `result` (String) Result of the query. Result may be empty if a field or map key cannot be located. + + + +### Nested Schema for `execution_environment` + +Optional: + +- `container_engine` (String) [Container engine](https://ansible.readthedocs.io/projects/navigator/settings/#container-engine) responsible for running the execution environment container image. Options: `podman`, `docker`, `auto`. Defaults to `auto`. +- `container_options` (List of String) [Extra parameters](https://ansible.readthedocs.io/projects/navigator/settings/#container-options) passed to the container engine command. +- `enabled` (Boolean) Enable or disable the use of an execution environment. Disabling requires `ansible-playbook` and is only recommended when without a container engine. Defaults to `true`. +- `environment_variables_pass` (List of String) Existing environment variables to be [passed](https://ansible.readthedocs.io/projects/navigator/settings/#pass-environment-variable) through to and set within the execution environment. +- `environment_variables_set` (Map of String) Environment variables to be [set](https://ansible.readthedocs.io/projects/navigator/settings/#set-environment-variable) within the execution environment. By default `ANSIBLE_TF_OPERATION` is set to `read`. +- `image` (String) Name of the execution environment container [image](https://ansible.readthedocs.io/projects/navigator/settings/#execution-environment-image). Defaults to `ghcr.io/ansible/community-ansible-dev-tools:v24.7.2`. +- `pull_arguments` (List of String) Additional [parameters](https://ansible.readthedocs.io/projects/navigator/settings/#pull-arguments) that should be added to the pull command when pulling an execution environment container image from a container registry. +- `pull_policy` (String) Container image [pull policy](https://ansible.readthedocs.io/projects/navigator/settings/#pull-policy). Defaults to `tag`. + + + +### Nested Schema for `timeouts` + +Optional: + +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). diff --git a/internal/provider/navigator_run_data_source.go b/internal/provider/navigator_run_data_source.go new file mode 100644 index 0000000..280ea0c --- /dev/null +++ b/internal/provider/navigator_run_data_source.go @@ -0,0 +1,420 @@ +package provider + +import ( + "context" + "fmt" + "regexp" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/marshallford/terraform-provider-ansible/pkg/ansible" +) + +var ( + _ datasource.DataSource = &NavigatorRunDataSource{} + _ datasource.DataSourceWithConfigure = &NavigatorRunDataSource{} +) + +func NewNavigatorRunDataSource() datasource.DataSource { //nolint:ireturn + return &NavigatorRunDataSource{} +} + +type NavigatorRunDataSource struct { + opts *providerOptions +} + +type NavigatorRunDataSourceModel struct { + Playbook types.String `tfsdk:"playbook"` + Inventory types.String `tfsdk:"inventory"` + WorkingDirectory types.String `tfsdk:"working_directory"` + ExecutionEnvironment types.Object `tfsdk:"execution_environment"` + AnsibleNavigatorBinary types.String `tfsdk:"ansible_navigator_binary"` + AnsibleOptions types.Object `tfsdk:"ansible_options"` + Timezone types.String `tfsdk:"timezone"` + ArtifactQueries types.Map `tfsdk:"artifact_queries"` + ID types.String `tfsdk:"id"` + Command types.String `tfsdk:"command"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +func (m NavigatorRunDataSourceModel) Value(ctx context.Context, run *navigatorRun, opts *providerOptions) diag.Diagnostics { + var diags diag.Diagnostics + + run.dir = runDir(opts.BaseRunDirectory, m.ID.ValueString(), 0) + run.persistDir = opts.PersistRunDirectory + run.playbook = m.Playbook.ValueString() + run.inventory = m.Inventory.ValueString() + run.workingDir = m.WorkingDirectory.ValueString() + run.navigatorBinary = m.AnsibleNavigatorBinary.ValueString() + + var eeModel ExecutionEnvironmentModel + diags.Append(m.ExecutionEnvironment.As(ctx, &eeModel, basetypes.ObjectAsOptions{})...) + + run.navigatorSettings.Timezone = m.Timezone.ValueString() + diags.Append(eeModel.Value(ctx, &run.navigatorSettings)...) + + var optsModel AnsibleOptionsModel + diags.Append(m.AnsibleOptions.As(ctx, &optsModel, basetypes.ObjectAsOptions{UnhandledNullAsEmpty: true})...) + + diags.Append(optsModel.Value(ctx, &run.options)...) + + var privateKeysModel []PrivateKeyModel + if !optsModel.PrivateKeys.IsNull() { + diags.Append(optsModel.PrivateKeys.ElementsAs(ctx, &privateKeysModel, false)...) + } + + run.privateKeys = make([]ansible.PrivateKey, 0, len(privateKeysModel)) + for _, model := range privateKeysModel { + var key ansible.PrivateKey + + diags.Append(model.Value(ctx, &key)...) + run.privateKeys = append(run.privateKeys, key) + } + + var queriesModel map[string]ArtifactQueryModel + diags.Append(m.ArtifactQueries.ElementsAs(ctx, &queriesModel, false)...) + + run.artifactQueries = map[string]ansible.ArtifactQuery{} + for name, model := range queriesModel { + var query ansible.ArtifactQuery + + diags.Append(model.Value(ctx, &query)...) + run.artifactQueries[name] = query + } + + return diags +} + +func (m *NavigatorRunDataSourceModel) Set(ctx context.Context, run navigatorRun) diag.Diagnostics { + var diags diag.Diagnostics + + m.Command = types.StringValue(run.command) + + var queriesModel map[string]ArtifactQueryModel + diags.Append(m.ArtifactQueries.ElementsAs(ctx, &queriesModel, false)...) + + for name, model := range queriesModel { + diags.Append(model.Set(ctx, run.artifactQueries[name])...) + queriesModel[name] = model + } + + queriesValue, newDiags := types.MapValueFrom(ctx, types.ObjectType{AttrTypes: ArtifactQueryModel{}.AttrTypes()}, queriesModel) + diags.Append(newDiags...) + m.ArtifactQueries = queriesValue + + return diags +} + +func (m *NavigatorRunDataSourceModel) SetDefaults(ctx context.Context) diag.Diagnostics { + var diags diag.Diagnostics + + if m.WorkingDirectory.IsNull() { + m.WorkingDirectory = types.StringValue(defaultNavigatorRunWorkingDir) + } + + if m.ExecutionEnvironment.IsNull() { + m.ExecutionEnvironment = ExecutionEnvironmentModel{}.Defaults() + } + + var eeModel ExecutionEnvironmentModel + diags.Append(m.ExecutionEnvironment.As(ctx, &eeModel, basetypes.ObjectAsOptions{})...) + + if eeModel.ContainerEngine.IsNull() { + eeModel.ContainerEngine = types.StringValue(defaultNavigatorRunContainerEngine) + } + + if eeModel.Enabled.IsNull() { + eeModel.Enabled = types.BoolValue(defaultNavigatorRunEEEnabled) + } + + if eeModel.Image.IsNull() { + eeModel.Image = types.StringValue(defaultNavigatorRunImage) + } + + if eeModel.PullPolicy.IsNull() { + eeModel.PullPolicy = types.StringValue(defaultNavigatorRunPullPolicy) + } + + eeValue, newDiags := types.ObjectValueFrom(ctx, ExecutionEnvironmentModel{}.AttrTypes(), eeModel) + diags.Append(newDiags...) + m.ExecutionEnvironment = eeValue + + if m.Timezone.IsNull() { + m.Timezone = types.StringValue(defaultNavigatorRunTimezone) + } + + return diags +} + +func (d *NavigatorRunDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_navigator_run", req.ProviderTypeName) +} + +//nolint:dupl +func (d *NavigatorRunDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("Run an Ansible playbook. Recommended to only run playbooks without observable side-effects. Requires '%s' and a container engine to run within an execution environment (EE).", ansible.NavigatorProgram), + MarkdownDescription: fmt.Sprintf("Run an Ansible playbook. Recommended to only run playbooks without observable side-effects. Requires `%s` and a container engine to run within an execution environment (EE).", ansible.NavigatorProgram), + Attributes: map[string]schema.Attribute{ + // required + "playbook": schema.StringAttribute{ + Description: "Ansible playbook contents.", + MarkdownDescription: "Ansible [playbook](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_intro.html) contents.", + Required: true, + Validators: []validator.String{ + stringIsYAML(), + }, + }, + "inventory": schema.StringAttribute{ + Description: "Ansible inventory contents.", + MarkdownDescription: "Ansible [inventory](https://docs.ansible.com/ansible/latest/getting_started/get_started_inventory.html) contents.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + // optional + "working_directory": schema.StringAttribute{ + Description: fmt.Sprintf("Directory which '%s' is run from. Recommended to be the root Ansible content directory (sometimes called the project directory), which is likely to contain 'ansible.cfg', 'roles/', etc.", ansible.NavigatorProgram), + MarkdownDescription: fmt.Sprintf("Directory which `%s` is run from. Recommended to be the root Ansible [content directory](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html#sample-directory-layout) (sometimes called the project directory), which is likely to contain `ansible.cfg`, `roles/`, etc.", ansible.NavigatorProgram), + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "execution_environment": schema.SingleNestedAttribute{ + Description: "Execution environment (EE) related configuration.", + MarkdownDescription: "[Execution environment](https://ansible.readthedocs.io/en/latest/getting_started_ee/index.html) (EE) related configuration.", + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "container_engine": schema.StringAttribute{ + Description: fmt.Sprintf("Container engine responsible for running the execution environment container image. Options: %s. Defaults to '%s'.", wrapElementsJoin(ansible.ContainerEngineOptions(true), "'"), defaultNavigatorRunContainerEngine), + MarkdownDescription: fmt.Sprintf("[Container engine](https://ansible.readthedocs.io/projects/navigator/settings/#container-engine) responsible for running the execution environment container image. Options: %s. Defaults to `%s`.", wrapElementsJoin(ansible.ContainerEngineOptions(true), "`"), defaultNavigatorRunContainerEngine), + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf(ansible.ContainerEngineOptions(true)...), + }, + }, + "enabled": schema.BoolAttribute{ + Description: fmt.Sprintf("Enable or disable the use of an execution environment. Disabling requires '%s' and is only recommended when without a container engine. Defaults to '%t'.", ansible.PlaybookProgram, defaultNavigatorRunEEEnabled), + MarkdownDescription: fmt.Sprintf("Enable or disable the use of an execution environment. Disabling requires `%s` and is only recommended when without a container engine. Defaults to `%t`.", ansible.PlaybookProgram, defaultNavigatorRunEEEnabled), + Optional: true, + Computed: true, + }, + "environment_variables_pass": schema.ListAttribute{ + Description: "Existing environment variables to be passed through to and set within the execution environment.", + MarkdownDescription: "Existing environment variables to be [passed](https://ansible.readthedocs.io/projects/navigator/settings/#pass-environment-variable) through to and set within the execution environment.", + Optional: true, + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.ValueStringsAre(stringIsEnvVarName()), + }, + }, + "environment_variables_set": schema.MapAttribute{ + Description: fmt.Sprintf("Environment variables to be set within the execution environment. By default '%s' is set to '%s'.", navigatorRunOperationEnvVar, terraformOperation(terraformOperationRead).String()), + MarkdownDescription: fmt.Sprintf("Environment variables to be [set](https://ansible.readthedocs.io/projects/navigator/settings/#set-environment-variable) within the execution environment. By default `%s` is set to `%s`.", navigatorRunOperationEnvVar, terraformOperation(terraformOperationRead).String()), + Optional: true, + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.KeysAre(stringIsEnvVarName()), + }, + }, + "image": schema.StringAttribute{ + Description: fmt.Sprintf("Name of the execution environment container image. Defaults to '%s'.", defaultNavigatorRunImage), + MarkdownDescription: fmt.Sprintf("Name of the execution environment container [image](https://ansible.readthedocs.io/projects/navigator/settings/#execution-environment-image). Defaults to `%s`.", defaultNavigatorRunImage), + Optional: true, + Computed: true, + }, + "pull_arguments": schema.ListAttribute{ + Description: "Additional parameters that should be added to the pull command when pulling an execution environment container image from a container registry.", + MarkdownDescription: "Additional [parameters](https://ansible.readthedocs.io/projects/navigator/settings/#pull-arguments) that should be added to the pull command when pulling an execution environment container image from a container registry.", + Optional: true, + ElementType: types.StringType, + }, + "pull_policy": schema.StringAttribute{ + Description: fmt.Sprintf("Container image pull policy. Defaults to '%s'.", defaultNavigatorRunPullPolicy), + MarkdownDescription: fmt.Sprintf("Container image [pull policy](https://ansible.readthedocs.io/projects/navigator/settings/#pull-policy). Defaults to `%s`.", defaultNavigatorRunPullPolicy), + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf(ansible.PullPolicyOptions()...), + }, + }, + "container_options": schema.ListAttribute{ + Description: "Extra parameters passed to the container engine command.", + MarkdownDescription: "[Extra parameters](https://ansible.readthedocs.io/projects/navigator/settings/#container-options) passed to the container engine command.", + Optional: true, + ElementType: types.StringType, + }, + }, + }, + "ansible_navigator_binary": schema.StringAttribute{ + Description: fmt.Sprintf("Path to the '%s' binary. By default '$PATH' is searched.", ansible.NavigatorProgram), + MarkdownDescription: fmt.Sprintf("Path to the `%s` binary. By default `$PATH` is searched.", ansible.NavigatorProgram), + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "ansible_options": schema.SingleNestedAttribute{ + Description: "Ansible playbook run related configuration.", + MarkdownDescription: "Ansible [playbook](https://docs.ansible.com/ansible/latest/cli/ansible-playbook.html) run related configuration.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "force_handlers": schema.BoolAttribute{ + Description: "Run handlers even if a task fails.", + Optional: true, + }, + "skip_tags": schema.ListAttribute{ + Description: "Only run plays and tasks whose tags do not match these values.", + Optional: true, + ElementType: types.StringType, + }, + "start_at_task": schema.StringAttribute{ + Description: "Start the playbook at the task matching this name.", + Optional: true, + }, + "limit": schema.ListAttribute{ + Description: "Further limit selected hosts to an additional pattern.", + Optional: true, + ElementType: types.StringType, + }, + "tags": schema.ListAttribute{ + Description: "Only run plays and tasks tagged with these values.", + Optional: true, + ElementType: types.StringType, + }, + "private_keys": schema.ListNestedAttribute{ + Description: "SSH private keys used for authentication in addition to the automatically mounted default named keys and SSH agent socket path.", + MarkdownDescription: "SSH private keys used for authentication in addition to the [automatically mounted](https://ansible.readthedocs.io/projects/navigator/faq/#how-do-i-use-my-ssh-keys-with-an-execution-environment) default named keys and SSH agent socket path.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Key name.", + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-zA-Z0-9]*$`), + "Must only contain only alphanumeric characters", + ), + }, + }, + "data": schema.StringAttribute{ + Description: "Key data.", + Required: true, + Sensitive: true, + Validators: []validator.String{ + stringIsSSHPrivateKey(), + }, + }, + }, + }, + }, + }, + }, + "timezone": schema.StringAttribute{ + Description: fmt.Sprintf("IANA time zone, use 'local' for the system time zone. Defaults to '%s'.", defaultNavigatorRunTimezone), + MarkdownDescription: fmt.Sprintf("IANA time zone, use `local` for the system time zone. Defaults to `%s`.", defaultNavigatorRunTimezone), + Optional: true, + Computed: true, + Validators: []validator.String{ + stringIsIANATimezone(), + }, + }, + "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.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "jsonpath": schema.StringAttribute{ + Description: "JSONPath expression.", + Required: true, + Validators: []validator.String{ + stringIsIsJSONPathExpression(), + }, + }, + "result": schema.StringAttribute{ + Description: "Result of the query. Result may be empty if a field or map key cannot be located.", + Computed: true, + }, + }, + }, + }, + // computed + "id": schema.StringAttribute{ + Description: "UUID.", + Computed: true, + }, + "command": schema.StringAttribute{ + Description: fmt.Sprintf("Generated '%s' run command. Useful for troubleshooting.", ansible.NavigatorProgram), + MarkdownDescription: fmt.Sprintf("Generated `%s` run command. Useful for troubleshooting.", ansible.NavigatorProgram), + Computed: true, + }, + // timeouts + // TODO include defaultNavigatorRunTimeout in description + "timeouts": timeouts.Attributes(ctx), + }, + } +} + +func (d *NavigatorRunDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + opts, ok := configureDataSourceClient(req, resp) + if !ok { + return + } + + d.opts = opts +} + +func (d *NavigatorRunDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *NavigatorRunDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + resp.Diagnostics.Append(data.SetDefaults(ctx)...) + + if resp.Diagnostics.HasError() { + return + } + + timeout, newDiags := terraformOperationDataSourceTimeout(ctx, data.Timeouts, defaultNavigatorRunTimeout) + resp.Diagnostics.Append(newDiags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + data.ID = types.StringValue(uuid.New().String()) + + var navigatorRun navigatorRun + resp.Diagnostics.Append(data.Value(ctx, &navigatorRun, d.opts)...) + + if resp.Diagnostics.HasError() { + return + } + + run(ctx, &resp.Diagnostics, timeout, terraformOperationRead, &navigatorRun) + resp.Diagnostics.Append(data.Set(ctx, navigatorRun)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/navigator_run_data_source_errors_test.go b/internal/provider/navigator_run_data_source_errors_test.go new file mode 100644 index 0000000..afd928a --- /dev/null +++ b/internal/provider/navigator_run_data_source_errors_test.go @@ -0,0 +1,48 @@ +package provider_test + +import ( + "path/filepath" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccNavigatorRunDataSource_errors(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + variables func(*testing.T) config.Variables + expected *regexp.Regexp + }{ + { + name: "playbook", + expected: regexp.MustCompile("Ansible navigator run failed"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + variables := config.Variables{} + if test.variables != nil { + variables = test.variables(t) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "errors", test.name)), + ConfigVariables: testConfigVariables(t, variables), + ExpectError: test.expected, + }, + }, + }) + }) + } +} diff --git a/internal/provider/navigator_run_data_source_test.go b/internal/provider/navigator_run_data_source_test.go new file mode 100644 index 0000000..b3cdbf3 --- /dev/null +++ b/internal/provider/navigator_run_data_source_test.go @@ -0,0 +1,196 @@ +package provider_test + +import ( + "path/filepath" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +const ( + navigatorRunDataSource = "data.ansible_navigator_run.test" +) + +func TestAccNavigatorRunDataSource_artifact_queries(t *testing.T) { + t.Parallel() + + fileContents := "acc" + fileContentsUpdate := "acc_update" + var dataSourceCommand, dataSourceCommandUpdate string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "artifact_queries")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "file_contents": config.StringVariable(fileContents), + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")), + testExtractResourceAttr(navigatorRunDataSource, "command", &dataSourceCommand), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact(fileContents)), + }, + }, + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "artifact_queries")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "file_contents": config.StringVariable(fileContentsUpdate), + }), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestMatchResourceAttr(navigatorRunDataSource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")), + testExtractResourceAttr(navigatorRunDataSource, "command", &dataSourceCommandUpdate), + testCheckAttributeValuesDiffer(&dataSourceCommand, &dataSourceCommandUpdate), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact(fileContentsUpdate)), + }, + }, + }, + }) +} + +func TestAccNavigatorRunDataSource_basic(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "basic")), + ConfigVariables: testDefaultConfigVariables(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "playbook"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "inventory"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "working_directory"), + // resource.TestCheckResourceAttrSet(navigatorRunDataSource, "execution_environment"), TODO check elements + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "ansible_navigator_binary"), + resource.TestCheckNoResourceAttr(navigatorRunDataSource, "ansible_options"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "timezone"), + resource.TestCheckNoResourceAttr(navigatorRunDataSource, "triggers"), + resource.TestCheckNoResourceAttr(navigatorRunDataSource, "replacement_triggers"), + resource.TestCheckNoResourceAttr(navigatorRunDataSource, "artifact_queries"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "id"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "command"), + resource.TestCheckNoResourceAttr(navigatorRunDataSource, "timeouts"), + ), + }, + }, + }) +} + +func TestAccNavigatorRunDataSource_ee_defaults(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "ee_defaults")), + ConfigVariables: testDefaultConfigVariables(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "id"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "command"), + ), + }, + }, + }) +} + +func TestAccNavigatorRunDataSource_env_vars(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "env_vars")), + ConfigVariables: testDefaultConfigVariables(t), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "id"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "command"), + ), + }, + }, + }) +} + +//nolint:dupl //TODO fix +func TestAccNavigatorRunDataSource_private_keys(t *testing.T) { //nolint:paralleltest + tests := []struct { + name string + variables func(*testing.T) config.Variables + setup func(*testing.T) + }{ + { + name: "ee_enabled", + variables: func(t *testing.T) config.Variables { //nolint:thelper + return config.Variables{ + "ee_enabled": config.BoolVariable(true), + } + }, + setup: func(t *testing.T) { //nolint:thelper + t.Parallel() + }, + }, + { + name: "ee_disabled", + variables: func(t *testing.T) config.Variables { //nolint:thelper + return config.Variables{ + "ee_enabled": config.BoolVariable(false), + } + }, + setup: func(t *testing.T) { //nolint:thelper + testPrependPlaybookToPath(t) + }, + }, + } + + for _, test := range tests { //nolint:paralleltest + t.Run(test.name, func(t *testing.T) { + test.setup(t) + + variables := config.Variables{} + if test.variables != nil { + variables = test.variables(t) + } + + publicKey, privateKey := testSSHKeygen(t) + port := testSSHServer(t, publicKey) + + variables["private_key_data"] = config.StringVariable(privateKey) + variables["ssh_port"] = config.IntegerVariable(port) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_data_source", "private_keys")), + ConfigVariables: testConfigVariables(t, variables), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "id"), + resource.TestCheckResourceAttrSet(navigatorRunDataSource, "command"), + ), + }, + }, + }) + }) + } +} diff --git a/internal/provider/navigator_run_resource.go b/internal/provider/navigator_run_resource.go index d65bfe3..d575e50 100644 --- a/internal/provider/navigator_run_resource.go +++ b/internal/provider/navigator_run_resource.go @@ -100,6 +100,22 @@ func (ExecutionEnvironmentModel) AttrTypes() map[string]attr.Type { } } +func (m ExecutionEnvironmentModel) Defaults() basetypes.ObjectValue { + return types.ObjectValueMust( + ExecutionEnvironmentModel{}.AttrTypes(), + map[string]attr.Value{ + "container_engine": types.StringValue(defaultNavigatorRunContainerEngine), + "enabled": types.BoolValue(defaultNavigatorRunEEEnabled), + "environment_variables_pass": types.ListNull(types.StringType), + "environment_variables_set": types.MapNull(types.StringType), + "image": types.StringValue(defaultNavigatorRunImage), + "pull_arguments": types.ListNull(types.StringType), + "pull_policy": types.StringValue(defaultNavigatorRunPullPolicy), + "container_options": types.ListNull(types.StringType), + }, + ) +} + func (m ExecutionEnvironmentModel) Value(ctx context.Context, settings *ansible.NavigatorSettings) diag.Diagnostics { var diags diag.Diagnostics @@ -285,6 +301,7 @@ func (r *NavigatorRunResource) Metadata(ctx context.Context, req resource.Metada resp.TypeName = fmt.Sprintf("%s_navigator_run", req.ProviderTypeName) } +//nolint:dupl func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: fmt.Sprintf("Run an Ansible playbook. Requires '%s' and a container engine to run within an execution environment (EE).", ansible.NavigatorProgram), @@ -313,7 +330,7 @@ func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: fmt.Sprintf("Directory which `%s` is run from. Recommended to be the root Ansible [content directory](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html#sample-directory-layout) (sometimes called the project directory), which is likely to contain `ansible.cfg`, `roles/`, etc.", ansible.NavigatorProgram), Optional: true, Computed: true, - Default: stringdefault.StaticString("."), + Default: stringdefault.StaticString(defaultNavigatorRunWorkingDir), Validators: []validator.String{ stringvalidator.LengthAtLeast(1), }, @@ -323,19 +340,7 @@ func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: "[Execution environment](https://ansible.readthedocs.io/en/latest/getting_started_ee/index.html) (EE) related configuration.", Optional: true, Computed: true, - Default: objectdefault.StaticValue(types.ObjectValueMust( - ExecutionEnvironmentModel{}.AttrTypes(), - map[string]attr.Value{ - "container_engine": types.StringValue(defaultNavigatorRunContainerEngine), - "enabled": types.BoolValue(defaultNavigatorRunEEEnabled), - "environment_variables_pass": types.ListNull(types.StringType), - "environment_variables_set": types.MapNull(types.StringType), - "image": types.StringValue(defaultNavigatorRunImage), - "pull_arguments": types.ListNull(types.StringType), - "pull_policy": types.StringValue(defaultNavigatorRunPullPolicy), - "container_options": types.ListNull(types.StringType), - }, - )), + Default: objectdefault.StaticValue(ExecutionEnvironmentModel{}.Defaults()), Attributes: map[string]schema.Attribute{ "container_engine": schema.StringAttribute{ Description: fmt.Sprintf("Container engine responsible for running the execution environment container image. Options: %s. Defaults to '%s'.", wrapElementsJoin(ansible.ContainerEngineOptions(true), "'"), defaultNavigatorRunContainerEngine), @@ -347,7 +352,7 @@ func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRe stringvalidator.OneOf(ansible.ContainerEngineOptions(true)...), }, }, - "enabled": schema.BoolAttribute{ // TODO update docs/readme/repo to reflect this option + "enabled": schema.BoolAttribute{ Description: fmt.Sprintf("Enable or disable the use of an execution environment. Disabling requires '%s' and is only recommended when without a container engine. Defaults to '%t'.", ansible.PlaybookProgram, defaultNavigatorRunEEEnabled), MarkdownDescription: fmt.Sprintf("Enable or disable the use of an execution environment. Disabling requires `%s` and is only recommended when without a container engine. Defaults to `%t`.", ansible.PlaybookProgram, defaultNavigatorRunEEEnabled), Optional: true, @@ -364,8 +369,8 @@ func (r *NavigatorRunResource) Schema(ctx context.Context, req resource.SchemaRe }, }, "environment_variables_set": schema.MapAttribute{ - Description: fmt.Sprintf("Environment variables to be set within the execution environment. By default '%s' is set to the current CRUD operation (%s).", navigatorRunOperationEnvVar, wrapElementsJoin(terraformOperations, "'")), - MarkdownDescription: fmt.Sprintf("Environment variables to be [set](https://ansible.readthedocs.io/projects/navigator/settings/#set-environment-variable) within the execution environment. By default `%s` is set to the current CRUD operation (%s).", navigatorRunOperationEnvVar, wrapElementsJoin(terraformOperations, "`")), + Description: fmt.Sprintf("Environment variables to be set within the execution environment. By default '%s' is set to the current CRUD operation (%s).", navigatorRunOperationEnvVar, wrapElementsJoin(remove(terraformOperations, "read"), "'")), + MarkdownDescription: fmt.Sprintf("Environment variables to be [set](https://ansible.readthedocs.io/projects/navigator/settings/#set-environment-variable) within the execution environment. By default `%s` is set to the current CRUD operation (%s).", navigatorRunOperationEnvVar, wrapElementsJoin(remove(terraformOperations, "read"), "`")), Optional: true, ElementType: types.StringType, Validators: []validator.Map{ @@ -647,7 +652,7 @@ func (r *NavigatorRunResource) Create(ctx context.Context, req resource.CreateRe tflog.SetField(ctx, "runs", runs) - timeout, newDiags := terraformOperationTimeout(ctx, terraformOperationCreate, data.Timeouts, defaultNavigatorRunTimeout) + timeout, newDiags := terraformOperationResourceTimeout(ctx, terraformOperationCreate, data.Timeouts, defaultNavigatorRunTimeout) resp.Diagnostics.Append(newDiags...) if resp.Diagnostics.HasError() { @@ -709,7 +714,7 @@ func (r *NavigatorRunResource) Update(ctx context.Context, req resource.UpdateRe tflog.SetField(ctx, "runs", runs) - timeout, newDiags := terraformOperationTimeout(ctx, terraformOperationUpdate, data.Timeouts, defaultNavigatorRunTimeout) + timeout, newDiags := terraformOperationResourceTimeout(ctx, terraformOperationUpdate, data.Timeouts, defaultNavigatorRunTimeout) resp.Diagnostics.Append(newDiags...) if resp.Diagnostics.HasError() { @@ -757,7 +762,7 @@ func (r *NavigatorRunResource) Delete(ctx context.Context, req resource.DeleteRe tflog.SetField(ctx, "runs", runs) - timeout, newDiags := terraformOperationTimeout(ctx, terraformOperationDelete, data.Timeouts, defaultNavigatorRunTimeout) + timeout, newDiags := terraformOperationResourceTimeout(ctx, terraformOperationDelete, data.Timeouts, defaultNavigatorRunTimeout) resp.Diagnostics.Append(newDiags...) if resp.Diagnostics.HasError() { diff --git a/internal/provider/navigator_run_resource_errors_test.go b/internal/provider/navigator_run_resource_errors_test.go index db19109..5ee65ad 100644 --- a/internal/provider/navigator_run_resource_errors_test.go +++ b/internal/provider/navigator_run_resource_errors_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestAccNavigatorRun_errors(t *testing.T) { +func TestAccNavigatorRunResource_errors(t *testing.T) { t.Parallel() tests := []struct { @@ -79,7 +79,7 @@ func TestAccNavigatorRun_errors(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "errors", test.name)), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "errors", test.name)), ConfigVariables: testConfigVariables(t, variables), ExpectError: test.expected, }, diff --git a/internal/provider/navigator_run_resource_test.go b/internal/provider/navigator_run_resource_test.go index ad37344..25d4803 100644 --- a/internal/provider/navigator_run_resource_test.go +++ b/internal/provider/navigator_run_resource_test.go @@ -18,7 +18,7 @@ const ( navigatorRunResource = "ansible_navigator_run.test" ) -func TestAccNavigatorRun_ansible_options(t *testing.T) { +func TestAccNavigatorRunResource_ansible_options(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -26,7 +26,7 @@ func TestAccNavigatorRun_ansible_options(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "ansible_options")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "ansible_options")), ConfigVariables: testDefaultConfigVariables(t), Check: resource.ComposeAggregateTestCheckFunc( resource.TestMatchResourceAttr(navigatorRunResource, "command", regexp.MustCompile("--force-handlers --skip-tags tag1,tag2 --start-at-task task name --limit host1,host2 --tags tag3,tag4")), @@ -36,9 +36,11 @@ func TestAccNavigatorRun_ansible_options(t *testing.T) { }) } -func TestAccNavigatorRun_artifact_queries(t *testing.T) { +func TestAccNavigatorRunResource_artifact_queries(t *testing.T) { t.Parallel() + fileContents := "acc" + fileContentsUpdate := "acc_update" var resourceCommand, resourceCommandUpdate string resource.Test(t, resource.TestCase{ @@ -46,19 +48,23 @@ func TestAccNavigatorRun_artifact_queries(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "artifact_queries")), - ConfigVariables: testDefaultConfigVariables(t), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "artifact_queries")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "file_contents": config.StringVariable(fileContents), + }), Check: resource.ComposeAggregateTestCheckFunc( resource.TestMatchResourceAttr(navigatorRunResource, "artifact_queries.stdout.result", regexp.MustCompile("ok=3")), testExtractResourceAttr(navigatorRunResource, "command", &resourceCommand), ), ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact("acc")), + statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact(fileContents)), }, }, { - Config: testTerraformFile(t, filepath.Join("navigator_run", "artifact_queries_update")), - ConfigVariables: testDefaultConfigVariables(t), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "artifact_queries")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "file_contents": config.StringVariable(fileContentsUpdate), + }), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectNonEmptyPlan(), @@ -72,14 +78,14 @@ func TestAccNavigatorRun_artifact_queries(t *testing.T) { testCheckAttributeValuesDiffer(&resourceCommand, &resourceCommandUpdate), ), ConfigStateChecks: []statecheck.StateCheck{ - statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact("acc_update")), + statecheck.ExpectKnownOutputValue("file_contents", knownvalue.StringExact(fileContentsUpdate)), }, }, }, }) } -func TestAccNavigatorRun_basic(t *testing.T) { +func TestAccNavigatorRunResource_basic(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -87,7 +93,7 @@ func TestAccNavigatorRun_basic(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "basic")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "basic")), ConfigVariables: testDefaultConfigVariables(t), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "playbook"), @@ -107,7 +113,7 @@ func TestAccNavigatorRun_basic(t *testing.T) { ), }, { - Config: testTerraformFile(t, filepath.Join("navigator_run", "basic")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "basic")), ConfigVariables: testDefaultConfigVariables(t), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ @@ -119,7 +125,7 @@ func TestAccNavigatorRun_basic(t *testing.T) { }) } -func TestAccNavigatorRun_binary_in_path(t *testing.T) { //nolint:paralleltest +func TestAccNavigatorRunResource_binary_in_path(t *testing.T) { //nolint:paralleltest testPrependNavigatorToPath(t) resource.Test(t, resource.TestCase{ @@ -127,7 +133,7 @@ func TestAccNavigatorRun_binary_in_path(t *testing.T) { //nolint:paralleltest ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "binary_in_path")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "binary_in_path")), ConfigVariables: testDefaultConfigVariables(t), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), @@ -138,7 +144,7 @@ func TestAccNavigatorRun_binary_in_path(t *testing.T) { //nolint:paralleltest }) } -func TestAccNavigatorRun_ee_disabled(t *testing.T) { //nolint:paralleltest +func TestAccNavigatorRunResource_ee_disabled(t *testing.T) { //nolint:paralleltest testPrependPlaybookToPath(t) resource.Test(t, resource.TestCase{ @@ -146,7 +152,7 @@ func TestAccNavigatorRun_ee_disabled(t *testing.T) { //nolint:paralleltest ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "ee_disabled")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "ee_disabled")), ConfigVariables: testDefaultConfigVariables(t), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), @@ -157,7 +163,7 @@ func TestAccNavigatorRun_ee_disabled(t *testing.T) { //nolint:paralleltest }) } -func TestAccNavigatorRun_env_vars(t *testing.T) { +func TestAccNavigatorRunResource_env_vars(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -165,16 +171,20 @@ func TestAccNavigatorRun_env_vars(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "env_vars")), - ConfigVariables: testDefaultConfigVariables(t), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "env_vars")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "operation": config.StringVariable("create"), + }), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), resource.TestCheckResourceAttrSet(navigatorRunResource, "command"), ), }, { - Config: testTerraformFile(t, filepath.Join("navigator_run", "env_vars_update")), - ConfigVariables: testDefaultConfigVariables(t), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "env_vars")), + ConfigVariables: testConfigVariables(t, config.Variables{ + "operation": config.StringVariable("update"), + }), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectNonEmptyPlan(), @@ -185,59 +195,71 @@ func TestAccNavigatorRun_env_vars(t *testing.T) { }) } -func TestAccNavigatorRun_private_keys_ee_disabled(t *testing.T) { //nolint:paralleltest - testPrependPlaybookToPath(t) - - publicKey, privateKey := testSSHKeygen(t) - port := testSSHServer(t, publicKey) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testTerraformFile(t, filepath.Join("navigator_run", "private_keys")), - ConfigVariables: testConfigVariables(t, config.Variables{ - "ee_enabled": config.BoolVariable(false), - "private_key_data": config.StringVariable(privateKey), - "ssh_port": config.IntegerVariable(port), - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), - resource.TestCheckResourceAttrSet(navigatorRunResource, "command"), - ), +//nolint:dupl //TODO fix +func TestAccNavigatorRunResource_private_keys(t *testing.T) { //nolint:paralleltest + tests := []struct { + name string + variables func(*testing.T) config.Variables + setup func(*testing.T) + }{ + { + name: "ee_enabled", + variables: func(t *testing.T) config.Variables { //nolint:thelper + return config.Variables{ + "ee_enabled": config.BoolVariable(true), + } + }, + setup: func(t *testing.T) { //nolint:thelper + t.Parallel() }, }, - }) -} - -func TestAccNavigatorRun_private_keys(t *testing.T) { - t.Parallel() - - publicKey, privateKey := testSSHKeygen(t) - port := testSSHServer(t, publicKey) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testTerraformFile(t, filepath.Join("navigator_run", "private_keys")), - ConfigVariables: testConfigVariables(t, config.Variables{ - "ee_enabled": config.BoolVariable(true), - "private_key_data": config.StringVariable(privateKey), - "ssh_port": config.IntegerVariable(port), - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), - resource.TestCheckResourceAttrSet(navigatorRunResource, "command"), - ), + { + name: "ee_disabled", + variables: func(t *testing.T) config.Variables { //nolint:thelper + return config.Variables{ + "ee_enabled": config.BoolVariable(false), + } + }, + setup: func(t *testing.T) { //nolint:thelper + testPrependPlaybookToPath(t) }, }, - }) + } + + for _, test := range tests { //nolint:paralleltest + t.Run(test.name, func(t *testing.T) { + test.setup(t) + + variables := config.Variables{} + if test.variables != nil { + variables = test.variables(t) + } + + publicKey, privateKey := testSSHKeygen(t) + port := testSSHServer(t, publicKey) + + variables["private_key_data"] = config.StringVariable(privateKey) + variables["ssh_port"] = config.IntegerVariable(port) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "private_keys")), + ConfigVariables: testConfigVariables(t, variables), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), + resource.TestCheckResourceAttrSet(navigatorRunResource, "command"), + ), + }, + }, + }) + }) + } } -func TestAccNavigatorRun_pull_args(t *testing.T) { +func TestAccNavigatorRunResource_pull_args(t *testing.T) { t.Parallel() arg := "--tls-verify=true" @@ -247,7 +269,7 @@ func TestAccNavigatorRun_pull_args(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "pull_args")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "pull_args")), ConfigVariables: testConfigVariables(t, config.Variables{ "pull_arguments": config.ListVariable(config.StringVariable(arg)), }), @@ -259,7 +281,7 @@ func TestAccNavigatorRun_pull_args(t *testing.T) { }) } -func TestAccNavigatorRun_relative_binary(t *testing.T) { +func TestAccNavigatorRunResource_relative_binary(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -267,7 +289,7 @@ func TestAccNavigatorRun_relative_binary(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "relative_binary")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "relative_binary")), ConfigVariables: testConfigVariables(t, config.Variables{ "working_directory": config.StringVariable(t.TempDir()), }), @@ -280,7 +302,7 @@ func TestAccNavigatorRun_relative_binary(t *testing.T) { }) } -func TestAccNavigatorRun_role(t *testing.T) { +func TestAccNavigatorRunResource_role(t *testing.T) { t.Parallel() resource.Test(t, resource.TestCase{ @@ -288,10 +310,10 @@ func TestAccNavigatorRun_role(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "role")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "role")), ConfigVariables: testConfigVariables(t, config.Variables{ // https://github.com/hashicorp/terraform-plugin-testing/issues/277 - "working_directory": config.StringVariable(filepath.Join("testdata", "navigator_run", "role-working-dir")), + "working_directory": config.StringVariable(filepath.Join("testdata", "navigator_run_resource", "role-working-dir")), }), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), @@ -302,7 +324,7 @@ func TestAccNavigatorRun_role(t *testing.T) { }) } -func TestAccNavigatorRun_skip_run(t *testing.T) { +func TestAccNavigatorRunResource_skip_run(t *testing.T) { t.Parallel() var resourceCommand, resourceCommandUpdate string @@ -312,7 +334,7 @@ func TestAccNavigatorRun_skip_run(t *testing.T) { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testTerraformFile(t, filepath.Join("navigator_run", "skip_run")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "skip_run")), ConfigVariables: testDefaultConfigVariables(t), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(navigatorRunResource, "id"), @@ -321,7 +343,7 @@ func TestAccNavigatorRun_skip_run(t *testing.T) { ), }, { - Config: testTerraformFile(t, filepath.Join("navigator_run", "skip_run_update")), + Config: testTerraformFile(t, filepath.Join("navigator_run_resource", "skip_run_update")), ConfigVariables: testConfigVariables(t, config.Variables{ "ansible_navigator_binary": config.StringVariable(acctest.RandString(8)), }), diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2097bf1..5b094c8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -108,7 +108,9 @@ func (p *AnsibleProvider) Resources(ctx context.Context) []func() resource.Resou } func (p *AnsibleProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + NewNavigatorRunDataSource, + } } func New(version string) func() provider.Provider { diff --git a/internal/provider/provider_utils.go b/internal/provider/provider_utils.go index c96f0fc..0ce47ce 100644 --- a/internal/provider/provider_utils.go +++ b/internal/provider/provider_utils.go @@ -6,7 +6,9 @@ import ( "strings" "time" - "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + dataSourceTimeouts "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + resourceTimeouts "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -14,6 +16,7 @@ import ( const ( terraformOperationCreate = iota + terraformOperationRead = iota terraformOperationUpdate = iota terraformOperationDelete = iota diagDetailPrefix = "Underlying error details" @@ -26,16 +29,18 @@ type providerOptions struct { type terraformOperation int -var terraformOperations = []string{"create", "update", "delete"} //nolint:gochecknoglobals +var terraformOperations = []string{"create", "read", "update", "delete"} //nolint:gochecknoglobals func (op terraformOperation) String() string { return terraformOperations[op] } -func terraformOperationTimeout(ctx context.Context, operation terraformOperation, value timeouts.Value, defaultTimeout time.Duration) (time.Duration, diag.Diagnostics) { +func terraformOperationResourceTimeout(ctx context.Context, operation terraformOperation, value resourceTimeouts.Value, defaultTimeout time.Duration) (time.Duration, diag.Diagnostics) { switch operation { case terraformOperationCreate: return value.Create(ctx, defaultTimeout) + case terraformOperationRead: + return value.Read(ctx, defaultTimeout) case terraformOperationUpdate: return value.Update(ctx, defaultTimeout) case terraformOperationDelete: @@ -45,6 +50,10 @@ func terraformOperationTimeout(ctx context.Context, operation terraformOperation } } +func terraformOperationDataSourceTimeout(ctx context.Context, value dataSourceTimeouts.Value, defaultTimeout time.Duration) (time.Duration, diag.Diagnostics) { + return value.Read(ctx, defaultTimeout) +} + func unknownProviderValue(value path.Path) (string, string) { return fmt.Sprintf("Unknown configuration value '%s'", value), fmt.Sprintf("The provider cannot be configured as there is an unknown configuration value for '%s'. ", value) + @@ -56,27 +65,27 @@ func unexpectedConfigureType(value string, providerData any) (string, string) { fmt.Sprintf("Expected *providerOptions, got: %T. Please report this issue to the provider developers.", providerData) } -// func configureDataSourceClient(req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) (*providerOptions, bool) { -// if req.ProviderData == nil { -// return nil, false -// } +func configureDataSourceClient(req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) (*providerOptions, bool) { + if req.ProviderData == nil { + return nil, false + } -// opts, ok := req.ProviderData.(*providerOptions) + opts, ok := req.ProviderData.(*providerOptions) -// if !ok { -// summary, detail := unexpectedConfigureType("Data Source", req.ProviderData) -// resp.Diagnostics.AddError(summary, detail) -// } + if !ok { + summary, detail := unexpectedConfigureType("Data Source", req.ProviderData) + resp.Diagnostics.AddError(summary, detail) + } -// return opts, ok -// } + return opts, ok +} func configureResourceClient(req resource.ConfigureRequest, resp *resource.ConfigureResponse) (*providerOptions, bool) { if req.ProviderData == nil { return nil, false } - opts, ok := req.ProviderData.(*providerOptions) //nolint:varnamelen + opts, ok := req.ProviderData.(*providerOptions) if !ok { summary, detail := unexpectedConfigureType("Resource", req.ProviderData) @@ -128,3 +137,14 @@ func wrapElements(input []string, wrap string) []string { func wrapElementsJoin(input []string, wrap string) string { return strings.Join(wrapElements(input, wrap), ", ") } + +func remove[T comparable](l []T, item T) []T { + out := make([]T, 0) + for _, element := range l { + if element != item { + out = append(out, element) + } + } + + return out +} diff --git a/internal/provider/run.go b/internal/provider/run.go index daea48e..5d6ddd8 100644 --- a/internal/provider/run.go +++ b/internal/provider/run.go @@ -16,6 +16,7 @@ import ( const ( navigatorRunDir = "tf-ansible-navigator-run" navigatorRunOperationEnvVar = "ANSIBLE_TF_OPERATION" + defaultNavigatorRunWorkingDir = "." defaultNavigatorRunTimeout = 10 * time.Minute defaultNavigatorRunContainerEngine = ansible.ContainerEngineAuto defaultNavigatorRunEEEnabled = true diff --git a/internal/provider/testdata/navigator_run/artifact_queries_update.tf b/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf similarity index 73% rename from internal/provider/testdata/navigator_run/artifact_queries_update.tf rename to internal/provider/testdata/navigator_run_data_source/artifact_queries.tf index 3f4543b..e14d4d8 100644 --- a/internal/provider/testdata/navigator_run/artifact_queries_update.tf +++ b/internal/provider/testdata/navigator_run_data_source/artifact_queries.tf @@ -1,4 +1,4 @@ -resource "ansible_navigator_run" "test" { +data "ansible_navigator_run" "test" { ansible_navigator_binary = var.ansible_navigator_binary playbook = <<-EOT - name: Test @@ -8,7 +8,7 @@ resource "ansible_navigator_run" "test" { - name: write file ansible.builtin.copy: dest: /tmp/test - content: acc_update + content: ${var.file_contents} - name: get file ansible.builtin.slurp: src: /tmp/test @@ -25,10 +25,15 @@ resource "ansible_navigator_run" "test" { } output "file_contents" { - value = base64decode(ansible_navigator_run.test.artifact_queries.file_contents.result) + value = base64decode(data.ansible_navigator_run.test.artifact_queries.file_contents.result) } variable "ansible_navigator_binary" { type = string nullable = false } + +variable "file_contents" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run_data_source/basic.tf b/internal/provider/testdata/navigator_run_data_source/basic.tf new file mode 100644 index 0000000..8fe1d40 --- /dev/null +++ b/internal/provider/testdata/navigator_run_data_source/basic.tf @@ -0,0 +1,29 @@ +data "ansible_navigator_run" "test" { + ansible_navigator_binary = var.ansible_navigator_binary + playbook = <<-EOT + - hosts: some_group + become: false + tasks: + - ansible.builtin.debug: + msg: "{{ some_var }}" + EOT + inventory = yamlencode({ + all = { + children = { + some_group = { + hosts = { + local_container = { + ansible_connection = "local" + some_var = "hello world!" + } + } + } + } + } + }) +} + +variable "ansible_navigator_binary" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run_data_source/ee_defaults.tf b/internal/provider/testdata/navigator_run_data_source/ee_defaults.tf new file mode 100644 index 0000000..f7c171e --- /dev/null +++ b/internal/provider/testdata/navigator_run_data_source/ee_defaults.tf @@ -0,0 +1,14 @@ +data "ansible_navigator_run" "test" { + ansible_navigator_binary = var.ansible_navigator_binary + playbook = <<-EOT + - hosts: localhost + become: false + EOT + inventory = "# localhost" + execution_environment = {} +} + +variable "ansible_navigator_binary" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run/env_vars.tf b/internal/provider/testdata/navigator_run_data_source/env_vars.tf similarity index 92% rename from internal/provider/testdata/navigator_run/env_vars.tf rename to internal/provider/testdata/navigator_run_data_source/env_vars.tf index e0f205b..1dcf960 100644 --- a/internal/provider/testdata/navigator_run/env_vars.tf +++ b/internal/provider/testdata/navigator_run_data_source/env_vars.tf @@ -1,4 +1,4 @@ -resource "ansible_navigator_run" "test" { +data "ansible_navigator_run" "test" { ansible_navigator_binary = var.ansible_navigator_binary playbook = <<-EOT - hosts: localhost @@ -6,7 +6,7 @@ resource "ansible_navigator_run" "test" { tasks: - ansible.builtin.assert: that: - - lookup('ansible.builtin.env', 'ANSIBLE_TF_OPERATION') == 'create' + - lookup('ansible.builtin.env', 'ANSIBLE_TF_OPERATION') == 'read' - lookup('ansible.builtin.env', 'TF_ACC') == '1' - lookup('ansible.builtin.env', 'TESTING') == 'abc' EOT diff --git a/internal/provider/testdata/navigator_run_data_source/errors/playbook.tf b/internal/provider/testdata/navigator_run_data_source/errors/playbook.tf new file mode 100644 index 0000000..f5e3ebb --- /dev/null +++ b/internal/provider/testdata/navigator_run_data_source/errors/playbook.tf @@ -0,0 +1,16 @@ +data "ansible_navigator_run" "test" { + ansible_navigator_binary = var.ansible_navigator_binary + playbook = <<-EOT + - hosts: localhost + become: false + tasks: + - ansible.builtin.fail: + msg: test + EOT + inventory = "# localhost" +} + +variable "ansible_navigator_binary" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run_data_source/private_keys.tf b/internal/provider/testdata/navigator_run_data_source/private_keys.tf new file mode 100644 index 0000000..a0bc410 --- /dev/null +++ b/internal/provider/testdata/navigator_run_data_source/private_keys.tf @@ -0,0 +1,58 @@ +data "ansible_navigator_run" "test" { + ansible_navigator_binary = var.ansible_navigator_binary + playbook = <<-EOT + - hosts: test + gather_facts: false + become: false + tasks: + - ansible.builtin.raw: test + register: connect + - ansible.builtin.assert: + that: connect.stdout == 'hello world!' + EOT + inventory = yamlencode({ + all = { + hosts = { + test = { + ansible_host = "127.0.0.1" + ansible_port = var.ssh_port + ansible_ssh_common_args = "-o UserKnownHostsFile=/dev/null" + } + } + } + }) + execution_environment = { + enabled = var.ee_enabled + container_options = [ + "--net=host", + ] + } + ansible_options = { + private_keys = [ + { + name = "test" + data = var.private_key_data + } + ] + } +} + +variable "ansible_navigator_binary" { + type = string + nullable = false +} + +variable "ee_enabled" { + type = bool + nullable = false +} + +variable "private_key_data" { + type = string + nullable = false +} + +variable "ssh_port" { + type = number + nullable = false +} diff --git a/internal/provider/testdata/navigator_run/ansible_options.tf b/internal/provider/testdata/navigator_run_resource/ansible_options.tf similarity index 100% rename from internal/provider/testdata/navigator_run/ansible_options.tf rename to internal/provider/testdata/navigator_run_resource/ansible_options.tf diff --git a/internal/provider/testdata/navigator_run/artifact_queries.tf b/internal/provider/testdata/navigator_run_resource/artifact_queries.tf similarity index 88% rename from internal/provider/testdata/navigator_run/artifact_queries.tf rename to internal/provider/testdata/navigator_run_resource/artifact_queries.tf index 95e22f4..be7b551 100644 --- a/internal/provider/testdata/navigator_run/artifact_queries.tf +++ b/internal/provider/testdata/navigator_run_resource/artifact_queries.tf @@ -8,7 +8,7 @@ resource "ansible_navigator_run" "test" { - name: write file ansible.builtin.copy: dest: /tmp/test - content: acc + content: ${var.file_contents} - name: get file ansible.builtin.slurp: src: /tmp/test @@ -32,3 +32,8 @@ variable "ansible_navigator_binary" { type = string nullable = false } + +variable "file_contents" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run/basic.tf b/internal/provider/testdata/navigator_run_resource/basic.tf similarity index 100% rename from internal/provider/testdata/navigator_run/basic.tf rename to internal/provider/testdata/navigator_run_resource/basic.tf diff --git a/internal/provider/testdata/navigator_run/binary_in_path.tf b/internal/provider/testdata/navigator_run_resource/binary_in_path.tf similarity index 100% rename from internal/provider/testdata/navigator_run/binary_in_path.tf rename to internal/provider/testdata/navigator_run_resource/binary_in_path.tf diff --git a/internal/provider/testdata/navigator_run/ee_disabled.tf b/internal/provider/testdata/navigator_run_resource/ee_disabled.tf similarity index 100% rename from internal/provider/testdata/navigator_run/ee_disabled.tf rename to internal/provider/testdata/navigator_run_resource/ee_disabled.tf diff --git a/internal/provider/testdata/navigator_run/env_vars_update.tf b/internal/provider/testdata/navigator_run_resource/env_vars.tf similarity index 78% rename from internal/provider/testdata/navigator_run/env_vars_update.tf rename to internal/provider/testdata/navigator_run_resource/env_vars.tf index 559d818..a0e8a39 100644 --- a/internal/provider/testdata/navigator_run/env_vars_update.tf +++ b/internal/provider/testdata/navigator_run_resource/env_vars.tf @@ -6,9 +6,10 @@ resource "ansible_navigator_run" "test" { tasks: - ansible.builtin.assert: that: - - lookup('ansible.builtin.env', 'ANSIBLE_TF_OPERATION') == 'update' + - lookup('ansible.builtin.env', 'ANSIBLE_TF_OPERATION') == '${var.operation}' - lookup('ansible.builtin.env', 'TF_ACC') == '1' - lookup('ansible.builtin.env', 'TESTING') == 'abc' + when: lookup('ansible.builtin.env', 'ANSIBLE_TF_OPERATION') != 'delete' EOT inventory = "# localhost" execution_environment = { @@ -19,9 +20,15 @@ resource "ansible_navigator_run" "test" { "TESTING" = "abc" } } + run_on_destroy = true } variable "ansible_navigator_binary" { type = string nullable = false } + +variable "operation" { + type = string + nullable = false +} diff --git a/internal/provider/testdata/navigator_run/errors/artifact_query.tf b/internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/artifact_query.tf rename to internal/provider/testdata/navigator_run_resource/errors/artifact_query.tf diff --git a/internal/provider/testdata/navigator_run/errors/env_var_name.tf b/internal/provider/testdata/navigator_run_resource/errors/env_var_name.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/env_var_name.tf rename to internal/provider/testdata/navigator_run_resource/errors/env_var_name.tf diff --git a/internal/provider/testdata/navigator_run/errors/navigator_preflight.tf b/internal/provider/testdata/navigator_run_resource/errors/navigator_preflight.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/navigator_preflight.tf rename to internal/provider/testdata/navigator_run_resource/errors/navigator_preflight.tf diff --git a/internal/provider/testdata/navigator_run/errors/playbook.tf b/internal/provider/testdata/navigator_run_resource/errors/playbook.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/playbook.tf rename to internal/provider/testdata/navigator_run_resource/errors/playbook.tf diff --git a/internal/provider/testdata/navigator_run/errors/playbook_yaml.tf b/internal/provider/testdata/navigator_run_resource/errors/playbook_yaml.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/playbook_yaml.tf rename to internal/provider/testdata/navigator_run_resource/errors/playbook_yaml.tf diff --git a/internal/provider/testdata/navigator_run/errors/private_keys.tf b/internal/provider/testdata/navigator_run_resource/errors/private_keys.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/private_keys.tf rename to internal/provider/testdata/navigator_run_resource/errors/private_keys.tf diff --git a/internal/provider/testdata/navigator_run/errors/timeout.tf b/internal/provider/testdata/navigator_run_resource/errors/timeout.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/timeout.tf rename to internal/provider/testdata/navigator_run_resource/errors/timeout.tf diff --git a/internal/provider/testdata/navigator_run/errors/timezone.tf b/internal/provider/testdata/navigator_run_resource/errors/timezone.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/timezone.tf rename to internal/provider/testdata/navigator_run_resource/errors/timezone.tf diff --git a/internal/provider/testdata/navigator_run/errors/working_directory.tf b/internal/provider/testdata/navigator_run_resource/errors/working_directory.tf similarity index 100% rename from internal/provider/testdata/navigator_run/errors/working_directory.tf rename to internal/provider/testdata/navigator_run_resource/errors/working_directory.tf diff --git a/internal/provider/testdata/navigator_run/private_keys.tf b/internal/provider/testdata/navigator_run_resource/private_keys.tf similarity index 100% rename from internal/provider/testdata/navigator_run/private_keys.tf rename to internal/provider/testdata/navigator_run_resource/private_keys.tf diff --git a/internal/provider/testdata/navigator_run/pull_args.tf b/internal/provider/testdata/navigator_run_resource/pull_args.tf similarity index 100% rename from internal/provider/testdata/navigator_run/pull_args.tf rename to internal/provider/testdata/navigator_run_resource/pull_args.tf diff --git a/internal/provider/testdata/navigator_run/relative_binary.tf b/internal/provider/testdata/navigator_run_resource/relative_binary.tf similarity index 100% rename from internal/provider/testdata/navigator_run/relative_binary.tf rename to internal/provider/testdata/navigator_run_resource/relative_binary.tf diff --git a/internal/provider/testdata/navigator_run/role-working-dir/ansible.cfg b/internal/provider/testdata/navigator_run_resource/role-working-dir/ansible.cfg similarity index 100% rename from internal/provider/testdata/navigator_run/role-working-dir/ansible.cfg rename to internal/provider/testdata/navigator_run_resource/role-working-dir/ansible.cfg diff --git a/internal/provider/testdata/navigator_run/role-working-dir/roles/test_role/tasks/main.yaml b/internal/provider/testdata/navigator_run_resource/role-working-dir/roles/test_role/tasks/main.yaml similarity index 100% rename from internal/provider/testdata/navigator_run/role-working-dir/roles/test_role/tasks/main.yaml rename to internal/provider/testdata/navigator_run_resource/role-working-dir/roles/test_role/tasks/main.yaml diff --git a/internal/provider/testdata/navigator_run/role.tf b/internal/provider/testdata/navigator_run_resource/role.tf similarity index 100% rename from internal/provider/testdata/navigator_run/role.tf rename to internal/provider/testdata/navigator_run_resource/role.tf diff --git a/internal/provider/testdata/navigator_run/skip_run.tf b/internal/provider/testdata/navigator_run_resource/skip_run.tf similarity index 100% rename from internal/provider/testdata/navigator_run/skip_run.tf rename to internal/provider/testdata/navigator_run_resource/skip_run.tf diff --git a/internal/provider/testdata/navigator_run/skip_run_update.tf b/internal/provider/testdata/navigator_run_resource/skip_run_update.tf similarity index 100% rename from internal/provider/testdata/navigator_run/skip_run_update.tf rename to internal/provider/testdata/navigator_run_resource/skip_run_update.tf