Skip to content

[CMCSMACD-4695] Add a function for syncing users and roles#10

Merged
hundt-corbalt merged 3 commits intomainfrom
hundt-users
Sep 25, 2025
Merged

[CMCSMACD-4695] Add a function for syncing users and roles#10
hundt-corbalt merged 3 commits intomainfrom
hundt-users

Conversation

@hundt-corbalt
Copy link
Contributor

This will be used instead of migrations for making sure that a database has the right users and roles.

Testing: pulled in to dso-metrics repo and added a call to the migrate binary. Verified that it appeared to add users and fix roles as necessary.

PR Checklist

  • New automated tests have been written to the extent possible.
  • The code has been checked for structural/syntactic validity.
    • AMI/application: a build was performed
    • terraform changes: "terraform plan" checked on every affected environment
  • (If applicable) the code has been manually tested on our infrastructure.
    • AMI/application: deployed an a test or dev environment
    • terraform changes: applied to test or dev environment
    • script: run against test or dev environment
  • Likely failure points and new functionality have been identified and tested manually.
    Examples:
    • Application manually run in a way that triggers any new branches
    • AMI logged into and changes verified from login shell
  • Pull request description includes a description of all the manual steps performed to accomplish the above.

To provide feedback on this template, visit https://docs.google.com/document/d/1YfTv7Amyop5G_8w1c2GJ_Mu-70L0KkZHhm9f9umDi3U/edit

This will be used instead of migrations for making sure that a
database has the right users and roles.
Copy link

@jonah-corbalt jonah-corbalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I left a few comments.

return fmt.Errorf("Failed to set password for user %q: %w", user.Username, err)
}
} else {
_, err = tx.Exec(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here we will also need to grant the rds_iam role per docs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the caller will pass rds_iam as one of the GrantRoles when they want to use RDS IAM auth

Copy link
Contributor

@ben-harvey ben-harvey Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's how it looks on my branch. UsePasswordAuth is false if we detect the Lambda runtime

func ensureUsersAndMigrate(usePasswordAuth bool) error {
	users := []migrations.PostgreSQLUser{
		{
			Username: "reader",
			GrantRoles: []string{
				postgresReadAllRole,
			},
		},
		{
			Username: "writer",
			GrantRoles: []string{
				postgresReadAllRole,
				postgresWriteAllRole,
			},
		},
	}

	// When using RDS IAM auth instead of passwords, users also need the rds_iam role
	if !usePasswordAuth {
		for i := range users {
			users[i].GrantRoles = append(users[i].GrantRoles, rdsIAMRole)
		}
	}

	err := migrations.EnsureUsersWithRoles(db, users, usePasswordAuth)
	if err != nil {
		return fmt.Errorf("error ensuring users with roles: %w", err)
	}
	return migrations.Migrate(db, schema.AllMigrations())
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay, so that logic is in the caller — makes sense.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, on second thought, maybe we do want to include that in the function since everyone will want it and it simplifies the caller.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would work too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check it out now and let me know what you all think.

if err != nil {
return fmt.Errorf("Error starting transaction: %w", err)
}
committed := false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the committed flag? This example suggests doing tx.Rollback in all cases since it will be a noop if the transaction has been committed. If we want to print the error from tx.Rollback, we could check that it's not sql.ErrTxDone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. The API docs don't really make abundantly clear that this is a good idea, but since this section of the docs suggests it I guess it's fine! Done.

}()

for _, user := range users {
createUserSQL := fmt.Sprintf(`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to create optimistically and catch an exception (static, less interpolation):

createUserSQL := fmt.Sprintf(`
  DO $$
  BEGIN
    CREATE USER %s;
  EXCEPTION
    WHEN duplicate_object THEN
      NULL;
  END
  $$;
`, pq.QuoteIdentifier(user.Username),
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do kind of like that, but also reading the docs about exception handling makes me a little nervous. They recommend not doing it if you can avoid it (although I think just for performance reasons?), and there are some semantics regarding transactions that I don't think would matter here but I'm not 100% confident. I'm inclined to leave it as is.

@jonahb jonahb assigned hundt-corbalt and unassigned jonahb Sep 25, 2025
Copy link
Contributor Author

@hundt-corbalt hundt-corbalt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reviews!

if err != nil {
return fmt.Errorf("Error starting transaction: %w", err)
}
committed := false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. The API docs don't really make abundantly clear that this is a good idea, but since this section of the docs suggests it I guess it's fine! Done.

}()

for _, user := range users {
createUserSQL := fmt.Sprintf(`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do kind of like that, but also reading the docs about exception handling makes me a little nervous. They recommend not doing it if you can avoid it (although I think just for performance reasons?), and there are some semantics regarding transactions that I don't think would matter here but I'm not 100% confident. I'm inclined to leave it as is.

@hundt-corbalt hundt-corbalt merged commit 43c603a into main Sep 25, 2025
1 check passed
@hundt-corbalt hundt-corbalt deleted the hundt-users branch September 25, 2025 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants