diff --git a/.env b/.env index 37fb56a..b3ffda5 100644 --- a/.env +++ b/.env @@ -4,4 +4,5 @@ CDK_BASE_URL=http://localhost:8080 CDK_ADMIN_EMAIL=admin@conduktor.io CDK_ADMIN_PASSWORD=test CDK_DEBUG=false +TF_LOG=INFO TF_LOG_PROVIDER_CONDUKTOR=INFO diff --git a/docs/resources/group_v2.md b/docs/resources/group_v2.md index a82e178..20123a4 100644 --- a/docs/resources/group_v2.md +++ b/docs/resources/group_v2.md @@ -20,8 +20,6 @@ resource "conduktor_group_v2" "example" { spec { display_name = "Simple Group" description = "Simple group description" - members = [] - permissions = [] } } ``` @@ -79,13 +77,13 @@ resource "conduktor_group_v2" "example" { Required: - `display_name` (String) Group display name -- `permissions` (Attributes List) Set of all group permissions (see [below for nested schema](#nestedatt--spec--permissions)) Optional: - `description` (String) Group description - `external_groups` (List of String) List of external groups from SSO mapped to this group - `members` (List of String) List of members of the group +- `permissions` (Attributes List) Set of all group permissions (see [below for nested schema](#nestedatt--spec--permissions)) Read-Only: diff --git a/docs/resources/user_v2.md b/docs/resources/user_v2.md index 55bedb0..8c80861 100644 --- a/docs/resources/user_v2.md +++ b/docs/resources/user_v2.md @@ -18,9 +18,8 @@ This resource allows you to create, read, update and delete users in Conduktor. resource "conduktor_user_v2" "example" { name = "bob@company.io" spec { - firstname = "Bob" - lastname = "Smith" - permissions = [] + firstname = "Bob" + lastname = "Smith" } } ``` @@ -64,7 +63,7 @@ resource "conduktor_user_v2" "example" { ### Nested Schema for `spec` -Required: +Optional: - `firstname` (String) User firstname - `lastname` (String) User lastname diff --git a/examples/resources/conduktor_group_v2/simple.tf b/examples/resources/conduktor_group_v2/simple.tf index 47b91d4..478ed39 100644 --- a/examples/resources/conduktor_group_v2/simple.tf +++ b/examples/resources/conduktor_group_v2/simple.tf @@ -3,7 +3,5 @@ resource "conduktor_group_v2" "example" { spec { display_name = "Simple Group" description = "Simple group description" - members = [] - permissions = [] } } diff --git a/examples/resources/conduktor_user_v2/simple.tf b/examples/resources/conduktor_user_v2/simple.tf index bae07aa..7080927 100644 --- a/examples/resources/conduktor_user_v2/simple.tf +++ b/examples/resources/conduktor_user_v2/simple.tf @@ -1,8 +1,7 @@ resource "conduktor_user_v2" "example" { name = "bob@company.io" spec { - firstname = "Bob" - lastname = "Smith" - permissions = [] + firstname = "Bob" + lastname = "Smith" } } diff --git a/go.mod b/go.mod index 51d2e56..512a48b 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/go-resty/resty/v2 v2.15.3 github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0 + github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-framework v1.12.0 github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 @@ -57,7 +57,7 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect - github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1 // indirect + github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/go.sum b/go.sum index d2924ad..0df6c88 100644 --- a/go.sum +++ b/go.sum @@ -133,10 +133,10 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0 h1:xB96Rakulnn7ZxikDj8glCyojKpTbW+SGm6QxipoNxU= -github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0/go.mod h1:nXlVjqHFBDr6EXnKTT9RCLmwnuTYj/3LE4ffkIdOtog= -github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1 h1:7djpLeqtz0x6w01W4hckWwINJUnfXn2zGr/EhSC8rek= -github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1/go.mod h1:PQn6bDD8UWoAVJoHXqFk2i/RmLbeQBjbiP38i+E+YIw= +github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 h1:eaI/3dsu2T5QAXbA+7N+B+UBj20GdtYnsRuYypKh3S4= +github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1/go.mod h1:kpYM23L7NtcfaQdWAN0QFkV/lU0w16qJ2ddAPCI4zAg= +github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 h1:91dQG1A/DxP6vRz9GiytDTrZTXDbhHPvmpYnAyWA/Vw= +github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0/go.mod h1:fywrEKpordQypmAjz/HIfm2LuNVmyJ6KDe8XT9GdJxQ= github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= diff --git a/internal/client/client.go b/internal/client/client.go index 70e15f9..8163ab8 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -38,7 +38,8 @@ type LoginResult struct { } type ApplyResult struct { - UpsertResult string + UpsertResult string `json:"upsertResult"` + Resource interface{} `json:"resource"` } func Make(ctx context.Context, apiParameter ApiParameter, providerVersion string) (*Client, error) { @@ -168,28 +169,29 @@ func (client *Client) ApplyGeneric(ctx context.Context, cliResource ctlresource. return upsertResponse.UpsertResult, nil } -func (client *Client) Apply(ctx context.Context, path string, resource interface{}) (string, error) { +func (client *Client) Apply(ctx context.Context, path string, resource interface{}) (ApplyResult, error) { url := client.baseUrl + path jsonData, err := jsoniter.Marshal(resource) if err != nil { - return "", fmt.Errorf("Error marshalling resource: %s", err) + return ApplyResult{}, fmt.Errorf("Error marshalling resource: %s", err) } - tflog.Trace(ctx, fmt.Sprintf("PUT on %s body : %s", path, string(jsonData))) + tflog.Trace(ctx, fmt.Sprintf("PUT %s request body : %s", path, string(jsonData))) builder := client.client.R().SetBody(jsonData) resp, err := builder.Put(url) if err != nil { - return "", err + return ApplyResult{}, err } else if resp.IsError() { - return "", fmt.Errorf("%s", extractApiError(resp)) + return ApplyResult{}, fmt.Errorf("%s", extractApiError(resp)) } bodyBytes := resp.Body() + tflog.Trace(ctx, fmt.Sprintf("PUT %s response body : %s", path, string(bodyBytes))) var upsertResponse ApplyResult err = jsoniter.Unmarshal(bodyBytes, &upsertResponse) if err != nil { - return "", fmt.Errorf("Error unmarshalling response: %s", err) + return ApplyResult{}, fmt.Errorf("Error unmarshalling response: %s", err) } - return upsertResponse.UpsertResult, nil + return upsertResponse, nil } func (client *Client) Describe(ctx context.Context, path string) ([]byte, error) { diff --git a/internal/mapper/group_v2/group_v2_resource_mapper.go b/internal/mapper/group_v2/group_v2_resource_mapper.go index 296c08a..89be9a6 100644 --- a/internal/mapper/group_v2/group_v2_resource_mapper.go +++ b/internal/mapper/group_v2/group_v2_resource_mapper.go @@ -10,11 +10,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" ) func TFToInternalModel(ctx context.Context, r *schema.GroupV2Model) (model.GroupConsoleResource, error) { externalGroups, diag := schemaUtils.ListValueToStringArray(ctx, r.Spec.ExternalGroups) + if r.Spec.ExternalGroups.IsNull() { + tflog.Debug(ctx, "ListValue externalGroups is null") + } + + if r.Spec.ExternalGroups.IsUnknown() { + tflog.Debug(ctx, "ListValue externalGroups is unknown") + } + if diag.HasError() { return model.GroupConsoleResource{}, mapper.WrapDiagError(diag, "externalGroups", mapper.FromTerraform) } @@ -30,25 +39,27 @@ func TFToInternalModel(ctx context.Context, r *schema.GroupV2Model) (model.Group } permissions := make([]model.Permission, 0) - var tfPermissions []schema.PermissionsValue - diag = r.Spec.Permissions.ElementsAs(ctx, &tfPermissions, false) - if diag.HasError() { - return model.GroupConsoleResource{}, mapper.WrapDiagError(diag, "permissions", mapper.FromTerraform) - } - for _, p := range tfPermissions { - flags, diag := schemaUtils.ListValueToStringArray(ctx, p.Permissions) + if !r.Spec.Permissions.IsNull() && !r.Spec.Permissions.IsUnknown() { + var tfPermissions []schema.PermissionsValue + diag = r.Spec.Permissions.ElementsAs(ctx, &tfPermissions, false) if diag.HasError() { - return model.GroupConsoleResource{}, mapper.WrapDiagError(diag, "permissions.permissions", mapper.FromTerraform) + return model.GroupConsoleResource{}, mapper.WrapDiagError(diag, "permissions", mapper.FromTerraform) + } + for _, p := range tfPermissions { + flags, diag := schemaUtils.ListValueToStringArray(ctx, p.Permissions) + if diag.HasError() { + return model.GroupConsoleResource{}, mapper.WrapDiagError(diag, "permissions.permissions", mapper.FromTerraform) + } + + permissions = append(permissions, model.Permission{ + Name: p.Name.ValueString(), + ResourceType: p.ResourceType.ValueString(), + Permissions: flags, + PatternType: p.PatternType.ValueString(), + Cluster: p.Cluster.ValueString(), + KafkaConnect: p.KafkaConnect.ValueString(), + }) } - - permissions = append(permissions, model.Permission{ - Name: p.Name.ValueString(), - ResourceType: p.ResourceType.ValueString(), - Permissions: flags, - PatternType: p.PatternType.ValueString(), - Cluster: p.Cluster.ValueString(), - KafkaConnect: p.KafkaConnect.ValueString(), - }) } return model.NewGroupConsoleResource( diff --git a/internal/mapper/user_v2/user_v2_resource_mapper.go b/internal/mapper/user_v2/user_v2_resource_mapper.go index 1b270f6..e7e2135 100644 --- a/internal/mapper/user_v2/user_v2_resource_mapper.go +++ b/internal/mapper/user_v2/user_v2_resource_mapper.go @@ -15,26 +15,29 @@ import ( func TFToInternalModel(ctx context.Context, r *schema.UserV2Model) (model.UserConsoleResource, error) { permissions := make([]model.Permission, 0) - var tfPermissions []schema.PermissionsValue - diag := r.Spec.Permissions.ElementsAs(ctx, &tfPermissions, false) - if diag.HasError() { - return model.UserConsoleResource{}, mapper.WrapDiagError(diag, "permissions", mapper.FromTerraform) - } - for _, p := range tfPermissions { - var flags []string - diag := p.Permissions.ElementsAs(ctx, &flags, false) + + if !r.Spec.Permissions.IsNull() && !r.Spec.Permissions.IsUnknown() { + var tfPermissions []schema.PermissionsValue + diag := r.Spec.Permissions.ElementsAs(ctx, &tfPermissions, false) if diag.HasError() { - return model.UserConsoleResource{}, mapper.WrapDiagError(diag, "permissions.permissions", mapper.FromTerraform) + return model.UserConsoleResource{}, mapper.WrapDiagError(diag, "permissions", mapper.FromTerraform) } + for _, p := range tfPermissions { + var flags []string + diag := p.Permissions.ElementsAs(ctx, &flags, false) + if diag.HasError() { + return model.UserConsoleResource{}, mapper.WrapDiagError(diag, "permissions.permissions", mapper.FromTerraform) + } - permissions = append(permissions, model.Permission{ - Name: p.Name.ValueString(), - ResourceType: p.ResourceType.ValueString(), - Permissions: flags, - PatternType: p.PatternType.ValueString(), - Cluster: p.Cluster.ValueString(), - KafkaConnect: p.KafkaConnect.ValueString(), - }) + permissions = append(permissions, model.Permission{ + Name: p.Name.ValueString(), + ResourceType: p.ResourceType.ValueString(), + Permissions: flags, + PatternType: p.PatternType.ValueString(), + Cluster: p.Cluster.ValueString(), + KafkaConnect: p.KafkaConnect.ValueString(), + }) + } } return model.NewUserConsoleResource( diff --git a/internal/model/group_v2.go b/internal/model/group_v2.go index e8a6cff..67d14d0 100644 --- a/internal/model/group_v2.go +++ b/internal/model/group_v2.go @@ -21,7 +21,7 @@ func (r GroupConsoleMetadata) String() string { } type GroupConsoleSpec struct { - Description string `json:"description"` + Description string `json:"description,omitempty"` DisplayName string `json:"displayName"` ExternalGroups []string `json:"externalGroups"` Members []string `json:"members"` @@ -75,6 +75,18 @@ func (r *GroupConsoleResource) FromClientResource(cliResource ctlresource.Resour return nil } +func (r *GroupConsoleResource) FromRawJsonInterface(jsonInterface interface{}) error { + jsonData, err := json.Marshal(jsonInterface) + if err != nil { + return err + } + err = jsoniter.Unmarshal(jsonData, r) + if err != nil { + return err + } + return nil +} + func NewGroupConsoleResourceFromClientResource(cliResource ctlresource.Resource) (GroupConsoleResource, error) { var consoleResource GroupConsoleResource err := consoleResource.FromClientResource(cliResource) diff --git a/internal/model/user_v2.go b/internal/model/user_v2.go index ff843b2..eef3dc4 100644 --- a/internal/model/user_v2.go +++ b/internal/model/user_v2.go @@ -21,8 +21,8 @@ func (r UserConsoleMetadata) String() string { } type UserConsoleSpec struct { - FirstName string `json:"firstName"` - LastName string `json:"lastName"` + FirstName string `json:"firstName,omitempty"` + LastName string `json:"lastName,omitempty"` Permissions []Permission `json:"permissions"` } @@ -72,6 +72,18 @@ func (r *UserConsoleResource) FromClientResource(cliResource ctlresource.Resourc return nil } +func (r *UserConsoleResource) FromRawJsonInterface(jsonInterface interface{}) error { + jsonData, err := json.Marshal(jsonInterface) + if err != nil { + return err + } + err = jsoniter.Unmarshal(jsonData, r) + if err != nil { + return err + } + return nil +} + func NewUserConsoleResourceFromClientResource(cliResource ctlresource.Resource) (UserConsoleResource, error) { var consoleResource UserConsoleResource err := consoleResource.FromClientResource(cliResource) diff --git a/internal/provider/group_v2_resource.go b/internal/provider/group_v2_resource.go index cd55d6a..fa33f7b 100644 --- a/internal/provider/group_v2_resource.go +++ b/internal/provider/group_v2_resource.go @@ -67,8 +67,8 @@ func (r *GroupV2Resource) Create(ctx context.Context, req resource.CreateRequest return } - tflog.Info(ctx, fmt.Sprintf("Create group named %s", data.Name.String())) - tflog.Trace(ctx, fmt.Sprintf("Create group with TF data: %+v", data)) + tflog.Info(ctx, fmt.Sprintf("Creating group named %s", data.Name.String())) + tflog.Trace(ctx, fmt.Sprintf("Create group with desired state : %+v", data)) consoleResource, err := mapper.TFToInternalModel(ctx, &data) if err != nil { @@ -83,7 +83,21 @@ func (r *GroupV2Resource) Create(ctx context.Context, req resource.CreateRequest return } - tflog.Debug(ctx, fmt.Sprintf("Group created with result: %s", apply)) + tflog.Debug(ctx, fmt.Sprintf("Group created with result: %s", apply.UpsertResult)) + + var consoleRes = model.GroupConsoleResource{} + err = consoleRes.FromRawJsonInterface(apply.Resource) + if err != nil { + resp.Diagnostics.AddError("Unmarshall Error", fmt.Sprintf("Response resource can't be cast as group : %v, got error: %s", apply.Resource, err)) + return + } + tflog.Debug(ctx, fmt.Sprintf("New group state : %+v", consoleRes)) + + data, err = mapper.InternalModelToTerraform(ctx, &consoleRes) + if err != nil { + resp.Diagnostics.AddError("Model Error", fmt.Sprintf("Unable to read group, got error: %s", err)) + return + } // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -140,7 +154,7 @@ func (r *GroupV2Resource) Update(ctx context.Context, req resource.UpdateRequest return } - tflog.Info(ctx, fmt.Sprintf("Update group named %s", data.Name.String())) + tflog.Info(ctx, fmt.Sprintf("Updating group named %s", data.Name.String())) tflog.Trace(ctx, fmt.Sprintf("Update group with TF data: %+v", data)) consoleResource, err := mapper.TFToInternalModel(ctx, &data) @@ -157,6 +171,19 @@ func (r *GroupV2Resource) Update(ctx context.Context, req resource.UpdateRequest } tflog.Debug(ctx, fmt.Sprintf("Group updated with result: %s", apply)) + var consoleRes = model.GroupConsoleResource{} + err = consoleRes.FromRawJsonInterface(apply.Resource) + if err != nil { + resp.Diagnostics.AddError("Unmarshall Error", fmt.Sprintf("Response resource can't be cast as group : %v, got error: %s", apply.Resource, err)) + return + } + tflog.Debug(ctx, fmt.Sprintf("New group state : %+v", consoleRes)) + + data, err = mapper.InternalModelToTerraform(ctx, &consoleRes) + if err != nil { + resp.Diagnostics.AddError("Model Error", fmt.Sprintf("Unable to read group, got error: %s", err)) + return + } // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -167,7 +194,7 @@ func (r *GroupV2Resource) Delete(ctx context.Context, req resource.DeleteRequest // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - tflog.Info(ctx, fmt.Sprintf("Delete group named %s", data.Name.String())) + tflog.Info(ctx, fmt.Sprintf("Deleting group named %s", data.Name.String())) if resp.Diagnostics.HasError() { return diff --git a/internal/provider/group_v2_resource_test.go b/internal/provider/group_v2_resource_test.go index 7b9cdd4..9b631ee 100644 --- a/internal/provider/group_v2_resource_test.go +++ b/internal/provider/group_v2_resource_test.go @@ -67,6 +67,29 @@ func TestAccGroupV2Resource(t *testing.T) { }) } +func TestAccGroupV2Minimal(t *testing.T) { + test.CheckEnterpriseEnabled(t) + resource.Test(t, resource.TestCase{ + PreCheck: func() { test.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read from minimal example + { + Config: providerConfig + test.TestAccTestdata(t, "group_v2_resource_minimal.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "name", "minimal"), + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "spec.display_name", "Minimal"), + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "spec.external_groups.#", "0"), + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "spec.members.#", "0"), + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "spec.members_from_external_groups.#", "0"), + resource.TestCheckResourceAttr("conduktor_group_v2.minimal", "spec.permissions.#", "0"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + func TestAccGroupV2ExampleResource(t *testing.T) { test.CheckEnterpriseEnabled(t) resource.Test(t, resource.TestCase{ diff --git a/internal/provider/user_v2_resource.go b/internal/provider/user_v2_resource.go index f1f7d50..827d6dd 100644 --- a/internal/provider/user_v2_resource.go +++ b/internal/provider/user_v2_resource.go @@ -84,6 +84,20 @@ func (r *UserV2Resource) Create(ctx context.Context, req resource.CreateRequest, tflog.Debug(ctx, fmt.Sprintf("User created with result: %s", apply)) + var consoleRes model.UserConsoleResource + err = consoleRes.FromRawJsonInterface(apply.Resource) + if err != nil { + resp.Diagnostics.AddError("Unmarshall Error", fmt.Sprintf("Response resource can't be cast as group : %v, got error: %s", apply.Resource, err)) + return + } + tflog.Debug(ctx, fmt.Sprintf("New group state : %+v", consoleRes)) + + data, err = mapper.InternalModelToTerraform(ctx, &consoleRes) + if err != nil { + resp.Diagnostics.AddError("Model Error", fmt.Sprintf("Unable to read group, got error: %s", err)) + return + } + // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -156,6 +170,20 @@ func (r *UserV2Resource) Update(ctx context.Context, req resource.UpdateRequest, } tflog.Debug(ctx, fmt.Sprintf("User updated with result: %s", apply)) + var consoleRes model.UserConsoleResource + err = consoleRes.FromRawJsonInterface(apply.Resource) + if err != nil { + resp.Diagnostics.AddError("Unmarshall Error", fmt.Sprintf("Response resource can't be cast as group : %v, got error: %s", apply.Resource, err)) + return + } + tflog.Debug(ctx, fmt.Sprintf("New group state : %+v", consoleRes)) + + data, err = mapper.InternalModelToTerraform(ctx, &consoleRes) + if err != nil { + resp.Diagnostics.AddError("Model Error", fmt.Sprintf("Unable to read group, got error: %s", err)) + return + } + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/user_v2_resource_test.go b/internal/provider/user_v2_resource_test.go index 7d428a6..5f8abf1 100644 --- a/internal/provider/user_v2_resource_test.go +++ b/internal/provider/user_v2_resource_test.go @@ -59,6 +59,25 @@ func TestAccUserV2Resource(t *testing.T) { }) } +func TestAccUserV2Minimal(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { test.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + + Steps: []resource.TestStep{ + // Create and Read from minimal example + { + Config: providerConfig + test.TestAccTestdata(t, "user_v2_resource_minimal.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("conduktor_user_v2.minimal", "name", "angela.martin@dunder.mifflin.com"), + resource.TestCheckResourceAttr("conduktor_user_v2.minimal", "spec.permissions.#", "0"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + func TestAccUserV2ExampleResource(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { test.TestAccPreCheck(t) }, diff --git a/internal/schema/resource_group_v2/group_v2_resource_gen.go b/internal/schema/resource_group_v2/group_v2_resource_gen.go index b33787e..4a34492 100644 --- a/internal/schema/resource_group_v2/group_v2_resource_gen.go +++ b/internal/schema/resource_group_v2/group_v2_resource_gen.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -59,6 +60,7 @@ func GroupV2ResourceSchema(ctx context.Context) schema.Schema { Validators: []validator.List{ listvalidator.UniqueValues(), }, + Default: listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{})), }, "members": schema.ListAttribute{ ElementType: types.StringType, @@ -69,6 +71,7 @@ func GroupV2ResourceSchema(ctx context.Context) schema.Schema { Validators: []validator.List{ listvalidator.UniqueValues(), }, + Default: listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{})), }, "members_from_external_groups": schema.ListAttribute{ ElementType: types.StringType, @@ -78,6 +81,7 @@ func GroupV2ResourceSchema(ctx context.Context) schema.Schema { Validators: []validator.List{ listvalidator.UniqueValues(), }, + Default: listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{})), }, "permissions": schema.ListNestedAttribute{ NestedObject: schema.NestedAttributeObject{ @@ -130,7 +134,8 @@ func GroupV2ResourceSchema(ctx context.Context) schema.Schema { }, }, }, - Required: true, + Optional: true, + Computed: true, Description: "Set of all group permissions", MarkdownDescription: "Set of all group permissions", }, @@ -694,11 +699,19 @@ func (v SpecValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, di ) } - externalGroupsVal, d := types.ListValue(types.StringType, v.ExternalGroups.Elements()) - - diags.Append(d...) + var externalGroupsVal basetypes.ListValue + switch { + case v.ExternalGroups.IsUnknown(): + externalGroupsVal = types.ListUnknown(types.StringType) + case v.ExternalGroups.IsNull(): + externalGroupsVal = types.ListNull(types.StringType) + default: + var d diag.Diagnostics + externalGroupsVal, d = types.ListValue(types.StringType, v.ExternalGroups.Elements()) + diags.Append(d...) + } - if d.HasError() { + if diags.HasError() { return types.ObjectUnknown(map[string]attr.Type{ "description": basetypes.StringType{}, "display_name": basetypes.StringType{}, @@ -717,11 +730,19 @@ func (v SpecValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, di }), diags } - membersVal, d := types.ListValue(types.StringType, v.Members.Elements()) - - diags.Append(d...) + var membersVal basetypes.ListValue + switch { + case v.Members.IsUnknown(): + membersVal = types.ListUnknown(types.StringType) + case v.Members.IsNull(): + membersVal = types.ListNull(types.StringType) + default: + var d diag.Diagnostics + membersVal, d = types.ListValue(types.StringType, v.Members.Elements()) + diags.Append(d...) + } - if d.HasError() { + if diags.HasError() { return types.ObjectUnknown(map[string]attr.Type{ "description": basetypes.StringType{}, "display_name": basetypes.StringType{}, @@ -740,11 +761,19 @@ func (v SpecValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, di }), diags } - membersFromExternalGroupsVal, d := types.ListValue(types.StringType, v.MembersFromExternalGroups.Elements()) - - diags.Append(d...) + var membersFromExternalGroupsVal basetypes.ListValue + switch { + case v.MembersFromExternalGroups.IsUnknown(): + membersFromExternalGroupsVal = types.ListUnknown(types.StringType) + case v.MembersFromExternalGroups.IsNull(): + membersFromExternalGroupsVal = types.ListNull(types.StringType) + default: + var d diag.Diagnostics + membersFromExternalGroupsVal, d = types.ListValue(types.StringType, v.MembersFromExternalGroups.Elements()) + diags.Append(d...) + } - if d.HasError() { + if diags.HasError() { return types.ObjectUnknown(map[string]attr.Type{ "description": basetypes.StringType{}, "display_name": basetypes.StringType{}, @@ -1380,11 +1409,19 @@ func (v PermissionsValue) String() string { func (v PermissionsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { var diags diag.Diagnostics - permissionsVal, d := types.ListValue(types.StringType, v.Permissions.Elements()) - - diags.Append(d...) + var permissionsVal basetypes.ListValue + switch { + case v.Permissions.IsUnknown(): + permissionsVal = types.ListUnknown(types.StringType) + case v.Permissions.IsNull(): + permissionsVal = types.ListNull(types.StringType) + default: + var d diag.Diagnostics + permissionsVal, d = types.ListValue(types.StringType, v.Permissions.Elements()) + diags.Append(d...) + } - if d.HasError() { + if diags.HasError() { return types.ObjectUnknown(map[string]attr.Type{ "cluster": basetypes.StringType{}, "kafka_connect": basetypes.StringType{}, diff --git a/internal/schema/resource_user_v2/user_v2_resource_gen.go b/internal/schema/resource_user_v2/user_v2_resource_gen.go index b40dd60..3fe1171 100644 --- a/internal/schema/resource_user_v2/user_v2_resource_gen.go +++ b/internal/schema/resource_user_v2/user_v2_resource_gen.go @@ -41,12 +41,12 @@ func UserV2ResourceSchema(ctx context.Context) schema.Schema { "spec": schema.SingleNestedBlock{ Attributes: map[string]schema.Attribute{ "firstname": schema.StringAttribute{ - Required: true, + Optional: true, Description: "User firstname", MarkdownDescription: "User firstname", }, "lastname": schema.StringAttribute{ - Required: true, + Optional: true, Description: "User lastname", MarkdownDescription: "User lastname", }, @@ -101,7 +101,8 @@ func UserV2ResourceSchema(ctx context.Context) schema.Schema { }, }, }, - Required: true, + Optional: true, + Computed: true, Description: "Set of all user permissions", MarkdownDescription: "Set of all user permissions", }, @@ -1099,11 +1100,19 @@ func (v PermissionsValue) String() string { func (v PermissionsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { var diags diag.Diagnostics - permissionsVal, d := types.ListValue(types.StringType, v.Permissions.Elements()) - - diags.Append(d...) + var permissionsVal basetypes.ListValue + switch { + case v.Permissions.IsUnknown(): + permissionsVal = types.ListUnknown(types.StringType) + case v.Permissions.IsNull(): + permissionsVal = types.ListNull(types.StringType) + default: + var d diag.Diagnostics + permissionsVal, d = types.ListValue(types.StringType, v.Permissions.Elements()) + diags.Append(d...) + } - if d.HasError() { + if diags.HasError() { return types.ObjectUnknown(map[string]attr.Type{ "cluster": basetypes.StringType{}, "kafka_connect": basetypes.StringType{}, diff --git a/internal/schema/schema_utils.go b/internal/schema/schema_utils.go index 33e7706..d69c888 100644 --- a/internal/schema/schema_utils.go +++ b/internal/schema/schema_utils.go @@ -52,10 +52,6 @@ func NewStringValue(s string) basetypes.StringValue { // Convert a ListValue to a string array. func ListValueToStringArray(ctx context.Context, list basetypes.ListValue) ([]string, diag.Diagnostics) { - if list.IsNull() { - return nil, diag.Diagnostics{} - } - var result []string diag := list.ElementsAs(ctx, &result, false) return result, diag diff --git a/internal/testdata/group_v2_resource_minimal.tf b/internal/testdata/group_v2_resource_minimal.tf new file mode 100644 index 0000000..ad465fe --- /dev/null +++ b/internal/testdata/group_v2_resource_minimal.tf @@ -0,0 +1,7 @@ + +resource "conduktor_group_v2" "minimal" { + name = "minimal" + spec { + display_name = "Minimal" + } +} diff --git a/internal/testdata/user_v2_resource_minimal.tf b/internal/testdata/user_v2_resource_minimal.tf new file mode 100644 index 0000000..e3e67ca --- /dev/null +++ b/internal/testdata/user_v2_resource_minimal.tf @@ -0,0 +1,6 @@ + +resource "conduktor_user_v2" "minimal" { + name = "angela.martin@dunder.mifflin.com" + spec { + } +} diff --git a/provider_code_spec.json b/provider_code_spec.json index eea514d..0f41e01 100644 --- a/provider_code_spec.json +++ b/provider_code_spec.json @@ -114,21 +114,21 @@ "name": "firstname", "string": { "description": "User firstname", - "computed_optional_required": "required" + "computed_optional_required": "optional" } }, { "name": "lastname", "string": { "description": "User lastname", - "computed_optional_required": "required" + "computed_optional_required": "optional" } }, { "name": "permissions", "list_nested": { "description": "Set of all user permissions", - "computed_optional_required": "required", + "computed_optional_required": "computed_optional", "nested_object": { "attributes": [ { @@ -310,6 +310,16 @@ "element_type": { "string": {} }, + "default": { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" + } + ], + "schema_definition": "listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{}))" + } + }, "validators": [ { "custom": { @@ -332,6 +342,16 @@ "element_type": { "string": {} }, + "default": { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" + } + ], + "schema_definition": "listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{}))" + } + }, "validators": [ { "custom": { @@ -354,6 +374,16 @@ "element_type": { "string": {} }, + "default": { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" + } + ], + "schema_definition": "listdefault.StaticValue(basetypes.NewListValueMust(types.StringType, []attr.Value{}))" + } + }, "validators": [ { "custom": { @@ -372,7 +402,7 @@ "name": "permissions", "list_nested": { "description": "Set of all group permissions", - "computed_optional_required": "required", + "computed_optional_required": "computed_optional", "nested_object": { "attributes": [ {