From d3cdbf0e2a78d139fbc94a0f3619586ceb2b5cf6 Mon Sep 17 00:00:00 2001 From: Vicente Pinto Date: Mon, 25 Sep 2023 10:47:28 +0100 Subject: [PATCH] Argus/Postgresflex Credentials import documentation and fix (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add documentation * Remove password from datasource * Fix bug§ * add warning * Create logAndWarning function --- MIGRATION.md | 15 +++- stackit/core/core.go | 6 ++ .../services/postgresflex/user/datasource.go | 70 +++++++++++++++++-- .../services/postgresflex/user/resource.go | 4 ++ 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index ac1509fb..a505deb0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -5,6 +5,7 @@ In this guide we want to offer some strategy for a migration of configurations o To import your existing infrastructure resources to the new provider, you'll need the internal ID of each resource. The structure of the new provider's internal ID can be located in the [documentation](./docs/resources) file for each resource, specifically within the description of the `id` attribute. ## How-to + Before you begin the migration process, please ensure that you have done the necessary steps for the [authentication](./README.md#authentication). For existing resources created with the old provider, you'll need to import them into your new configuration. Terraform provides a feature for importing existing resources and auto-generating new Terraform configuration files. To generate configuration code for the imported resources, refer to the official [Terraform documentation](https://developer.hashicorp.com/terraform/language/import/generating-configuration) for step-by-step guidance. @@ -14,7 +15,9 @@ Once the configuration is generated, compare the generated file with your existi If you encounter any other issues or have additional questions, don't hesitate to raise them by [opening an issue](https://github.com/stackitcloud/terraform-provider-stackit/issues/new/choose). ### Example (SKE service) + Import configuration: + ```terraform # Import import { @@ -29,6 +32,7 @@ import { ``` Generated configuration: + ```terraform # __generated__ by Terraform # Please review these resources and move them into your main configuration files. @@ -85,6 +89,7 @@ resource "stackit_ske_cluster" "cluster-example" { ### Example (LogMe service) Import configuration: + ```terraform import { id = "project_id,instance_id" @@ -98,6 +103,7 @@ import { ``` Generated configuration: + ```terraform # __generated__ by Terraform # Please review these resources and move them into your main configuration files. @@ -120,4 +126,11 @@ resource "stackit_logme_credentials" "example-credentials" { } ``` -**_Note:_** Currently, when importing the LogMe (or any other DSA), you will see a `Missing Configuration for Required Attribute` error. This issue refers to the `plan_name` and `version` attributes, which are not returned by the API, and currently are solely used by the TFP to calculate the corresponding `plan_id`. We plan to enhance the provider's functionality soon to perform the reverse operation, generating the `plan_name` and `version` correctly. However, for the time being, you'll need to retrieve these values from an alternative source, e.g., the Portal. \ No newline at end of file +## Notes + +### Import credentials + +There are some resources which provide fields **only** upon creation, such as users or credentials, which cannot be imported. Here is the list of the resources for which that happens: + +- Argus credentials: `import` is not available +- PostgresFlex user: `import` is available but the `password` field will be empty diff --git a/stackit/core/core.go b/stackit/core/core.go index 74619d12..b2444dfc 100644 --- a/stackit/core/core.go +++ b/stackit/core/core.go @@ -54,3 +54,9 @@ func LogAndAddError(ctx context.Context, diags *diag.Diagnostics, summary, detai tflog.Error(ctx, summary) diags.AddError(summary, detail) } + +// LogAndAddWarning Logs the warning and adds it to the diags +func LogAndAddWarning(ctx context.Context, diags *diag.Diagnostics, summary, detail string) { + tflog.Warn(ctx, summary) + diags.AddWarning(summary, detail) +} diff --git a/stackit/services/postgresflex/user/datasource.go b/stackit/services/postgresflex/user/datasource.go index 08de46a7..53a12b22 100644 --- a/stackit/services/postgresflex/user/datasource.go +++ b/stackit/services/postgresflex/user/datasource.go @@ -3,10 +3,13 @@ package postgresflex import ( "context" "fmt" + "strings" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/terraform-provider-stackit/stackit/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/validate" @@ -21,6 +24,17 @@ var ( _ datasource.DataSource = &userDataSource{} ) +type DataSourceModel struct { + Id types.String `tfsdk:"id"` // needed by TF + UserId types.String `tfsdk:"user_id"` + InstanceId types.String `tfsdk:"instance_id"` + ProjectId types.String `tfsdk:"project_id"` + Username types.String `tfsdk:"username"` + Roles types.Set `tfsdk:"roles"` + Host types.String `tfsdk:"host"` + Port types.Int64 `tfsdk:"port"` +} + // NewUserDataSource is a helper function to simplify the provider implementation. func NewUserDataSource() datasource.DataSource { return &userDataSource{} @@ -119,10 +133,6 @@ func (r *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r ElementType: types.StringType, Computed: true, }, - "password": schema.StringAttribute{ - Computed: true, - Sensitive: true, - }, "host": schema.StringAttribute{ Computed: true, }, @@ -135,7 +145,7 @@ func (r *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r // Read refreshes the Terraform state with the latest data. func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform - var model Model + var model DataSourceModel diags := req.Config.Get(ctx, &model) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -154,8 +164,8 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } - // Map response body to schema - err = mapFields(recordSetResp, &model) + // Map response body to schema and populate Computed attribute values + err = mapDataSourceFields(recordSetResp, &model) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err)) return @@ -169,3 +179,49 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } tflog.Info(ctx, "PostgresFlex user read") } + +func mapDataSourceFields(userResp *postgresflex.UserResponse, model *DataSourceModel) error { + if userResp == nil || userResp.Item == nil { + return fmt.Errorf("response is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + user := userResp.Item + + var userId string + if model.UserId.ValueString() != "" { + userId = model.UserId.ValueString() + } else if user.Id != nil { + userId = *user.Id + } else { + return fmt.Errorf("user id not present") + } + idParts := []string{ + model.ProjectId.ValueString(), + model.InstanceId.ValueString(), + userId, + } + model.Id = types.StringValue( + strings.Join(idParts, core.Separator), + ) + model.UserId = types.StringValue(userId) + model.Username = types.StringPointerValue(user.Username) + + if user.Roles == nil { + model.Roles = types.SetNull(types.StringType) + } else { + roles := []attr.Value{} + for _, role := range *user.Roles { + roles = append(roles, types.StringValue(role)) + } + rolesSet, diags := types.SetValue(types.StringType, roles) + if diags.HasError() { + return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags)) + } + model.Roles = rolesSet + } + model.Host = types.StringPointerValue(user.Host) + model.Port = conversion.ToTypeInt64(user.Port) + return nil +} diff --git a/stackit/services/postgresflex/user/resource.go b/stackit/services/postgresflex/user/resource.go index a42d799c..cfa5772d 100644 --- a/stackit/services/postgresflex/user/resource.go +++ b/stackit/services/postgresflex/user/resource.go @@ -326,6 +326,10 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[2])...) + core.LogAndAddWarning(ctx, &resp.Diagnostics, + "Postgresflex user imported with empty password", + "The user password is not imported as it is only available upon creation of a new user. The password field will be empty.", + ) tflog.Info(ctx, "Postgresflex user state imported") }