Documentation ¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var CheckPlatform = &s.Step{ ID: "check_platform", Description: "Check whether this platform is supported by pganalyze guided setup", Check: func(state *s.SetupState) (bool, error) { hostInfo, err := host.Info() if err != nil { return false, err } state.OperatingSystem = hostInfo.OS state.Platform = hostInfo.Platform state.PlatformFamily = hostInfo.PlatformFamily state.PlatformVersion = hostInfo.PlatformVersion platVerNum, err := strconv.ParseFloat(state.PlatformVersion, 32) if err != nil { return false, fmt.Errorf("could not parse current platform version: %s / version %s", state.Platform, state.PlatformVersion) } if state.Platform == "ubuntu" { if platVerNum < 14.04 { return false, errors.New("Ubuntu versions older than 14.04 are not supported") } } else if state.Platform == "debian" { if platVerNum < 10.0 { return false, errors.New("Debian versions older than 10 are not supported") } } else { return false, fmt.Errorf("the current platform (%s) is not currently supported; please contact support", state.Platform) } return true, nil }, }
View Source
var CheckPostgresVersion = &s.Step{ ID: "check_postgres_version", Description: "Check whether this Postgres version is supported by pganalyze guided setup", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow("SELECT current_setting('server_version'), current_setting('server_version_num')::integer") if err != nil { return false, err } state.PGVersionStr = row.GetString(0) state.PGVersionNum = row.GetInt(1) if state.PGVersionNum < 100000 { return false, fmt.Errorf("not supported for Postgres versions older than 10; found %s", state.PGVersionStr) } return true, nil }, }
View Source
var CheckReplicationStatus = &s.Step{ ID: "check_replication_status", Description: "Check whether the database is a replica, which is currently unsupported by pganalyze guided setup", Check: func(state *s.SetupState) (bool, error) { result, err := state.QueryRunner.QueryRow("SELECT pg_is_in_recovery()") if err != nil { return false, err } isInRecovery := result.GetBool(0) if isInRecovery { return false, errors.New("Postgres server is a replica; this is currently not supported") } return true, nil }, }
View Source
var CheckRestartNeeded = &s.Step{ ID: "check_restart_needed", Description: "Check whether a Postgres restart will be necessary in a future step to install the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow( `SELECT current_setting('shared_preload_libraries') LIKE '%pg_stat_statements%', current_setting('shared_preload_libraries') LIKE '%auto_explain%'`, ) if err != nil { return false, err } hasPgss := row.GetBool(0) hasAutoExplain := row.GetBool(1) if !hasPgss { state.Log( ` NOTICE: A Postgres restart will be required to set up query performance monitoring. A prompt will ask to confirm the restart before this guided setup performs it. `, ) } else if !hasAutoExplain { state.Log( ` NOTICE: A Postgres restart will not be required to set up query performance monitoring. However, a restart *will* be required for the recommended setup of the Automated EXPLAIN feature. You can still use the alternative log-based setup to explore the feature without having to restart Postgres. `, ) } else { state.Log( ` NOTICE: A Postgres restart will *not* be required to set up any features. Your system is ready to configure query performance monitoring, Log Insights, and Automated EXPLAIN. `, ) } if state.Inputs.Scripted { return true, nil } var doSetup bool err = survey.AskOne(&survey.Confirm{ Message: "Continue with setup?", Default: hasPgss && hasAutoExplain, }, &doSetup) if err != nil { return false, err } if !doSetup { return false, errors.New("setup aborted") } return true, nil }, }
View Source
var ConfigureLogMinDurationStatement = &s.Step{ ID: "li_ensure_supported_log_min_duration_statement", Kind: state.LogInsightsStep, Description: "Ensure the log_min_duration_statement setting in Postgres is supported by the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_min_duration_statement'`) if err != nil { return false, err } lmdsVal := row.GetInt(0) needsUpdate := !isSupportedLmds(lmdsVal) || (state.Inputs.Scripted && state.Inputs.GUCS.LogMinDurationStatement.Valid && int(state.Inputs.GUCS.LogMinDurationStatement.Int64) != lmdsVal) return !needsUpdate, nil }, Run: func(state *s.SetupState) error { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_min_duration_statement'`) if err != nil { return err } oldVal := fmt.Sprintf("%sms", row.GetString(0)) var newVal string if state.Inputs.Scripted { if !state.Inputs.GUCS.LogMinDurationStatement.Valid { return errors.New("log_min_duration_statement not provided and current value is unsupported") } newValNum := int(state.Inputs.GUCS.LogMinDurationStatement.Int64) if !isSupportedLmds(newValNum) { return fmt.Errorf("log_min_duration_statement provided as unsupported value '%d'", newValNum) } newVal = strconv.Itoa(newValNum) } else { err = survey.AskOne(&survey.Input{ Message: fmt.Sprintf( "Setting 'log_min_duration_statement' is set to '%s', below supported threshold of 10ms; enter supported value in ms or -1 to disable (will be saved to Postgres):", oldVal, ), }, &newVal, survey.WithValidator(util.ValidateLogMinDurationStatement)) if err != nil { return err } } return util.ApplyConfigSetting("log_min_duration_statement", newVal, state.QueryRunner) }, }
View Source
var ConfirmAutoExplainAvailable = &s.Step{ Kind: s.AutomatedExplainStep, ID: "aemod_check_auto_explain_available", Description: "Confirm the auto_explain contrib module is available", Check: func(state *s.SetupState) (bool, error) { logExplain, err := util.UsingLogExplain(state.CurrentSection) if err != nil || logExplain { return logExplain, err } err = state.QueryRunner.Exec("LOAD 'auto_explain'") if err != nil { if strings.Contains(err.Error(), "No such file or directory") { return false, nil } return false, err } return true, err }, Run: func(state *s.SetupState) error { return errors.New("contrib module auto_explain is not available") }, }
View Source
var ConfirmAutomatedExplainMode = &s.Step{ Kind: s.AutomatedExplainStep, ID: "ae_confirm_automated_explain_mode", Description: "Confirm whether to implement Automated EXPLAIN via the recommended auto_explain module or the alternative log-based EXPLAIN", Check: func(state *s.SetupState) (bool, error) { return state.CurrentSection.HasKey("enable_log_explain"), nil }, Run: func(state *s.SetupState) error { var useLogBased bool if state.Inputs.Scripted { if !state.Inputs.UseLogBasedExplain.Valid { return errors.New("use_log_based_explain not set") } useLogBased = state.Inputs.UseLogBasedExplain.Bool } else { var optIdx int err := survey.AskOne(&survey.Select{ Message: "Select automated EXPLAIN mechanism to use (will be saved to collector config):", Help: "Learn more about the options at https://pganalyze.com/docs/explain/setup", Options: []string{"auto_explain (recommended)", "Log-based EXPLAIN"}, }, &optIdx) if err != nil { return err } useLogBased = optIdx == 1 } _, err := state.CurrentSection.NewKey("enable_log_explain", strconv.FormatBool(useLogBased)) if err != nil { return err } return state.SaveConfig() }, }
View Source
var ConfirmEmitTestExplain = &s.Step{ Kind: s.AutomatedExplainStep, ID: "ae_confirm_emit_test_explain", Description: "Invoke the collector EXPLAIN test to generate an EXPLAIN plan based on pg_sleep", Check: func(state *s.SetupState) (bool, error) { return state.DidTestExplainCommand || state.Inputs.Scripted && (!state.Inputs.ConfirmRunTestExplainCommand.Valid || !state.Inputs.ConfirmRunTestExplainCommand.Bool), nil }, Run: func(state *s.SetupState) error { var doTestCommand bool if state.Inputs.Scripted { doTestCommand = state.Inputs.ConfirmRunTestExplainCommand.Valid && state.Inputs.ConfirmRunTestExplainCommand.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Issue pg_sleep statement on server to test EXPLAIN configuration", Help: "Learn more about pg_sleep here: https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-DELAY", Default: false, }, &doTestCommand) if err != nil { return err } state.Inputs.ConfirmRunTestExplainCommand = null.BoolFrom(doTestCommand) } if !doTestCommand { return nil } state.Log("") args := []string{"--test-explain", fmt.Sprintf("--config=%s", state.ConfigFilename)} cmd := exec.Command("pganalyze-collector", args...) var stdOut bytes.Buffer cmd.Stdout = &stdOut cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { addlInfo := err.Error() stdOutStr := stdOut.String() if stdOutStr != "" { addlInfo = addlInfo + "\n" + stdOutStr } return fmt.Errorf("test explain command failed: %s", addlInfo) } state.Log("") state.DidTestExplainCommand = true return nil }, }
View Source
var ConfirmPgssAvailable = &s.Step{ ID: "check_pgss_available", Description: "Confirm the pg_stat_statements extension is ready to be installed", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow( fmt.Sprintf( "SELECT true FROM pg_available_extensions WHERE name = 'pg_stat_statements'", ), ) if err == query.ErrNoRows { return false, nil } else if err != nil { return false, err } return row.GetBool(0), nil }, Run: func(state *s.SetupState) error { return errors.New("contrib extension pg_stat_statements is not available") }, }
View Source
var ConfirmRestartPostgres = &s.Step{ ID: "confirm_restart_postgres", Description: "Confirm whether Postgres should be restarted to have pending configuration changes take effect", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow("SELECT COUNT(*) FROM pg_settings WHERE pending_restart;") if err != nil { return false, err } return row.GetInt(0) == 0, nil }, Run: func(state *s.SetupState) error { rows, err := state.QueryRunner.Query("SELECT name FROM pg_settings WHERE pending_restart") if err != nil { return err } var pendingSettings []string for _, row := range rows { pendingSettings = append(pendingSettings, row.GetString(0)) } pendingList := util.JoinWithAnd(pendingSettings) var restartNow bool if state.Inputs.Scripted { if !state.Inputs.ConfirmPostgresRestart.Valid || !state.Inputs.ConfirmPostgresRestart.Bool { return fmt.Errorf("confirm_postgres_restart flag not set but Postgres restart required for settings %s", pendingList) } restartNow = state.Inputs.ConfirmPostgresRestart.Bool } else { err = survey.AskOne(&survey.Confirm{ Message: fmt.Sprintf("WARNING: Postgres must be restarted for changes to %s to take effect; restart Postgres now?", pendingList), Default: false, }, &restartNow) if err != nil { return err } if !restartNow { return nil } err = survey.AskOne(&survey.Confirm{ Message: "WARNING: Your database will be restarted. Are you sure?", Default: false, }, &restartNow) if err != nil { return err } } if !restartNow { return nil } return service.RestartPostgres(state) }, }
View Source
var ConfirmRunTestCommand = &s.Step{ ID: "confirm_run_test_command", Description: "Invoke the collector self-test to verify the installation", Check: func(state *s.SetupState) (bool, error) { return state.DidTestCommand || state.Inputs.Scripted && (!state.Inputs.ConfirmRunTestCommand.Valid || !state.Inputs.ConfirmRunTestCommand.Bool), nil }, Run: func(state *s.SetupState) error { var doTestCommand bool if state.Inputs.Scripted { doTestCommand = state.Inputs.ConfirmRunTestCommand.Valid && state.Inputs.ConfirmRunTestCommand.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "The collector is now ready to begin monitoring. Run test command and reload collector configuration if successful?", Default: false, }, &doTestCommand) if err != nil { return err } state.Inputs.ConfirmRunTestCommand = null.BoolFrom(doTestCommand) } if !doTestCommand { return nil } state.Log("") args := []string{"--test", "--reload", fmt.Sprintf("--config=%s", state.ConfigFilename)} extraArgsStr := os.Getenv("PGA_SETUP_COLLECTOR_TEST_EXTRA_ARGS") if extraArgsStr != "" { extraArgs := strings.Split(extraArgsStr, " ") args = append(args, extraArgs...) } cmd := exec.Command("pganalyze-collector", args...) var stdOut bytes.Buffer cmd.Stdout = &stdOut cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { addlInfo := err.Error() stdOutStr := stdOut.String() if stdOutStr != "" { addlInfo = addlInfo + "\n" + stdOutStr } return fmt.Errorf("test command failed: %s", addlInfo) } state.Log("") state.DidTestCommand = true return nil }, }
View Source
var ConfirmSetUpAutoExplain = &s.Step{ ID: "li_confirm_set_up_auto_explain", Kind: state.LogInsightsStep, Description: "Confirm whether to set up the optional Automated EXPLAIN feature", Check: func(state *s.SetupState) (bool, error) { if state.Inputs.ConfirmSetUpAutomatedExplain.Valid { return true, nil } if !state.CurrentSection.HasKey("enable_log_explain") { return false, nil } isLogExplainKey, err := state.CurrentSection.GetKey("enable_log_explain") if err != nil { return false, err } isLogExplain, err := isLogExplainKey.Bool() if err != nil { return false, err } if isLogExplain { return true, nil } spl, err := util.GetPendingSharedPreloadLibraries(state.QueryRunner) if err != nil { return false, err } return strings.Contains(spl, "auto_explain"), nil }, Run: func(state *s.SetupState) error { if state.Inputs.Scripted { return errors.New("skip_auto_explain value must be specified") } state.Log(` Log Insights and query performance setup is almost complete. You can complete it now, or proceed to configuring the optional Automated EXPLAIN feature. Automated EXPLAIN will require either setting up the auto_explain module (recommended) or creating helper functions in all monitored databases. The auto_explain module has minimal impact on most query workloads with our recommended settings; we will review these during setup. Learn more at https://pganalyze.com/postgres-explain `) var setUpExplain bool err := survey.AskOne(&survey.Confirm{ Message: "Proceed to configuring optional Automated EXPLAIN feature?", Default: false, }, &setUpExplain) if err != nil { return err } state.Inputs.ConfirmSetUpAutomatedExplain = null.BoolFrom(setUpExplain) return nil }, }
View Source
var ConfirmSetUpLogInsights = &s.Step{ ID: "confirm_set_up_log_insights", Description: "Confirm whether to set up the optional Log Insights feature", Check: func(state *s.SetupState) (bool, error) { return state.Inputs.ConfirmSetUpLogInsights.Valid || state.PGAnalyzeSection.HasKey("db_log_location"), nil }, Run: func(state *s.SetupState) error { if state.Inputs.Scripted { return errors.New("skip_log_insights value must be specified") } state.Log(` Basic setup is almost complete. You can complete it now, or proceed to configuring the optional Log Insights feature. Log Insights will require specifying your database log file (we may be able to detect this), and may require changes to some logging-related settings. Setting up Log Insights is required for the Automated EXPLAIN feature. Learn more at https://pganalyze.com/log-insights `) var setUpLogInsights bool err := survey.AskOne(&survey.Confirm{ Message: "Proceed to configuring optional Log Insights feature?", Default: false, }, &setUpLogInsights) if err != nil { return err } state.Inputs.ConfirmSetUpLogInsights = null.BoolFrom(setUpLogInsights) return nil }, }
View Source
var ConfirmSuperuserConnection = &s.Step{ ID: "confirm_superuser_connection", Description: "Confirm the Postgres superuser connection to use only for this guided setup session", Check: func(state *s.SetupState) (bool, error) { if state.QueryRunner == nil { return false, nil } err := state.QueryRunner.PingSuper() return err == nil, err }, Run: func(state *s.SetupState) error { localPgs, err := discoverLocalPgFromUnixSockets() if err != nil { return err } var selectedPg LocalPostgres if len(localPgs) == 0 { return errors.New("failed to find a running local Postgres install") } else if len(localPgs) > 1 { return errors.New("found multiple local Postgres installs; this is not supported for guided setup") } else { selectedPg = localPgs[0] } if state.Inputs.Scripted { if selectedPg.Port != 0 { if (state.Inputs.PGSetupConnPort.Valid && int(state.Inputs.PGSetupConnPort.Int64) != selectedPg.Port) || (state.Inputs.PGSetupConnSocketDir.Valid && state.Inputs.PGSetupConnSocketDir.String != selectedPg.SocketDir) { selectedPg = LocalPostgres{} } } else { if !state.Inputs.PGSetupConnPort.Valid { return errors.New("no port specified for setup Postgres connection") } for _, pg := range localPgs { if int(state.Inputs.PGSetupConnPort.Int64) == pg.Port && (!state.Inputs.PGSetupConnSocketDir.Valid || state.Inputs.PGSetupConnSocketDir.String == pg.SocketDir) { selectedPg = pg break } } } if selectedPg.Port == 0 { var portStr string if state.Inputs.PGSetupConnPort.Valid { portStr = " on " + strconv.Itoa(int(state.Inputs.PGSetupConnPort.Int64)) } var socketDirStr string if state.Inputs.PGSetupConnSocketDir.Valid { socketDirStr = " in " + state.Inputs.PGSetupConnSocketDir.String } return fmt.Errorf("no Postgres server found listening%s%s", portStr, socketDirStr) } } else { if selectedPg.Port == 0 { var opts []string for _, localPg := range localPgs { opts = append(opts, fmt.Sprintf("port %d in socket dir %s", localPg.Port, localPg.SocketDir)) } var selectedIdx int err := survey.AskOne(&survey.Select{ Message: "Found several Postgres installations; please select one", Options: opts, }, &selectedIdx) if err != nil { return err } selectedPg = localPgs[selectedIdx] } } var pgSuperuser string if state.Inputs.Scripted { if !state.Inputs.PGSetupConnUser.Valid { return errors.New("no user specified for setup Postgres connection") } pgSuperuser = state.Inputs.PGSetupConnUser.String } else { err = survey.AskOne(&survey.Select{ Message: "Select Postgres superuser to connect as for initial setup:", Help: "We will create a separate, restricted monitoring user for the collector later", Options: []string{"postgres", "another user..."}, }, &pgSuperuser) if err != nil { return err } if pgSuperuser != "postgres" { err = survey.AskOne(&survey.Input{ Message: "Enter Postgres superuser to connect as for initial setup:", Help: "We will create a separate, restricted monitoring user for the collector later", }, &pgSuperuser, survey.WithValidator(survey.Required)) if err != nil { return err } } } state.QueryRunner = query.NewRunner(pgSuperuser, selectedPg.SocketDir, selectedPg.Port) return nil }, }
View Source
var EnablePgssInSpl = &s.Step{ ID: "ensure_pgss_in_spl", Description: "Ensure the pg_stat_statements extension is included in the shared_preload_libraries setting in Postgres", Check: func(state *s.SetupState) (bool, error) { spl, err := util.GetPendingSharedPreloadLibraries(state.QueryRunner) if err != nil { return false, err } return strings.Contains(spl, "pg_stat_statements"), nil }, Run: func(state *s.SetupState) error { var doAdd bool if state.Inputs.Scripted { if !state.Inputs.EnsurePgStatStatementsLoaded.Valid || !state.Inputs.EnsurePgStatStatementsLoaded.Bool { return errors.New("enable_pg_stat_statements flag not set but pg_stat_statements not in shared_preload_libraries") } doAdd = state.Inputs.EnsurePgStatStatementsLoaded.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Add pg_stat_statements to shared_preload_libraries (will be saved to Postgres--requires restart in a later step)?", Default: false, Help: "Postgres will have to be restarted in a later step to apply this configuration change; learn more about shared_preload_libraries here: https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-SHARED-PRELOAD-LIBRARIES", }, &doAdd) if err != nil { return err } } if !doAdd { return nil } existingSpl, err := util.GetPendingSharedPreloadLibraries(state.QueryRunner) if err != nil { return err } var newSpl string if existingSpl == "" { newSpl = "pg_stat_statements" } else { newSpl = existingSpl + ",pg_stat_statements" } return util.ApplyConfigSetting("shared_preload_libraries", newSpl, state.QueryRunner) }, }
View Source
var EnsureAutoExplainInSpl = &s.Step{ Kind: s.AutomatedExplainStep, ID: "aemod_ensure_auto_explain_in_spl", Description: "Ensure the auto_explain module is included in the shared_preload_libraries setting in Postgres", Check: func(state *s.SetupState) (bool, error) { logExplain, err := util.UsingLogExplain(state.CurrentSection) if err != nil || logExplain { return logExplain, err } spl, err := util.GetPendingSharedPreloadLibraries(state.QueryRunner) if err != nil { return false, err } return strings.Contains(spl, "auto_explain"), nil }, Run: func(state *s.SetupState) error { var doAdd bool if state.Inputs.Scripted { if !state.Inputs.EnsureAutoExplainLoaded.Valid || !state.Inputs.EnsureAutoExplainLoaded.Bool { return errors.New("enable_auto_explain flag not set but auto_explain configuration selected") } doAdd = state.Inputs.EnsureAutoExplainLoaded.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Add auto_explain to shared_preload_libraries (will be saved to Postgres--requires restart in a later step)?", Default: false, Help: "Postgres will have to be restarted in a later step to apply this configuration change; learn more about Automated EXPLAIN at https://pganalyze.com/postgres-explain", }, &doAdd) if err != nil { return err } } if !doAdd { return nil } existingSpl, err := util.GetPendingSharedPreloadLibraries(state.QueryRunner) if err != nil { return err } var newSpl string if existingSpl == "" { newSpl = "auto_explain" } else { newSpl = existingSpl + ",auto_explain" } return util.ApplyConfigSetting("shared_preload_libraries", newSpl, state.QueryRunner) }, }
View Source
var EnsureLogExplainHelpers = &s.Step{ Kind: s.AutomatedExplainStep, ID: "aelog_ensure_log_explain_helpers", Description: "Ensure EXPLAIN helper functions for log-based EXPLAIN exist in all monitored Postgres databases", Check: func(state *s.SetupState) (bool, error) { logExplain, err := util.UsingLogExplain(state.CurrentSection) if err != nil { return false, err } if !logExplain { return true, nil } monitoredDBs, err := getMonitoredDBs(state) if err != nil { return false, err } for _, db := range monitoredDBs { dbRunner := state.QueryRunner.InDB(db) isValid, err := util.ValidateHelperFunction(util.ExplainHelper, dbRunner) if !isValid || err != nil { return isValid, err } } return true, nil }, Run: func(state *s.SetupState) error { var doCreate bool if state.Inputs.Scripted { if !state.Inputs.EnsureLogExplainHelpers.Valid || !state.Inputs.EnsureLogExplainHelpers.Bool { return errors.New("create_explain_helper flag not set and helper function does not exist or does not match expected signature on all monitored databases") } doCreate = state.Inputs.EnsureLogExplainHelpers.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Create (or update) EXPLAIN helper function in each monitored database (will be saved to Postgres)?", Default: false, }, &doCreate) if err != nil { return err } } if !doCreate { return nil } monitoredDBs, err := getMonitoredDBs(state) if err != nil { return err } for _, db := range monitoredDBs { err := createHelperInDB(state, db) if err != nil { return err } } return nil }, }
View Source
var EnsureMonitoringUser = &s.Step{ ID: "ensure_monitoring_user", Description: "Ensure the monitoring user (db_user in the collector config file) exists in Postgres", Check: func(state *s.SetupState) (bool, error) { pgaUserKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return false, err } pgaUser := pgaUserKey.String() var result query.Row result, err = state.QueryRunner.QueryRow(fmt.Sprintf("SELECT true FROM pg_user WHERE usename = %s", pq.QuoteLiteral(pgaUser))) if err == query.ErrNoRows { return false, nil } else if err != nil { return false, err } return result.GetBool(0), nil }, Run: func(state *s.SetupState) error { pgaUserKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return err } pgaUser := pgaUserKey.String() var doCreateUser bool if state.Inputs.Scripted { if !state.Inputs.EnsureMonitoringUser.Valid || !state.Inputs.EnsureMonitoringUser.Bool { return fmt.Errorf("create_monitoring_user flag not set and specified monitoring user %s does not exist", pgaUser) } doCreateUser = state.Inputs.EnsureMonitoringUser.Bool } else { err = survey.AskOne(&survey.Confirm{ Message: fmt.Sprintf("User %s does not exist in Postgres; create user (will be saved to Postgres)?", pgaUser), Help: "If you skip this step, create the user manually before proceeding", Default: false, }, &doCreateUser) if err != nil { return err } } if !doCreateUser { return nil } return state.QueryRunner.Exec( fmt.Sprintf( "CREATE USER %s CONNECTION LIMIT 5", pq.QuoteIdentifier(pgaUser), ), ) }, }
View Source
var EnsureMonitoringUserPassword = &s.Step{ ID: "ensure_monitoring_user_password", Description: "Ensure the monitoring user password in Postgres matches db_password in the collector config file", Check: func(state *s.SetupState) (bool, error) { cfg, err := config.Read( &mainUtil.Logger{Destination: log.New(os.Stderr, "", 0)}, state.ConfigFilename, ) if err != nil { return false, err } if len(cfg.Servers) != 1 { return false, fmt.Errorf("expected one server in config; found %d", len(cfg.Servers)) } serverCfg := cfg.Servers[0] pqStr, err := serverCfg.GetPqOpenString("", "") if err != nil { return false, err } conn, err := sql.Open("postgres", pqStr) err = conn.Ping() if err != nil { isAuthErr := strings.Contains(err.Error(), "authentication failed") if isAuthErr { return false, nil } return false, err } return true, nil }, Run: func(state *s.SetupState) error { pgaUserKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return err } pgaUser := pgaUserKey.String() pgaPasswdKey, err := state.CurrentSection.GetKey("db_password") if err != nil { return err } pgaPasswd := pgaPasswdKey.String() var doPasswdUpdate bool if state.Inputs.Scripted { if !state.Inputs.EnsureMonitoringPassword.Valid { return errors.New("update_monitoring_password flag not set and cannot log in with current credentials") } doPasswdUpdate = state.Inputs.EnsureMonitoringPassword.Bool } else { err = survey.AskOne(&survey.Confirm{ Message: fmt.Sprintf("Update password for user %s with configured value (will be saved to Postgres)?", pgaUser), Help: "If you skip this step, ensure the password matches before proceeding", }, &doPasswdUpdate) if err != nil { return err } } if !doPasswdUpdate { return nil } err = state.QueryRunner.Exec( fmt.Sprintf( "SET log_statement = none; ALTER USER %s WITH ENCRYPTED PASSWORD %s", pq.QuoteIdentifier(pgaUser), pq.QuoteLiteral(pgaPasswd), ), ) return err }, }
View Source
var EnsureMonitoringUserPermissions = &s.Step{ ID: "ensure_monitoring_user_permissions", Description: "Ensure the monitoring user has sufficient permissions in Postgres for access to queries and monitoring metadata", Check: func(state *s.SetupState) (bool, error) { pgaUserKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return false, err } pgaUser := pgaUserKey.String() row, err := state.QueryRunner.QueryRow( fmt.Sprintf( "SELECT usesuper OR pg_has_role(usename, 'pg_monitor', 'usage') FROM pg_user WHERE usename = %s", pq.QuoteLiteral(pgaUser), ), ) if err == query.ErrNoRows { return false, nil } else if err != nil { return false, err } return row.GetBool(0), nil }, Run: func(state *s.SetupState) error { pgaUserKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return err } pgaUser := pgaUserKey.String() var doGrant bool if state.Inputs.Scripted { if !state.Inputs.EnsureMonitoringPermissions.Valid || !state.Inputs.EnsureMonitoringPermissions.Bool { return errors.New("set_up_monitoring_user flag not set and monitoring user does not have adequate permissions") } doGrant = state.Inputs.EnsureMonitoringPermissions.Bool } else { err = survey.AskOne(&survey.Confirm{ Message: fmt.Sprintf("Grant role pg_monitor to user %s (will be saved to Postgres)?", pgaUser), Help: "Learn more about pg_monitor here: https://www.postgresql.org/docs/current/default-roles.html", }, &doGrant) if err != nil { return err } } if !doGrant { return nil } return state.QueryRunner.Exec( fmt.Sprintf( "GRANT pg_monitor to %s", pq.QuoteIdentifier(pgaUser), ), ) }, }
View Source
var EnsurePganalyzeSchema = &s.Step{ ID: "ensure_pganalyze_schema", Description: "Ensure the pganalyze schema exists and db_user in the collector config file has USAGE privilege on it", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow("SELECT COUNT(*) FROM pg_namespace WHERE nspname = 'pganalyze'") if err != nil { return false, err } count := row.GetInt(0) if count != 1 { return false, nil } userKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return false, err } pgaUser := userKey.String() row, err = state.QueryRunner.QueryRow(fmt.Sprintf("SELECT has_schema_privilege(%s, 'pganalyze', 'USAGE')", pq.QuoteLiteral(pgaUser))) if err != nil { return false, err } hasUsage := row.GetBool(0) if !hasUsage { return false, nil } return true, nil }, Run: func(state *s.SetupState) error { var doSetup bool if state.Inputs.Scripted { if !state.Inputs.EnsureHelperFunctions.Valid || !state.Inputs.EnsureHelperFunctions.Bool { return errors.New("create_helper_functions flag not set and pganalyze schema or helper functions do not exist") } doSetup = state.Inputs.EnsureHelperFunctions.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Create pganalyze schema and helper functions (will be saved to Postgres)?", Default: false, Help: "These helper functions allow the collector to monitor database statistics without being able to read your data; learn more here: https://github.com/pganalyze/collector/#setting-up-a-restricted-monitoring-user", }, &doSetup) if err != nil { return err } } if !doSetup { return nil } userKey, err := state.CurrentSection.GetKey("db_username") if err != nil { return err } pgaUser := userKey.String() return state.QueryRunner.Exec( fmt.Sprintf( `CREATE SCHEMA IF NOT EXISTS pganalyze; GRANT USAGE ON SCHEMA pganalyze TO %s;`, pq.QuoteIdentifier(pgaUser), ), ) }, }
View Source
var EnsurePgssExtInstalled = &s.Step{ ID: "ensure_pgss_ext_installed", Description: "Ensure the pg_stat_statements extension is installed in Postgres", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow( fmt.Sprintf( "SELECT extnamespace::regnamespace::text FROM pg_extension WHERE extname = 'pg_stat_statements'", ), ) if err == query.ErrNoRows { return false, nil } else if err != nil { return false, err } extNsp := row.GetString(0) if extNsp != "public" { return false, fmt.Errorf("pg_stat_statements is installed, but in unsupported schema %s; must be installed in 'public'", extNsp) } return true, nil }, Run: func(state *s.SetupState) error { var doCreate bool if state.Inputs.Scripted { if !state.Inputs.EnsurePgStatStatementsInstalled.Valid || !state.Inputs.EnsurePgStatStatementsInstalled.Bool { return errors.New("create_pg_stat_statements flag not set and pg_stat_statements does not exist in primary database") } doCreate = state.Inputs.EnsurePgStatStatementsInstalled.Bool } else { err := survey.AskOne(&survey.Confirm{ Message: "Create extension pg_stat_statements in public schema for query performance monitoring (will be saved to Postgres)?", Default: false, Help: "Learn more about pg_stat_statements here: https://www.postgresql.org/docs/current/pgstatstatements.html", }, &doCreate) if err != nil { return err } } if !doCreate { return nil } return state.QueryRunner.Exec("CREATE EXTENSION pg_stat_statements SCHEMA public") }, }
View Source
var EnsureRecommendedAutoExplainSettings = &s.Step{ Kind: s.AutomatedExplainStep, ID: "aemod_ensure_recommended_settings", Description: "Ensure auto_explain settings in Postgres are configured as recommended, if desired", Check: func(state *s.SetupState) (bool, error) { if state.DidAutoExplainRecommendedSettings || (state.Inputs.EnsureAutoExplainRecommendedSettings.Valid && !state.Inputs.EnsureAutoExplainRecommendedSettings.Bool) { return true, nil } logExplain, err := util.UsingLogExplain(state.CurrentSection) if err != nil || logExplain { return logExplain, err } autoExplainGucsQuery := getAutoExplainGUCSQuery(state) rows, err := state.QueryRunner.Query( autoExplainGucsQuery, ) if err != nil { return false, fmt.Errorf("error checking existing settings: %s", err) } return len(rows) == 0, nil }, Run: func(state *s.SetupState) error { var doReview bool if state.Inputs.Scripted { if state.Inputs.EnsureAutoExplainRecommendedSettings.Valid { doReview = state.Inputs.EnsureAutoExplainRecommendedSettings.Bool } } else { err := survey.AskOne(&survey.Confirm{ Message: "Review auto_explain configuration settings?", Default: false, Help: "Optional, but will ensure best balance of monitoring visibility and performance; review these settings at https://pganalyze.com/docs/explain/setup/auto_explain", }, &doReview) if err != nil { return err } state.Inputs.EnsureAutoExplainRecommendedSettings = null.BoolFrom(doReview) } if !doReview { return nil } autoExplainGucsQuery := getAutoExplainGUCSQuery(state) rows, err := state.QueryRunner.Query( autoExplainGucsQuery, ) if err != nil { return fmt.Errorf("error checking existing settings: %s", err) } if len(rows) == 0 { state.Log("all auto_explain configuration settings using recommended values") state.DidAutoExplainRecommendedSettings = true return nil } settingsToReview := make(map[string]string) for _, row := range rows { settingsToReview[row.GetString(0)] = row.GetString(1) } if currValue, ok := settingsToReview["auto_explain.log_timing"]; ok { logTiming, err := getLogTimingValue(state, currValue) if err != nil { return err } if logTiming != currValue { err = util.ApplyConfigSetting("auto_explain.log_timing", logTiming, state.QueryRunner) if err != nil { return err } } } if currValue, ok := settingsToReview["auto_explain.log_analyze"]; ok { logAnalyze, err := getLogAnalyzeValue(state, currValue) if err != nil { return err } if logAnalyze != currValue { err = util.ApplyConfigSetting("auto_explain.log_analyze", logAnalyze, state.QueryRunner) if err != nil { return err } } } row, err := state.QueryRunner.QueryRow("SHOW auto_explain.log_analyze") if err != nil { return err } isLogAnalyzeOn := row.GetString(0) == "on" if isLogAnalyzeOn { if currValue, ok := settingsToReview["auto_explain.log_buffers"]; ok { logBuffers, err := getLogBuffersValue(state, currValue) if err != nil { return err } if logBuffers != currValue { err = util.ApplyConfigSetting("auto_explain.log_buffers", logBuffers, state.QueryRunner) if err != nil { return err } } } if currValue, ok := settingsToReview["auto_explain.log_triggers"]; ok { logTriggers, err := getLogTriggersValue(state, currValue) if err != nil { return err } if logTriggers != currValue { err = util.ApplyConfigSetting("auto_explain.log_triggers", logTriggers, state.QueryRunner) if err != nil { return err } } } if currValue, ok := settingsToReview["auto_explain.log_verbose"]; ok { logVerbose, err := getLogVerboseValue(state, currValue) if err != nil { return err } if logVerbose != currValue { err = util.ApplyConfigSetting("auto_explain.log_verbose", logVerbose, state.QueryRunner) if err != nil { return err } } } } if currValue, ok := settingsToReview["auto_explain.log_format"]; ok { logFormat, err := getLogFormatValue(state, currValue) if err != nil { return err } if logFormat != currValue { err = util.ApplyConfigSetting("auto_explain.log_format", logFormat, state.QueryRunner) if err != nil { return err } } } if currValue, ok := settingsToReview["auto_explain.log_min_duration"]; ok { logMinDuration, err := getLogMinDurationValue(state, currValue) if err != nil { return err } if logMinDuration != currValue { err = util.ApplyConfigSetting("auto_explain.log_min_duration", logMinDuration, state.QueryRunner) if err != nil { return err } } } if currValue, ok := settingsToReview["auto_explain.log_nested_statements"]; ok { logNested, err := getLogNestedStatements(state, currValue) if err != nil { return err } if logNested != currValue { err = util.ApplyConfigSetting("auto_explain.log_nested_statements", logNested, state.QueryRunner) if err != nil { return err } } } state.DidAutoExplainRecommendedSettings = true return nil }, }
N.B.: this needs to happen *after* the Postgres restart so that ALTER SYSTEM recognizes these as valid configuration settings
View Source
var EnsureSupportedLogDuration = &s.Step{ ID: "li_ensure_supported_log_duration", Kind: state.LogInsightsStep, Description: "Ensure the log_duration setting in Postgres is supported by the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_duration'`) if err != nil { return false, err } currValue := row.GetString(0) needsUpdate := currValue == "on" || (state.Inputs.Scripted && state.Inputs.GUCS.LogDuration.Valid && state.Inputs.GUCS.LogDuration.String != currValue) return !needsUpdate, nil }, Run: func(state *s.SetupState) error { var turnOffLogDuration bool if state.Inputs.Scripted { if !state.Inputs.GUCS.LogDuration.Valid { return errors.New("log_duration value not provided and current value not supported") } if state.Inputs.GUCS.LogDuration.String == "on" { return errors.New("log_duration provided as unsupported value 'on'") } turnOffLogDuration = state.Inputs.GUCS.LogDuration.String == "off" } else { err := survey.AskOne(&survey.Confirm{ Message: "Setting 'log_duration' is set to unsupported value 'on'; set to 'off' (will be saved to Postgres)?", Default: false, }, &turnOffLogDuration) if err != nil { return err } } if !turnOffLogDuration { return nil } return util.ApplyConfigSetting("log_duration", "off", state.QueryRunner) }, }
View Source
var EnsureSupportedLogErrorVerbosity = &s.Step{ ID: "li_ensure_supported_log_error_verbosity", Kind: state.LogInsightsStep, Description: "Ensure the log_error_verbosity setting in Postgres is supported by the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_error_verbosity'`) if err != nil { return false, err } currVal := row.GetString(0) needsUpdate := currVal == "verbose" || (state.Inputs.Scripted && state.Inputs.GUCS.LogErrorVerbosity.Valid && currVal != state.Inputs.GUCS.LogErrorVerbosity.String) return !needsUpdate, nil }, Run: func(state *s.SetupState) error { var newVal string if state.Inputs.Scripted { if !state.Inputs.GUCS.LogErrorVerbosity.Valid { return errors.New("log_error_verbosity value not provided and current value not supported") } if state.Inputs.GUCS.LogErrorVerbosity.String == "verbose" { return errors.New("log_error_verbosity provided as unsupported value 'verbose'") } newVal = state.Inputs.GUCS.LogErrorVerbosity.String } else { err := survey.AskOne(&survey.Select{ Message: "Setting 'log_error_verbosity' is set to unsupported value 'verbose'; select supported value (will be saved to Postgres):", Options: []string{"terse", "default"}, }, &newVal) if err != nil { return err } } return util.ApplyConfigSetting("log_error_verbosity", newVal, state.QueryRunner) }, }
View Source
var EnsureSupportedLogLinePrefix = &s.Step{ ID: "li_ensure_supported_log_line_prefix", Kind: s.LogInsightsStep, Description: "Ensure the log_line_prefix setting in Postgres is supported by the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_line_prefix'`) if err != nil { return false, err } currValue := row.GetString(0) hasDb := strings.Contains(currValue, "%d") hasUser := strings.Contains(currValue, "%u") hasTs := strings.Contains(currValue, "%m") || strings.Contains(currValue, "%n") || strings.Contains(currValue, "%t") supported := hasDb && hasUser && hasTs needsUpdate := !supported || (state.Inputs.Scripted && state.Inputs.GUCS.LogLinePrefix.Valid && currValue != state.Inputs.GUCS.LogLinePrefix.String) return !needsUpdate, nil }, Run: func(state *s.SetupState) error { var selectedPrefix string if state.Inputs.Scripted { if !state.Inputs.GUCS.LogLinePrefix.Valid { return errors.New("log_line_prefix not provided and current setting is not supported") } selectedPrefix = state.Inputs.GUCS.LogLinePrefix.String } else { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_line_prefix'`) if err != nil { return err } oldVal := row.GetString(0) err = survey.AskOne(&survey.Input{ Message: fmt.Sprintf("Setting 'log_line_prefix' (%s) is missing user (%%u), database (%%d), or timestamp (%%n, %%m, or %%t); set to (will be saved to Postgres):", oldVal), Suggest: func(toComplete string) []string { if toComplete == "" { return []string{logs.LogPrefixRecommended} } return []string{} }, Help: "Check format specifier reference in Postgres documentation: https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-LINE-PREFIX", }, &selectedPrefix) if err != nil { return err } } return util.ApplyConfigSetting("log_line_prefix", pq.QuoteLiteral(selectedPrefix), state.QueryRunner) }, }
View Source
var EnsureSupportedLogStatement = &s.Step{ ID: "li_ensure_supported_log_statement", Kind: state.LogInsightsStep, Description: "Ensure the log_statement setting in Postgres is supported by the collector", Check: func(state *s.SetupState) (bool, error) { row, err := state.QueryRunner.QueryRow(`SELECT setting FROM pg_settings WHERE name = 'log_statement'`) if err != nil { return false, err } currValue := row.GetString(0) needsUpdate := currValue == "all" || (state.Inputs.Scripted && state.Inputs.GUCS.LogStatement.Valid && currValue != state.Inputs.GUCS.LogStatement.String) return !needsUpdate, nil }, Run: func(state *s.SetupState) error { var newVal string if state.Inputs.Scripted { if !state.Inputs.GUCS.LogStatement.Valid { return errors.New("log_statement value not provided and current value not supported") } if state.Inputs.GUCS.LogStatement.String == "all" { return errors.New("log_statement provided as unsupported value 'all'") } newVal = state.Inputs.GUCS.LogStatement.String } else { err := survey.AskOne(&survey.Select{ Message: "Setting 'log_statement' is set to unsupported value 'all'; select supported value (will be saved to Postgres):", Options: []string{"none", "ddl", "mod"}, }, &newVal) if err != nil { return err } } return util.ApplyConfigSetting("log_statement", newVal, state.QueryRunner) }, }
View Source
var SpecifyAPIKey = &s.Step{ ID: "specify_api_key", Description: "Specify the pganalyze API key (api_key) in the collector config file", Check: func(state *s.SetupState) (bool, error) { return state.PGAnalyzeSection.HasKey("api_key"), nil }, Run: func(state *s.SetupState) error { var apiKey string var apiBaseURL string if state.Inputs.Settings.APIKey.Valid { apiKey = state.Inputs.Settings.APIKey.String } if state.Inputs.Settings.APIBaseURL.Valid { apiBaseURL = state.Inputs.Settings.APIBaseURL.String } var configWriteConfirmed bool if state.Inputs.Scripted { if apiKey != "" { configWriteConfirmed = true } else { return errors.New("no api_key setting specified") } } else if apiKey == "" { err := survey.AskOne(&survey.Input{ Message: "Please enter API key (will be saved to collector config):", Help: "The key can be found on the API keys page for your organization in the pganalyze app", }, &apiKey, survey.WithValidator(survey.Required)) if err != nil { return err } configWriteConfirmed = true } else { err := survey.AskOne(&survey.Confirm{ Message: "Save pganalyze API key to collector config?", Default: false, }, &configWriteConfirmed) if err != nil { return err } } if !configWriteConfirmed { return nil } _, err := state.PGAnalyzeSection.NewKey("api_key", apiKey) if err != nil { return err } if apiBaseURL != "" { _, err := state.PGAnalyzeSection.NewKey("api_base_url", apiBaseURL) if err != nil { return err } } return state.SaveConfig() }, }
View Source
var SpecifyDatabases = &s.Step{ ID: "specify_databases", Description: "Specify database(s) to monitor (db_name) in the collector config file", Check: func(state *s.SetupState) (bool, error) { hasDb := state.CurrentSection.HasKey("db_name") if !hasDb { return false, nil } key, err := state.CurrentSection.GetKey("db_name") if err != nil { return false, err } dbs := key.Strings(",") if len(dbs) == 0 || dbs[0] == "" { return false, nil } db := dbs[0] state.QueryRunner.Database = db return true, nil }, Run: func(state *s.SetupState) error { rows, err := state.QueryRunner.Query("SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate") if err != nil { return err } var dbOpts []string for _, row := range rows { dbOpts = append(dbOpts, row.GetString(0)) } var dbNames []string if state.Inputs.Scripted { if !state.Inputs.Settings.DBName.Valid { return errors.New("no db_name setting specified") } dbNameInputs := strings.Split(state.Inputs.Settings.DBName.String, ",") for i, dbNameInput := range dbNameInputs { trimmed := strings.TrimSpace(dbNameInput) if trimmed == "*" { dbNames = append(dbNames, trimmed) } else { for _, opt := range dbOpts { if trimmed == opt { dbNames = append(dbNames, trimmed) break } } } if len(dbNames) != i+1 { return fmt.Errorf("database %s configured for db_name but not found in Postgres", trimmed) } } } else { var primaryDb string err = survey.AskOne(&survey.Select{ Message: "Choose a primary database to monitor (will be saved to collector config):", Options: dbOpts, Help: "The collector will connect to this database for monitoring; others can be added next", }, &primaryDb) if err != nil { return err } dbNames = append(dbNames, primaryDb) if len(dbOpts) == 1 { var monitorAll bool err = survey.AskOne(&survey.Confirm{ Message: "Monitor all other databases created in the future (will be saved to collector config)?", Default: true, }, &monitorAll) if err != nil { return err } if monitorAll { dbNames = append(dbNames, "*") } } else if len(dbOpts) > 1 { var otherDbs []string for _, db := range dbOpts { if db == primaryDb { continue } otherDbs = append(otherDbs, db) } var othersOptIdx int err = survey.AskOne(&survey.Select{ Message: "Monitor other databases (will be saved to collector config)?", Help: "The 'all' option will also automatically monitor all future databases created on this server", Options: []string{"all other databases (including future ones)", "no other databases", "select databases..."}, }, &othersOptIdx) if err != nil { return err } if othersOptIdx == 0 { dbNames = append(dbNames, "*") } else if othersOptIdx == 1 { } else if othersOptIdx == 2 { var otherDbsSelected []string err = survey.AskOne(&survey.MultiSelect{ Message: "Select other databases to monitor (will be saved to collector config):", Options: otherDbs, }, &otherDbsSelected) if err != nil { return err } dbNames = append(dbNames, otherDbsSelected...) } else { panic(fmt.Sprintf("unexpected other databases selection: %d", othersOptIdx)) } } } dbNamesStr := strings.Join(dbNames, ",") _, err = state.CurrentSection.NewKey("db_name", dbNamesStr) if err != nil { return err } return state.SaveConfig() }, }
View Source
var SpecifyDbLogLocation = &s.Step{ ID: "li_specify_db_log_location", Kind: state.LogInsightsStep, Description: "Specify the location of Postgres log files (db_log_location) in the collector config file", Check: func(state *s.SetupState) (bool, error) { return state.CurrentSection.HasKey("db_log_location"), nil }, Run: func(state *s.SetupState) error { var logLocation string if state.Inputs.Scripted { loc, err := getLogLocationScripted(state) if err != nil { return err } logLocation = loc } else { loc, err := getLogLocationInteractive(state) if err != nil { return err } logLocation = loc } _, err := state.CurrentSection.NewKey("db_log_location", logLocation) if err != nil { return err } return state.SaveConfig() }, }
View Source
var SpecifyMonitoringUser = &s.Step{ ID: "specify_monitoring_user", Description: "Specify the monitoring user to connect as (db_username) in the collector config file", Check: func(state *s.SetupState) (bool, error) { hasUser := state.CurrentSection.HasKey("db_username") return hasUser, nil }, Run: func(state *s.SetupState) error { var pgaUser string if state.Inputs.Scripted { if !state.Inputs.Settings.DBUsername.Valid { return errors.New("no db_username setting specified") } pgaUser = state.Inputs.Settings.DBUsername.String } else { var monitoringUserIdx int err := survey.AskOne(&survey.Select{ Message: "Select Postgres user for the collector to use (will be saved to collector config):", Help: "If the user does not exist, it can be created in a later step", Options: []string{"pganalyze (recommended)", "a different user"}, }, &monitoringUserIdx) if err != nil { return err } if monitoringUserIdx == 0 { pgaUser = "pganalyze" } else if monitoringUserIdx == 1 { err := survey.AskOne(&survey.Input{ Message: "Enter Postgres user for the collector to use (will be saved to collector config):", Help: "If the user does not exist, it can be created in a later step", }, &pgaUser, survey.WithValidator(survey.Required)) if err != nil { return err } } else { panic(fmt.Sprintf("unexpected user selection: %d", monitoringUserIdx)) } } _, err := state.CurrentSection.NewKey("db_username", pgaUser) if err != nil { return err } return state.SaveConfig() }, }
View Source
var SpecifyMonitoringUserPasswd = &s.Step{ ID: "specify_monitoring_user_password", Description: "Specify monitoring user password (db_password) in the collector config file", Check: func(state *s.SetupState) (bool, error) { return state.CurrentSection.HasKey("db_password"), nil }, Run: func(state *s.SetupState) error { var passwordStrategy int if state.Inputs.Scripted { if state.Inputs.GenerateMonitoringPassword.Valid && state.Inputs.GenerateMonitoringPassword.Bool { if state.Inputs.Settings.DBPassword.Valid && state.Inputs.Settings.DBPassword.String != "" { return errors.New("cannot specify both generate password and set explicit password") } passwordStrategy = 0 } else if state.Inputs.Settings.DBPassword.Valid && state.Inputs.Settings.DBPassword.String != "" { passwordStrategy = 1 } else { return errors.New("no db_password specified and generate_monitoring_password flag not set") } } else { err := survey.AskOne(&survey.Select{ Message: "Select how to set up the collector user password (will be saved to collector config):", Options: []string{"generate random password (recommended)", "enter password"}, }, &passwordStrategy) if err != nil { return err } } var pgaPasswd string if passwordStrategy == 0 { passwdBytes := make([]byte, 16) rand.Read(passwdBytes) pgaPasswd = hex.EncodeToString(passwdBytes) } else if passwordStrategy == 1 { if state.Inputs.Scripted { pgaPasswd = state.Inputs.Settings.DBPassword.String } else { err := survey.AskOne(&survey.Input{ Message: "Enter password for the collector to use (will be saved to collector config):", }, &pgaPasswd, survey.WithValidator(survey.Required)) if err != nil { return err } } } else { panic(fmt.Sprintf("unexpected password option selection: %d", passwordStrategy)) } _, err := state.CurrentSection.NewKey("db_password", pgaPasswd) if err != nil { return err } return state.SaveConfig() }, }
Functions ¶
This section is empty.
Types ¶
type LocalPostgres ¶
Source Files ¶
- ae_confirm_automated_explain_mode.go
- ae_confirm_emit_test_explain.go
- aelog_ensure_log_explain_helpers.go
- aemod_check_auto_explain_available.go
- aemod_ensure_auto_explain_in_spl.go
- aemod_ensure_recommended_settings.go
- check_pgss_available.go
- check_platform.go
- check_postgres_version.go
- check_replication_status.go
- check_restart_needed.go
- confirm_restart_postgres.go
- confirm_run_test_command.go
- confirm_set_up_log_insights.go
- confirm_superuser_connection.go
- ensure_monitoring_user.go
- ensure_monitoring_user_password.go
- ensure_monitoring_user_permissions.go
- ensure_pganalyze_schema.go
- ensure_pgss_ext_installed.go
- ensure_pgss_in_spl.go
- li_confirm_set_up_auto_explain.go
- li_ensure_supported_log_duration.go
- li_ensure_supported_log_error_verbosity.go
- li_ensure_supported_log_line_prefix.go
- li_ensure_supported_log_min_duration_statement.go
- li_ensure_supported_log_statement.go
- li_specify_db_log_location.go
- specify_api_key.go
- specify_databases.go
- specify_monitoring_user.go
- specify_monitoring_user_password.go
Click to show internal directories.
Click to hide internal directories.