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

246 lines
7.0 KiB
Go

package postgres
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Test getTLSSettings.
func TestGetTLSSettings(t *testing.T) {
mockValidateCertFilePaths()
t.Cleanup(resetValidateCertFilePaths)
updatedTime := time.Now()
testCases := []struct {
desc string
expErr string
jsonData sqleng.JsonData
secureJSONData map[string]string
uid string
tlsSettings tlsSettings
updated time.Time
}{
{
desc: "Custom TLS authentication disabled",
updated: updatedTime,
jsonData: sqleng.JsonData{
Mode: "disable",
RootCertFile: "i/am/coding/ca.crt",
CertFile: "i/am/coding/client.crt",
CertKeyFile: "i/am/coding/client.key",
ConfigurationMethod: "file-path",
},
tlsSettings: tlsSettings{Mode: "disable"},
},
{
desc: "Custom TLS authentication with file path",
updated: updatedTime.Add(time.Minute),
jsonData: sqleng.JsonData{
Mode: "verify-full",
ConfigurationMethod: "file-path",
RootCertFile: "i/am/coding/ca.crt",
CertFile: "i/am/coding/client.crt",
CertKeyFile: "i/am/coding/client.key",
},
tlsSettings: tlsSettings{
Mode: "verify-full",
ConfigurationMethod: "file-path",
RootCertFile: "i/am/coding/ca.crt",
CertFile: "i/am/coding/client.crt",
CertKeyFile: "i/am/coding/client.key",
},
},
}
for _, tt := range testCases {
t.Run(tt.desc, func(t *testing.T) {
var settings tlsSettings
var err error
mng := tlsManager{
logger: backend.NewLoggerWith("logger", "tsdb.postgres"),
}
ds := sqleng.DataSourceInfo{
JsonData: tt.jsonData,
DecryptedSecureJSONData: tt.secureJSONData,
UID: tt.uid,
Updated: tt.updated,
}
settings, err = mng.getTLSSettings(ds)
if tt.expErr == "" {
require.NoError(t, err, tt.desc)
assert.Equal(t, tt.tlsSettings, settings)
} else {
require.Error(t, err, tt.desc)
assert.True(t, strings.HasPrefix(err.Error(), tt.expErr),
fmt.Sprintf("%s: %q doesn't start with %q", tt.desc, err, tt.expErr))
}
})
}
}
func mockValidateCertFilePaths() {
validateCertFunc = func(rootCert, clientCert, clientKey string) error {
return nil
}
}
func resetValidateCertFilePaths() {
validateCertFunc = validateCertFilePaths
}
func TestTLSManager_GetTLSSettings(t *testing.T) {
logger := log.New()
tlsManager := newTLSManager(logger)
dsInfo := sqleng.DataSourceInfo{
JsonData: sqleng.JsonData{
Mode: "require",
ConfigurationMethod: "file-content",
},
DecryptedSecureJSONData: map[string]string{
"tlsCACert": "root-cert-content",
"tlsClientCert": "client-cert-content",
"tlsClientKey": "client-key-content",
},
}
tlsConfig, err := tlsManager.getTLSSettings(dsInfo)
require.NoError(t, err)
assert.Equal(t, "require", tlsConfig.Mode)
assert.NotEmpty(t, tlsConfig.RootCertFile)
assert.NotEmpty(t, tlsConfig.CertFile)
assert.NotEmpty(t, tlsConfig.CertKeyFile)
// Cleanup temporary files
tlsManager.cleanupCertFiles(tlsConfig)
assert.NoFileExists(t, tlsConfig.RootCertFile)
assert.NoFileExists(t, tlsConfig.CertFile)
assert.NoFileExists(t, tlsConfig.CertKeyFile)
}
func TestTLSManager_CleanupCertFiles_FilePath(t *testing.T) {
logger := log.New()
tlsManager := newTLSManager(logger)
// Create temporary files for testing
rootCertFile, err := tlsManager.writeCertFile("root-*.crt", "root-cert-content")
require.NoError(t, err)
clientCertFile, err := tlsManager.writeCertFile("client-*.crt", "client-cert-content")
require.NoError(t, err)
clientKeyFile, err := tlsManager.writeCertFile("client-*.key", "client-key-content")
require.NoError(t, err)
// Simulate a configuration where the method is "file-path"
tlsConfig := tlsSettings{
ConfigurationMethod: "file-path",
RootCertFile: rootCertFile,
CertFile: clientCertFile,
CertKeyFile: clientKeyFile,
}
// Call cleanupCertFiles
tlsManager.cleanupCertFiles(tlsConfig)
// Verify the files are NOT deleted
assert.FileExists(t, rootCertFile, "Root certificate file should not be deleted")
assert.FileExists(t, clientCertFile, "Client certificate file should not be deleted")
assert.FileExists(t, clientKeyFile, "Client key file should not be deleted")
// Cleanup the files manually
err = os.Remove(rootCertFile)
require.NoError(t, err)
err = os.Remove(clientCertFile)
require.NoError(t, err)
err = os.Remove(clientKeyFile)
require.NoError(t, err)
}
func TestTLSManager_CreateCertFiles(t *testing.T) {
logger := log.New()
tlsManager := newTLSManager(logger)
dsInfo := sqleng.DataSourceInfo{
DecryptedSecureJSONData: map[string]string{
"tlsCACert": "root-cert-content",
"tlsClientCert": "client-cert-content",
"tlsClientKey": "client-key-content",
},
}
tlsConfig := tlsSettings{
ConfigurationMethod: "file-content",
}
err := tlsManager.createCertFiles(dsInfo, &tlsConfig)
require.NoError(t, err)
assert.FileExists(t, tlsConfig.RootCertFile)
assert.FileExists(t, tlsConfig.CertFile)
assert.FileExists(t, tlsConfig.CertKeyFile)
// Cleanup temporary files
tlsManager.cleanupCertFiles(tlsConfig)
assert.NoFileExists(t, tlsConfig.RootCertFile)
assert.NoFileExists(t, tlsConfig.CertFile)
assert.NoFileExists(t, tlsConfig.CertKeyFile)
}
func TestTLSManager_WriteCertFile(t *testing.T) {
logger := log.New()
tlsManager := newTLSManager(logger)
// Test writing a valid certificate file
filePath, err := tlsManager.writeCertFile("test-*.crt", "test-cert-content")
require.NoError(t, err)
assert.FileExists(t, filePath)
content, err := os.ReadFile(filepath.Clean(filePath))
require.NoError(t, err)
assert.Equal(t, "test-cert-content", string(content))
// Cleanup the file
err = os.Remove(filePath)
require.NoError(t, err)
assert.NoFileExists(t, filePath)
}
func TestTLSManager_CleanupCertFiles(t *testing.T) {
logger := log.New()
tlsManager := newTLSManager(logger)
// Create temporary files for testing
rootCertFile, err := tlsManager.writeCertFile("root-*.crt", "root-cert-content")
require.NoError(t, err)
clientCertFile, err := tlsManager.writeCertFile("client-*.crt", "client-cert-content")
require.NoError(t, err)
clientKeyFile, err := tlsManager.writeCertFile("client-*.key", "client-key-content")
require.NoError(t, err)
tlsConfig := tlsSettings{
ConfigurationMethod: "file-content",
RootCertFile: rootCertFile,
CertFile: clientCertFile,
CertKeyFile: clientKeyFile,
}
// Cleanup the files
tlsManager.cleanupCertFiles(tlsConfig)
// Verify the files are deleted
assert.NoFileExists(t, rootCertFile)
assert.NoFileExists(t, clientCertFile)
assert.NoFileExists(t, clientKeyFile)
}