diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3783e9ac..d55ac7d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,20 @@ on: required: false 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 @@ -42,7 +56,6 @@ jobs: do_not_skip: '["workflow_dispatch", "schedule", "push"]' concurrent_skipping: false - lint: runs-on: ubuntu-24.04 needs: detect-noop @@ -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 || '18' }} + MARIADB_VERSION=${{ inputs.mariadb_version || '12' }} + MSSQL_VERSION=${{ inputs.mssql_version || '2022-CU24-ubuntu-22.04' }} publish-artifacts: runs-on: ubuntu-24.04 diff --git a/Makefile b/Makefile index 31517508..71708c38 100644 --- a/Makefile +++ b/Makefile @@ -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) @@ -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 diff --git a/cluster/local/integration_tests.sh b/cluster/local/integration_tests.sh index 903eb896..538ca56b 100755 --- a/cluster/local/integration_tests.sh +++ b/cluster/local/integration_tests.sh @@ -37,13 +37,10 @@ projectdir="$( cd "$( dirname "${BASH_SOURCE[0]}")"/../.. && pwd )" scriptdir="$(dirname "$0")" # get the build environment variables from the special build.vars target in the main makefile -eval $(make --no-print-directory -C ${projectdir} build.vars) +eval "$(make --no-print-directory -C "${projectdir}" build.vars)" # ------------------------------ -SAFEHOSTARCH="${SAFEHOSTARCH:-amd64}" -CONTROLLER_IMAGE="${BUILD_REGISTRY}/${PROJECT_NAME}-${SAFEHOSTARCH}" - K8S_CLUSTER="${K8S_CLUSTER:-${BUILD_REGISTRY}-inttests}" PACKAGE_NAME="provider-sql" @@ -117,7 +114,7 @@ setup_crossplane() { setup_provider() { echo_step "deploying provider via local.xpkg.deploy" - make -C "${projectdir}" local.xpkg.deploy.provider.${PACKAGE_NAME} KIND_CLUSTER_NAME="${K8S_CLUSTER}" + make -C "${projectdir}" "local.xpkg.deploy.provider.${PACKAGE_NAME}" KIND_CLUSTER_NAME="${K8S_CLUSTER}" echo_step "waiting for provider to be installed" "${KUBECTL}" wait "provider.pkg.crossplane.io/${PACKAGE_NAME}" --for=condition=healthy --timeout=60s @@ -127,13 +124,13 @@ cleanup_provider() { echo_step "uninstalling provider" "${KUBECTL}" delete provider.pkg.crossplane.io "${PACKAGE_NAME}" - "${KUBECTL}" delete deploymentruntimeconfig.pkg.crossplane.io runtimeconfig-${PACKAGE_NAME} + "${KUBECTL}" delete deploymentruntimeconfig.pkg.crossplane.io "runtimeconfig-${PACKAGE_NAME}" echo_step "waiting for provider pods to be deleted" timeout=60 current=0 step=3 - while [[ $(kubectl get providerrevision.pkg.crossplane.io -o name | wc -l | tr -d '[:space:]') != "0" ]]; do + while [[ $("${KUBECTL}" get providerrevision.pkg.crossplane.io -o name | wc -l | tr -d '[:space:]') != "0" ]]; do echo "waiting another $step seconds" current=$((current + step)) if [[ $current -ge $timeout ]]; then @@ -192,7 +189,7 @@ setup_provider_config_tls() { cleanup_provider_config() { echo_step "cleaning up ProviderConfig" - "${KUBECTL}" delete providerconfig.mysql.sql.${APIGROUP_SUFFIX}crossplane.io default + "${KUBECTL}" delete "providerconfig.mysql.sql.${APIGROUP_SUFFIX}crossplane.io" default } setup_mariadb_no_tls() { @@ -203,7 +200,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 @@ -228,7 +225,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 @@ -244,10 +241,10 @@ cleanup_mariadb() { test_create_database() { echo_step "test creating MySQL Database resource" - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/database.yaml + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mysql/database.yaml" echo_info "check if is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mysql/database.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mysql/database.yaml" echo_step_completed } @@ -278,7 +275,7 @@ test_update_database_charset() { echo_step "test updating MySQL Database charset and collation" # Patch the database to use a different collation - "${KUBECTL}" patch database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io example-db --type merge \ + "${KUBECTL}" patch "database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io" example-db --type merge \ -p '{"spec":{"forProvider":{"defaultCollation":"utf8mb4_general_ci"}}}' # Wait for the controller to reconcile the change @@ -298,7 +295,7 @@ test_update_database_charset() { echo_step_completed # Restore original collation for subsequent tests - "${KUBECTL}" patch database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io example-db --type merge \ + "${KUBECTL}" patch "database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io" example-db --type merge \ -p '{"spec":{"forProvider":{"defaultCollation":"utf8mb4_bin"}}}' sleep 10 } @@ -307,14 +304,14 @@ test_remove_database_charset() { echo_step "test removing charset/collation from spec leaves database unchanged" # Remove charset and collation from the spec (set forProvider to only have empty fields) - "${KUBECTL}" patch database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io example-db --type json \ + "${KUBECTL}" patch "database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io" example-db --type json \ -p '[{"op":"remove","path":"/spec/forProvider/defaultCharacterSet"},{"op":"remove","path":"/spec/forProvider/defaultCollation"}]' # Wait for the controller to reconcile -- late init should re-populate the fields sleep 15 echo_info "check database resource is still Ready" - "${KUBECTL}" wait --timeout 30s --for condition=Ready database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io/example-db + "${KUBECTL}" wait --timeout 30s --for condition=Ready "database.mysql.sql.${APIGROUP_SUFFIX}crossplane.io/example-db" echo_step_completed echo_info "check charset/collation unchanged in MariaDB" @@ -342,10 +339,10 @@ test_create_user() { echo_step "test creating MySQL User resource" local user_pw="asdf1234" "${KUBECTL}" create secret generic example-pw --from-literal password="${user_pw}" --save-config - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/user.yaml + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mysql/user.yaml" echo_info "check if is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mysql/user.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mysql/user.yaml" echo_step_completed echo_info "check if connection secret exists" @@ -361,7 +358,7 @@ test_update_user_password() { "${KUBECTL}" apply -f - # trigger reconcile - "${KUBECTL}" annotate -f ${projectdir}/examples/${API_TYPE}/mysql/user.yaml reconcile=now + "${KUBECTL}" annotate -f "${projectdir}/examples/${API_TYPE}/mysql/user.yaml" reconcile=now sleep 3 @@ -376,12 +373,12 @@ test_create_grant() { "${KUBECTL}" exec mariadb-0 -- bash -c \ 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "CREATE TABLE \`example-db\`.\`example-table\` (id INT, status VARCHAR(50), updated_at TIMESTAMP);"' - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml" + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml" echo_info "check if is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml" + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml" echo_step_completed } @@ -397,10 +394,10 @@ test_all() { cleanup_test_resources() { echo_step "cleaning up test resources" - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mysql/database.yaml - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mysql/user.yaml + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/mysql/grant_database.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/mysql/grant_table.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/mysql/database.yaml" + "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/mysql/user.yaml" "${KUBECTL}" delete secret example-pw } diff --git a/cluster/local/mariadb.server.yaml b/cluster/local/mariadb.server.yaml index dc60ec1b..0662e8e1 100644 --- a/cluster/local/mariadb.server.yaml +++ b/cluster/local/mariadb.server.yaml @@ -28,7 +28,7 @@ spec: spec: containers: - name: mariadb - image: mariadb:12 + image: mariadb:${MARIADB_VERSION} env: - name: MARIADB_ROOT_PASSWORD valueFrom: diff --git a/cluster/local/mariadb.tls.server.yaml b/cluster/local/mariadb.tls.server.yaml index ed4a2094..81b5edca 100644 --- a/cluster/local/mariadb.tls.server.yaml +++ b/cluster/local/mariadb.tls.server.yaml @@ -28,7 +28,7 @@ spec: spec: containers: - name: mariadb - image: mariadb:12 + image: mariadb:${MARIADB_VERSION} args: - --ssl - --require-secure-transport=ON diff --git a/cluster/local/mssql.server.yaml b/cluster/local/mssql.server.yaml index 9168416b..80b76b0b 100644 --- a/cluster/local/mssql.server.yaml +++ b/cluster/local/mssql.server.yaml @@ -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: diff --git a/cluster/local/mssqldb_functions.sh b/cluster/local/mssqldb_functions.sh index ddeef31a..45316d2c 100644 --- a/cluster/local/mssqldb_functions.sh +++ b/cluster/local/mssqldb_functions.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash set -e -MSSQL_IMAGE="${MSSQL_IMAGE:-mcr.microsoft.com/mssql/server:2019-CU32-ubuntu-20.04}" +scriptdir=$(dirname "$0") setup_mssql() { - echo_step "installing MSSQL Server (image ${MSSQL_IMAGE})" + echo_step "installing MSSQL Server (version ${MSSQL_VERSION:-2022-CU24-ubuntu-22.04})" "${KUBECTL}" create secret generic mssql-creds \ --from-literal username="sa" \ @@ -15,8 +15,7 @@ setup_mssql() { echo_step "Verifying secret creation" "${KUBECTL}" get secret mssql-creds -o yaml - sed "s|image: mcr.microsoft.com/mssql/server:.*|image: ${MSSQL_IMAGE}|" \ - "${scriptdir}/mssql.server.yaml" | "${KUBECTL}" apply -f - + 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 @@ -27,7 +26,7 @@ setup_mssql() { cleanup_mssql() { echo_step "cleaning up MSSQL server" - "${KUBECTL}" delete -f ${scriptdir}/mssql.server.yaml --ignore-not-found=true + "${KUBECTL}" delete -f "${scriptdir}/mssql.server.yaml" --ignore-not-found=true "${KUBECTL}" delete secret mssql-creds --ignore-not-found=true } @@ -43,10 +42,10 @@ cleanup_mssql_provider_config() { test_create_mssql_database() { echo_step "test creating MSSQL Database resource" - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mssql/database.yaml + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mssql/database.yaml" echo_step "Waiting for MSSQL Database to be ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mssql/database.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mssql/database.yaml" echo_step_completed } @@ -56,10 +55,10 @@ test_create_mssql_user() { # Create password secret first "${KUBECTL}" create secret generic example-pw --from-literal password="Test123!" --dry-run=client -o yaml | "${KUBECTL}" apply -f - - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mssql/user.yaml + "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/mssql/user.yaml" echo_step "Waiting for MSSQL User to be ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mssql/user.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/mssql/user.yaml" echo_step_completed } diff --git a/pkg/clients/mssql/mssql.go b/pkg/clients/mssql/mssql.go index 4be0e99c..27aec9e3 100644 --- a/pkg/clients/mssql/mssql.go +++ b/pkg/clients/mssql/mssql.go @@ -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 diff --git a/pkg/clients/mysql/mysql.go b/pkg/clients/mysql/mysql.go index da4b94c4..fa1ed03b 100644 --- a/pkg/clients/mysql/mysql.go +++ b/pkg/clients/mysql/mysql.go @@ -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" + "github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed" xpv2 "github.com/crossplane/crossplane/apis/v2/core/v2" ) @@ -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 diff --git a/pkg/clients/postgresql/postgresql.go b/pkg/clients/postgresql/postgresql.go index 9dd60287..6329478e 100644 --- a/pkg/clients/postgresql/postgresql.go +++ b/pkg/clients/postgresql/postgresql.go @@ -138,8 +138,8 @@ func (c postgresDB) GetConnectionDetails(username, password string) managed.Conn } } -// GetServerVersion returns the PostgreSQL server version as an integer -// For example, PostgreSQL 16.2 would return 160200. +// GetServerVersion returns the PostgreSQL server version as an integer. +// For example, PostgreSQL 16.2 returns 160002 (major*10000 + minor). func (c postgresDB) GetServerVersion(ctx context.Context) (int, error) { db, err := sql.Open("postgres", c.dsn) if err != nil { @@ -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 diff --git a/pkg/clients/xsql/common.go b/pkg/clients/xsql/common.go index b1c78444..6bf3a8b5 100644 --- a/pkg/clients/xsql/common.go +++ b/pkg/clients/xsql/common.go @@ -3,6 +3,9 @@ package xsql import ( "context" "errors" + "fmt" + "strconv" + "strings" "database/sql" @@ -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) diff --git a/pkg/clients/xsql/common_test.go b/pkg/clients/xsql/common_test.go index 89fad36e..8f71f5b0 100644 --- a/pkg/clients/xsql/common_test.go +++ b/pkg/clients/xsql/common_test.go @@ -110,3 +110,51 @@ 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}, + + // Patch boundary cases + {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: "MajorOnly", version: "8", wantErr: true}, + {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 { + if err == nil { + t.Fatal("ParseVersion(...): expected error, got nil") + } + return + } + if err != nil { + t.Fatalf("ParseVersion(...): unexpected error: %v", err) + } + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("ParseVersion(...): -want, +got:\n%s", diff) + } + }) + } +} diff --git a/pkg/controller/cluster/mssql/database/reconciler_test.go b/pkg/controller/cluster/mssql/database/reconciler_test.go index a75c0094..86454f44 100644 --- a/pkg/controller/cluster/mssql/database/reconciler_test.go +++ b/pkg/controller/cluster/mssql/database/reconciler_test.go @@ -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) } diff --git a/pkg/controller/cluster/mssql/grant/reconciler_test.go b/pkg/controller/cluster/mssql/grant/reconciler_test.go index c5379242..7795582d 100644 --- a/pkg/controller/cluster/mssql/grant/reconciler_test.go +++ b/pkg/controller/cluster/mssql/grant/reconciler_test.go @@ -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) } diff --git a/pkg/controller/cluster/mssql/user/reconciler_test.go b/pkg/controller/cluster/mssql/user/reconciler_test.go index ac69dcff..506c28e7 100644 --- a/pkg/controller/cluster/mssql/user/reconciler_test.go +++ b/pkg/controller/cluster/mssql/user/reconciler_test.go @@ -71,6 +71,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) } diff --git a/pkg/controller/cluster/mysql/database/reconciler_test.go b/pkg/controller/cluster/mysql/database/reconciler_test.go index 4df7cf65..4f0650d4 100644 --- a/pkg/controller/cluster/mysql/database/reconciler_test.go +++ b/pkg/controller/cluster/mysql/database/reconciler_test.go @@ -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) } diff --git a/pkg/controller/cluster/mysql/grant/reconciler_test.go b/pkg/controller/cluster/mysql/grant/reconciler_test.go index ccddbcda..5b19ff8e 100644 --- a/pkg/controller/cluster/mysql/grant/reconciler_test.go +++ b/pkg/controller/cluster/mysql/grant/reconciler_test.go @@ -69,6 +69,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) } diff --git a/pkg/controller/cluster/mysql/user/reconciler_test.go b/pkg/controller/cluster/mysql/user/reconciler_test.go index b6baca70..ca267f17 100644 --- a/pkg/controller/cluster/mysql/user/reconciler_test.go +++ b/pkg/controller/cluster/mysql/user/reconciler_test.go @@ -67,6 +67,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) } diff --git a/pkg/controller/namespaced/mssql/database/reconciler_test.go b/pkg/controller/namespaced/mssql/database/reconciler_test.go index c84cec1c..3069e8ed 100644 --- a/pkg/controller/namespaced/mssql/database/reconciler_test.go +++ b/pkg/controller/namespaced/mssql/database/reconciler_test.go @@ -61,6 +61,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) } diff --git a/pkg/controller/namespaced/mssql/grant/reconciler_test.go b/pkg/controller/namespaced/mssql/grant/reconciler_test.go index bf6bda2b..32e5fcd3 100644 --- a/pkg/controller/namespaced/mssql/grant/reconciler_test.go +++ b/pkg/controller/namespaced/mssql/grant/reconciler_test.go @@ -70,6 +70,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) } diff --git a/pkg/controller/namespaced/mssql/user/reconciler_test.go b/pkg/controller/namespaced/mssql/user/reconciler_test.go index 0a308ce8..7799e1db 100644 --- a/pkg/controller/namespaced/mssql/user/reconciler_test.go +++ b/pkg/controller/namespaced/mssql/user/reconciler_test.go @@ -72,6 +72,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) } diff --git a/pkg/controller/namespaced/mysql/database/reconciler_test.go b/pkg/controller/namespaced/mysql/database/reconciler_test.go index dd4d3acf..677dbc39 100644 --- a/pkg/controller/namespaced/mysql/database/reconciler_test.go +++ b/pkg/controller/namespaced/mysql/database/reconciler_test.go @@ -61,6 +61,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) } diff --git a/pkg/controller/namespaced/mysql/grant/reconciler_test.go b/pkg/controller/namespaced/mysql/grant/reconciler_test.go index 774cb182..c0025576 100644 --- a/pkg/controller/namespaced/mysql/grant/reconciler_test.go +++ b/pkg/controller/namespaced/mysql/grant/reconciler_test.go @@ -70,6 +70,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) } diff --git a/pkg/controller/namespaced/mysql/user/reconciler_test.go b/pkg/controller/namespaced/mysql/user/reconciler_test.go index 772f481c..a0390487 100644 --- a/pkg/controller/namespaced/mysql/user/reconciler_test.go +++ b/pkg/controller/namespaced/mysql/user/reconciler_test.go @@ -68,6 +68,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) }