diff --git a/.github/workflows/index.jsonl b/.github/workflows/index.jsonl index ebef23b..93de758 100644 --- a/.github/workflows/index.jsonl +++ b/.github/workflows/index.jsonl @@ -1,5 +1,5 @@ { "index" : { "_index" : "kestra_roles", "_id" : "admin" } } -{"id":"admin","name":"Admin", "isDefault": false,"permissions":{"FLOW":["READ","CREATE","UPDATE","DELETE"],"TEMPLATE":["READ","CREATE","UPDATE","DELETE"],"EXECUTION":["READ","CREATE","UPDATE","DELETE"],"USER":["READ","CREATE","UPDATE","DELETE"],"NAMESPACE":["READ","CREATE","UPDATE","DELETE"],"GROUP":["READ","CREATE","UPDATE","DELETE"],"ROLE":["READ","CREATE","UPDATE","DELETE"],"AUDITLOG":["READ"],"SECRET":["READ","CREATE","UPDATE","DELETE"],"BINDING":["READ","CREATE","UPDATE","DELETE"],"TENANT":["READ","CREATE","UPDATE","DELETE"]},"deleted":false} +{"id":"admin","name":"Admin", "isDefault": false,"permissions":{"FLOW":["READ","CREATE","UPDATE","DELETE"],"TEMPLATE":["READ","CREATE","UPDATE","DELETE"],"EXECUTION":["READ","CREATE","UPDATE","DELETE"],"USER":["READ","CREATE","UPDATE","DELETE"],"NAMESPACE":["READ","CREATE","UPDATE","DELETE"],"GROUP":["READ","CREATE","UPDATE","DELETE"],"ROLE":["READ","CREATE","UPDATE","DELETE"],"AUDITLOG":["READ"],"SECRET":["READ","CREATE","UPDATE","DELETE"],"BINDING":["READ","CREATE","UPDATE","DELETE"],"TENANT":["READ","CREATE","UPDATE","DELETE"],"KVSTORE":["READ","CREATE","UPDATE","DELETE"]},"deleted":false} { "index" : { "_index" : "kestra_roles", "_id" : "admin2" } } {"id":"admin2","name":"Admin","tenantId":"unit_test", "isDefault": false,"permissions":{"FLOW":["READ","CREATE","UPDATE","DELETE"],"TEMPLATE":["READ","CREATE","UPDATE","DELETE"],"EXECUTION":["READ","CREATE","UPDATE","DELETE"],"USER":["READ","CREATE","UPDATE","DELETE"],"NAMESPACE":["READ","CREATE","UPDATE","DELETE"],"GROUP":["READ","CREATE","UPDATE","DELETE"],"ROLE":["READ","CREATE","UPDATE","DELETE"],"AUDITLOG":["READ"],"SECRET":["READ","CREATE","UPDATE","DELETE"],"BINDING":["READ","CREATE","UPDATE","DELETE"],"TENANT":["READ","CREATE","UPDATE","DELETE"]},"deleted":false} { "index" : { "_index" : "kestra_groups", "_id" : "admin" } } diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9a9949..9f61728 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,6 +80,16 @@ jobs: curl -H "Content-Type: application/x-ndjson" -XPOST "127.27.27.27:9200/_bulk?pretty" --data-binary @.github/workflows/index.jsonl sleep 3 curl -H "Content-Type: multipart/form-data" -u john@doe.com:pass -X POST -F fileContent=@internal/resources/flow.py "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/files?path=/flow.py" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d '"stringValue"' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/string" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d '1' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/int" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d '1.5' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/double" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d 'false' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/falseBoolean" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d 'true' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/trueBoolean" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d '2022-05-01T03:02:01Z' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/dateTime" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d '2022-05-01' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/date" + curl -H "Content-Type: text/plain" -u john@doe.com:pass -X PUT -d 'P3DT3H2M1S' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/duration" + curl -H "Content-Type: application/json" -u john@doe.com:pass -X PUT -d '{"some":"value","in":"object"}' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/object" + curl -H "Content-Type: application/json" -u john@doe.com:pass -X PUT -d '[{"some":"value","in":"object"},{"yet":"another","array":"object"}]' "127.27.27.27:8080/api/v1/namespaces/io.kestra.terraform.data/kv/array" - name: Set up Go diff --git a/internal/provider/data_source_kv.go b/internal/provider/data_source_kv.go new file mode 100644 index 0000000..0ba099b --- /dev/null +++ b/internal/provider/data_source_kv.go @@ -0,0 +1,112 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceKv() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to access value for an existing Key-Value pair.", + + ReadContext: dataSourceKvRead, + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "The tenant id.", + Type: schema.TypeString, + Computed: true, + }, + "namespace": { + Description: "The namespace of the Key-Value pair.", + Type: schema.TypeString, + Required: true, + }, + "key": { + Description: "The key to fetch value for.", + Type: schema.TypeString, + Required: true, + }, + "type": { + Description: "The type of the value. One of STRING, NUMBER, BOOLEAN, DATETIME, DATE, DURATION, JSON.", + Type: schema.TypeString, + Computed: true, + }, + "value": { + Description: "The fetched value.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceKvRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + tenantId := c.TenantId + namespace := d.Get("namespace").(string) + key := d.Get("key").(string) + + url := c.Url + fmt.Sprintf("%s/namespaces/%s/kv/%s", apiRoot(tenantId), namespace, key) + + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf(url), nil) + if err != nil { + return diag.FromErr(err) + } + + statusCode, body, reqErr := c.rawResponseRequest("GET", req) + if reqErr != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + return diags + } + return diag.FromErr(reqErr.Err) + } + + if tenantId != nil { + if err := d.Set("tenant_id", *tenantId); err != nil { + return diag.FromErr(err) + } + } + d.SetId(fmt.Sprintf("%s/%s", namespace, key)) + + var kvResponsePtr struct { + Type string `json:"type"` + Value any `json:"value"` + } + if err := json.Unmarshal(body, &kvResponsePtr); err != nil { + return diag.FromErr(err) + } + if err := d.Set("namespace", namespace); err != nil { + return diag.FromErr(err) + } + if err := d.Set("key", key); err != nil { + return diag.FromErr(err) + } + valueType := kvResponsePtr.Type + if err := d.Set("type", valueType); err != nil { + return diag.FromErr(err) + } + + value := "" + if valueType == "JSON" { + valueBytes, err := json.Marshal(kvResponsePtr.Value) + if err != nil { + return diag.FromErr(err) + } + value = string(valueBytes) + } else { + value = fmt.Sprint(kvResponsePtr.Value) + } + if err := d.Set("value", value); err != nil { + return diag.FromErr(err) + } + + return diags +} diff --git a/internal/provider/data_source_kv_test.go b/internal/provider/data_source_kv_test.go new file mode 100644 index 0000000..ae81a0d --- /dev/null +++ b/internal/provider/data_source_kv_test.go @@ -0,0 +1,161 @@ +package provider + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceKv(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "string"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "string", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "stringValue", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "int"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "int", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "1", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "double"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "double", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "1.5", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "falseBoolean"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "falseBoolean", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "false", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "trueBoolean"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "trueBoolean", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "true", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "dateTime"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "dateTime", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "2022-05-01T03:02:01Z", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "date"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "date", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "2022-05-01", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "duration"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "duration", + ), + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "value", "PT75H2M1S", + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "object"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "object", + ), + resource.TestCheckResourceAttrWith( + "data.kestra_kv.new", "value", func(value string) error { + var obj map[string]string + err := json.Unmarshal([]byte(value), &obj) + if err != nil { + return err + } + if obj["some"] == "value" && obj["in"] == "object" { + return nil + } + return fmt.Errorf("unexpected value: %s", value) + }, + ), + ), + }, + { + Config: testAccDataSourceKv("io.kestra.terraform.data", "array"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "data.kestra_kv.new", "key", "array", + ), + resource.TestCheckResourceAttrWith( + "data.kestra_kv.new", "value", func(value string) error { + var obj []map[string]string + err := json.Unmarshal([]byte(value), &obj) + if err != nil { + return err + } + if obj[0]["some"] == "value" && obj[0]["in"] == "object" && + obj[1]["yet"] == "another" && obj[1]["array"] == "object" { + return nil + } + return fmt.Errorf("unexpected value: %s", value) + }, + ), + ), + }, + }, + }) +} + +func testAccDataSourceKv(namespace, key any) string { + return fmt.Sprintf( + ` + data "kestra_kv" "new" { + namespace = "%s" + key = "%s" + }`, + namespace, + key, + ) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4e0d779..4a2c19b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -92,6 +92,7 @@ func New(version string, tenant *string) func() *schema.Provider { "kestra_namespace_file": dataSourceNamespaceFile(), "kestra_service_account": dataSourceServiceAccount(), "kestra_user_api_tokens": dataSourceUserApiTokens(), + "kestra_kv": dataSourceKv(), }, ResourcesMap: map[string]*schema.Resource{ "kestra_binding": resourceBinding(), @@ -107,6 +108,7 @@ func New(version string, tenant *string) func() *schema.Provider { "kestra_namespace_file": resourceNamespaceFile(), "kestra_service_account": resourceServiceAccount(), "kestra_user_api_token": resourceUserApiToken(), + "kestra_kv": resourceKv(), }, } diff --git a/internal/provider/resource_kv.go b/internal/provider/resource_kv.go new file mode 100644 index 0000000..2c0ceb3 --- /dev/null +++ b/internal/provider/resource_kv.go @@ -0,0 +1,208 @@ +package provider + +import ( + "context" + "encoding/json" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "net/http" + "regexp" + "strings" +) + +func resourceKv() *schema.Resource { + return &schema.Resource{ + Description: "Manages a Kestra Namespace File.", + + CreateContext: resourceKvSet, + UpdateContext: resourceKvSet, + ReadContext: resourceKvRead, + DeleteContext: resourceKvDelete, + Schema: map[string]*schema.Schema{ + "tenant_id": { + Description: "The tenant id.", + Type: schema.TypeString, + Computed: true, + ForceNew: true, + }, + "namespace": { + Description: "The namespace of the Key-Value pair.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "key": { + Description: "The key of the pair.", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": { + Description: "The type of the value. If not provided, we will try to deduce the type based on the value. Useful in case you provide numbers, booleans, dates or json that you want to be stored as string." + + " Accepted values are: STRING, NUMBER, BOOLEAN, DATETIME, DATE, DURATION, JSON.", + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: func(k, oldValue, newValue string, d *schema.ResourceData) bool { + return newValue == "" || oldValue == newValue + }, + DiffSuppressOnRefresh: true, + }, + "value": { + Description: "The fetched value.", + Type: schema.TypeString, + Required: true, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceKvSet(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + tenantId := c.TenantId + namespace := d.Get("namespace").(string) + key := d.Get("key").(string) + typeValue, typeWasProvided := d.GetOk("type") + value := d.Get("value").(string) + + formattedValue := value + if typeWasProvided { + if typeValue == "STRING" && !strings.HasPrefix(value, "\"") { + formattedValue = fmt.Sprintf("\"%s\"", strings.ReplaceAll(value, "\"", "\\\"")) + } + } else { + stringPattern := "^(?![\\d{\\[\"]+)(?!false)(?!true)(?!P(?=[^T]|T.)(?:\\d*D)?(?:T(?=.)(?:\\d*H)?(?:\\d*M)?(?:\\d*S)?)?).*$" + matched, _ := regexp.MatchString(stringPattern, value) + if matched { + formattedValue = fmt.Sprintf("\"%s\"", value) + } + } + + url := c.Url + fmt.Sprintf("%s/namespaces/%s/kv/%s", apiRoot(tenantId), namespace, key) + + httpMethod := "PUT" + req, err := http.NewRequestWithContext(ctx, httpMethod, fmt.Sprintf(url), strings.NewReader(formattedValue)) + if err != nil { + return diag.FromErr(err) + } + + _, _, reqErr := c.rawResponseRequest(httpMethod, req) + if reqErr != nil { + return diag.FromErr(reqErr.Err) + } + + if tenantId != nil { + if err := d.Set("tenant_id", c.TenantId); err != nil { + return diag.FromErr(err) + } + } + d.SetId(fmt.Sprintf("%s/%s", namespace, key)) + + return diags +} + +func resourceKvRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + tenantId := c.TenantId + namespace, key := idToNamespaceAndKey(d.Id()) + + url := c.Url + fmt.Sprintf("%s/namespaces/%s/kv/%s", apiRoot(tenantId), namespace, key) + + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf(url), nil) + if err != nil { + return diag.FromErr(err) + } + + statusCode, body, reqErr := c.rawResponseRequest("GET", req) + if reqErr != nil { + if statusCode == http.StatusNotFound { + d.SetId("") + return diags + } + return diag.FromErr(reqErr.Err) + } + + if tenantId != nil { + if err := d.Set("tenant_id", *tenantId); err != nil { + return diag.FromErr(err) + } + } + d.SetId(fmt.Sprintf("%s/%s", namespace, key)) + + var kvResponsePtr struct { + Type string `json:"type"` + Value any `json:"value"` + } + if err := json.Unmarshal(body, &kvResponsePtr); err != nil { + return diag.FromErr(err) + } + if err := d.Set("namespace", namespace); err != nil { + return diag.FromErr(err) + } + if err := d.Set("key", key); err != nil { + return diag.FromErr(err) + } + valueType := kvResponsePtr.Type + if err := d.Set("type", valueType); err != nil { + return diag.FromErr(err) + } + + value := "" + if valueType == "JSON" { + valueBytes, err := json.Marshal(kvResponsePtr.Value) + if err != nil { + return diag.FromErr(err) + } + value = string(valueBytes) + } else { + value = fmt.Sprint(kvResponsePtr.Value) + } + if err := d.Set("value", value); err != nil { + return diag.FromErr(err) + } + + return diags +} + +func resourceKvDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + tenantId := c.TenantId + namespace, key := idToNamespaceAndKey(d.Id()) + + url := c.Url + fmt.Sprintf("%s/namespaces/%s/kv/%s", apiRoot(tenantId), namespace, key) + + httpMethod := "DELETE" + req, err := http.NewRequestWithContext(ctx, httpMethod, fmt.Sprintf(url), nil) + if err != nil { + return diag.FromErr(err) + } + + statusCode, _, reqErr := c.rawResponseRequest(httpMethod, req) + if reqErr != nil { + if statusCode != http.StatusNotFound { + return diag.FromErr(reqErr.Err) + } + } + + d.SetId("") + + return diags +} + +func idToNamespaceAndKey(id string) (string, string) { + parts := strings.Split(id, "/") + if len(parts) < 2 { + return "", "" + } + + return parts[0], parts[1] +} diff --git a/internal/provider/resource_kv_test.go b/internal/provider/resource_kv_test.go new file mode 100644 index 0000000..641909d --- /dev/null +++ b/internal/provider/resource_kv_test.go @@ -0,0 +1,229 @@ +package provider + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "net/http" + "os" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccKv(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccResourceKv( + "io.kestra.terraform", + "string", + "stringValue", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "id", "io.kestra.terraform/string", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "key", "string", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "stringValue", + ), + resource.TestCheckNoResourceAttr( + "kestra_kv.new", "type", + ), + ), + }, + { + ResourceName: "kestra_kv.new", + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "STRING", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "stringValue", + ), + ), + }, + { + Config: testAccResourceKv( + "io.kestra.terraform", + "int", + "1", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "id", "io.kestra.terraform/int", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "key", "int", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "1", + ), + resource.TestCheckNoResourceAttr( + "kestra_kv.new", "type", + ), + ), + }, + { + ResourceName: "kestra_kv.new", + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "NUMBER", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "1", + ), + ), + }, + { + Config: testAccResourceKvWithType( + "io.kestra.terraform", + "int", + "1", + "type = \"STRING\"", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "id", "io.kestra.terraform/int", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "key", "int", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "1", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "STRING", + ), + ), + }, + { + ResourceName: "kestra_kv.new", + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "STRING", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "1", + ), + ), + }, + { + Config: testAccResourceKv( + "io.kestra.terraform", + "object", + "{\\\"some\\\":\\\"json\\\"}", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "id", "io.kestra.terraform/object", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "key", "object", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "{\"some\":\"json\"}", + ), + resource.TestCheckNoResourceAttr( + "kestra_kv.new", "type", + ), + ), + }, + { + ResourceName: "kestra_kv.new", + ImportState: true, + ImportStateVerify: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "JSON", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "{\"some\":\"json\"}", + ), + ), + }, + { + Config: testAccResourceKvWithType( + "io.kestra.terraform", + "object", + "{\\\"some\\\":\\\"json\\\"}", + "type = \"STRING\"", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_kv.new", "id", "io.kestra.terraform/object", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "key", "object", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "value", "{\"some\":\"json\"}", + ), + resource.TestCheckResourceAttr( + "kestra_kv.new", "type", "STRING", + ), + ), + }, + }, + }) + + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: func(state *terraform.State) error { + urlEnv := strings.TrimRight(os.Getenv("KESTRA_URL"), "/") + usernameEnv := os.Getenv("KESTRA_USERNAME") + passwordEnv := os.Getenv("KESTRA_PASSWORD") + c, _ := NewClient(urlEnv, &usernameEnv, &passwordEnv, nil, nil, nil, nil, nil) + url := c.Url + fmt.Sprintf("%s/namespaces/io.kestra.terraform/kv/string", apiRoot(nil)) + request, _ := http.NewRequest("GET", url, nil) + _, _, httpError := c.rawResponseRequest("GET", request) + + if httpError.StatusCode != http.StatusNotFound { + return fmt.Errorf("resource 'string' should have been destroyed") + } + + return nil + }, + Steps: []resource.TestStep{ + { + Config: testAccResourceKv( + "io.kestra.terraform", + "string", + "stringValue", + ), + }, + }, + }) +} + +func testAccResourceKv(namespace string, key string, value string) string { + return testAccResourceKvWithType(namespace, key, value, "") +} + +func testAccResourceKvWithType(namespace string, key string, value string, valueType string) string { + return fmt.Sprintf( + ` + resource "kestra_kv" "new" { + namespace = "%s" + key = "%s" + value = "%s" + %s + }`, + namespace, + key, + value, + valueType, + ) +}