Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,30 @@ on:
workflow_dispatch:
inputs:
postgres_version:
description: 'PostgreSQL major version to test against (e.g. 16, 17, 18)'
description: "PostgreSQL major version to test against (e.g. 16, 17, 18)"
required: false
default: '18'
default: "18"
type: string
mariadb_version:
description: "MariaDB major version to test against (e.g. 11, 12)"
required: false
default: "12"
type: string
mssql_version:
description: "MSSQL Server image tag (e.g. 2022-CU24-ubuntu-22.04)"
required: false
default: "2022-CU24-ubuntu-22.04"
type: string

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
# Common versions
GO_VERSION: '1.26'
GOLANGCI_VERSION: 'v2.10.1'
DOCKER_BUILDX_VERSION: 'v0.23.0'
GO_VERSION: "1.26"
GOLANGCI_VERSION: "v2.10.1"
DOCKER_BUILDX_VERSION: "v0.23.0"

# Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run
# a step 'if env.AWS_USR' != ""', so we copy these to succinctly test whether
Expand All @@ -42,7 +56,6 @@ jobs:
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false


lint:
runs-on: ubuntu-24.04
needs: detect-noop
Expand Down Expand Up @@ -245,7 +258,11 @@ jobs:
# BUILD_ARGS: "--load"

- name: Run E2E Tests
run: make e2e USE_HELM=true POSTGRES_VERSION=${{ inputs.postgres_version || '18' }}
run: >-
make e2e USE_HELM=true
POSTGRES_VERSION=${{ inputs.postgres_version }}
MARIADB_VERSION=${{ inputs.mariadb_version }}
MSSQL_VERSION=${{ inputs.mssql_version }}

publish-artifacts:
runs-on: ubuntu-24.04
Expand Down
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ e2e.run: test-integration
CROSSPLANE_HELM_CHANNEL ?= stable
CROSSPLANE_HELM_CHART_VERSION ?=
POSTGRES_VERSION ?= 18
MARIADB_VERSION ?= 12
MSSQL_VERSION ?= 2022-CU24-ubuntu-22.04

# Run integration tests.
test-integration: $(KIND) $(KUBECTL) $(CROSSPLANE_CLI) $(HELM)
Expand All @@ -95,6 +97,8 @@ test-integration: $(KIND) $(KUBECTL) $(CROSSPLANE_CLI) $(HELM)
CROSSPLANE_HELM_CHANNEL=${CROSSPLANE_HELM_CHANNEL} \
CROSSPLANE_HELM_CHART_VERSION=${CROSSPLANE_HELM_CHART_VERSION} \
POSTGRES_VERSION=${POSTGRES_VERSION} \
MARIADB_VERSION=${MARIADB_VERSION} \
MSSQL_VERSION=${MSSQL_VERSION} \
$(ROOT_DIR)/cluster/local/integration_tests.sh || $(FAIL)
@$(OK) integration tests passed

