From 68f9c56241a011809f571e2f47e4c81bdeb97587 Mon Sep 17 00:00:00 2001 From: Chris Gwilliams <517923+encima@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:56:48 +0200 Subject: [PATCH] feat(inspect): add role-configs command to list db roles with custom settings (#2818) * added role-configs command to list users with custom settings * Update internal/inspect/role_configs/role_configs.go --------- Co-authored-by: Han Qiao --- cmd/inspect.go | 10 ++++ internal/inspect/report_test.go | 5 +- internal/inspect/role_configs/role_configs.go | 46 +++++++++++++++++++ .../inspect/role_configs/role_configs.sql | 5 ++ .../inspect/role_configs/role_configs_test.go | 38 +++++++++++++++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 internal/inspect/role_configs/role_configs.go create mode 100644 internal/inspect/role_configs/role_configs.sql create mode 100644 internal/inspect/role_configs/role_configs_test.go diff --git a/cmd/inspect.go b/cmd/inspect.go index 299524fbe..2b55c3876 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -22,6 +22,7 @@ import ( "github.com/supabase/cli/internal/inspect/long_running_queries" "github.com/supabase/cli/internal/inspect/outliers" "github.com/supabase/cli/internal/inspect/replication_slots" + "github.com/supabase/cli/internal/inspect/role_configs" "github.com/supabase/cli/internal/inspect/role_connections" "github.com/supabase/cli/internal/inspect/seq_scans" "github.com/supabase/cli/internal/inspect/table_index_sizes" @@ -194,6 +195,14 @@ var ( }, } + inspectRoleConfigsCmd = &cobra.Command{ + Use: "role-configs", + Short: "Show configuration settings for database roles when they have been modified", + RunE: func(cmd *cobra.Command, args []string) error { + return role_configs.Run(cmd.Context(), flags.DbConfig, afero.NewOsFs()) + }, + } + inspectRoleConnectionsCmd = &cobra.Command{ Use: "role-connections", Short: "Show number of active connections for all database roles", @@ -247,6 +256,7 @@ func init() { inspectDBCmd.AddCommand(inspectTableRecordCountsCmd) inspectDBCmd.AddCommand(inspectBloatCmd) inspectDBCmd.AddCommand(inspectVacuumStatsCmd) + inspectDBCmd.AddCommand(inspectRoleConfigsCmd) inspectDBCmd.AddCommand(inspectRoleConnectionsCmd) inspectCmd.AddCommand(inspectDBCmd) reportCmd.Flags().StringVar(&outputDir, "output-dir", "", "Path to save CSV files in") diff --git a/internal/inspect/report_test.go b/internal/inspect/report_test.go index 4de1ab602..6b4220451 100644 --- a/internal/inspect/report_test.go +++ b/internal/inspect/report_test.go @@ -18,6 +18,7 @@ import ( "github.com/supabase/cli/internal/inspect/long_running_queries" "github.com/supabase/cli/internal/inspect/outliers" "github.com/supabase/cli/internal/inspect/replication_slots" + "github.com/supabase/cli/internal/inspect/role_configs" "github.com/supabase/cli/internal/inspect/role_connections" "github.com/supabase/cli/internal/inspect/seq_scans" "github.com/supabase/cli/internal/inspect/table_index_sizes" @@ -65,6 +66,8 @@ func TestReportCommand(t *testing.T) { Reply("COPY 0"). Query(wrapQuery(replication_slots.ReplicationSlotsQuery)). Reply("COPY 0"). + Query(wrapQuery(role_configs.RoleConfigsQuery)). + Reply("COPY 0"). Query(wrapQuery(role_connections.RoleConnectionsQuery)). Reply("COPY 0"). Query(wrapQuery(seq_scans.SeqScansQuery)). @@ -89,7 +92,7 @@ func TestReportCommand(t *testing.T) { assert.NoError(t, err) matches, err := afero.Glob(fsys, "*.csv") assert.NoError(t, err) - assert.Len(t, matches, 19) + assert.Len(t, matches, 20) }) } diff --git a/internal/inspect/role_configs/role_configs.go b/internal/inspect/role_configs/role_configs.go new file mode 100644 index 000000000..f4fb79382 --- /dev/null +++ b/internal/inspect/role_configs/role_configs.go @@ -0,0 +1,46 @@ +package role_configs + +import ( + "context" + _ "embed" + "fmt" + + "github.com/go-errors/errors" + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" + "github.com/spf13/afero" + "github.com/supabase/cli/internal/migration/list" + "github.com/supabase/cli/internal/utils" + "github.com/supabase/cli/pkg/pgxv5" +) + +//go:embed role_configs.sql +var RoleConfigsQuery string + +type Result struct { + Role_name string + Custom_config string +} + +func Run(ctx context.Context, config pgconn.Config, fsys afero.Fs, options ...func(*pgx.ConnConfig)) error { + conn, err := utils.ConnectByConfig(ctx, config, options...) + if err != nil { + return err + } + defer conn.Close(context.Background()) + rows, err := conn.Query(ctx, RoleConfigsQuery) + if err != nil { + return errors.Errorf("failed to query rows: %w", err) + } + result, err := pgxv5.CollectRows[Result](rows) + if err != nil { + return err + } + + table := "|Role name|Custom config|\n|-|-|\n" + for _, r := range result { + table += fmt.Sprintf("|`%s`|`%s`|\n", r.Role_name, r.Custom_config) + } + + return list.RenderTable(table) +} diff --git a/internal/inspect/role_configs/role_configs.sql b/internal/inspect/role_configs/role_configs.sql new file mode 100644 index 000000000..d568d6862 --- /dev/null +++ b/internal/inspect/role_configs/role_configs.sql @@ -0,0 +1,5 @@ +select + rolname as role_name, + array_to_string(rolconfig, ',', '*') as custom_config +from + pg_roles where rolconfig is not null; diff --git a/internal/inspect/role_configs/role_configs_test.go b/internal/inspect/role_configs/role_configs_test.go new file mode 100644 index 000000000..554a12526 --- /dev/null +++ b/internal/inspect/role_configs/role_configs_test.go @@ -0,0 +1,38 @@ +package role_configs + +import ( + "context" + "testing" + + "github.com/jackc/pgconn" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/supabase/cli/pkg/pgtest" +) + +var dbConfig = pgconn.Config{ + Host: "127.0.0.1", + Port: 5432, + User: "admin", + Password: "password", + Database: "postgres", +} + +func TestRoleCommand(t *testing.T) { + t.Run("inspects role connections", func(t *testing.T) { + // Setup in-memory fs + fsys := afero.NewMemMapFs() + // Setup mock postgres + conn := pgtest.NewConn() + defer conn.Close(t) + conn.Query(RoleConfigsQuery). + Reply("SELECT 1", Result{ + Role_name: "postgres", + Custom_config: "statement_timeout=3s", + }) + // Run test + err := Run(context.Background(), dbConfig, fsys, conn.Intercept) + // Check error + assert.NoError(t, err) + }) +}