Skip to content

Commit

Permalink
fix(pg_user): creating in bulk occasionally results in a 404 error
Browse files Browse the repository at this point in the history
  • Loading branch information
byashimov committed Jan 9, 2025
1 parent ccdf60d commit 6680f3c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 93 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ nav_order: 1
- Replaced `aiven-go-client/v2` with `aiven/go-client-codegen` in `account_team` resource/data source
- Replaced `aiven-go-client/v2` with `aiven/go-client-codegen` in `account_team_member` resource/data source
- Replaced `aiven-go-client/v2` with `aiven/go-client-codegen` in `account_team_project` resource/data source
- Fix `aiven_pg_user` creating in bulk occasionally results in a 404 error

## [4.31.1] - 2024-12-23

Expand Down
130 changes: 65 additions & 65 deletions internal/sdkprovider/service/pg/pg_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ package pg

import (
"context"
"fmt"

"github.com/aiven/aiven-go-client/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
avngen "github.com/aiven/go-client-codegen"
"github.com/aiven/go-client-codegen/handler/service"
"github.com/avast/retry-go"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/aiven/terraform-provider-aiven/internal/common"
"github.com/aiven/terraform-provider-aiven/internal/schemautil"
"github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig"
)

var aivenPGUserSchema = map[string]*schema.Schema{
var SchemaResourcePGUser = map[string]*schema.Schema{
"project": schemautil.CommonSchemaProjectReference,
"service_name": schemautil.CommonSchemaServiceNameReference,

"username": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -59,124 +61,122 @@ var aivenPGUserSchema = map[string]*schema.Schema{
func ResourcePGUser() *schema.Resource {
return &schema.Resource{
Description: "Creates and manages an Aiven for PostgreSQL® service user.",
CreateContext: resourcePGUserCreate,
UpdateContext: resourcePGUserUpdate,
ReadContext: resourcePGUserRead,
DeleteContext: schemautil.ResourceServiceUserDelete,
CreateContext: common.WithGenClient(CreateResourcePGUser),
UpdateContext: common.WithGenClient(UpdateResourcePGUser),
ReadContext: common.WithGenClient(ReadResourcePGUser),
DeleteContext: common.WithGenClient(schemautil.DeleteResourceServiceUser),
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: schemautil.DefaultResourceTimeouts(),

Schema: aivenPGUserSchema,
Schema: SchemaResourcePGUser,
}
}

func resourcePGUserCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*aiven.Client)

func CreateResourcePGUser(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
projectName := d.Get("project").(string)
serviceName := d.Get("service_name").(string)

// Validates that the service is an Pg service
pg, err := client.Services.Get(ctx, projectName, serviceName)
if err != nil {
return diag.FromErr(err)
}

if pg.Type != schemautil.ServiceTypePG {
return diag.Errorf("expected service type %q, got %q", schemautil.ServiceTypePG, pg.Type)
}

username := d.Get("username").(string)
allowReplication := d.Get("pg_allow_replication").(bool)
_, err = client.ServiceUsers.Create(
_, err := client.ServiceUserCreate(
ctx,
projectName,
serviceName,
aiven.CreateServiceUserRequest{
&service.ServiceUserCreateIn{
Username: username,
AccessControl: &aiven.AccessControl{
PostgresAllowReplication: &allowReplication,
AccessControl: &service.AccessControlIn{
PgAllowReplication: &allowReplication,
},
},
)
if err != nil {
return diag.FromErr(err)
return err
}

if _, ok := d.GetOk("password"); ok {
_, err := client.ServiceUsers.Update(ctx, projectName, serviceName, username,
aiven.ModifyServiceUserRequest{
_, err = client.ServiceUserCredentialsModify(
ctx, projectName, serviceName, username,
&service.ServiceUserCredentialsModifyIn{
Operation: service.ServiceUserCredentialsModifyOperationTypeResetCredentials,
NewPassword: schemautil.OptionalStringPointer(d, "password"),
})
},
)
if err != nil {
return diag.FromErr(err)
return fmt.Errorf("error setting password: %w", err)
}
}

d.SetId(schemautil.BuildResourceID(projectName, serviceName, username))

return resourcePGUserRead(ctx, d, m)
return RetryReadResourcePGUser(ctx, d, client)
}

func resourcePGUserUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*aiven.Client)

func UpdateResourcePGUser(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
projectName, serviceName, username, err := schemautil.SplitResourceID3(d.Id())
if err != nil {
return diag.FromErr(err)
return err
}

_, err = client.ServiceUsers.Update(ctx, projectName, serviceName, username,
aiven.ModifyServiceUserRequest{
_, err = client.ServiceUserCredentialsModify(
ctx, projectName, serviceName, username,
&service.ServiceUserCredentialsModifyIn{
Operation: service.ServiceUserCredentialsModifyOperationTypeResetCredentials,
NewPassword: schemautil.OptionalStringPointer(d, "password"),
})
},
)

if err != nil {
return diag.FromErr(err)
return err
}

if d.HasChange("pg_allow_replication") {
allowReplication := d.Get("pg_allow_replication").(bool)

op := "set-access-control"

_, err = client.ServiceUsers.Update(ctx, projectName, serviceName, username,
aiven.ModifyServiceUserRequest{
AccessControl: &aiven.AccessControl{
PostgresAllowReplication: &allowReplication,
},
Operation: &op,
})
req := &service.ServiceUserCredentialsModifyIn{
Operation: service.ServiceUserCredentialsModifyOperationTypeSetAccessControl,
AccessControl: &service.AccessControlIn{
PgAllowReplication: &allowReplication,
},
}
_, err = client.ServiceUserCredentialsModify(ctx, projectName, serviceName, username, req)
if err != nil {
return diag.FromErr(err)
return fmt.Errorf("error updating credentials: %w", err)
}
}

return resourcePGUserRead(ctx, d, m)
return RetryReadResourcePGUser(ctx, d, client)
}

func resourcePGUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*aiven.Client)

func ReadResourcePGUser(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
projectName, serviceName, username, err := schemautil.SplitResourceID3(d.Id())
if err != nil {
return diag.FromErr(err)
return err
}

user, err := client.ServiceUsers.Get(ctx, projectName, serviceName, username)
user, err := client.ServiceUserGet(ctx, projectName, serviceName, username)
if err != nil {
return diag.FromErr(schemautil.ResourceReadHandleNotFound(err, d))
return schemautil.ResourceReadHandleNotFound(err, d)
}

err = schemautil.CopyServiceUserPropertiesFromAPIResponseToTerraform(d, user, projectName, serviceName)
err = schemautil.ResourceDataSet(SchemaResourcePGUser, d, user)
if err != nil {
return diag.FromErr(err)
return err
}

if err := d.Set("pg_allow_replication", user.AccessControl.PostgresAllowReplication); err != nil {
return diag.FromErr(err)
if user.AccessControl != nil && user.AccessControl.PgAllowReplication != nil {
err = d.Set("pg_allow_replication", *user.AccessControl.PgAllowReplication)
if err != nil {
return err
}
}

return nil
}

func RetryReadResourcePGUser(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
return retry.Do(
func() error {
return ReadResourcePGUser(ctx, d, client)
},
retry.Context(ctx),
retry.RetryIf(avngen.IsNotFound),
)
}
31 changes: 10 additions & 21 deletions internal/sdkprovider/service/pg/pg_user_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,30 @@ package pg
import (
"context"

"github.com/aiven/aiven-go-client/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
avngen "github.com/aiven/go-client-codegen"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/aiven/terraform-provider-aiven/internal/common"
"github.com/aiven/terraform-provider-aiven/internal/schemautil"
)

func DatasourcePGUser() *schema.Resource {
return &schema.Resource{
ReadContext: datasourcePGUserRead,
ReadContext: common.WithGenClient(ReadDatasourcePGUser),
Description: "Gets information about an Aiven for PostgreSQL® service user.",
Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenPGUserSchema,
"project", "service_name", "username"),
Schema: SchemaDatasourcePGUser(),
}
}

func datasourcePGUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
client := m.(*aiven.Client)
func SchemaDatasourcePGUser() map[string]*schema.Schema {
return schemautil.ResourceSchemaAsDatasourceSchema(SchemaResourcePGUser, "project", "service_name", "username")
}

func ReadDatasourcePGUser(ctx context.Context, d *schema.ResourceData, client avngen.Client) error {
projectName := d.Get("project").(string)
serviceName := d.Get("service_name").(string)
userName := d.Get("username").(string)

list, err := client.ServiceUsers.List(ctx, projectName, serviceName)
if err != nil {
return diag.FromErr(err)
}

for _, u := range list {
if u.Username == userName {
d.SetId(schemautil.BuildResourceID(projectName, serviceName, userName))
return resourcePGUserRead(ctx, d, m)
}
}

return diag.Errorf("pg user %s/%s/%s not found",
projectName, serviceName, userName)
d.SetId(schemautil.BuildResourceID(projectName, serviceName, userName))
return ReadResourcePGUser(ctx, d, client)
}
16 changes: 9 additions & 7 deletions internal/sdkprovider/service/pg/pg_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

func TestAccAivenPGUser_basic(t *testing.T) {
resourceName := "aiven_pg_user.foo"
resourceName := "aiven_pg_user.foo.0" // checking the first user only
rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acc.TestAccPreCheck(t) },
Expand All @@ -30,8 +30,8 @@ func TestAccAivenPGUser_basic(t *testing.T) {
schemautil.TestAccCheckAivenServiceUserAttributes("data.aiven_pg_user.user"),
resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)),
resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")),
resource.TestCheckResourceAttr(resourceName, "username", fmt.Sprintf("user-%s", rName)),
resource.TestCheckResourceAttr(resourceName, "password", "Test$1234"),
resource.TestCheckResourceAttr(resourceName, "username", "user-1"),
resource.TestCheckResourceAttr(resourceName, "password", "P4$$word"),
),
},
},
Expand Down Expand Up @@ -234,6 +234,7 @@ data "aiven_pg_user" "user" {
}`, os.Getenv("AIVEN_PROJECT_NAME"), name, name)
}

// testAccPGUserNewPasswordResource creates 100 users to test bulk creation
func testAccPGUserNewPasswordResource(name string) string {
return fmt.Sprintf(`
data "aiven_project" "foo" {
Expand All @@ -250,21 +251,22 @@ resource "aiven_pg" "bar" {
}
resource "aiven_pg_user" "foo" {
count = 42
service_name = aiven_pg.bar.service_name
project = data.aiven_project.foo.project
username = "user-%s"
password = "Test$1234"
username = "user-${count.index + 1}"
password = "P4$$word"
depends_on = [aiven_pg.bar]
}
data "aiven_pg_user" "user" {
service_name = aiven_pg.bar.service_name
project = aiven_pg.bar.project
username = aiven_pg_user.foo.username
username = aiven_pg_user.foo.0.username
depends_on = [aiven_pg_user.foo]
}`, os.Getenv("AIVEN_PROJECT_NAME"), name, name)
}`, os.Getenv("AIVEN_PROJECT_NAME"), name)
}

func testAccPGUserNoPasswordResource(name string) string {
Expand Down

0 comments on commit 6680f3c

Please sign in to comment.