Files
Zoltán Bedi 1e383b0c1e Postgres: Switch the datasource plugin from lib/pq to pgx (#103961)
* Create libpqToPGX feature toggle

* Refactor PostgreSQL datasource to support PGX with feature toggle

- Updated `ProvideService` to accept feature toggles for enabling PGX.
- Modified integration tests to use the new PGX connection method.
- Introduced new functions for handling PGX connections and queries.
- Enhanced TLS configuration handling for PostgreSQL connections.
- Updated existing tests to ensure compatibility with PGX and new connection methods.

* Update PostgreSQL datasource to enhance connection pooling and error handling

- Increased `MaxOpenConns` to 10 in integration tests for improved connection management.
- Refactored connection handling in `newPostgresPGX` to return a connection pool instead of a single connection.
- Updated health check error handling to utilize context and feature toggles for better error reporting.
- Adjusted `DisposePGX` method to close the connection pool properly.
- Enhanced query execution to acquire connections from the pool, ensuring efficient resource usage.

* Cleanup

* Revert postgres_test unnecessary changes

* Rename feature toggle from `libpqToPGX` to `postgresDSUsePGX`

* Add null check to dispose method

* Fix lint issues

* Refactor connection string generation

* Address comment in health check file

* Rename p to pool

* Refactor executeQueryPGX and split into multiple functions

* Fix lint issues

* The returning error message from PGX is enough no need to separate the error code.

* Move TLS handling to newPostgresPGX function

* Disable ssl for integration tests

* Use MaxIdleConns option

* Remove old feature toggle

* Rename`generateConnectionConfigPGX` to `generateConnectionStringPGX`

* Add back part of the error messages

* Don't show max idle connections option when PGX enabled

* Address comments from Sriram

* Add back Sriram's changes

* PostgreSQL: Rework tls manager to use temporary files instead (#105330)

* Rework tls manager to use temporary files instead

* Lint and test fixes

* Update pkg/tsdb/grafana-postgresql-datasource/postgres.go

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

* Update betterer

---------

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

---------

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2025-05-26 08:54:18 +02:00

160 lines
4.4 KiB
Go

package postgres
import (
"fmt"
"os"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng"
)
var validateCertFunc = validateCertFilePaths
type tlsManager struct {
logger log.Logger
}
func newTLSManager(logger log.Logger) *tlsManager {
return &tlsManager{
logger: logger,
}
}
type tlsSettings struct {
Mode string
ConfigurationMethod string
RootCertFile string
CertFile string
CertKeyFile string
}
// getTLSSettings retrieves TLS settings and handles certificate file creation if needed.
func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) {
tlsConfig := tlsSettings{
Mode: dsInfo.JsonData.Mode,
}
if tlsConfig.Mode == "disable" {
m.logger.Debug("Postgres TLS/SSL is disabled")
return tlsConfig, nil
}
tlsConfig.ConfigurationMethod = dsInfo.JsonData.ConfigurationMethod
tlsConfig.RootCertFile = dsInfo.JsonData.RootCertFile
tlsConfig.CertFile = dsInfo.JsonData.CertFile
tlsConfig.CertKeyFile = dsInfo.JsonData.CertKeyFile
if tlsConfig.ConfigurationMethod == "file-content" {
if err := m.createCertFiles(dsInfo, &tlsConfig); err != nil {
return tlsConfig, fmt.Errorf("failed to create TLS certificate files: %w", err)
}
} else {
if err := validateCertFunc(tlsConfig.RootCertFile, tlsConfig.CertFile, tlsConfig.CertKeyFile); err != nil {
return tlsConfig, fmt.Errorf("invalid TLS certificate file paths: %w", err)
}
}
return tlsConfig, nil
}
// createCertFiles writes certificate files to temporary locations.
func (m *tlsManager) createCertFiles(dsInfo sqleng.DataSourceInfo, tlsConfig *tlsSettings) error {
m.logger.Debug("Writing TLS certificate files to temporary locations")
var err error
if tlsConfig.RootCertFile, err = m.writeCertFile("root-*.crt", dsInfo.DecryptedSecureJSONData["tlsCACert"]); err != nil {
return err
}
if tlsConfig.CertFile, err = m.writeCertFile("client-*.crt", dsInfo.DecryptedSecureJSONData["tlsClientCert"]); err != nil {
return err
}
if tlsConfig.CertKeyFile, err = m.writeCertFile("client-*.key", dsInfo.DecryptedSecureJSONData["tlsClientKey"]); err != nil {
return err
}
return nil
}
// writeCertFile writes a single certificate file to a temporary location.
func (m *tlsManager) writeCertFile(pattern, content string) (string, error) {
if content == "" {
return "", nil
}
m.logger.Debug("Writing certificate file", "pattern", pattern)
file, err := os.CreateTemp("", pattern)
if err != nil {
return "", fmt.Errorf("failed to create temporary file: %w", err)
}
defer func() {
if err := file.Close(); err != nil {
m.logger.Error("Failed to close file", "error", err)
}
}()
if _, err := file.WriteString(content); err != nil {
return "", fmt.Errorf("failed to write to temporary file: %w", err)
}
return file.Name(), nil
}
// cleanupCertFiles removes temporary certificate files.
func (m *tlsManager) cleanupCertFiles(tlsConfig tlsSettings) {
// Only clean up if the configuration method is "file-content"
if tlsConfig.ConfigurationMethod != "file-content" {
m.logger.Debug("Skipping cleanup of TLS certificate files")
return
}
m.logger.Debug("Cleaning up TLS certificate files")
files := []struct {
path string
name string
}{
{tlsConfig.RootCertFile, "root certificate"},
{tlsConfig.CertFile, "client certificate"},
{tlsConfig.CertKeyFile, "client key"},
}
for _, file := range files {
if file.path == "" {
continue
}
if err := os.Remove(file.path); err != nil {
m.logger.Error("Failed to remove file", "type", file.name, "path", file.path, "error", err)
} else {
m.logger.Debug("Successfully removed file", "type", file.name, "path", file.path)
}
}
}
// validateCertFilePaths validates the existence of configured certificate file paths.
func validateCertFilePaths(rootCert, clientCert, clientKey string) error {
for _, path := range []string{rootCert, clientCert, clientKey} {
if path == "" {
continue
}
exists, err := fileExists(path)
if err != nil {
return fmt.Errorf("error checking file existence: %w", err)
}
if !exists {
return sqleng.ErrCertFileNotExist
}
}
return nil
}
// fileExists checks if a file exists at the given path.
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}