From cd9abba16fca3edc6e211a6dff82311939266250 Mon Sep 17 00:00:00 2001 From: Florian Hussonnois Date: Wed, 2 Oct 2024 12:50:20 +0200 Subject: [PATCH] feat: add resource to manage worker group --- .github/workflows/index.jsonl | 2 +- internal/provider/client.go | 2 +- internal/provider/data_source_worker_group.go | 59 ++++++++ internal/provider/provider.go | 2 + internal/provider/resource_worker_group.go | 138 ++++++++++++++++++ .../provider/resource_worker_group_test.go | 73 +++++++++ internal/provider/utils_worker_group.go | 48 ++++++ 7 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 internal/provider/data_source_worker_group.go create mode 100644 internal/provider/resource_worker_group.go create mode 100644 internal/provider/resource_worker_group_test.go create mode 100644 internal/provider/utils_worker_group.go diff --git a/.github/workflows/index.jsonl b/.github/workflows/index.jsonl index 93de758..eb265e3 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"],"KVSTORE":["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"], "INFRASTRUCTURE":["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/internal/provider/client.go b/internal/provider/client.go index 2d90a26..1665661 100644 --- a/internal/provider/client.go +++ b/internal/provider/client.go @@ -189,7 +189,7 @@ func (c *Client) rawResponseRequest(method string, req *http.Request) (int, []by } } - if (res.StatusCode != http.StatusOK) && (res.StatusCode != http.StatusNoContent) { + if (res.StatusCode != http.StatusOK) && (res.StatusCode != http.StatusCreated) && (res.StatusCode != http.StatusNoContent) { return 0, nil, &RequestError{ StatusCode: res.StatusCode, Err: fmt.Errorf("status: %d, method: %s, body: %s", res.StatusCode, method, bodyResult), diff --git a/internal/provider/data_source_worker_group.go b/internal/provider/data_source_worker_group.go new file mode 100644 index 0000000..57957c8 --- /dev/null +++ b/internal/provider/data_source_worker_group.go @@ -0,0 +1,59 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceWorkerGroup() *schema.Resource { + return &schema.Resource{ + Description: "Use this data source to access information about an existing Kestra Worker Group.", + + ReadContext: dataSourceUserRead, + Schema: map[string]*schema.Schema{ + "id": { + Description: "The worker group id.", + Type: schema.TypeString, + Computed: true, + }, + "key": { + Description: "The worker group key.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "The worker group description.", + Type: schema.TypeString, + Computed: true, + }, + "allowed_tenants": { + Description: "The list of tenants allowed to use the worker group.", + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceWorkerGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + id := d.Get("id").(string) + tenantId := c.TenantId + + r, reqErr := c.request("GET", fmt.Sprintf("%s/cluster/workergroups/%s", apiRoot(tenantId), id), nil) + if reqErr != nil { + return diag.FromErr(reqErr.Err) + } + + errs := workerGroupApiToSchema(r.(map[string]interface{}), d) + if errs != nil { + return errs + } + + return diags +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 4a2c19b..c5200df 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -93,6 +93,7 @@ func New(version string, tenant *string) func() *schema.Provider { "kestra_service_account": dataSourceServiceAccount(), "kestra_user_api_tokens": dataSourceUserApiTokens(), "kestra_kv": dataSourceKv(), + "kestra_worker_group": dataSourceWorkerGroup(), }, ResourcesMap: map[string]*schema.Resource{ "kestra_binding": resourceBinding(), @@ -109,6 +110,7 @@ func New(version string, tenant *string) func() *schema.Provider { "kestra_service_account": resourceServiceAccount(), "kestra_user_api_token": resourceUserApiToken(), "kestra_kv": resourceKv(), + "kestra_worker_group": resourceWorkerGroup(), }, } diff --git a/internal/provider/resource_worker_group.go b/internal/provider/resource_worker_group.go new file mode 100644 index 0000000..9bf5013 --- /dev/null +++ b/internal/provider/resource_worker_group.go @@ -0,0 +1,138 @@ +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "net/http" +) + +func resourceWorkerGroup() *schema.Resource { + return &schema.Resource{ + Description: "Manages a Kestra Worker Group.", + + CreateContext: resourceWorkerGroupCreate, + ReadContext: resourceWorkerGroupRead, + UpdateContext: resourceWorkerGroupUpdate, + DeleteContext: resourceWorkerGroupDelete, + Schema: map[string]*schema.Schema{ + "key": { + Description: "The worker group key.", + Type: schema.TypeString, + Required: true, + }, + "description": { + Description: "The worker group description.", + Type: schema.TypeString, + Optional: true, + }, + "allowed_tenants": { + Description: "The list of tenants allowed to use the worker group.", + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceWorkerGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + body, err := workerGroupSchemaToApi(d) + if err != nil { + return diag.FromErr(err) + } + + tenantId := c.TenantId + + r, reqErr := c.request("POST", fmt.Sprintf("%s/cluster/workergroups", apiRoot(tenantId)), body) + if reqErr != nil { + return diag.FromErr(reqErr.Err) + } + + errs := workerGroupApiToSchema(r.(map[string]interface{}), d) + if errs != nil { + return errs + } + + return diags +} + +func resourceWorkerGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + workerGroupId := d.Id() + tenantId := c.TenantId + + r, reqErr := c.request("GET", fmt.Sprintf("%s/cluster/workergroups/%s", apiRoot(tenantId), workerGroupId), nil) + if reqErr != nil { + if reqErr.StatusCode == http.StatusNotFound { + d.SetId("") + return diags + } + + return diag.FromErr(reqErr.Err) + } + + errs := workerGroupApiToSchema(r.(map[string]interface{}), d) + if errs != nil { + return errs + } + + return diags +} + +func resourceWorkerGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + if d.HasChanges("key", "description", "allowed_tenants") { + body, err := workerGroupSchemaToApi(d) + if err != nil { + return diag.FromErr(err) + } + + workerGroupId := d.Id() + tenantId := c.TenantId + + r, reqErr := c.request("PUT", fmt.Sprintf("%s/cluster/workergroups/%s", apiRoot(tenantId), workerGroupId), body) + if reqErr != nil { + return diag.FromErr(reqErr.Err) + } + + errs := workerGroupApiToSchema(r.(map[string]interface{}), d) + if errs != nil { + return errs + } + + return diags + } else { + return resourceWorkerGroupRead(ctx, d, meta) + } +} + +func resourceWorkerGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*Client) + var diags diag.Diagnostics + + workerGroupId := d.Id() + tenantId := c.TenantId + + _, reqErr := c.request("DELETE", fmt.Sprintf("%s/cluster/workergroups/%s", apiRoot(tenantId), workerGroupId), nil) + if reqErr != nil { + return diag.FromErr(reqErr.Err) + } + + d.SetId("") + + return diags +} diff --git a/internal/provider/resource_worker_group_test.go b/internal/provider/resource_worker_group_test.go new file mode 100644 index 0000000..85999c5 --- /dev/null +++ b/internal/provider/resource_worker_group_test.go @@ -0,0 +1,73 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccWorkerGroup(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccResourceWorkerGroup( + "test-key", + "test-description", + "[\"test-tenant\"]", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "key", "test-key", + ), + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "description", "test-description", + ), + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "allowed_tenants.#", "1", + ), + ), + }, + { + Config: testAccResourceWorkerGroup( + "test-key", + "test-description", + "[]", + ), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "key", "test-key", + ), + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "description", "test-description", + ), + resource.TestCheckResourceAttr( + "kestra_worker_group.new", "allowed_tenants.#", "0", + ), + ), + }, + { + ResourceName: "kestra_worker_group.new", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccResourceWorkerGroup(key, description, tenants string) string { + return fmt.Sprintf( + ` + resource "kestra_worker_group" "new" { + key = "%s" + description = "%s" + allowed_tenants = %s + }`, + key, + description, + tenants, + ) + +} diff --git a/internal/provider/utils_worker_group.go b/internal/provider/utils_worker_group.go new file mode 100644 index 0000000..a9a953d --- /dev/null +++ b/internal/provider/utils_worker_group.go @@ -0,0 +1,48 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func workerGroupSchemaToApi(d *schema.ResourceData) (map[string]interface{}, error) { + body := make(map[string]interface{}, 0) + + if d.Id() != "" { + body["id"] = d.Id() + } + + body["key"] = d.Get("key").(string) + body["description"] = d.Get("description").(string) + body["allowedTenants"] = d.Get("allowed_tenants").([]interface{}) + + return body, nil +} + +func workerGroupApiToSchema(r map[string]interface{}, d *schema.ResourceData) diag.Diagnostics { + var diags diag.Diagnostics + + d.SetId(r["id"].(string)) + + if err := d.Set("key", r["key"].(string)); err != nil { + return diag.FromErr(err) + } + + if _, ok := r["description"]; ok { + if r["description"].(string) != "" { + if err := d.Set("description", r["description"].(string)); err != nil { + return diag.FromErr(err) + } + } + } + + if _, ok := r["allowedTenants"]; ok { + if len(r["allowedTenants"].([]interface{})) > 0 { + if err := d.Set("allowed_tenants", r["allowedTenants"].([]interface{})); err != nil { + return diag.FromErr(err) + } + } + } + + return diags +}