mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
* fix(collector): Let pgx library parse TLS parameters This allows the collector to respect the sslmode parameters Fix: #1163 * Add comment * Improve postgres collector test
184 lines
5.2 KiB
Go
184 lines
5.2 KiB
Go
package collect
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/pkg/errors"
|
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
type CollectPostgres struct {
|
|
Collector *troubleshootv1beta2.Database
|
|
BundlePath string
|
|
Namespace string
|
|
ClientConfig *rest.Config
|
|
Client kubernetes.Interface
|
|
Context context.Context
|
|
RBACErrors
|
|
}
|
|
|
|
func (c *CollectPostgres) Title() string {
|
|
return getCollectorName(c)
|
|
}
|
|
|
|
func (c *CollectPostgres) IsExcluded() (bool, error) {
|
|
return isExcluded(c.Collector.Exclude)
|
|
}
|
|
|
|
func (c *CollectPostgres) createConnectConfig() (*pgx.ConnConfig, error) {
|
|
if c.Collector.URI == "" {
|
|
return nil, errors.New("postgres uri cannot be empty")
|
|
}
|
|
|
|
if c.Collector.TLS != nil {
|
|
klog.V(2).Infof("Connecting to postgres with TLS client config")
|
|
// Set the libpq TLS environment variables since pgx parses them to
|
|
// create the TLS configuration (tls.Config instance) to connect with
|
|
// https://www.postgresql.org/docs/current/libpq-envars.html
|
|
caCert, clientCert, clientKey, err := getTLSParamTriplet(c.Context, c.Client, c.Collector.TLS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Drop the TLS params to files and set the paths to their
|
|
// respective environment variables
|
|
// The environment variables are unset after the connection config
|
|
// is created. Their respective files are deleted as well.
|
|
tmpdir, err := os.MkdirTemp("", "ts-postgres-collector")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to create temp dir to store postgres collector TLS files")
|
|
}
|
|
defer os.RemoveAll(tmpdir)
|
|
|
|
if caCert != "" {
|
|
caCertPath := filepath.Join(tmpdir, "ca.crt")
|
|
err = os.WriteFile(caCertPath, []byte(caCert), 0644)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to write ca cert to file")
|
|
}
|
|
err = os.Setenv("PGSSLROOTCERT", caCertPath)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to set PGSSLROOTCERT environment variable")
|
|
}
|
|
klog.V(2).Infof("'PGSSLROOTCERT' environment variable set to %q", caCertPath)
|
|
defer os.Unsetenv("PGSSLROOTCERT")
|
|
}
|
|
|
|
if clientCert != "" {
|
|
clientCertPath := filepath.Join(tmpdir, "client.crt")
|
|
err = os.WriteFile(clientCertPath, []byte(clientCert), 0644)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to write client cert to file")
|
|
}
|
|
err = os.Setenv("PGSSLCERT", clientCertPath)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to set PGSSLCERT environment variable")
|
|
}
|
|
klog.V(2).Infof("'PGSSLCERT' environment variable set to %q", clientCertPath)
|
|
defer os.Unsetenv("PGSSLCERT")
|
|
}
|
|
|
|
if clientKey != "" {
|
|
clientKeyPath := filepath.Join(tmpdir, "client.key")
|
|
err = os.WriteFile(clientKeyPath, []byte(clientKey), 0600)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to write client key to file")
|
|
}
|
|
err = os.Setenv("PGSSLKEY", clientKeyPath)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to set PGSSLKEY environment variable")
|
|
}
|
|
klog.V(2).Infof("'PGSSLKEY' environment variable set to %q", clientKeyPath)
|
|
defer os.Unsetenv("PGSSLKEY")
|
|
}
|
|
}
|
|
|
|
cfg, err := pgx.ParseConfig(c.Collector.URI)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to parse postgres config")
|
|
}
|
|
klog.V(2).Infof("Successfully parsed postgres config")
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (c *CollectPostgres) connect() (*pgx.Conn, error) {
|
|
connCfg, err := c.createConnectConfig()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn, err := pgx.ConnectConfig(c.Context, connCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (c *CollectPostgres) Collect(progressChan chan<- interface{}) (CollectorResult, error) {
|
|
databaseConnection := DatabaseConnection{}
|
|
|
|
conn, err := c.connect()
|
|
if err != nil {
|
|
klog.V(2).Infof("Postgres connection error: %s", err.Error())
|
|
databaseConnection.Error = err.Error()
|
|
} else {
|
|
klog.V(2).Info("Successfully connected to postgres")
|
|
defer conn.Close(c.Context)
|
|
|
|
query := `select version()`
|
|
row := conn.QueryRow(c.Context, query)
|
|
version := ""
|
|
if err := row.Scan(&version); err != nil {
|
|
databaseConnection.Error = err.Error()
|
|
} else {
|
|
databaseConnection.IsConnected = true
|
|
|
|
postgresVersion, err := parsePostgresVersion(version)
|
|
if err != nil {
|
|
databaseConnection.Version = "Unknown"
|
|
databaseConnection.Error = err.Error()
|
|
} else {
|
|
databaseConnection.Version = postgresVersion
|
|
}
|
|
}
|
|
}
|
|
|
|
b, err := json.Marshal(databaseConnection)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to marshal database connection")
|
|
}
|
|
|
|
collectorName := c.Collector.CollectorName
|
|
if collectorName == "" {
|
|
collectorName = "postgres"
|
|
}
|
|
|
|
output := NewResult()
|
|
output.SaveResult(c.BundlePath, fmt.Sprintf("postgres/%s.json", collectorName), bytes.NewBuffer(b))
|
|
|
|
return output, nil
|
|
}
|
|
|
|
func parsePostgresVersion(postgresVersion string) (string, error) {
|
|
re := regexp.MustCompile("PostgreSQL ([0-9.]*)")
|
|
matches := re.FindStringSubmatch(postgresVersion)
|
|
if len(matches) < 2 {
|
|
return "", errors.Errorf("postgres version did not match regex: %q", postgresVersion)
|
|
}
|
|
|
|
return matches[1], nil
|
|
|
|
}
|