Expand Down
4 changes: 2 additions & 2 deletions cluster/local/integration_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ setup_mariadb_no_tls() {
--from-literal endpoint="mariadb.default.svc.cluster.local" \
--from-literal port="3306"

"${KUBECTL}" apply -f ${scriptdir}/mariadb.server.yaml
MARIADB_VERSION="${MARIADB_VERSION:-12}" envsubst '${MARIADB_VERSION}' < "${scriptdir}/mariadb.server.yaml" | "${KUBECTL}" apply -f -

echo_step "Waiting for MariaDB to be ready"
"${KUBECTL}" rollout status statefulset/mariadb --timeout=120s
Expand All @@ -228,7 +228,7 @@ setup_mariadb_tls() {
"

# Deploy MariaDB using official mariadb image with TLS
"${KUBECTL}" apply -f "${scriptdir}/mariadb.tls.server.yaml"
MARIADB_VERSION="${MARIADB_VERSION:-12}" envsubst '${MARIADB_VERSION}' < "${scriptdir}/mariadb.tls.server.yaml" | "${KUBECTL}" apply -f -

echo_step "Waiting for MariaDB to be ready"
"${KUBECTL}" rollout status statefulset/mariadb --timeout=120s
Expand Down
2 changes: 1 addition & 1 deletion cluster/local/mariadb.server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: mariadb
image: mariadb:12
image: mariadb:${MARIADB_VERSION}
env:
- name: MARIADB_ROOT_PASSWORD
valueFrom:
Expand Down
2 changes: 1 addition & 1 deletion cluster/local/mariadb.tls.server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: mariadb
image: mariadb:12
image: mariadb:${MARIADB_VERSION}
args:
- --ssl
- --require-secure-transport=ON
Expand Down
2 changes: 1 addition & 1 deletion cluster/local/mssql.server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: mssql
image: mcr.microsoft.com/mssql/server:2019-CU32-ubuntu-20.04
image: mcr.microsoft.com/mssql/server:${MSSQL_VERSION}
env:
- name: SA_PASSWORD
valueFrom:
Expand Down
2 changes: 1 addition & 1 deletion cluster/local/mssqldb_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ setup_mssql() {
echo_step "Verifying secret creation"
"${KUBECTL}" get secret mssql-creds -o yaml

"${KUBECTL}" apply -f ${scriptdir}/mssql.server.yaml
MSSQL_VERSION="${MSSQL_VERSION:-2022-CU24-ubuntu-22.04}" envsubst '${MSSQL_VERSION}' < "${scriptdir}/mssql.server.yaml" | "${KUBECTL}" apply -f -

echo_step "Waiting for MSSQL Server to be ready"
"${KUBECTL}" rollout status statefulset/mssql --timeout=300s
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/lib/pq v1.12.3
github.com/microsoft/go-mssqldb v1.10.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.11.1
k8s.io/api v0.35.0
k8s.io/apimachinery v0.35.0
k8s.io/utils v0.0.0-20260108192941-914a6e750570
Expand Down
19 changes: 15 additions & 4 deletions pkg/clients/mssql/mssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,22 @@ func (c mssqlDB) GetConnectionDetails(username, password string) managed.Connect
}
}

// GetServerVersion is not supported by the MSSQL client (only used by PostgreSQL).
// GetServerVersion returns the MSSQL server version as an integer.
// For example, SQL Server 2022 (16.0.1125) returns 160000 (major*10000 + minor*100).
// Build number is ignored as it exceeds the encoding range.
func (c mssqlDB) GetServerVersion(ctx context.Context) (int, error) {
// This method should never be called for MSSQL clients
// but is implemented to satisfy the xsql.DB interface
return 0, nil
db, err := sql.Open(driverName, c.dsn)
if err != nil {
return 0, err
}
defer db.Close() //nolint:errcheck

var version string
if err := db.QueryRowContext(ctx, "SELECT SERVERPROPERTY('ProductVersion')").Scan(&version); err != nil {
return 0, err
}

return xsql.ParseVersion(version)
}

// QuoteIdentifier for mssql queries
Expand Down
21 changes: 16 additions & 5 deletions pkg/clients/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
"strconv"
"strings"

"github.com/crossplane-contrib/provider-sql/pkg/clients/xsql"
"github.com/pkg/errors"

"github.com/crossplane-contrib/provider-sql/pkg/clients/xsql"

xpv1 "github.com/crossplane/crossplane-runtime/v2/apis/common/v1"
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed"
)
Expand Down Expand Up @@ -117,11 +118,21 @@ func (c mySQLDB) GetConnectionDetails(username, password string) managed.Connect
}
}

// GetServerVersion is not supported by the MySQL client (only used by PostgreSQL).
// GetServerVersion returns the MySQL server version as an integer.
// For example, MySQL 8.0.35 returns 80035 (major*10000 + minor*100 + patch).
func (c mySQLDB) GetServerVersion(ctx context.Context) (int, error) {
// This method should never be called for MySQL clients
// but is implemented to satisfy the xsql.DB interface
return 0, nil
db, err := sql.Open("mysql", c.dsn)
if err != nil {
return 0, err
}
defer db.Close() //nolint:errcheck

var version string
if err := db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&version); err != nil {
return 0, err
}

return xsql.ParseVersion(version)
}

// QuoteIdentifier for MySQL queries
Expand Down
9 changes: 6 additions & 3 deletions pkg/clients/postgresql/postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (c postgresDB) GetConnectionDetails(username, password string) managed.Conn
}
}

