fix: remove identity from postgres-flex
[skip ci]
This commit is contained in:
parent
c1f463935b
commit
ba579760d5
20 changed files with 570 additions and 581 deletions
|
|
@ -45,7 +45,7 @@ func DatabaseDataSourceSchema(ctx context.Context) schema.Schema {
|
|||
MarkdownDescription: "The STACKIT project ID.",
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Required: true,
|
||||
Optional: true,
|
||||
Description: "The region which should be addressed",
|
||||
MarkdownDescription: "The region which should be addressed",
|
||||
Validators: []validator.String{
|
||||
|
|
|
|||
|
|
@ -65,15 +65,19 @@ func mapResourceFields(source *v3alpha1api.GetDatabaseResponse, model *resourceM
|
|||
}
|
||||
|
||||
var databaseId int64
|
||||
if model.Id.ValueInt64() != 0 {
|
||||
databaseId = model.Id.ValueInt64()
|
||||
if model.DatabaseId.ValueInt64() != 0 {
|
||||
if source.Id != 0 {
|
||||
if model.DatabaseId.ValueInt64() != int64(source.Id) {
|
||||
return fmt.Errorf("retrieved ID does not match known ID")
|
||||
}
|
||||
}
|
||||
databaseId = model.DatabaseId.ValueInt64()
|
||||
} else if source.Id != 0 {
|
||||
databaseId = int64(source.Id)
|
||||
} else {
|
||||
return fmt.Errorf("database id not present")
|
||||
}
|
||||
|
||||
model.Id = types.Int64Value(databaseId)
|
||||
model.DatabaseId = types.Int64Value(databaseId)
|
||||
model.Name = types.StringValue(source.GetName())
|
||||
model.Owner = types.StringValue(cleanString(source.Owner))
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex/v3alpha1api"
|
||||
|
||||
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
|
|
@ -30,7 +30,6 @@ var (
|
|||
_ resource.ResourceWithConfigure = &databaseResource{}
|
||||
_ resource.ResourceWithImportState = &databaseResource{}
|
||||
_ resource.ResourceWithModifyPlan = &databaseResource{}
|
||||
_ resource.ResourceWithIdentity = &databaseResource{}
|
||||
|
||||
// Error message constants
|
||||
extractErrorSummary = "extracting failed"
|
||||
|
|
@ -45,14 +44,6 @@ func NewDatabaseResource() resource.Resource {
|
|||
// resourceModel describes the resource data model.
|
||||
type resourceModel = postgresflexalphaResGen.DatabaseModel
|
||||
|
||||
// DatabaseResourceIdentityModel describes the resource's identity attributes.
|
||||
type DatabaseResourceIdentityModel struct {
|
||||
ProjectID types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
InstanceID types.String `tfsdk:"instance_id"`
|
||||
DatabaseID types.Int64 `tfsdk:"database_id"`
|
||||
}
|
||||
|
||||
// databaseResource is the resource implementation.
|
||||
type databaseResource struct {
|
||||
client *v3alpha1api.APIClient
|
||||
|
|
@ -138,30 +129,6 @@ func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest,
|
|||
resp.Schema = s
|
||||
}
|
||||
|
||||
// IdentitySchema defines the schema for the resource's identity attributes.
|
||||
func (r *databaseResource) IdentitySchema(
|
||||
_ context.Context,
|
||||
_ resource.IdentitySchemaRequest,
|
||||
response *resource.IdentitySchemaResponse,
|
||||
) {
|
||||
response.IdentitySchema = identityschema.Schema{
|
||||
Attributes: map[string]identityschema.Attribute{
|
||||
"project_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"region": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"instance_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"database_id": identityschema.Int64Attribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *databaseResource) Create(
|
||||
ctx context.Context,
|
||||
|
|
@ -178,12 +145,12 @@ func (r *databaseResource) Create(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
projectID := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
instanceId := model.InstanceId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Generate API request body from model
|
||||
|
|
@ -200,9 +167,9 @@ func (r *databaseResource) Create(
|
|||
// Create new database
|
||||
databaseResp, err := r.client.DefaultAPI.CreateDatabaseRequest(
|
||||
ctx,
|
||||
projectId,
|
||||
projectID,
|
||||
region,
|
||||
instanceId,
|
||||
instanceID,
|
||||
).CreateDatabaseRequestPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, funcErrorSummary, fmt.Sprintf("Calling API: %v", err))
|
||||
|
|
@ -219,23 +186,33 @@ func (r *databaseResource) Create(
|
|||
)
|
||||
return
|
||||
}
|
||||
databaseId := int64(*dbID)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
databaseID := int64(*dbID)
|
||||
databaseIDString := strconv.Itoa(int(*dbID))
|
||||
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseID)
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Save identity into Terraform state
|
||||
identity := DatabaseResourceIdentityModel{
|
||||
ProjectID: types.StringValue(projectId),
|
||||
Region: types.StringValue(region),
|
||||
InstanceID: types.StringValue(instanceId),
|
||||
DatabaseID: types.Int64Value(databaseId),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
model.DatabaseId = types.Int64Value(databaseID)
|
||||
model.Id = utils.BuildInternalTerraformId(projectID, region, instanceID, databaseIDString)
|
||||
|
||||
database, err := postgresflexalphaWait.GetDatabaseByIdWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, region, databaseId).
|
||||
// Set data returned by API in id
|
||||
resp.Diagnostics.Append(
|
||||
resp.State.SetAttribute(
|
||||
ctx,
|
||||
path.Root("database_id"),
|
||||
databaseID,
|
||||
)...,
|
||||
)
|
||||
// Set data returned by API in id
|
||||
resp.Diagnostics.Append(
|
||||
resp.State.SetAttribute(
|
||||
ctx,
|
||||
path.Root("id"),
|
||||
model.Id,
|
||||
)...,
|
||||
)
|
||||
|
||||
database, err := postgresflexalphaWait.GetDatabaseByIdWaitHandler(ctx, r.client.DefaultAPI, projectID, instanceID, region, databaseID).
|
||||
SetTimeout(15 * time.Minute).
|
||||
SetSleepBeforeWait(15 * time.Second).
|
||||
WaitWithContext(ctx)
|
||||
|
|
@ -284,17 +261,28 @@ func (r *databaseResource) Read(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
instanceId := model.InstanceId.ValueString()
|
||||
projectID := model.ProjectId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
databaseId := model.DatabaseId.ValueInt64()
|
||||
databaseID := model.DatabaseId.ValueInt64()
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
databaseIDString := strconv.Itoa(int(databaseID))
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseID)
|
||||
|
||||
databaseResp, err := postgresflexalphaWait.GetDatabaseByIdWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, region, databaseId).
|
||||
// Set data returned by API in id
|
||||
resp.Diagnostics.Append(
|
||||
resp.State.SetAttribute(
|
||||
ctx,
|
||||
path.Root("id"),
|
||||
utils.BuildInternalTerraformId(projectID, region, instanceID, databaseIDString),
|
||||
)...,
|
||||
)
|
||||
|
||||
databaseResp, err := postgresflexalphaWait.GetDatabaseByIdWaitHandler(ctx, r.client.DefaultAPI, projectID, instanceID, region, databaseID).
|
||||
SetTimeout(15 * time.Minute).
|
||||
SetSleepBeforeWait(15 * time.Second).
|
||||
WaitWithContext(ctx)
|
||||
|
|
@ -322,18 +310,6 @@ func (r *databaseResource) Read(
|
|||
return
|
||||
}
|
||||
|
||||
// Save identity into Terraform state
|
||||
identity := DatabaseResourceIdentityModel{
|
||||
ProjectID: types.StringValue(projectId),
|
||||
Region: types.StringValue(region),
|
||||
InstanceID: types.StringValue(instanceId),
|
||||
DatabaseID: types.Int64Value(int64(databaseResp.GetId())),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -436,18 +412,6 @@ func (r *databaseResource) Update(
|
|||
return
|
||||
}
|
||||
|
||||
// Save identity into Terraform state
|
||||
identity := DatabaseResourceIdentityModel{
|
||||
ProjectID: types.StringValue(projectId),
|
||||
Region: types.StringValue(region),
|
||||
InstanceID: types.StringValue(instanceId),
|
||||
DatabaseID: types.Int64Value(databaseId),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Set state to fully populated data
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -469,38 +433,33 @@ func (r *databaseResource) Delete(
|
|||
return
|
||||
}
|
||||
|
||||
// Read identity data
|
||||
var identityData DatabaseResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId, region, instanceId, databaseId64, errExt := r.extractIdentityData(model, identityData)
|
||||
if errExt != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
extractErrorSummary,
|
||||
fmt.Sprintf(extractErrorMessage, errExt),
|
||||
)
|
||||
}
|
||||
projectID := model.ProjectId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
databaseID64 := model.DatabaseId.ValueInt64()
|
||||
|
||||
if databaseId64 > math.MaxInt32 {
|
||||
if databaseID64 > math.MaxInt32 {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")
|
||||
return
|
||||
}
|
||||
databaseId := int32(databaseId64) // nolint:gosec // check is performed above
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
databaseID := int32(databaseID64) // nolint:gosec // check is performed above
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseID)
|
||||
|
||||
// Delete existing record set
|
||||
err := r.client.DefaultAPI.DeleteDatabaseRequest(ctx, projectId, region, instanceId, databaseId).Execute()
|
||||
err := r.client.DefaultAPI.DeleteDatabaseRequest(ctx, projectID, region, instanceID, databaseID).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) // nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok {
|
||||
if oapiErr.StatusCode == 404 {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting database", fmt.Sprintf("Calling API: %v", err))
|
||||
}
|
||||
|
||||
|
|
@ -517,109 +476,44 @@ func (r *databaseResource) ImportState(
|
|||
resp *resource.ImportStateResponse,
|
||||
) {
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if req.ID != "" {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing database",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id],[database_id], got %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing database",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id],[database_id], got %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
databaseId, err := strconv.ParseInt(idParts[3], 10, 64)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Error importing database",
|
||||
fmt.Sprintf("Invalid database_id format: %q. It must be a valid integer.", idParts[3]),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...)
|
||||
|
||||
core.LogAndAddWarning(
|
||||
databaseID, err := strconv.ParseInt(idParts[3], 10, 64)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Postgresflex database imported with empty password",
|
||||
"The database password is not imported as it is only available upon creation of a new database. The password field will be empty.",
|
||||
"Error importing database",
|
||||
fmt.Sprintf("Invalid database_id format: %q. It must be a valid integer.", idParts[3]),
|
||||
)
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex database state imported")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If no ID is provided, attempt to read identity attributes from the import configuration
|
||||
var identityData DatabaseResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tfIDString := utils.BuildInternalTerraformId(idParts...).ValueString()
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), tfIDString)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseID)...)
|
||||
|
||||
projectId := identityData.ProjectID.ValueString()
|
||||
region := identityData.Region.ValueString()
|
||||
instanceId := identityData.InstanceID.ValueString()
|
||||
databaseId := identityData.DatabaseID.ValueInt64()
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...)
|
||||
core.LogAndAddWarning(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Postgresflex database imported with empty password",
|
||||
"The database password is not imported as it is only available upon creation of a new database. The password field will be empty.",
|
||||
)
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex database state imported")
|
||||
}
|
||||
|
||||
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
|
||||
func (r *databaseResource) extractIdentityData(
|
||||
model resourceModel,
|
||||
identity DatabaseResourceIdentityModel,
|
||||
) (projectId, region, instanceId string, databaseId int64, err error) {
|
||||
if !model.DatabaseId.IsNull() && !model.DatabaseId.IsUnknown() {
|
||||
databaseId = model.DatabaseId.ValueInt64()
|
||||
} else {
|
||||
if identity.DatabaseID.IsNull() || identity.DatabaseID.IsUnknown() {
|
||||
return "", "", "", 0, fmt.Errorf("database_id not found in config")
|
||||
}
|
||||
databaseId = identity.DatabaseID.ValueInt64()
|
||||
}
|
||||
|
||||
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
} else {
|
||||
if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
|
||||
return "", "", "", 0, fmt.Errorf("project_id not found in config")
|
||||
}
|
||||
projectId = identity.ProjectID.ValueString()
|
||||
}
|
||||
|
||||
if !model.Region.IsNull() && !model.Region.IsUnknown() {
|
||||
region = r.providerData.GetRegionWithOverride(model.Region)
|
||||
} else {
|
||||
if identity.Region.IsNull() || identity.Region.IsUnknown() {
|
||||
return "", "", "", 0, fmt.Errorf("region not found in config")
|
||||
}
|
||||
region = r.providerData.GetRegionWithOverride(identity.Region)
|
||||
}
|
||||
|
||||
if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
|
||||
instanceId = model.InstanceId.ValueString()
|
||||
} else {
|
||||
if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() {
|
||||
return "", "", "", 0, fmt.Errorf("instance_id not found in config")
|
||||
}
|
||||
instanceId = identity.InstanceID.ValueString()
|
||||
}
|
||||
return projectId, region, instanceId, databaseId, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func DatabaseResourceSchema(ctx context.Context) schema.Schema {
|
|||
Description: "The ID of the database.",
|
||||
MarkdownDescription: "The ID of the database.",
|
||||
},
|
||||
"id": schema.Int64Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The id of the database.",
|
||||
MarkdownDescription: "The id of the database.",
|
||||
|
|
@ -65,7 +65,7 @@ func DatabaseResourceSchema(ctx context.Context) schema.Schema {
|
|||
|
||||
type DatabaseModel struct {
|
||||
DatabaseId types.Int64 `tfsdk:"database_id"`
|
||||
Id types.Int64 `tfsdk:"id"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
InstanceId types.String `tfsdk:"instance_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Owner types.String `tfsdk:"owner"`
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex/v3alpha1api"
|
||||
|
||||
|
|
@ -72,7 +73,13 @@ func (r *instanceDataSource) Configure(
|
|||
|
||||
// Schema defines the schema for the data source.
|
||||
func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = postgresflexalpha2.InstanceDataSourceSchema(ctx)
|
||||
sch := postgresflexalpha2.InstanceDataSourceSchema(ctx)
|
||||
sch.Attributes["id"] = schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "internal ID",
|
||||
MarkdownDescription: "internal ID",
|
||||
}
|
||||
resp.Schema = sch
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
|
|
@ -90,22 +97,22 @@ func (r *instanceDataSource) Read(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
instanceId := model.InstanceId.ValueString()
|
||||
projectID := model.ProjectId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
instanceResp, err := r.client.DefaultAPI.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
instanceResp, err := r.client.DefaultAPI.GetInstanceRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
err,
|
||||
"Reading instance",
|
||||
fmt.Sprintf("Instance with ID %q does not exist in project %q.", instanceId, projectId),
|
||||
fmt.Sprintf("Instance with ID %q does not exist in project %q.", instanceID, projectID),
|
||||
map[int]string{
|
||||
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
|
||||
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectID),
|
||||
},
|
||||
)
|
||||
resp.State.RemoveResource(ctx)
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema {
|
|||
MarkdownDescription: "The STACKIT project ID.",
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Required: true,
|
||||
Optional: true,
|
||||
Description: "The region which should be addressed",
|
||||
MarkdownDescription: "The region which should be addressed",
|
||||
Validators: []validator.String{
|
||||
|
|
|
|||
|
|
@ -66,12 +66,12 @@ func mapGetInstanceResponseToModel(
|
|||
|
||||
m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
|
||||
|
||||
netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
|
||||
netACL, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
|
||||
if diags.HasError() {
|
||||
return fmt.Errorf("failed converting network acl from response")
|
||||
}
|
||||
|
||||
m.Acl = netAcl
|
||||
m.Acl = netACL
|
||||
|
||||
netInstAdd := types.StringValue("")
|
||||
if instAdd, ok := resp.Network.GetInstanceAddressOk(); ok {
|
||||
|
|
@ -87,7 +87,7 @@ func mapGetInstanceResponseToModel(
|
|||
postgresflexalpharesource.NetworkValue{}.AttributeTypes(ctx),
|
||||
map[string]attr.Value{
|
||||
"access_scope": basetypes.NewStringValue(string(resp.Network.GetAccessScope())),
|
||||
"acl": netAcl,
|
||||
"acl": netACL,
|
||||
"instance_address": netInstAdd,
|
||||
"router_address": netRtrAdd,
|
||||
},
|
||||
|
|
@ -130,7 +130,8 @@ func mapGetDataInstanceResponseToModel(
|
|||
handleConnectionInfo(ctx, m, resp)
|
||||
|
||||
m.FlavorId = types.StringValue(resp.GetFlavorId())
|
||||
m.Id = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString(), m.InstanceId.ValueString())
|
||||
m.Id = types.StringValue(resp.Id)
|
||||
m.TerraformID = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString(), m.InstanceId.ValueString())
|
||||
m.InstanceId = types.StringValue(resp.Id)
|
||||
m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
|
||||
m.Name = types.StringValue(resp.GetName())
|
||||
|
|
@ -212,14 +213,14 @@ func handleNetwork(ctx context.Context, m *dataSourceModel, resp *postgresflex.G
|
|||
}
|
||||
|
||||
func handleEncryption(m *dataSourceModel, resp *postgresflex.GetInstanceResponse) {
|
||||
keyId := ""
|
||||
if keyIdVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
|
||||
keyId = *keyIdVal
|
||||
keyID := ""
|
||||
if keyIDVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
|
||||
keyID = *keyIDVal
|
||||
}
|
||||
|
||||
keyRingId := ""
|
||||
if keyRingIdVal, ok := resp.Encryption.GetKekKeyRingIdOk(); ok {
|
||||
keyRingId = *keyRingIdVal
|
||||
keyRingID := ""
|
||||
if keyRingIDVal, ok := resp.Encryption.GetKekKeyRingIdOk(); ok {
|
||||
keyRingID = *keyRingIDVal
|
||||
}
|
||||
|
||||
keyVersion := ""
|
||||
|
|
@ -233,8 +234,8 @@ func handleEncryption(m *dataSourceModel, resp *postgresflex.GetInstanceResponse
|
|||
}
|
||||
|
||||
m.Encryption = postgresflexalphadatasource.EncryptionValue{
|
||||
KekKeyId: types.StringValue(keyId),
|
||||
KekKeyRingId: types.StringValue(keyRingId),
|
||||
KekKeyId: types.StringValue(keyID),
|
||||
KekKeyRingId: types.StringValue(keyRingID),
|
||||
KekKeyVersion: types.StringValue(keyVersion),
|
||||
ServiceAccount: types.StringValue(svcAcc),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
coreUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
|
|
@ -33,7 +31,6 @@ var (
|
|||
_ resource.ResourceWithImportState = &instanceResource{}
|
||||
_ resource.ResourceWithModifyPlan = &instanceResource{}
|
||||
_ resource.ResourceWithValidateConfig = &instanceResource{}
|
||||
_ resource.ResourceWithIdentity = &instanceResource{}
|
||||
)
|
||||
|
||||
// NewInstanceResource is a helper function to simplify the provider implementation.
|
||||
|
|
@ -41,15 +38,6 @@ func NewInstanceResource() resource.Resource {
|
|||
return &instanceResource{}
|
||||
}
|
||||
|
||||
// resourceModel describes the resource data model.
|
||||
type resourceModel = postgresflexalpha.InstanceModel
|
||||
|
||||
type InstanceResourceIdentityModel struct {
|
||||
ProjectID types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
InstanceID types.String `tfsdk:"instance_id"`
|
||||
}
|
||||
|
||||
// instanceResource is the resource implementation.
|
||||
type instanceResource struct {
|
||||
client *v3alpha1api.APIClient
|
||||
|
|
@ -61,7 +49,7 @@ func (r *instanceResource) ValidateConfig(
|
|||
req resource.ValidateConfigRequest,
|
||||
resp *resource.ValidateConfigResponse,
|
||||
) {
|
||||
var data resourceModel
|
||||
var data postgresflexalpha.InstanceModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -85,7 +73,7 @@ func (r *instanceResource) ModifyPlan(
|
|||
req resource.ModifyPlanRequest,
|
||||
resp *resource.ModifyPlanResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
var configModel resourceModel
|
||||
var configModel postgresflexalpha.InstanceModel
|
||||
// skip initial empty configuration to avoid follow-up errors
|
||||
if req.Config.Raw.IsNull() {
|
||||
return
|
||||
|
|
@ -95,7 +83,7 @@ func (r *instanceResource) ModifyPlan(
|
|||
return
|
||||
}
|
||||
|
||||
var planModel resourceModel
|
||||
var planModel postgresflexalpha.InstanceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -161,33 +149,13 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest,
|
|||
resp.Schema = schema
|
||||
}
|
||||
|
||||
func (r *instanceResource) IdentitySchema(
|
||||
_ context.Context,
|
||||
_ resource.IdentitySchemaRequest,
|
||||
resp *resource.IdentitySchemaResponse,
|
||||
) {
|
||||
resp.IdentitySchema = identityschema.Schema{
|
||||
Attributes: map[string]identityschema.Attribute{
|
||||
"project_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true, // must be set during import by the practitioner
|
||||
},
|
||||
"region": identityschema.StringAttribute{
|
||||
RequiredForImport: true, // can be defaulted by the provider configuration
|
||||
},
|
||||
"instance_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true, // can be defaulted by the provider configuration
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *instanceResource) Create(
|
||||
ctx context.Context,
|
||||
req resource.CreateRequest,
|
||||
resp *resource.CreateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
var model resourceModel
|
||||
var model postgresflexalpha.InstanceModel
|
||||
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -202,15 +170,15 @@ func (r *instanceResource) Create(
|
|||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
var netAcl []string
|
||||
diag := model.Network.Acl.ElementsAs(ctx, &netAcl, false)
|
||||
var netACL []string
|
||||
diag := model.Network.Acl.ElementsAs(ctx, &netACL, false)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diag.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
replVal := model.Replicas.ValueInt64() // nolint:gosec // check is performed above
|
||||
payload := modelToCreateInstancePayload(netAcl, model, replVal)
|
||||
payload := modelToCreateInstancePayload(netACL, model, replVal)
|
||||
|
||||
// Create new instance
|
||||
createResp, err := r.client.DefaultAPI.CreateInstanceRequest(
|
||||
|
|
@ -230,16 +198,14 @@ func (r *instanceResource) Create(
|
|||
return
|
||||
}
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := InstanceResourceIdentityModel{
|
||||
ProjectID: types.StringValue(projectID),
|
||||
Region: types.StringValue(region),
|
||||
InstanceID: types.StringPointerValue(instanceID),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Set data returned by API in id
|
||||
resp.Diagnostics.Append(
|
||||
resp.State.SetAttribute(
|
||||
ctx,
|
||||
path.Root("id"),
|
||||
utils.BuildInternalTerraformId(projectID, region, *instanceID),
|
||||
)...,
|
||||
)
|
||||
|
||||
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectID, region, *instanceID).
|
||||
SetTimeout(30 * time.Minute).
|
||||
|
|
@ -317,7 +283,7 @@ func (r *instanceResource) Read(
|
|||
) { // nolint:gocritic // function signature required by Terraform
|
||||
functionErrorSummary := "read instance failed"
|
||||
|
||||
var model resourceModel
|
||||
var model postgresflexalpha.InstanceModel
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -326,9 +292,9 @@ func (r *instanceResource) Read(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
var projectId string
|
||||
var projectID string
|
||||
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
|
||||
projectId = model.ProjectId.ValueString()
|
||||
projectID = model.ProjectId.ValueString()
|
||||
}
|
||||
|
||||
var region string
|
||||
|
|
@ -336,16 +302,16 @@ func (r *instanceResource) Read(
|
|||
region = r.providerData.GetRegionWithOverride(model.Region)
|
||||
}
|
||||
|
||||
var instanceId string
|
||||
var instanceID string
|
||||
if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
|
||||
instanceId = model.InstanceId.ValueString()
|
||||
instanceID = model.InstanceId.ValueString()
|
||||
}
|
||||
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
instanceResp, err := r.client.DefaultAPI.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
instanceResp, err := r.client.DefaultAPI.GetInstanceRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode == http.StatusNotFound {
|
||||
|
|
@ -364,7 +330,7 @@ func (r *instanceResource) Read(
|
|||
return
|
||||
}
|
||||
if !model.InstanceId.IsUnknown() && !model.InstanceId.IsNull() {
|
||||
if *respInstanceID != instanceId {
|
||||
if *respInstanceID != instanceID {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
|
|
@ -375,6 +341,10 @@ func (r *instanceResource) Read(
|
|||
}
|
||||
}
|
||||
|
||||
if !model.Id.IsUnknown() && !model.Id.IsNull() {
|
||||
model.Id = utils.BuildInternalTerraformId(projectID, region, instanceID)
|
||||
}
|
||||
|
||||
err = mapGetInstanceResponseToModel(ctx, &model, instanceResp)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
|
|
@ -392,17 +362,6 @@ func (r *instanceResource) Read(
|
|||
return
|
||||
}
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := InstanceResourceIdentityModel{
|
||||
ProjectID: types.StringValue(projectId),
|
||||
Region: types.StringValue(region),
|
||||
InstanceID: types.StringValue(instanceId),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex instance read")
|
||||
}
|
||||
|
||||
|
|
@ -412,7 +371,7 @@ func (r *instanceResource) Update(
|
|||
req resource.UpdateRequest,
|
||||
resp *resource.UpdateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
var model resourceModel
|
||||
var model postgresflexalpha.InstanceModel
|
||||
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -422,15 +381,8 @@ func (r *instanceResource) Update(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
// Read identity data
|
||||
var identityData InstanceResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectID := identityData.ProjectID.ValueString()
|
||||
instanceID := identityData.InstanceID.ValueString()
|
||||
projectID := model.ProjectId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
|
|
@ -532,7 +484,7 @@ func (r *instanceResource) Delete(
|
|||
req resource.DeleteRequest,
|
||||
resp *resource.DeleteResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
var model resourceModel
|
||||
var model postgresflexalpha.InstanceModel
|
||||
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -542,15 +494,15 @@ func (r *instanceResource) Delete(
|
|||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
instanceId := model.InstanceId.ValueString()
|
||||
projectID := model.ProjectId.ValueString()
|
||||
instanceID := model.InstanceId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectID)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceID)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Delete existing instance
|
||||
err := r.client.DefaultAPI.DeleteInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
err := r.client.DefaultAPI.DeleteInstanceRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
|
|
@ -558,7 +510,7 @@ func (r *instanceResource) Delete(
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = r.client.DefaultAPI.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
_, err = r.client.DefaultAPI.GetInstanceRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if err != nil {
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode != http.StatusNotFound {
|
||||
|
|
@ -580,41 +532,30 @@ func (r *instanceResource) ImportState(
|
|||
) {
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
if req.ID != "" {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing instance",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id] Got: %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing instance",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id] Got: %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// If no ID is provided, attempt to read identity attributes from the import configuration
|
||||
var identityData InstanceResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
projectId := identityData.ProjectID.ValueString()
|
||||
region := identityData.Region.ValueString()
|
||||
instanceId := identityData.InstanceID.ValueString()
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceId)...)
|
||||
resp.Diagnostics.Append(
|
||||
resp.State.SetAttribute(
|
||||
ctx,
|
||||
path.Root("id"),
|
||||
utils.BuildInternalTerraformId(idParts...),
|
||||
)...,
|
||||
)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex instance state imported")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
pfx = "stackitprivatepreview_postgresflexalpha"
|
||||
pfx = "stackitprivatepreview_postgresflexalpha"
|
||||
dataPfx = "data.stackitprivatepreview_postgresflexalpha"
|
||||
|
||||
singleFlavorID = "2.4"
|
||||
replicasFlavorID = "2.4-replica"
|
||||
|
|
@ -100,6 +101,7 @@ type resData struct {
|
|||
Version string
|
||||
Users []User
|
||||
Databases []Database
|
||||
DataSourceTest bool
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
|
@ -137,6 +139,7 @@ func getExample() resData {
|
|||
|
||||
func TestAccInstance(t *testing.T) {
|
||||
exData := getExample()
|
||||
exData.Version = "16"
|
||||
|
||||
updNameData := exData
|
||||
updNameData.Name = "name-updated"
|
||||
|
|
@ -151,6 +154,9 @@ func TestAccInstance(t *testing.T) {
|
|||
updNetACL := updBackupSched
|
||||
updNetACL.ACLStrings = append(updNetACL.ACLStrings, "192.168.0.0/24")
|
||||
|
||||
updVersion := updNetACL
|
||||
updVersion.Version = "17"
|
||||
|
||||
testItemID := testutils.ResStr(pfx, "instance", exData.TfName)
|
||||
compareValuesSame := statecheck.CompareValue(compare.ValuesSame())
|
||||
resource.ParallelTest(
|
||||
|
|
@ -299,19 +305,31 @@ func TestAccInstance(t *testing.T) {
|
|||
resource.TestCheckResourceAttr(testItemID, "network.acl.#", "2"),
|
||||
),
|
||||
},
|
||||
// Update version
|
||||
{
|
||||
Config: testutils.StringFromTemplateMust(
|
||||
"testdata/instance_template.gompl",
|
||||
updVersion,
|
||||
),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(
|
||||
testItemID,
|
||||
"version",
|
||||
updVersion.Version,
|
||||
),
|
||||
),
|
||||
},
|
||||
// Import test
|
||||
/*
|
||||
{
|
||||
ImportState: true,
|
||||
ImportStateKind: resource.ImportBlockWithResourceIdentity,
|
||||
ResourceName: testItemID,
|
||||
},
|
||||
{
|
||||
ImportState: true,
|
||||
ImportStateKind: resource.ImportCommandWithID,
|
||||
ResourceName: testItemID,
|
||||
},
|
||||
*/
|
||||
// test instance imports
|
||||
{
|
||||
ResourceName: testItemID,
|
||||
// ImportStateIdPrefix: "",
|
||||
// ImportStateVerifyIdentifierAttribute: "id",
|
||||
ImportStateIdFunc: getInstanceTestID(exData.TfName),
|
||||
ImportStateKind: resource.ImportCommandWithID,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -410,6 +428,7 @@ func TestAccInstanceWithDatabases(t *testing.T) {
|
|||
Owner: userName,
|
||||
},
|
||||
}
|
||||
data.DataSourceTest = true
|
||||
|
||||
testItemID := testutils.ResStr(pfx, "instance", data.TfName)
|
||||
resource.ParallelTest(
|
||||
|
|
@ -432,11 +451,145 @@ func TestAccInstanceWithDatabases(t *testing.T) {
|
|||
|
||||
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
|
||||
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testItemID, "project_id",
|
||||
testutils.ResStr(pfx, "user", userName), "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testItemID, "instance_id",
|
||||
testutils.ResStr(pfx, "user", userName), "instance_id",
|
||||
),
|
||||
|
||||
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "name", dbName),
|
||||
resource.TestCheckResourceAttr(testutils.ResStr(pfx, "database", dbName), "owner", userName),
|
||||
resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "database", dbName), "id"),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testItemID, "project_id",
|
||||
testutils.ResStr(pfx, "database", dbName), "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testItemID, "instance_id",
|
||||
testutils.ResStr(pfx, "database", dbName), "instance_id",
|
||||
),
|
||||
),
|
||||
},
|
||||
// data source
|
||||
{
|
||||
Config: testutils.StringFromTemplateMust(
|
||||
"testdata/instance_template.gompl",
|
||||
data,
|
||||
),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
// Instance data
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "instance", data.TfName),
|
||||
"project_id",
|
||||
data.ProjectID,
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "instance", data.TfName),
|
||||
"name",
|
||||
data.Name,
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "instance", data.TfName), "project_id",
|
||||
testutils.ResStr(pfx, "instance", data.TfName), "project_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "database", dbName), "instance_id",
|
||||
testutils.ResStr(pfx, "instance", data.TfName), "instance_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "user", userName), "instance_id",
|
||||
testutils.ResStr(pfx, "instance", data.TfName), "instance_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "user", userName), "instance_id",
|
||||
testutils.ResStr(pfx, "user", userName), "instance_id",
|
||||
),
|
||||
|
||||
// User data
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"project_id",
|
||||
data.ProjectID,
|
||||
),
|
||||
resource.TestCheckResourceAttrSet(
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"user_id",
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"name",
|
||||
data.Users[0].Name,
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"roles.#",
|
||||
"1",
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"roles.0",
|
||||
data.Users[0].Roles[0],
|
||||
),
|
||||
|
||||
// Database data
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "database", dbName),
|
||||
"project_id",
|
||||
data.ProjectID,
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
testutils.ResStr(dataPfx, "database", dbName),
|
||||
"name",
|
||||
dbName,
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "database", dbName),
|
||||
"instance_id",
|
||||
testutils.ResStr(pfx, "database", dbName),
|
||||
"instance_id",
|
||||
),
|
||||
resource.TestCheckResourceAttrPair(
|
||||
testutils.ResStr(dataPfx, "database", dbName),
|
||||
"owner",
|
||||
testutils.ResStr(dataPfx, "user", userName),
|
||||
"name",
|
||||
),
|
||||
),
|
||||
},
|
||||
// test instance imports
|
||||
{
|
||||
ResourceName: testItemID,
|
||||
// ImportStateIdPrefix: "",
|
||||
ImportStateVerifyIdentifierAttribute: "id",
|
||||
ImportStateIdFunc: getInstanceTestID(data.TfName),
|
||||
ImportStateKind: resource.ImportCommandWithID,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// test database imports
|
||||
{
|
||||
ResourceName: testutils.ResStr(pfx, "database", dbName),
|
||||
// ImportStateIdPrefix: "",
|
||||
// ImportStateVerifyIdentifierAttribute: "id",
|
||||
ImportStateIdFunc: getDatabaseTestID(dbName),
|
||||
ImportStateKind: resource.ImportCommandWithID,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
// test user imports
|
||||
{
|
||||
ResourceName: testutils.ResStr(pfx, "user", userName),
|
||||
// ImportStateIdPrefix: "",
|
||||
// ImportStateVerifyIdentifierAttribute: "id",
|
||||
ImportStateIdFunc: getUserTestID(userName),
|
||||
ImportStateKind: resource.ImportCommandWithID,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateVerifyIgnore: []string{"password"},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -614,10 +767,6 @@ func defaultNoEncInstanceTestChecks(testItemID string, data resData) resource.Te
|
|||
return resource.ComposeAggregateTestCheckFunc(
|
||||
defaultInstanceTestChecks(testItemID, data),
|
||||
|
||||
// on unencrypted instances we expect this to be empty
|
||||
resource.TestCheckResourceAttr(testItemID, "network.instance_address", ""),
|
||||
resource.TestCheckResourceAttr(testItemID, "network.router_address", ""),
|
||||
|
||||
// check absent attr
|
||||
resource.TestCheckNoResourceAttr(testItemID, "encryption"),
|
||||
resource.TestCheckNoResourceAttr(testItemID, "encryption.kek_key_id"),
|
||||
|
|
@ -631,10 +780,6 @@ func defaultEncInstanceTestChecks(testItemID string, data resData) resource.Test
|
|||
return resource.ComposeAggregateTestCheckFunc(
|
||||
defaultInstanceTestChecks(testItemID, data),
|
||||
|
||||
// on unencrypted instances we expect this to be empty
|
||||
resource.TestCheckResourceAttrSet(testItemID, "network.instance_address"),
|
||||
resource.TestCheckResourceAttrSet(testItemID, "network.router_address"),
|
||||
|
||||
// check absent attr
|
||||
resource.TestCheckResourceAttr(testItemID, "encryption.%", "4"),
|
||||
resource.TestCheckResourceAttrSet(testItemID, "encryption.kek_key_id"),
|
||||
|
|
@ -649,6 +794,24 @@ func defaultEncInstanceTestChecks(testItemID string, data resData) resource.Test
|
|||
}
|
||||
|
||||
func defaultInstanceTestChecks(testItemID string, data resData) resource.TestCheckFunc {
|
||||
// if AccessScope == SNA these are set
|
||||
if data.AccessScope == "SNA" {
|
||||
return resource.ComposeAggregateTestCheckFunc(
|
||||
basicInstanceTestChecks(testItemID, data),
|
||||
resource.TestCheckResourceAttrSet(testItemID, "network.instance_address"),
|
||||
resource.TestCheckResourceAttrSet(testItemID, "network.router_address"),
|
||||
)
|
||||
}
|
||||
|
||||
// if AccessScope == PUBLIC these are empty - but they are set
|
||||
return resource.ComposeAggregateTestCheckFunc(
|
||||
basicInstanceTestChecks(testItemID, data),
|
||||
resource.TestCheckResourceAttr(testItemID, "network.instance_address", ""),
|
||||
resource.TestCheckResourceAttr(testItemID, "network.router_address", ""),
|
||||
)
|
||||
}
|
||||
|
||||
func basicInstanceTestChecks(testItemID string, data resData) resource.TestCheckFunc {
|
||||
return resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet(testItemID, "backup_schedule"),
|
||||
resource.TestCheckResourceAttr(testItemID, "backup_schedule", data.BackupSchedule),
|
||||
|
|
@ -704,3 +867,77 @@ func defaultInstanceTestChecks(testItemID string, data resData) resource.TestChe
|
|||
resource.TestCheckResourceAttr(testItemID, "version", data.Version),
|
||||
)
|
||||
}
|
||||
|
||||
func getInstanceTestID(name string) func(s *terraform.State) (string, error) {
|
||||
return func(s *terraform.State) (string, error) {
|
||||
r, ok := s.RootModule().Resources[testutils.ResStr(pfx, "instance", name)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackitprivatepreview_postgresflexalpha_instance.%s", name)
|
||||
}
|
||||
projectID, ok := r.Primary.Attributes["project_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute project_id")
|
||||
}
|
||||
region, ok := r.Primary.Attributes["region"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute region")
|
||||
}
|
||||
instanceID, ok := r.Primary.Attributes["instance_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute instance_id")
|
||||
}
|
||||
return fmt.Sprintf("%s,%s,%s", projectID, region, instanceID), nil
|
||||
}
|
||||
}
|
||||
|
||||
func getDatabaseTestID(name string) func(s *terraform.State) (string, error) {
|
||||
return func(s *terraform.State) (string, error) {
|
||||
r, ok := s.RootModule().Resources[testutils.ResStr(pfx, "database", name)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackitprivatepreview_postgresflexalpha_instance.%s", name)
|
||||
}
|
||||
projectID, ok := r.Primary.Attributes["project_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute project_id")
|
||||
}
|
||||
region, ok := r.Primary.Attributes["region"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute region")
|
||||
}
|
||||
instanceID, ok := r.Primary.Attributes["instance_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute instance_id")
|
||||
}
|
||||
databaseID, ok := r.Primary.Attributes["database_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute database_id")
|
||||
}
|
||||
return fmt.Sprintf("%s,%s,%s,%s", projectID, region, instanceID, databaseID), nil
|
||||
}
|
||||
}
|
||||
|
||||
func getUserTestID(name string) func(s *terraform.State) (string, error) {
|
||||
return func(s *terraform.State) (string, error) {
|
||||
r, ok := s.RootModule().Resources[testutils.ResStr(pfx, "user", name)]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find resource stackitprivatepreview_postgresflexalpha_instance.%s", name)
|
||||
}
|
||||
projectID, ok := r.Primary.Attributes["project_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute project_id")
|
||||
}
|
||||
region, ok := r.Primary.Attributes["region"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute region")
|
||||
}
|
||||
instanceID, ok := r.Primary.Attributes["instance_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute instance_id")
|
||||
}
|
||||
userID, ok := r.Primary.Attributes["user_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't find attribute user_id")
|
||||
}
|
||||
return fmt.Sprintf("%s,%s,%s,%s", projectID, region, instanceID, userID), nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "{{ .TfName }}" {
|
|||
{{ $tfName := .TfName }}
|
||||
{{ range $user := .Users }}
|
||||
resource "stackitprivatepreview_postgresflexalpha_user" "{{ $user.Name }}" {
|
||||
depends_on = [
|
||||
stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}
|
||||
]
|
||||
project_id = "{{ $user.ProjectID }}"
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.instance_id
|
||||
name = "{{ $user.Name }}"
|
||||
|
|
@ -45,6 +48,10 @@ resource "stackitprivatepreview_postgresflexalpha_user" "{{ $user.Name }}" {
|
|||
{{ $tfName := .TfName }}
|
||||
{{ range $db := .Databases }}
|
||||
resource "stackitprivatepreview_postgresflexalpha_database" "{{ $db.Name }}" {
|
||||
depends_on = [
|
||||
stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }},
|
||||
stackitprivatepreview_postgresflexalpha_user.{{ $db.Owner }}
|
||||
]
|
||||
project_id = "{{ $db.ProjectID }}"
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.instance_id
|
||||
name = "{{ $db.Name }}"
|
||||
|
|
@ -52,3 +59,32 @@ resource "stackitprivatepreview_postgresflexalpha_database" "{{ $db.Name }}" {
|
|||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .DataSourceTest }}
|
||||
data "stackitprivatepreview_postgresflexalpha_instance" "{{ .TfName }}" {
|
||||
project_id = stackitprivatepreview_postgresflexalpha_instance.{{ .TfName }}.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ .TfName }}.instance_id
|
||||
}
|
||||
|
||||
{{ if .Users }}
|
||||
{{ $tfName := .TfName }}
|
||||
{{ range $user := .Users }}
|
||||
data "stackitprivatepreview_postgresflexalpha_user" "{{ $user.Name }}" {
|
||||
project_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.instance_id
|
||||
user_id = stackitprivatepreview_postgresflexalpha_user.{{ $user.Name }}.user_id
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .Databases }}
|
||||
{{ $tfName := .TfName }}
|
||||
{{ range $db := .Databases }}
|
||||
data "stackitprivatepreview_postgresflexalpha_database" "{{ $db.Name }}" {
|
||||
project_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.{{ $tfName }}.instance_id
|
||||
database_id = stackitprivatepreview_postgresflexalpha_database.{{ $db.Name }}.database_id
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func UserDataSourceSchema(ctx context.Context) schema.Schema {
|
|||
MarkdownDescription: "The STACKIT project ID.",
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Required: true,
|
||||
Optional: true,
|
||||
Description: "The region which should be addressed",
|
||||
MarkdownDescription: "The region which should be addressed",
|
||||
Validators: []validator.String{
|
||||
|
|
|
|||
|
|
@ -116,7 +116,12 @@ func mapResourceFields(userResp *v3alpha1api.GetUserResponse, model *resourceMod
|
|||
return fmt.Errorf("user id not present")
|
||||
}
|
||||
|
||||
model.Id = types.Int64Value(userID)
|
||||
model.Id = utils.BuildInternalTerraformId(
|
||||
model.ProjectId.ValueString(),
|
||||
model.Region.ValueString(),
|
||||
model.InstanceId.ValueString(),
|
||||
strconv.FormatInt(userID, 10),
|
||||
)
|
||||
model.UserId = types.Int64Value(userID)
|
||||
model.Name = types.StringValue(user.Name)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/postgresflex/v3alpha1api"
|
||||
|
||||
postgresflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/resources_gen"
|
||||
|
|
@ -34,7 +34,6 @@ var (
|
|||
_ resource.ResourceWithConfigure = &userResource{}
|
||||
_ resource.ResourceWithImportState = &userResource{}
|
||||
_ resource.ResourceWithModifyPlan = &userResource{}
|
||||
_ resource.ResourceWithIdentity = &userResource{}
|
||||
_ resource.ResourceWithValidateConfig = &userResource{}
|
||||
|
||||
// Error message constants
|
||||
|
|
@ -50,14 +49,6 @@ func NewUserResource() resource.Resource {
|
|||
// resourceModel represents the Terraform resource state for a PostgreSQL Flex user.
|
||||
type resourceModel = postgresflexalpha.UserModel
|
||||
|
||||
// UserResourceIdentityModel describes the resource's identity attributes.
|
||||
type UserResourceIdentityModel struct {
|
||||
ProjectID types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
InstanceID types.String `tfsdk:"instance_id"`
|
||||
UserID types.Int64 `tfsdk:"user_id"`
|
||||
}
|
||||
|
||||
// userResource implements the resource handling for a PostgreSQL Flex user.
|
||||
type userResource struct {
|
||||
client *v3alpha1api.APIClient
|
||||
|
|
@ -232,23 +223,14 @@ func (r *userResource) Create(
|
|||
}
|
||||
arg.userID = int64(*id)
|
||||
|
||||
model.Id = utils.BuildInternalTerraformId(arg.projectID, arg.region, arg.instanceID, strconv.FormatInt(arg.userID, 10))
|
||||
|
||||
ctx = tflog.SetField(ctx, "id", model.Id.ValueString())
|
||||
ctx = tflog.SetField(ctx, "user_id", id)
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := UserResourceIdentityModel{
|
||||
ProjectID: types.StringValue(arg.projectID),
|
||||
Region: types.StringValue(arg.region),
|
||||
InstanceID: types.StringValue(arg.instanceID),
|
||||
UserID: types.Int64Value(int64(*id)),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
model.Id = types.Int64Value(int64(*id))
|
||||
model.Id = utils.BuildInternalTerraformId(arg.projectID, arg.region, arg.instanceID, strconv.FormatInt(arg.userID, 10))
|
||||
model.UserId = types.Int64Value(int64(*id))
|
||||
model.Password = types.StringValue(userResp.GetPassword())
|
||||
model.Status = types.StringValue(userResp.GetStatus())
|
||||
|
|
@ -370,15 +352,14 @@ func (r *userResource) Read(
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := UserResourceIdentityModel{
|
||||
ProjectID: types.StringValue(arg.projectID),
|
||||
Region: types.StringValue(arg.region),
|
||||
InstanceID: types.StringValue(arg.instanceID),
|
||||
UserID: types.Int64Value(arg.userID),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
err = mapResourceFields(waitResp, &model, model.Region.ValueString())
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"read user",
|
||||
fmt.Sprintf("Wait response mapping: %v", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -457,18 +438,6 @@ func (r *userResource) Update(
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := UserResourceIdentityModel{
|
||||
ProjectID: types.StringValue(arg.projectID),
|
||||
Region: types.StringValue(arg.region),
|
||||
InstanceID: types.StringValue(arg.instanceID),
|
||||
UserID: types.Int64Value(userID64),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify update
|
||||
waitResp, err := postgresflexalphaWait.GetUserByIdWaitHandler(
|
||||
ctx,
|
||||
|
|
@ -525,26 +494,17 @@ func (r *userResource) Delete(
|
|||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Read identity data
|
||||
var identityData UserResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
arg, errExt := r.extractIdentityData(model, identityData)
|
||||
if errExt != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
extractErrorSummary,
|
||||
fmt.Sprintf(extractErrorMessage, errExt),
|
||||
)
|
||||
arg := clientArg{
|
||||
projectID: model.ProjectId.ValueString(),
|
||||
instanceID: model.InstanceId.ValueString(),
|
||||
region: model.Region.ValueString(),
|
||||
userID: model.UserId.ValueInt64(),
|
||||
}
|
||||
|
||||
ctx = r.setTFLogFields(ctx, arg)
|
||||
ctx = r.setTFLogFields(ctx, &arg)
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
userID64 := arg.userID
|
||||
|
|
@ -557,7 +517,14 @@ func (r *userResource) Delete(
|
|||
// Delete existing record set
|
||||
err := r.client.DefaultAPI.DeleteUserRequest(ctx, arg.projectID, arg.region, arg.instanceID, userID).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err))
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) // nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok {
|
||||
if oapiErr.StatusCode == 404 {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
}
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("error from API: %v", err))
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
|
@ -581,30 +548,6 @@ func (r *userResource) Delete(
|
|||
tflog.Info(ctx, "Postgres Flex user deleted")
|
||||
}
|
||||
|
||||
// IdentitySchema defines the fields that are required to uniquely identify a resource.
|
||||
func (r *userResource) IdentitySchema(
|
||||
_ context.Context,
|
||||
_ resource.IdentitySchemaRequest,
|
||||
response *resource.IdentitySchemaResponse,
|
||||
) {
|
||||
response.IdentitySchema = identityschema.Schema{
|
||||
Attributes: map[string]identityschema.Attribute{
|
||||
"project_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"region": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"instance_id": identityschema.StringAttribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
"user_id": identityschema.Int64Attribute{
|
||||
RequiredForImport: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// clientArg holds the arguments for API calls.
|
||||
type clientArg struct {
|
||||
projectID string
|
||||
|
|
@ -622,112 +565,41 @@ func (r *userResource) ImportState(
|
|||
) {
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
if req.ID != "" {
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing user",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id],[user_id], got %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := strconv.ParseInt(idParts[3], 10, 64)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Error importing user",
|
||||
fmt.Sprintf("Invalid user_id format: %q. It must be a valid integer.", idParts[3]),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), userID)...)
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex user state imported")
|
||||
idParts := strings.Split(req.ID, core.Separator)
|
||||
|
||||
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Error importing user",
|
||||
fmt.Sprintf(
|
||||
"Expected import identifier with format [project_id],[region],[instance_id],[user_id], got %q",
|
||||
req.ID,
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// If no ID is provided, attempt to read identity attributes from the import configuration
|
||||
var identityData UserResourceIdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
userID, err := strconv.ParseInt(idParts[3], 10, 64)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Error importing user",
|
||||
fmt.Sprintf("Invalid user_id format: %q. It must be a valid integer.", idParts[3]),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
projectID := identityData.ProjectID.ValueString()
|
||||
region := identityData.Region.ValueString()
|
||||
instanceID := identityData.InstanceID.ValueString()
|
||||
userID := identityData.UserID.ValueInt64()
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectID)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceID)...)
|
||||
idString := utils.BuildInternalTerraformId(idParts...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idString)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), userID)...)
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex user state imported")
|
||||
}
|
||||
|
||||
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
|
||||
func (r *userResource) extractIdentityData(
|
||||
model resourceModel,
|
||||
identity UserResourceIdentityModel,
|
||||
) (*clientArg, error) {
|
||||
var projectID, region, instanceID string
|
||||
var userID int64
|
||||
if !model.UserId.IsNull() && !model.UserId.IsUnknown() {
|
||||
userID = model.UserId.ValueInt64()
|
||||
} else {
|
||||
if identity.UserID.IsNull() || identity.UserID.IsUnknown() {
|
||||
return nil, fmt.Errorf("user_id not found in config")
|
||||
}
|
||||
userID = identity.UserID.ValueInt64()
|
||||
}
|
||||
|
||||
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
|
||||
projectID = model.ProjectId.ValueString()
|
||||
} else {
|
||||
if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
|
||||
return nil, fmt.Errorf("project_id not found in config")
|
||||
}
|
||||
projectID = identity.ProjectID.ValueString()
|
||||
}
|
||||
|
||||
if !model.Region.IsNull() && !model.Region.IsUnknown() {
|
||||
region = r.providerData.GetRegionWithOverride(model.Region)
|
||||
} else {
|
||||
if identity.Region.IsNull() || identity.Region.IsUnknown() {
|
||||
return nil, fmt.Errorf("region not found in config")
|
||||
}
|
||||
region = r.providerData.GetRegionWithOverride(identity.Region)
|
||||
}
|
||||
|
||||
if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
|
||||
instanceID = model.InstanceId.ValueString()
|
||||
} else {
|
||||
if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() {
|
||||
return nil, fmt.Errorf("instance_id not found in config")
|
||||
}
|
||||
instanceID = identity.InstanceID.ValueString()
|
||||
}
|
||||
return &clientArg{
|
||||
projectID: projectID,
|
||||
instanceID: instanceID,
|
||||
region: region,
|
||||
userID: userID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setTFLogFields adds relevant fields to the context for terraform logging purposes.
|
||||
func (r *userResource) setTFLogFields(ctx context.Context, arg *clientArg) context.Context {
|
||||
ctx = tflog.SetField(ctx, "project_id", arg.projectID)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
func UserResourceSchema(ctx context.Context) schema.Schema {
|
||||
return schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.Int64Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The ID of the user.",
|
||||
MarkdownDescription: "The ID of the user.",
|
||||
|
|
@ -75,7 +75,7 @@ func UserResourceSchema(ctx context.Context) schema.Schema {
|
|||
}
|
||||
|
||||
type UserModel struct {
|
||||
Id types.Int64 `tfsdk:"id"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
InstanceId types.String `tfsdk:"instance_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ type APIClientDatabaseInterface interface {
|
|||
|
||||
// CreateInstanceWaitHandler will wait for instance creation
|
||||
func CreateInstanceWaitHandler(
|
||||
ctx context.Context, a APIClientInstanceInterface, projectId, region,
|
||||
instanceId string,
|
||||
ctx context.Context, a APIClientInstanceInterface, projectID, region,
|
||||
instanceID string,
|
||||
) *wait.AsyncActionHandler[v3alpha1api.GetInstanceResponse] {
|
||||
instanceCreated := false
|
||||
var instanceGetResponse *v3alpha1api.GetInstanceResponse
|
||||
|
|
@ -67,11 +67,11 @@ func CreateInstanceWaitHandler(
|
|||
handler := wait.New(
|
||||
func() (waitFinished bool, response *v3alpha1api.GetInstanceResponse, err error) {
|
||||
if !instanceCreated {
|
||||
s, err := a.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
s, getErr := a.GetInstanceRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if getErr != nil {
|
||||
return false, nil, getErr
|
||||
}
|
||||
if s == nil || s.Id != instanceId {
|
||||
if s == nil || s.Id != instanceID {
|
||||
return false, nil, nil
|
||||
}
|
||||
tflog.Debug(
|
||||
|
|
@ -81,7 +81,7 @@ func CreateInstanceWaitHandler(
|
|||
)
|
||||
switch s.Status {
|
||||
default:
|
||||
return true, s, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, s.Status)
|
||||
return true, s, fmt.Errorf("instance with id %s has unexpected status %s", instanceID, s.Status)
|
||||
case InstanceStateEmpty:
|
||||
return false, nil, nil
|
||||
case InstanceStatePending:
|
||||
|
|
@ -98,30 +98,15 @@ func CreateInstanceWaitHandler(
|
|||
"Wait handler still got status %s after %v for instance: %s",
|
||||
InstanceStateProgressing,
|
||||
maxWait,
|
||||
instanceId,
|
||||
instanceID,
|
||||
),
|
||||
)
|
||||
if extendedTimeout < 3 {
|
||||
maxWait += time.Minute * 5
|
||||
extendedTimeout++
|
||||
if *s.Network.AccessScope == "SNA" {
|
||||
ready := true
|
||||
if s.Network.InstanceAddress == nil {
|
||||
tflog.Warn(ctx, "Waiting for instance_address")
|
||||
ready = false
|
||||
}
|
||||
if s.Network.RouterAddress == nil {
|
||||
tflog.Warn(ctx, "Waiting for router_address")
|
||||
ready = false
|
||||
}
|
||||
if !ready {
|
||||
return false, nil, nil
|
||||
}
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
instanceCreated = true
|
||||
instanceGetResponse = s
|
||||
return false, nil, fmt.Errorf("instance after max timeout still in state %s", InstanceStateProgressing)
|
||||
case InstanceStateSuccess:
|
||||
if s.Network.AccessScope != nil && *s.Network.AccessScope == "SNA" {
|
||||
if s.Network.InstanceAddress == nil {
|
||||
|
|
@ -144,9 +129,9 @@ func CreateInstanceWaitHandler(
|
|||
},
|
||||
)
|
||||
var waitCounter int64 = 1
|
||||
maxWait := big.NewInt(7)
|
||||
n, err := rand.Int(rand.Reader, maxWait)
|
||||
if err == nil {
|
||||
maxWaitInt := big.NewInt(7)
|
||||
n, randErr := rand.Int(rand.Reader, maxWaitInt)
|
||||
if randErr == nil {
|
||||
waitCounter = n.Int64() + 1
|
||||
}
|
||||
time.Sleep(time.Duration(waitCounter*30) * time.Second) //nolint:gosec // not that important and temporary
|
||||
|
|
@ -154,7 +139,7 @@ func CreateInstanceWaitHandler(
|
|||
}
|
||||
return true, s, fmt.Errorf(
|
||||
"update got status FAILURE for instance with id %s after %d retries",
|
||||
instanceId,
|
||||
instanceID,
|
||||
failedCount,
|
||||
)
|
||||
// API responds with FAILURE for some seconds and then the instance goes to READY
|
||||
|
|
@ -165,7 +150,7 @@ func CreateInstanceWaitHandler(
|
|||
tflog.Info(ctx, "Waiting for instance (calling list users")
|
||||
// // User operations aren't available right after an instance is deemed successful
|
||||
// // To check if they are, perform a users request
|
||||
_, err = a.ListUsersRequest(ctx, projectId, region, instanceId).Execute()
|
||||
_, err = a.ListUsersRequest(ctx, projectID, region, instanceID).Execute()
|
||||
if err == nil {
|
||||
return true, instanceGetResponse, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue