diff --git a/docs/resources/elasticsearch_ml_job_state.md b/docs/resources/elasticsearch_ml_job_state.md
new file mode 100644
index 000000000..7e586c7ce
--- /dev/null
+++ b/docs/resources/elasticsearch_ml_job_state.md
@@ -0,0 +1,145 @@
+---
+page_title: "elasticstack_elasticsearch_ml_job_state Resource - terraform-provider-elasticstack"
+subcategory: ""
+description: |-
+ ML Job State Resource
+ Manages the state of an Elasticsearch Machine Learning (ML) job, allowing you to open or close ML jobs.
+ This resource uses the following Elasticsearch APIs:
+ Open ML Job API https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.htmlClose ML Job API https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.htmlGet ML Job Stats API https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html
+ Important Notes
+ This resource manages the state of an existing ML job, not the job configuration itself.The ML job must already exist before using this resource.Opening a job allows it to receive and process data.Closing a job stops data processing and frees up resources.Jobs can be opened and closed multiple times throughout their lifecycle.
+---
+
+# elasticstack_elasticsearch_ml_job_state (Resource)
+
+# ML Job State Resource
+
+Manages the state of an Elasticsearch Machine Learning (ML) job, allowing you to open or close ML jobs.
+
+This resource uses the following Elasticsearch APIs:
+- [Open ML Job API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.html)
+- [Close ML Job API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.html)
+- [Get ML Job Stats API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html)
+
+## Important Notes
+
+- This resource manages the **state** of an existing ML job, not the job configuration itself.
+- The ML job must already exist before using this resource.
+- Opening a job allows it to receive and process data.
+- Closing a job stops data processing and frees up resources.
+- Jobs can be opened and closed multiple times throughout their lifecycle.
+
+## Example Usage
+
+```terraform
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+# First create an ML anomaly detection job
+resource "elasticstack_elasticsearch_ml_anomaly_detector" "example" {
+ job_id = "example-ml-job"
+ description = "Example anomaly detection job"
+
+ analysis_config = {
+ bucket_span = "15m"
+ detectors = [
+ {
+ function = "count"
+ detector_description = "Count detector"
+ }
+ ]
+ }
+
+ data_description = {
+ time_field = "@timestamp"
+ time_format = "epoch_ms"
+ }
+}
+
+# Manage the state of the ML job - open it
+resource "elasticstack_elasticsearch_ml_job_state" "example" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.example.job_id
+ state = "opened"
+
+ # Optional settings
+ force = false
+ job_timeout = "30s"
+
+ # Timeouts for asynchronous operations
+ timeouts {
+ create = "5m"
+ update = "5m"
+ }
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.example]
+}
+
+# Example with different configuration options
+resource "elasticstack_elasticsearch_ml_job_state" "example_with_options" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.example.job_id
+ state = "closed"
+
+ # Use force close for quicker shutdown
+ force = true
+
+ # Custom timeout
+ job_timeout = "2m"
+
+ # Custom timeouts for asynchronous operations
+ timeouts {
+ create = "10m"
+ update = "3m"
+ }
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.example]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `job_id` (String) Identifier for the anomaly detection job.
+- `state` (String) The desired state for the ML job. Valid values are `opened` and `closed`.
+
+### Optional
+
+- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection))
+- `force` (Boolean) When closing a job, use to forcefully close it. This method is quicker but can miss important clean up tasks.
+- `job_timeout` (String) Timeout for the operation. Examples: `30s`, `5m`, `1h`. Default is `30s`.
+- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
+
+### Read-Only
+
+- `id` (String) Internal identifier of the resource
+
+
+### Nested Schema for `elasticsearch_connection`
+
+Optional:
+
+- `api_key` (String, Sensitive) API Key to use for authentication to Elasticsearch
+- `bearer_token` (String, Sensitive) Bearer Token to use for authentication to Elasticsearch
+- `ca_data` (String) PEM-encoded custom Certificate Authority certificate
+- `ca_file` (String) Path to a custom Certificate Authority certificate
+- `cert_data` (String) PEM encoded certificate for client auth
+- `cert_file` (String) Path to a file containing the PEM encoded certificate for client auth
+- `endpoints` (List of String, Sensitive) A list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number.
+- `es_client_authentication` (String, Sensitive) ES Client Authentication field to be used with the JWT token
+- `headers` (Map of String, Sensitive) A list of headers to be sent with each request to Elasticsearch.
+- `insecure` (Boolean) Disable TLS certificate validation
+- `key_data` (String, Sensitive) PEM encoded private key for client auth
+- `key_file` (String) Path to a file containing the PEM encoded private key for client auth
+- `password` (String, Sensitive) Password to use for API authentication to Elasticsearch.
+- `username` (String) Username to use for API authentication to Elasticsearch.
+
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (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).
+- `update` (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).
\ No newline at end of file
diff --git a/examples/resources/elasticstack_elasticsearch_ml_job_state/resource.tf b/examples/resources/elasticstack_elasticsearch_ml_job_state/resource.tf
new file mode 100644
index 000000000..1bfc44c00
--- /dev/null
+++ b/examples/resources/elasticstack_elasticsearch_ml_job_state/resource.tf
@@ -0,0 +1,62 @@
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+# First create an ML anomaly detection job
+resource "elasticstack_elasticsearch_ml_anomaly_detector" "example" {
+ job_id = "example-ml-job"
+ description = "Example anomaly detection job"
+
+ analysis_config = {
+ bucket_span = "15m"
+ detectors = [
+ {
+ function = "count"
+ detector_description = "Count detector"
+ }
+ ]
+ }
+
+ data_description = {
+ time_field = "@timestamp"
+ time_format = "epoch_ms"
+ }
+}
+
+# Manage the state of the ML job - open it
+resource "elasticstack_elasticsearch_ml_job_state" "example" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.example.job_id
+ state = "opened"
+
+ # Optional settings
+ force = false
+ job_timeout = "30s"
+
+ # Timeouts for asynchronous operations
+ timeouts {
+ create = "5m"
+ update = "5m"
+ }
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.example]
+}
+
+# Example with different configuration options
+resource "elasticstack_elasticsearch_ml_job_state" "example_with_options" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.example.job_id
+ state = "closed"
+
+ # Use force close for quicker shutdown
+ force = true
+
+ # Custom timeout
+ job_timeout = "2m"
+
+ # Custom timeouts for asynchronous operations
+ timeouts {
+ create = "10m"
+ update = "3m"
+ }
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.example]
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index a553bf000..b58bbd729 100644
--- a/go.mod
+++ b/go.mod
@@ -11,10 +11,11 @@ require (
github.com/hashicorp/go-cty v1.5.0
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
- github.com/hashicorp/terraform-plugin-framework v1.15.1
+ github.com/hashicorp/terraform-plugin-framework v1.16.0
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
+ github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0
- github.com/hashicorp/terraform-plugin-go v0.28.0
+ github.com/hashicorp/terraform-plugin-go v0.29.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.20.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0
@@ -27,11 +28,11 @@ require (
require (
al.essio.dev/pkg/shellescape v1.6.0 // indirect
- cel.dev/expr v0.22.1 // indirect
+ cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.120.0 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
- cloud.google.com/go/compute/metadata v0.6.0 // indirect
+ cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/iam v1.4.2 // indirect
cloud.google.com/go/kms v1.21.1 // indirect
cloud.google.com/go/longrunning v0.6.6 // indirect
@@ -59,7 +60,7 @@ require (
github.com/Azure/go-autorest/tracing v0.6.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
+ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
@@ -131,7 +132,7 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
- github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
+ github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
@@ -170,8 +171,8 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.14.0 // indirect
- github.com/go-jose/go-jose/v4 v4.1.0 // indirect
- github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-jose/go-jose/v4 v4.1.1 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect
@@ -216,7 +217,7 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
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.3 // indirect
+ github.com/hashicorp/go-plugin v1.7.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect
@@ -225,9 +226,9 @@ require (
github.com/hashicorp/terraform-exec v0.23.0 // indirect
github.com/hashicorp/terraform-json v0.25.0 // indirect
github.com/hashicorp/terraform-plugin-docs v0.22.0 // indirect
- github.com/hashicorp/terraform-registry-address v0.2.5 // indirect
+ github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
- github.com/hashicorp/yamux v0.1.1 // indirect
+ github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/in-toto/attestation v1.1.1 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
@@ -361,14 +362,14 @@ require (
go.mongodb.org/mongo-driver v1.17.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
+ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
- go.opentelemetry.io/otel v1.35.0 // indirect
- go.opentelemetry.io/otel/metric v1.35.0 // indirect
- go.opentelemetry.io/otel/sdk v1.35.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
- go.opentelemetry.io/otel/trace v1.35.0 // indirect
+ go.opentelemetry.io/otel v1.37.0 // indirect
+ go.opentelemetry.io/otel/metric v1.37.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.37.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
+ go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
@@ -389,10 +390,10 @@ require (
google.golang.org/api v0.228.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect
- google.golang.org/grpc v1.72.1 // indirect
- google.golang.org/protobuf v1.36.6 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
+ google.golang.org/grpc v1.75.1 // indirect
+ google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index 101f85864..e1f50eed3 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
-cel.dev/expr v0.22.1 h1:xoFEsNh972Yzey8N9TCPx2nDvMN7TMhQEzxLuj/iRrI=
-cel.dev/expr v0.22.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
+cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
+cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
@@ -9,8 +9,8 @@ cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
-cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
-cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
+cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
+cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q=
cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34=
cloud.google.com/go/kms v1.21.1 h1:r1Auo+jlfJSf8B7mUnVw5K0fI7jWyoUy65bV53VjKyk=
@@ -98,8 +98,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
-github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
@@ -230,8 +230,8 @@ github.com/bluesky-social/indigo v0.0.0-20240813042137-4006c0eca043/go.mod h1:dX
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
-github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM=
-github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
+github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
+github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk=
@@ -282,8 +282,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
-github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
@@ -407,11 +407,11 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
-github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
-github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
+github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
+github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
@@ -572,8 +572,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
-github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
-github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
+github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
+github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
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-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
@@ -607,14 +607,16 @@ github.com/hashicorp/terraform-json v0.25.0 h1:rmNqc/CIfcWawGiwXmRuiXJKEiJu1ntGo
github.com/hashicorp/terraform-json v0.25.0/go.mod h1:sMKS8fiRDX4rVlR6EJUMudg1WcanxCMoWwTLkgZP/vc=
github.com/hashicorp/terraform-plugin-docs v0.22.0 h1:fwIDStbFel1PPNkM+mDPnpB4efHZBdGoMz/zt5FbTDw=
github.com/hashicorp/terraform-plugin-docs v0.22.0/go.mod h1:55DJVyZ7BNK4t/lANcQ1YpemRuS6KsvIO1BbGA+xzGE=
-github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8=
-github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI=
+github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE=
+github.com/hashicorp/terraform-plugin-framework v1.16.0/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA=
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0 h1:Vv16e7EW4nT9668IV0RhdpEmnLl0im7BZx6J+QMlUkg=
+github.com/hashicorp/terraform-plugin-framework-timeouts v0.6.0/go.mod h1:rpHo9hZLn4vEkvNL5xsSdLRdaDZKSinuc0xL+BdOpVA=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw=
github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA=
-github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA=
-github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o=
+github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
+github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-mux v0.20.0 h1:3QpBnI9uCuL0Yy2Rq/kR9cOdmOFNhw88A2GoZtk5aXM=
@@ -623,14 +625,14 @@ github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsor
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA=
github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo=
github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE=
-github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M=
-github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg=
+github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
+github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4=
github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA=
-github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
-github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
+github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM=
github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -693,8 +695,8 @@ github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8Fb
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7/go.mod h1:BMxO138bOokdgt4UaxZiEfypcSHX0t6SIFimVP1oRfk=
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
-github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
-github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
+github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
+github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -1080,16 +1082,16 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
-go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
-go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
+go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
+go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
-go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
-go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
+go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
@@ -1114,16 +1116,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
-go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
-go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
-go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
-go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
+go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
+go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
+go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
+go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
-go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
-go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
-go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
-go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
+go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
+go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.step.sm/crypto v0.60.0 h1:UgSw8DFG5xUOGB3GUID17UA32G4j1iNQ4qoMhBmsVFw=
@@ -1304,6 +1306,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@@ -1315,17 +1319,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw=
google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0=
-google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs=
-google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
+google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
-google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
+google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1337,8 +1341,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
-google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
+google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/clients/elasticsearch/ml_job.go b/internal/clients/elasticsearch/ml_job.go
new file mode 100644
index 000000000..d03ac6225
--- /dev/null
+++ b/internal/clients/elasticsearch/ml_job.go
@@ -0,0 +1,138 @@
+package elasticsearch
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/elastic/go-elasticsearch/v8/esapi"
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients"
+ "github.com/elastic/terraform-provider-elasticstack/internal/diagutil"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+)
+
+// MLJobStats represents the statistics structure for an ML job
+type MLJobStats struct {
+ Jobs []MLJob `json:"jobs"`
+}
+
+// MLJob represents a single ML job in the stats response
+type MLJob struct {
+ JobId string `json:"job_id"`
+ State string `json:"state"`
+ Node *MLJobNode `json:"node,omitempty"`
+}
+
+// MLJobNode represents the node information for an ML job
+type MLJobNode struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Attributes map[string]interface{} `json:"attributes"`
+}
+
+// OpenMLJob opens a machine learning job
+func OpenMLJob(ctx context.Context, apiClient *clients.ApiClient, jobId string) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ esClient, err := apiClient.GetESClient()
+ if err != nil {
+ diags.AddError("Failed to get Elasticsearch client", err.Error())
+ return diags
+ }
+
+ res, err := esClient.ML.OpenJob(jobId, esClient.ML.OpenJob.WithContext(ctx))
+ if err != nil {
+ diags.AddError("Failed to open ML job", err.Error())
+ return diags
+ }
+ defer res.Body.Close()
+
+ fwDiags := diagutil.CheckErrorFromFW(res, fmt.Sprintf("Unable to open ML job: %s", jobId))
+ diags.Append(fwDiags...)
+
+ return diags
+}
+
+// CloseMLJob closes a machine learning job
+func CloseMLJob(ctx context.Context, apiClient *clients.ApiClient, jobId string, force bool, timeout time.Duration) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ esClient, err := apiClient.GetESClient()
+ if err != nil {
+ diags.AddError("Failed to get Elasticsearch client", err.Error())
+ return diags
+ }
+
+ options := []func(*esapi.MLCloseJobRequest){
+ esClient.ML.CloseJob.WithContext(ctx),
+ esClient.ML.CloseJob.WithForce(force),
+ esClient.ML.CloseJob.WithAllowNoMatch(true),
+ }
+
+ if timeout > 0 {
+ options = append(options, esClient.ML.CloseJob.WithTimeout(timeout))
+ }
+
+ res, err := esClient.ML.CloseJob(jobId, options...)
+ if err != nil {
+ diags.AddError("Failed to close ML job", err.Error())
+ return diags
+ }
+ defer res.Body.Close()
+
+ fwDiags := diagutil.CheckErrorFromFW(res, fmt.Sprintf("Unable to close ML job: %s", jobId))
+ diags.Append(fwDiags...)
+
+ return diags
+}
+
+// GetMLJobStats retrieves the stats for a specific machine learning job
+func GetMLJobStats(ctx context.Context, apiClient *clients.ApiClient, jobId string) (*MLJob, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ esClient, err := apiClient.GetESClient()
+ if err != nil {
+ diags.AddError("Failed to get Elasticsearch client", err.Error())
+ return nil, diags
+ }
+
+ options := []func(*esapi.MLGetJobStatsRequest){
+ esClient.ML.GetJobStats.WithContext(ctx),
+ esClient.ML.GetJobStats.WithJobID(jobId),
+ esClient.ML.GetJobStats.WithAllowNoMatch(true),
+ }
+
+ res, err := esClient.ML.GetJobStats(options...)
+ if err != nil {
+ diags.AddError("Failed to get ML job stats", err.Error())
+ return nil, diags
+ }
+ defer res.Body.Close()
+
+ if res.StatusCode == http.StatusNotFound {
+ return nil, diags
+ }
+
+ if fwDiags := diagutil.CheckErrorFromFW(res, fmt.Sprintf("Unable to get ML job stats: %s", jobId)); fwDiags.HasError() {
+ diags.Append(fwDiags...)
+ return nil, diags
+ }
+
+ var jobStats MLJobStats
+ if err := json.NewDecoder(res.Body).Decode(&jobStats); err != nil {
+ diags.AddError("Failed to decode ML job stats response", err.Error())
+ return nil, diags
+ }
+
+ // Find the specific job in the response
+ for _, job := range jobStats.Jobs {
+ if job.JobId == jobId {
+ return &job, diags
+ }
+ }
+
+ // Job not found in response
+ return nil, diags
+}
diff --git a/internal/elasticsearch/ml/job_state/acc_test.go b/internal/elasticsearch/ml/job_state/acc_test.go
new file mode 100644
index 000000000..dd95da6b9
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/acc_test.go
@@ -0,0 +1,192 @@
+package job_state_test
+
+import (
+ "fmt"
+ "regexp"
+ "testing"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/acctest"
+ sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccResourceMLJobState(t *testing.T) {
+ jobID := fmt.Sprintf("test-ml-job-state-%s", sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.Providers,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceMLJobStateConfig(jobID, "opened"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_id", jobID),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "state", "opened"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "force", "false"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_timeout", "30s"),
+ resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_ml_job_state.test", "id"),
+ // Verify that the ML job was created by the anomaly detector resource
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_anomaly_detector.test", "job_id", jobID),
+ ),
+ },
+ {
+ Config: testAccResourceMLJobStateConfig(jobID, "closed"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_id", jobID),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "state", "closed"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "force", "false"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_timeout", "30s"),
+ resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_ml_job_state.test", "id"),
+ ),
+ },
+ {
+ Config: testAccResourceMLJobStateConfigWithOptions(jobID, "opened", true, "1m"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_id", jobID),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "state", "opened"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "force", "true"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "force", "true"),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_timeout", "1m"),
+ resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_ml_job_state.test", "id"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccResourceMLJobStateNonExistent(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.Providers,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceMLJobStateNonExistent,
+ ExpectError: regexp.MustCompile(`ML job .* does not exist`),
+ },
+ },
+ })
+}
+
+func TestAccResourceMLJobStateImport(t *testing.T) {
+ jobID := fmt.Sprintf("test-ml-job-state-import-%s", sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.Providers,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceMLJobStateConfig(jobID, "opened"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "job_id", jobID),
+ resource.TestCheckResourceAttr("elasticstack_elasticsearch_ml_job_state.test", "state", "opened"),
+ ),
+ },
+ {
+ ResourceName: "elasticstack_elasticsearch_ml_job_state.test",
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ rs := s.RootModule().Resources["elasticstack_elasticsearch_ml_job_state.test"]
+ return rs.Primary.ID, nil
+ },
+ },
+ },
+ })
+}
+
+func testAccResourceMLJobStateConfig(jobID, state string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+# First create an ML anomaly detection job
+resource "elasticstack_elasticsearch_ml_anomaly_detector" "test" {
+ job_id = "%s"
+ description = "Test anomaly detection job for state management"
+
+ analysis_config = {
+ bucket_span = "15m"
+ detectors = [
+ {
+ function = "count"
+ detector_description = "Count detector"
+ }
+ ]
+ }
+
+ analysis_limits = {
+ model_memory_limit = "100mb"
+ }
+
+ data_description = {
+ time_field = "@timestamp"
+ time_format = "epoch_ms"
+ }
+}
+
+# Then manage the state of that ML job
+resource "elasticstack_elasticsearch_ml_job_state" "test" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.test.job_id
+ state = "%s"
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.test]
+}
+`, jobID, state)
+}
+
+func testAccResourceMLJobStateConfigWithOptions(jobID, state string, force bool, timeout string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+# First create an ML anomaly detection job
+resource "elasticstack_elasticsearch_ml_anomaly_detector" "test" {
+ job_id = "%s"
+ description = "Test anomaly detection job for state management with options"
+
+ analysis_config = {
+ bucket_span = "15m"
+ detectors = [
+ {
+ function = "count"
+ detector_description = "Count detector"
+ }
+ ]
+ }
+
+ analysis_limits = {
+ model_memory_limit = "100mb"
+ }
+
+ data_description = {
+ time_field = "@timestamp"
+ time_format = "epoch_ms"
+ }
+}
+
+# Then manage the state of that ML job with custom options
+resource "elasticstack_elasticsearch_ml_job_state" "test" {
+ job_id = elasticstack_elasticsearch_ml_anomaly_detector.test.job_id
+ state = "%s"
+ force = %t
+ job_timeout = "%s"
+
+ depends_on = [elasticstack_elasticsearch_ml_anomaly_detector.test]
+}
+`, jobID, state, force, timeout)
+}
+
+const testAccResourceMLJobStateNonExistent = `
+provider "elasticstack" {
+ elasticsearch {}
+}
+
+# Try to manage state of a non-existent ML job
+resource "elasticstack_elasticsearch_ml_job_state" "test" {
+ job_id = "non-existent-ml-job"
+ state = "opened"
+}
+`
diff --git a/internal/elasticsearch/ml/job_state/create.go b/internal/elasticsearch/ml/job_state/create.go
new file mode 100644
index 000000000..10e7914cd
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/create.go
@@ -0,0 +1,27 @@
+package job_state
+
+import (
+ "context"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+)
+
+func (r *mlJobStateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var data MLJobStateData
+ diags := req.Plan.Get(ctx, &data)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get create timeout
+ createTimeout, fwDiags := data.Timeouts.Create(ctx, 5*time.Minute) // Default 5 minutes
+ resp.Diagnostics.Append(fwDiags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ diags = r.update(ctx, req.Plan, &resp.State, createTimeout)
+ resp.Diagnostics.Append(diags...)
+}
diff --git a/internal/elasticsearch/ml/job_state/delete.go b/internal/elasticsearch/ml/job_state/delete.go
new file mode 100644
index 000000000..fa8457cc6
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/delete.go
@@ -0,0 +1,21 @@
+package job_state
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func (r *mlJobStateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ // ML job state resource only manages the state, not the job itself.
+ // When the resource is deleted, we simply remove it from Terraform state
+ // without affecting the actual ML job state in Elasticsearch.
+ // The job will remain in its current state (opened or closed).
+ var jobId basetypes.StringValue
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("job_id"), &jobId)...)
+ tflog.Info(ctx, fmt.Sprintf(`Dropping ML job state "%s", this does not close the job`, jobId.ValueString()))
+}
diff --git a/internal/elasticsearch/ml/job_state/models.go b/internal/elasticsearch/ml/job_state/models.go
new file mode 100644
index 000000000..fd9638343
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/models.go
@@ -0,0 +1,36 @@
+package job_state
+
+import (
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type MLJobStateData struct {
+ Id types.String `tfsdk:"id"`
+ ElasticsearchConnection types.List `tfsdk:"elasticsearch_connection"`
+ JobId types.String `tfsdk:"job_id"`
+ State types.String `tfsdk:"state"`
+ Force types.Bool `tfsdk:"force"`
+ Timeout customtypes.Duration `tfsdk:"job_timeout"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+}
+
+// MLJobStats represents the statistics structure for an ML job
+type MLJobStats struct {
+ Jobs []MLJob `json:"jobs"`
+}
+
+// MLJob represents a single ML job in the stats response
+type MLJob struct {
+ JobId string `json:"job_id"`
+ State string `json:"state"`
+ Node *MLJobNode `json:"node,omitempty"`
+}
+
+// MLJobNode represents the node information for an ML job
+type MLJobNode struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Attributes map[string]interface{} `json:"attributes"`
+}
diff --git a/internal/elasticsearch/ml/job_state/read.go b/internal/elasticsearch/ml/job_state/read.go
new file mode 100644
index 000000000..63c49227c
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/read.go
@@ -0,0 +1,61 @@
+package job_state
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients"
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func (r *mlJobStateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var data MLJobStateData
+ resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString())
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ jobId := compId.ResourceId
+
+ client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get job stats to check current state
+ currentJob, fwDiags := elasticsearch.GetMLJobStats(ctx, client, jobId)
+ resp.Diagnostics.Append(fwDiags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if currentJob == nil {
+ tflog.Warn(ctx, fmt.Sprintf(`ML job "%s" not found, removing from state`, jobId))
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ // Update the state with current job information
+ data.JobId = types.StringValue(jobId)
+ data.State = types.StringValue(currentJob.State)
+
+ // Set defaults for computed attributes if they're not already set (e.g., during import)
+ if data.Force.IsNull() {
+ data.Force = types.BoolValue(false)
+ }
+ if data.Timeout.IsNull() {
+ data.Timeout = customtypes.NewDurationValue("30s")
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
diff --git a/internal/elasticsearch/ml/job_state/resource-description.md b/internal/elasticsearch/ml/job_state/resource-description.md
new file mode 100644
index 000000000..6da15e32a
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/resource-description.md
@@ -0,0 +1,16 @@
+# ML Job State Resource
+
+Manages the state of an Elasticsearch Machine Learning (ML) job, allowing you to open or close ML jobs.
+
+This resource uses the following Elasticsearch APIs:
+- [Open ML Job API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-open-job.html)
+- [Close ML Job API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-close-job.html)
+- [Get ML Job Stats API](https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-job-stats.html)
+
+## Important Notes
+
+- This resource manages the **state** of an existing ML job, not the job configuration itself.
+- The ML job must already exist before using this resource.
+- Opening a job allows it to receive and process data.
+- Closing a job stops data processing and frees up resources.
+- Jobs can be opened and closed multiple times throughout their lifecycle.
\ No newline at end of file
diff --git a/internal/elasticsearch/ml/job_state/resource.go b/internal/elasticsearch/ml/job_state/resource.go
new file mode 100644
index 000000000..0067a7689
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/resource.go
@@ -0,0 +1,32 @@
+package job_state
+
+import (
+ "context"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+)
+
+func NewMLJobStateResource() resource.Resource {
+ return &mlJobStateResource{}
+}
+
+type mlJobStateResource struct {
+ client *clients.ApiClient
+}
+
+func (r *mlJobStateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_elasticsearch_ml_job_state"
+}
+
+func (r *mlJobStateResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ client, diags := clients.ConvertProviderData(req.ProviderData)
+ resp.Diagnostics.Append(diags...)
+ r.client = client
+}
+
+func (r *mlJobStateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ // Retrieve import ID and save to id attribute
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/internal/elasticsearch/ml/job_state/schema.go b/internal/elasticsearch/ml/job_state/schema.go
new file mode 100644
index 000000000..a5ddb67d2
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/schema.go
@@ -0,0 +1,80 @@
+package job_state
+
+import (
+ "context"
+ _ "embed"
+ "regexp"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/utils/customtypes"
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "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/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+
+ providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema"
+)
+
+//go:embed resource-description.md
+var mlJobStateResourceDescription string
+
+func (r *mlJobStateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = GetSchema()
+}
+
+func GetSchema() schema.Schema {
+ return schema.Schema{
+ MarkdownDescription: mlJobStateResourceDescription,
+ Blocks: map[string]schema.Block{
+ "elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false),
+ },
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Internal identifier of the resource",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "job_id": schema.StringAttribute{
+ MarkdownDescription: "Identifier for the anomaly detection job.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.LengthBetween(1, 64),
+ stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), "must contain only alphanumeric characters, hyphens, and underscores"),
+ },
+ },
+ "state": schema.StringAttribute{
+ MarkdownDescription: "The desired state for the ML job. Valid values are `opened` and `closed`.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("opened", "closed"),
+ },
+ },
+ "force": schema.BoolAttribute{
+ MarkdownDescription: "When closing a job, use to forcefully close it. This method is quicker but can miss important clean up tasks.",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ },
+ "job_timeout": schema.StringAttribute{
+ MarkdownDescription: "Timeout for the operation. Examples: `30s`, `5m`, `1h`. Default is `30s`.",
+ Optional: true,
+ Computed: true,
+ Default: stringdefault.StaticString("30s"),
+ CustomType: customtypes.DurationType{},
+ },
+ "timeouts": timeouts.Attributes(context.Background(), timeouts.Opts{
+ Create: true,
+ Update: true,
+ }),
+ },
+ }
+}
diff --git a/internal/elasticsearch/ml/job_state/update.go b/internal/elasticsearch/ml/job_state/update.go
new file mode 100644
index 000000000..d52699ac0
--- /dev/null
+++ b/internal/elasticsearch/ml/job_state/update.go
@@ -0,0 +1,181 @@
+package job_state
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients"
+ "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/tfsdk"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func (r *mlJobStateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var data MLJobStateData
+ diags := req.Plan.Get(ctx, &data)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Get update timeout
+ updateTimeout, fwDiags := data.Timeouts.Update(ctx, 5*time.Minute) // Default 5 minutes
+ resp.Diagnostics.Append(fwDiags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ diags = r.update(ctx, req.Plan, &resp.State, updateTimeout)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+func (r *mlJobStateResource) update(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State, operationTimeout time.Duration) diag.Diagnostics {
+ var data MLJobStateData
+ diags := plan.Get(ctx, &data)
+ if diags.HasError() {
+ return diags
+ }
+
+ client, fwDiags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client)
+ diags.Append(fwDiags...)
+ if diags.HasError() {
+ return diags
+ }
+
+ jobId := data.JobId.ValueString()
+ desiredState := data.State.ValueString()
+
+ // First, get the current job stats to check if the job exists and its current state
+ currentJob, fwDiags := elasticsearch.GetMLJobStats(ctx, client, jobId)
+ diags.Append(fwDiags...)
+ if diags.HasError() {
+ return diags
+ }
+
+ if currentJob == nil {
+ diags.AddError(
+ "ML Job not found",
+ fmt.Sprintf("ML job %s does not exist", jobId),
+ )
+ return diags
+ }
+
+ currentState := currentJob.State
+
+ // Perform state transition if needed
+ fwDiags = r.performStateTransition(ctx, client, data, currentState, operationTimeout)
+ diags.Append(fwDiags...)
+ if diags.HasError() {
+ return diags
+ }
+
+ // Generate composite ID
+ compId, sdkDiags := client.ID(ctx, jobId)
+ if len(sdkDiags) > 0 {
+ for _, d := range sdkDiags {
+ diags.AddError(d.Summary, d.Detail)
+ }
+ return diags
+ }
+
+ // Set the response state
+ data.Id = types.StringValue(compId.String())
+ data.JobId = types.StringValue(jobId)
+ data.State = types.StringValue(desiredState)
+
+ diags.Append(state.Set(ctx, data)...)
+ return diags
+}
+
+// waitForJobStateTransition waits for an ML job to reach the desired state
+func waitForJobStateTransition(ctx context.Context, client *clients.ApiClient, jobId, desiredState string) error {
+ const pollInterval = 2 * time.Second
+ ticker := time.NewTicker(pollInterval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-ticker.C:
+ currentJob, fwDiags := elasticsearch.GetMLJobStats(ctx, client, jobId)
+ if fwDiags.HasError() {
+ return fmt.Errorf("failed to get job stats during state transition check")
+ }
+
+ if currentJob == nil {
+ return fmt.Errorf("job not found during state transition check")
+ }
+
+ if currentJob.State == desiredState {
+ return nil // Successfully reached desired state
+ }
+ tflog.Debug(ctx, fmt.Sprintf("ML job %s current state: %s, waiting for: %s", jobId, currentJob.State, desiredState))
+ }
+ }
+}
+
+// performStateTransition handles the ML job state transition process
+func (r *mlJobStateResource) performStateTransition(ctx context.Context, client *clients.ApiClient, data MLJobStateData, currentState string, operationTimeout time.Duration) diag.Diagnostics {
+ jobId := data.JobId.ValueString()
+ desiredState := data.State.ValueString()
+ force := data.Force.ValueBool()
+
+ // Parse timeout duration
+ timeout, parseErrs := data.Timeout.Parse()
+ if parseErrs.HasError() {
+ return parseErrs
+ }
+
+ // Return early if no state change is needed
+ if currentState == desiredState {
+ tflog.Debug(ctx, fmt.Sprintf("ML job %s is already in desired state %s", jobId, desiredState))
+ return nil
+ }
+
+ // Create context with timeout
+ ctx, cancel := context.WithTimeout(ctx, operationTimeout)
+ defer cancel()
+
+ // Initiate the state change
+ switch desiredState {
+ case "opened":
+ diags := elasticsearch.OpenMLJob(ctx, client, jobId)
+ if diags.HasError() {
+ return diags
+ }
+ case "closed":
+ diags := elasticsearch.CloseMLJob(ctx, client, jobId, force, timeout) // Always allow no match
+ if diags.HasError() {
+ return diags
+ }
+ default:
+ return diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "Invalid state",
+ fmt.Sprintf("Invalid state %s. Valid states are 'opened' and 'closed'", desiredState),
+ ),
+ }
+ }
+
+ // Wait for state transition to complete
+ err := waitForJobStateTransition(ctx, client, jobId, desiredState)
+ if err != nil {
+ return diag.Diagnostics{
+ diag.NewErrorDiagnostic(
+ "State transition timeout",
+ fmt.Sprintf("ML job %s did not transition to state %s within timeout: %s", jobId, desiredState, err.Error()),
+ ),
+ }
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("ML job %s successfully transitioned to state %s", jobId, desiredState))
+ return nil
+}
diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go
index bba736241..2c96cab21 100644
--- a/provider/plugin_framework.go
+++ b/provider/plugin_framework.go
@@ -10,6 +10,7 @@ import (
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle"
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/index"
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/indices"
+ "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/ml/job_state"
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/api_key"
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/role_mapping"
"github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security/system_user"
@@ -116,5 +117,6 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource {
maintenance_window.NewResource,
enrich.NewEnrichPolicyResource,
role_mapping.NewRoleMappingResource,
+ job_state.NewMLJobStateResource,
}
}
diff --git a/templates/resources/elasticsearch_ml_job_state.md.tmpl b/templates/resources/elasticsearch_ml_job_state.md.tmpl
new file mode 100644
index 000000000..113f174f2
--- /dev/null
+++ b/templates/resources/elasticsearch_ml_job_state.md.tmpl
@@ -0,0 +1,18 @@
+---
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | trimspace }}
+
+{{ if .HasExample -}}
+## Example Usage
+
+{{ tffile .ExampleFile }}
+{{- end }}
+
+{{ .SchemaMarkdown | trimspace }}
\ No newline at end of file