// GetServerVersion returns the PostgreSQL server version as an integer
// GetServerVersion returns the PostgreSQL server version as an integer.
// For example, PostgreSQL 16.2 would return 160200.
func (c postgresDB) GetServerVersion(ctx context.Context) (int, error) {
db, err := sql.Open("postgres", c.dsn)
Expand All @@ -148,8 +148,11 @@ func (c postgresDB) GetServerVersion(ctx context.Context) (int, error) {
defer db.Close() //nolint:errcheck

var version int
err = db.QueryRowContext(ctx, "SELECT current_setting('server_version_num')::int").Scan(&version)
return version, err
if err := db.QueryRowContext(ctx, "SELECT current_setting('server_version_num')::int").Scan(&version); err != nil {
return 0, err
}

return version, nil
}

// IsInvalidCatalog returns true if passed a pq error indicating
Expand Down
38 changes: 38 additions & 0 deletions pkg/clients/xsql/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package xsql
import (
"context"
"errors"
"fmt"
"strconv"
"strings"

"database/sql"

Expand All @@ -25,6 +28,41 @@ type DB interface {
GetServerVersion(ctx context.Context) (int, error)
}

// ParseVersion parses a database version string into an integer
// encoded as major*10000 + minor*100 + patch.
// Suffixes after '-' are stripped (e.g. "8.0.35-ubuntu" → 80035).
// Patch values above 99 are ignored (e.g. MSSQL build numbers).
func ParseVersion(version string) (int, error) {
if idx := strings.IndexByte(version, '-'); idx >= 0 {
version = version[:idx]
}

parts := strings.SplitN(version, ".", 3)
if len(parts) < 2 {
return 0, fmt.Errorf("unexpected version format: %s", version)
}

major, err := strconv.Atoi(parts[0])
if err != nil {
return 0, fmt.Errorf("parsing major version %q: %w", parts[0], err)
}

minor, err := strconv.Atoi(parts[1])
if err != nil {
return 0, fmt.Errorf("parsing minor version %q: %w", parts[1], err)
}

var patch int
if len(parts) == 3 {
p, err := strconv.Atoi(parts[2])
if err == nil && p <= 99 {
patch = p
}
}

return major*10000 + minor*100 + patch, nil
}

// IsNoRows returns true if the supplied error indicates no rows were returned.
func IsNoRows(err error) bool {
return errors.Is(err, sql.ErrNoRows)
Expand Down
43 changes: 43 additions & 0 deletions pkg/clients/xsql/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRemapCredentialKeys(t *testing.T) {
Expand Down Expand Up @@ -110,3 +112,44 @@ func TestRemapCredentialKeys(t *testing.T) {
}
})
}

func TestParseVersion(t *testing.T) {
tests := []struct {
name string
version string
want int
wantErr bool
}{
// MySQL-style versions
{name: "MySQL_Standard", version: "8.0.35", want: 80035},
{name: "MySQL_WithSuffix", version: "8.0.35-0ubuntu0.22.04.1", want: 80035},
{name: "MySQL_MajorMinorOnly", version: "8.0", want: 80000},
{name: "MySQL_9", version: "9.1.0", want: 90100},
{name: "MariaDB", version: "10.11.6-MariaDB", want: 101106},

// MSSQL-style versions (build number > 99, ignored)
{name: "MSSQL_2022", version: "16.0.1125.1", want: 160000},
{name: "MSSQL_2019", version: "15.0.4355.3", want: 150000},
{name: "MSSQL_NonZeroMinor", version: "16.5.100.1", want: 160500},

// PostgreSQL-style (for reference, PG uses its own query)
{name: "ThreeDigitPatch", version: "14.2.1", want: 140201},
{name: "PatchExactly99", version: "14.0.99", want: 140099},
{name: "PatchOver99", version: "14.0.100", want: 140000},

// Errors
{name: "InvalidFormat", version: "invalid", wantErr: true},
{name: "EmptyString", version: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseVersion(tt.version)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
2 changes: 2 additions & 0 deletions pkg/controller/cluster/mssql/database/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ func (c *connector) Connect(ctx context.Context, mg *clusterv1alpha1.Database) (
}

secretData := xsql.RemapCredentialKeys(s.Data, pc.Spec.Credentials.SecretKeyMapping.ToMap())
// To add version-gated logic, call db.GetServerVersion(ctx) here
// and store it in the external struct (see PostgreSQL grant reconciler).
return &external{db: c.newClient(secretData, "")}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/cluster/mssql/database/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ func (m mockDB) GetConnectionDetails(username, password string) managed.Connecti
return m.MockGetConnectionDetails(username, password)
}
func (m mockDB) GetServerVersion(ctx context.Context) (int, error) {
if m.MockGetServerVersion == nil {
return 0, nil
}
return m.MockGetServerVersion(ctx)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/cluster/mssql/grant/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ func (c *connector) Connect(ctx context.Context, mg *v1alpha1.Grant) (managed.Ty
}

secretData := xsql.RemapCredentialKeys(s.Data, pc.Spec.Credentials.SecretKeyMapping.ToMap())
// To add version-gated logic, call db.GetServerVersion(ctx) here
// and store it in the external struct (see PostgreSQL grant reconciler).
return &external{db: c.newClient(secretData, ptr.Deref(mg.Spec.ForProvider.Database, ""))}, nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/cluster/mssql/grant/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func (m mockDB) GetConnectionDetails(username, password string) managed.Connecti
return m.MockGetConnectionDetails(username, password)
}
func (m mockDB) GetServerVersion(ctx context.Context) (int, error) {
if m.MockGetServerVersion == nil {
return 0, nil
}
return m.MockGetServerVersion(ctx)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/cluster/mssql/user/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ func (c *connector) Connect(ctx context.Context, mg *v1alpha1.User) (managed.Typ
loginDB = c.newClient(secretData, ptr.Deref(mg.Spec.ForProvider.LoginDatabase, ""))
}

// To add version-gated logic, call userDB.GetServerVersion(ctx) here
// and store it in the external struct (see PostgreSQL grant reconciler).
return &external{
userDB: userDB,
loginDB: loginDB,
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/cluster/mssql/user/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func (m mockDB) GetConnectionDetails(username, password string) managed.Connecti
}
}
func (m mockDB) GetServerVersion(ctx context.Context) (int, error) {
if m.MockGetServerVersion == nil {
return 0, nil
}
return m.MockGetServerVersion(ctx)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/cluster/mysql/database/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ func (c *connector) Connect(ctx context.Context, mg *v1alpha1.Database) (managed
}

secretData := xsql.RemapCredentialKeys(s.Data, pc.Spec.Credentials.SecretKeyMapping.ToMap())
// To add version-gated logic, call db.GetServerVersion(ctx) here
// and store it in the external struct (see PostgreSQL grant reconciler).
return &external{db: c.newDB(secretData, tlsName, mg.Spec.ForProvider.BinLog)}, nil
}

Expand Down
Loading
Loading