diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7d2aacd..611a17a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ on: type: string env: - # Common versions DOCKER_BUILDX_VERSION: 'v0.23.0' # Common users. We can't run a step 'if secrets.AWS_USR != ""' but we can run @@ -197,6 +196,10 @@ jobs: runs-on: ubuntu-26.04 needs: detect-noop if: needs.detect-noop.outputs.noop != 'true' + strategy: + fail-fast: false + matrix: + engine: [mysql, postgresql, mssql] steps: - name: Setup QEMU @@ -251,7 +254,7 @@ 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 ENGINE=${{ matrix.engine }} POSTGRES_VERSION=${{ inputs.postgres_version || '18' }} publish-artifacts: runs-on: ubuntu-26.04 diff --git a/Makefile b/Makefile index 7735a178..0cc75c88 100644 --- a/Makefile +++ b/Makefile @@ -87,8 +87,8 @@ e2e.run: test-integration CROSSPLANE_HELM_CHANNEL ?= stable CROSSPLANE_HELM_CHART_VERSION ?= POSTGRES_VERSION ?= 18 -# DB selects the integration test suite: all, mysql, postgresql or mssql. -DB ?= all +# ENGINE selects the integration test suite: all, mysql, postgresql or mssql. +ENGINE ?= all # skipcleanup=true keeps the kind cluster and provider after the run; the next run reuses them. skipcleanup ?= false @@ -99,7 +99,7 @@ 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} \ - DB=${DB} \ + ENGINE=${ENGINE} \ skipcleanup=${skipcleanup} \ $(ROOT_DIR)/cluster/local/integration_tests.sh || $(FAIL) @$(OK) integration tests passed diff --git a/README.md b/README.md index 459a7314..2c65692c 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,11 @@ make build make e2e # or: make test-integration ``` -Options (pass as `make` variables, e.g. `make e2e DB=postgresql`): +Options (pass as `make` variables, e.g. `make e2e ENGINE=postgresql`): | Variable | Default | Description | |---|---|---| -| `DB` | `all` | Database suite to run: `all`, `mysql`, `postgresql` or `mssql`. | +| `ENGINE` | `all` | Database engine suite to run: `all`, `mysql`, `postgresql` or `mssql`. | | `skipcleanup` | `false` | `true` keeps the kind cluster and the provider after the run. The next run reuses the cluster (skips cluster and Crossplane setup) and redeploys the provider. | | `QUICK_TEST` | unset | `true` stops after the provider is healthy, without running database tests. | | `POSTGRES_VERSION` | `18` | PostgreSQL server image version. | diff --git a/cluster/local/.shellcheckrc b/cluster/local/.shellcheckrc new file mode 100644 index 00000000..8226afb6 --- /dev/null +++ b/cluster/local/.shellcheckrc @@ -0,0 +1 @@ +external-sources=true diff --git a/cluster/local/integration_tests.sh b/cluster/local/integration_tests.sh index b2ff336a..6d2096dc 100755 --- a/cluster/local/integration_tests.sh +++ b/cluster/local/integration_tests.sh @@ -1,55 +1,50 @@ #!/usr/bin/env bash +# shellcheck source-path=SCRIPTDIR set -e # setting up colors BLU='\033[0;34m' -YLW='\033[0;33m' GRN='\033[0;32m' RED='\033[0;31m' NOC='\033[0m' # No Color echo_info() { - printf "\n${BLU}%s${NOC}" "$1" + printf '\n%b%s%b' "${BLU}" "$1" "${NOC}" } echo_step() { - printf "\n${BLU}>>>>>>> %s${NOC}\n" "$1" + printf '\n%b>>>>>>> %s%b\n' "${BLU}" "$1" "${NOC}" } echo_sub_step() { - printf "\n${BLU}>>> %s${NOC}\n" "$1" + printf '\n%b>>> %s%b\n' "${BLU}" "$1" "${NOC}" } echo_step_completed() { - printf "${GRN} [✔]${NOC}" + printf '%b [✔]%b' "${GRN}" "${NOC}" } echo_success() { - printf "\n${GRN}%s${NOC}\n" "$1" -} -echo_warn() { - printf "\n${YLW}%s${NOC}" "$1" + printf '\n%b%s%b\n' "${GRN}" "$1" "${NOC}" } echo_error() { - printf "\n${RED}%s${NOC}" "$1" + printf '\n%b%s%b' "${RED}" "$1" "${NOC}" exit 1 } # ------------------------------ -projectdir="$( cd "$( dirname "${BASH_SOURCE[0]}")"/../.. && pwd )" -scriptdir="$(dirname "$0")" +scriptdir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +projectdir="$(cd "${scriptdir}/../.." && pwd)" # 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" MARIADB_ROOT_PW=$(openssl rand -base64 32) MARIADB_TEST_PW=$(openssl rand -base64 32) -MSSQL_SA_PW="$(openssl rand -base64 16)Aa1!" # MSSQL requires complex password + +skipcleanup="${skipcleanup:-false}" # cleanup on exit if [ "$skipcleanup" != true ]; then @@ -65,20 +60,8 @@ fi # Global variable to control API type API_TYPE="cluster" -SCRIPT_DIR="$(dirname "$(realpath "$0")")" -# shellcheck source="$SCRIPT_DIR/postgresdb_functions.sh" -source "$SCRIPT_DIR/postgresdb_functions.sh" -if [ $? -ne 0 ]; then - echo "postgresdb_functions.sh failed. Exiting." - exit 1 -fi - -# shellcheck source="$SCRIPT_DIR/mssqldb_functions.sh" -source "$SCRIPT_DIR/mssqldb_functions.sh" -if [ $? -ne 0 ]; then - echo "mssqldb_functions.sh failed. Exiting." - exit 1 -fi +source "${scriptdir}/postgresdb_functions.sh" +source "${scriptdir}/mssqldb_functions.sh" integration_tests_end() { if [ "$skipcleanup" != true ]; then @@ -119,7 +102,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 @@ -129,13 +112,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 @@ -146,38 +129,37 @@ cleanup_provider() { } setup_tls_certs() { + certdir="$(mktemp -d)" echo_step "generating CA key and certificate" - openssl genrsa -out ca-key.pem 2048 - openssl req -new -x509 -key ca-key.pem -out ca-cert.pem -days 365 -subj "/CN=CA" + openssl genrsa -out "${certdir}/ca-key.pem" 2048 + openssl req -new -x509 -key "${certdir}/ca-key.pem" -out "${certdir}/ca-cert.pem" -days 365 -subj "/CN=CA" echo_step "generating server key and certificate" - openssl genrsa -out server-key.pem 2048 - openssl req -new -key server-key.pem -out server-req.pem -subj "/CN=mariadb.default.svc.cluster.local" - openssl x509 -req -in server-req.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365 + openssl genrsa -out "${certdir}/server-key.pem" 2048 + openssl req -new -key "${certdir}/server-key.pem" -out "${certdir}/server-req.pem" -subj "/CN=mariadb.default.svc.cluster.local" + openssl x509 -req -in "${certdir}/server-req.pem" -CA "${certdir}/ca-cert.pem" -CAkey "${certdir}/ca-key.pem" -CAcreateserial -out "${certdir}/server-cert.pem" -days 365 echo_step "generating client key and certificate" - openssl genrsa -out client-key.pem 2048 - openssl req -new -key client-key.pem -out client-req.pem -subj "/CN=client" - openssl x509 -req -in client-req.pem -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365 + openssl genrsa -out "${certdir}/client-key.pem" 2048 + openssl req -new -key "${certdir}/client-key.pem" -out "${certdir}/client-req.pem" -subj "/CN=client" + openssl x509 -req -in "${certdir}/client-req.pem" -CA "${certdir}/ca-cert.pem" -CAkey "${certdir}/ca-key.pem" -CAcreateserial -out "${certdir}/client-cert.pem" -days 365 echo_step "creating secret for the TLS certificates and keys" "${KUBECTL}" create secret generic mariadb-server-tls \ - --from-file=ca-cert.pem \ - --from-file=server-cert.pem \ - --from-file=server-key.pem + --from-file="${certdir}/ca-cert.pem" \ + --from-file="${certdir}/server-cert.pem" \ + --from-file="${certdir}/server-key.pem" echo_step "creating secret for the client TLS certificates and keys" "${KUBECTL}" create secret generic mariadb-client-tls \ - --from-file=ca-cert.pem \ - --from-file=client-cert.pem \ - --from-file=client-key.pem + --from-file="${certdir}/ca-cert.pem" \ + --from-file="${certdir}/client-cert.pem" \ + --from-file="${certdir}/client-key.pem" } cleanup_tls_certs() { echo_step "cleaning up TLS certificate files and secrets" - for file in *.pem *.srl; do - rm -f "$file" - done + rm -rf "${certdir}" "${KUBECTL}" delete secret mariadb-server-tls "${KUBECTL}" delete secret mariadb-client-tls } @@ -194,7 +176,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() { @@ -205,7 +187,7 @@ setup_mariadb_no_tls() { --from-literal endpoint="mariadb.default.svc.cluster.local" \ --from-literal port="3306" - "${KUBECTL}" apply -f ${scriptdir}/mariadb.server.yaml + "${KUBECTL}" apply -f "${scriptdir}/mariadb.server.yaml" echo_step "Waiting for MariaDB to be ready" "${KUBECTL}" rollout status statefulset/mariadb --timeout=120s @@ -218,9 +200,9 @@ setup_mariadb_tls() { --from-literal password="${MARIADB_TEST_PW}" \ --from-literal endpoint="mariadb.default.svc.cluster.local" \ --from-literal port="3306" \ - --from-file=ca-cert.pem \ - --from-file=client-cert.pem \ - --from-file=client-key.pem + --from-file="${certdir}/ca-cert.pem" \ + --from-file="${certdir}/client-cert.pem" \ + --from-file="${certdir}/client-key.pem" # Create init script ConfigMap "${KUBECTL}" create configmap mariadb-init-script --from-literal=init.sql=" @@ -244,12 +226,23 @@ cleanup_mariadb() { "${KUBECTL}" delete secret mariadb-creds } +# Runs SQL as root inside the MariaDB pod. The password expands in the pod, the SQL is passed as $1. +mariadb_query() { + # shellcheck disable=SC2016 + "${KUBECTL}" exec mariadb-0 -- bash -c 'mariadb -uroot -p"${MARIADB_ROOT_PASSWORD}" -N -e "$1"' _ "$1" +} + +# Prints a column of information_schema.schemata for example-db. +example_db_schema_field() { + mariadb_query "SELECT $1 FROM information_schema.schemata WHERE schema_name = 'example-db'" | tr -d '[:space:]' +} + test_create_database() { echo_step "test creating MySQL Database resource" - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mysql/database.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/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 "${EXAMPLES}/mysql/database.yaml" echo_step_completed } @@ -257,13 +250,8 @@ test_database_charset() { echo_step "test database has correct charset and collation" local charset collation - charset=$("${KUBECTL}" exec mariadb-0 -- bash -c \ - 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "SELECT default_character_set_name FROM information_schema.schemata WHERE schema_name = '"'"'example-db'"'"'"') - collation=$("${KUBECTL}" exec mariadb-0 -- bash -c \ - 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "SELECT default_collation_name FROM information_schema.schemata WHERE schema_name = '"'"'example-db'"'"'"') - - charset=$(echo "${charset}" | tr -d '[:space:]') - collation=$(echo "${collation}" | tr -d '[:space:]') + charset=$(example_db_schema_field default_character_set_name) + collation=$(example_db_schema_field default_collation_name) echo_info "charset=${charset}, collation=${collation}" @@ -280,7 +268,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 @@ -288,9 +276,7 @@ test_update_database_charset() { echo_info "check if collation was updated in MariaDB" local collation - collation=$("${KUBECTL}" exec mariadb-0 -- bash -c \ - 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "SELECT default_collation_name FROM information_schema.schemata WHERE schema_name = '"'"'example-db'"'"'"') - collation=$(echo "${collation}" | tr -d '[:space:]') + collation=$(example_db_schema_field default_collation_name) echo_info "collation=${collation}" @@ -300,7 +286,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 } @@ -309,25 +295,20 @@ 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" local charset collation - charset=$("${KUBECTL}" exec mariadb-0 -- bash -c \ - 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "SELECT default_character_set_name FROM information_schema.schemata WHERE schema_name = '"'"'example-db'"'"'"') - collation=$("${KUBECTL}" exec mariadb-0 -- bash -c \ - 'mariadb -uroot -p${MARIADB_ROOT_PASSWORD} -N -e "SELECT default_collation_name FROM information_schema.schemata WHERE schema_name = '"'"'example-db'"'"'"') - - charset=$(echo "${charset}" | tr -d '[:space:]') - collation=$(echo "${collation}" | tr -d '[:space:]') + charset=$(example_db_schema_field default_character_set_name) + collation=$(example_db_schema_field default_collation_name) echo_info "charset=${charset}, collation=${collation}" @@ -344,14 +325,15 @@ 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 "${EXAMPLES}/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 "${EXAMPLES}/mysql/user.yaml" echo_step_completed echo_info "check if connection secret exists" - local pw=$("${KUBECTL}" get secret example-connection-secret -ojsonpath='{.data.password}' | base64 --decode) + local pw + pw=$("${KUBECTL}" get secret example-connection-secret -ojsonpath='{.data.password}' | base64 --decode) [ "${pw}" == "${user_pw}" ] echo_step_completed } @@ -363,27 +345,28 @@ test_update_user_password() { "${KUBECTL}" apply -f - # trigger reconcile - "${KUBECTL}" annotate -f ${projectdir}/examples/${API_TYPE}/mysql/user.yaml reconcile=now + "${KUBECTL}" annotate -f "${EXAMPLES}/mysql/user.yaml" reconcile=now sleep 3 echo_info "check if connection secret has been updated" - local pw=$("${KUBECTL}" get secret example-connection-secret -ojsonpath='{.data.password}' | base64 --decode) + local pw + pw=$("${KUBECTL}" get secret example-connection-secret -ojsonpath='{.data.password}' | base64 --decode) [ "${pw}" == "${user_pw}" ] echo_step_completed } test_create_grant() { echo_step "test creating MySQL Grant resource" - "${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);"' + # shellcheck disable=SC2016 # backticks are MariaDB identifier quotes + mariadb_query '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 "${EXAMPLES}/mysql/grant_database.yaml" + "${KUBECTL}" apply -f "${EXAMPLES}/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 "${EXAMPLES}/mysql/grant_database.yaml" + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/mysql/grant_table.yaml" echo_step_completed } @@ -399,22 +382,22 @@ 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 "${EXAMPLES}/mysql/grant_database.yaml" + "${KUBECTL}" delete -f "${EXAMPLES}/mysql/grant_table.yaml" + "${KUBECTL}" delete -f "${EXAMPLES}/mysql/database.yaml" + "${KUBECTL}" delete -f "${EXAMPLES}/mysql/user.yaml" "${KUBECTL}" delete secret example-pw } -# DB selects which database suite to run: all (default), mysql, postgresql or mssql. -DB="${DB:-all}" -case "${DB}" in +# ENGINE selects which database engine suite to run: all (default), mysql, postgresql or mssql. +ENGINE="${ENGINE:-all}" +case "${ENGINE}" in all|mysql|postgresql|mssql) ;; - *) echo_error "Invalid DB='${DB}'. Expected one of: all, mysql, postgresql, mssql." ;; + *) echo_error "Invalid ENGINE='${ENGINE}'. Expected one of: all, mysql, postgresql, mssql." ;; esac -db_selected() { - [[ "${DB}" == "all" || "${DB}" == "$1" ]] +engine_selected() { + [[ "${ENGINE}" == "all" || "${ENGINE}" == "$1" ]] } # reuse a cluster kept by a previous run with skipcleanup=true @@ -459,29 +442,30 @@ run_test() { APIGROUP_SUFFIX="m." fi + EXAMPLES="${projectdir}/examples/${API_TYPE}" local testmain="$1" echo_step "--- TESTING $testmain $API_TYPE WITH TLS=$TLS ---" start=$(date +%s) - $testmain + "$testmain" duration=$(( $(date +%s) - start )) echo_step "--- TESTING $testmain DONE IN ${duration}s ---" } -if db_selected mysql; then +if engine_selected mysql; then TLS=true API_TYPE="namespaced" run_test integration_tests_mariadb TLS=false API_TYPE="cluster" run_test integration_tests_mariadb fi -if db_selected postgresql; then +if engine_selected postgresql; then TLS=false API_TYPE="cluster" run_test integration_tests_postgres TLS=false API_TYPE="namespaced" run_test integration_tests_postgres fi # no TLS=false variant - MSSQL uses built-in encryption -if db_selected mssql; then +if engine_selected mssql; then TLS=true API_TYPE="cluster" run_test integration_tests_mssql TLS=true API_TYPE="namespaced" run_test integration_tests_mssql fi diff --git a/cluster/local/mssqldb_functions.sh b/cluster/local/mssqldb_functions.sh index ddeef31a..c663ce80 100644 --- a/cluster/local/mssqldb_functions.sh +++ b/cluster/local/mssqldb_functions.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash -set -e +# Sourced by integration_tests.sh, which defines KUBECTL, scriptdir, EXAMPLES, APIGROUP_SUFFIX and the echo_* helpers. +# shellcheck disable=SC2154 MSSQL_IMAGE="${MSSQL_IMAGE:-mcr.microsoft.com/mssql/server:2019-CU32-ubuntu-20.04}" setup_mssql() { echo_step "installing MSSQL Server (image ${MSSQL_IMAGE})" + local sa_pw + sa_pw="$(openssl rand -base64 16)Aa1!" # MSSQL requires complex password "${KUBECTL}" create secret generic mssql-creds \ --from-literal username="sa" \ - --from-literal password="${MSSQL_SA_PW}" \ + --from-literal password="${sa_pw}" \ --from-literal endpoint="mssql.default.svc.cluster.local" \ --from-literal port="1433" - 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 - @@ -27,7 +27,9 @@ 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 + # StatefulSet PVCs outlive the StatefulSet; a reused volume keeps the previous run's SA password. + "${KUBECTL}" delete pvc data-mssql-0 --ignore-not-found=true "${KUBECTL}" delete secret mssql-creds --ignore-not-found=true } @@ -38,15 +40,15 @@ setup_mssql_provider_config() { cleanup_mssql_provider_config() { echo_step "cleaning up MSSQL provider config" - "${KUBECTL}" delete providerconfig.mssql.sql.${APIGROUP_SUFFIX}crossplane.io default --ignore-not-found=true + "${KUBECTL}" delete "providerconfig.mssql.sql.${APIGROUP_SUFFIX}crossplane.io" default --ignore-not-found=true } test_create_mssql_database() { echo_step "test creating MSSQL Database resource" - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mssql/database.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/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 "${EXAMPLES}/mssql/database.yaml" echo_step_completed } @@ -56,10 +58,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 "${EXAMPLES}/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 "${EXAMPLES}/mssql/user.yaml" echo_step_completed } @@ -68,10 +70,10 @@ test_update_mssql_user_password() { echo_step "test updating MSSQL User password" # Update password secret - "${KUBECTL}" patch secret example-pw -p '{"data":{"password":"'$(echo -n "NewTest123!" | base64)'"}}' + "${KUBECTL}" create secret generic example-pw --from-literal password="NewTest123!" --dry-run=client -o yaml | "${KUBECTL}" apply -f - # Force reconcile by adding annotation - "${KUBECTL}" annotate -f ${projectdir}/examples/${API_TYPE}/mssql/user.yaml reconcile=now + "${KUBECTL}" annotate -f "${EXAMPLES}/mssql/user.yaml" reconcile=now # Wait a bit for password update sleep 10 @@ -81,10 +83,10 @@ test_update_mssql_user_password() { test_create_mssql_grant() { echo_step "test creating MSSQL Grant resource" - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mssql/grant.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/mssql/grant.yaml" echo_step "Waiting for MSSQL Grant to be ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/mssql/grant.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/mssql/grant.yaml" echo_step_completed } @@ -95,18 +97,18 @@ test_create_mssql_shared_login_users() { "${KUBECTL}" create secret generic shared-login-pw \ --from-literal password="Shared123!" --dry-run=client -o yaml | "${KUBECTL}" apply -f - - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/mssql/user-shared-login.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/mssql/user-shared-login.yaml" echo_step "Waiting for db1/db2 to be ready" "${KUBECTL}" wait --timeout 2m --for condition=Ready \ - database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db1 \ - database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db2 + "database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db1" \ + "database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db2" echo_step "Waiting for both shared-login Users to be ready" # Before the fix the second User is stuck with "server principal already exists". "${KUBECTL}" wait --timeout 2m --for condition=Ready \ - user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/shared-login-user-db1 \ - user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/shared-login-user-db2 + "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/shared-login-user-db1" \ + "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/shared-login-user-db2" echo_step_completed # DROP LOGIN guard: enable deletion (examples orphan) and delete sequentially - @@ -119,10 +121,10 @@ test_create_mssql_shared_login_users() { enable_delete='{"spec":{"deletionPolicy":"Delete"}}' fi for u in shared-login-user-db1 shared-login-user-db2; do - "${KUBECTL}" patch user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u \ + "${KUBECTL}" patch "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u" \ --type merge -p "${enable_delete}" - "${KUBECTL}" delete user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u - "${KUBECTL}" wait --for=delete user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u --timeout=90s + "${KUBECTL}" delete "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u" + "${KUBECTL}" wait --for=delete "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/$u" --timeout=90s done echo_step_completed } @@ -137,20 +139,20 @@ test_mssql_all() { cleanup_mssql_test_resources() { echo_step "cleaning up MSSQL test resources" - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mssql/grant.yaml --ignore-not-found=true - "${KUBECTL}" wait --for=delete grant.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-grant --timeout=60s + "${KUBECTL}" delete -f "${EXAMPLES}/mssql/grant.yaml" --ignore-not-found=true + "${KUBECTL}" wait --for=delete "grant.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-grant" --timeout=60s - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mssql/user.yaml --ignore-not-found=true - "${KUBECTL}" wait --for=delete user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-user --timeout=60s + "${KUBECTL}" delete -f "${EXAMPLES}/mssql/user.yaml" --ignore-not-found=true + "${KUBECTL}" wait --for=delete "user.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-user" --timeout=60s # Users already deleted by the test; this removes db1/db2 and the secret. - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mssql/user-shared-login.yaml --ignore-not-found=true - "${KUBECTL}" wait --for=delete database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db1 --timeout=60s - "${KUBECTL}" wait --for=delete database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db2 --timeout=60s + "${KUBECTL}" delete -f "${EXAMPLES}/mssql/user-shared-login.yaml" --ignore-not-found=true + "${KUBECTL}" wait --for=delete "database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db1" --timeout=60s + "${KUBECTL}" wait --for=delete "database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/db2" --timeout=60s "${KUBECTL}" delete secret shared-login-pw --ignore-not-found=true - "${KUBECTL}" delete -f ${projectdir}/examples/${API_TYPE}/mssql/database.yaml --ignore-not-found=true - "${KUBECTL}" wait --for=delete database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-db --timeout=60s + "${KUBECTL}" delete -f "${EXAMPLES}/mssql/database.yaml" --ignore-not-found=true + "${KUBECTL}" wait --for=delete "database.mssql.sql.${APIGROUP_SUFFIX}crossplane.io/example-db" --timeout=60s echo_step "deleting example password secret" "${KUBECTL}" delete secret example-pw --ignore-not-found=true diff --git a/cluster/local/postgresdb_functions.sh b/cluster/local/postgresdb_functions.sh index 48622c1b..950fbf52 100644 --- a/cluster/local/postgresdb_functions.sh +++ b/cluster/local/postgresdb_functions.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash -set -e +# Sourced by integration_tests.sh, which defines KUBECTL, scriptdir, EXAMPLES, APIGROUP_SUFFIX and the echo_* helpers. +# shellcheck disable=SC2154 + +# Runs SQL as postgres inside the PostgreSQL pod (local socket, trust auth): pg_query +pg_query() { + "${KUBECTL}" exec postgresdb-postgresql-0 -- psql -U postgres -d "$1" -wtAc "$2" +} setup_postgresdb_no_tls() { echo_step "Installing PostgresDB into default namespace" @@ -11,16 +17,13 @@ setup_postgresdb_no_tls() { --from-literal endpoint="postgresdb-postgresql.default.svc.cluster.local" \ --from-literal port="5432" - scriptdir=$(dirname "$0") + # shellcheck disable=SC2016 POSTGRES_VERSION="${POSTGRES_VERSION:-18}" envsubst '${POSTGRES_VERSION}' < "${scriptdir}/postgres.server.yaml" | "${KUBECTL}" apply -f - echo_step "Waiting for PostgreSQL to be ready" "${KUBECTL}" rollout status statefulset/postgresdb-postgresql --timeout=120s - "${KUBECTL}" port-forward --namespace default svc/postgresdb-postgresql 5432:5432 > >(grep -v "Handling connection for") 2>&1 & - PORT_FORWARD_PID=$! - - while ! PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc "SELECT 1;"; do + until pg_query postgres "SELECT 1;" > /dev/null 2>&1; do echo "Waiting for PostgresDB to be ready..." sleep 2 done @@ -57,12 +60,8 @@ create_grantable_objects() { CREATE FOREIGN DATA WRAPPER test_foreign_data_wrapper; CREATE SERVER test_foreign_server FOREIGN DATA WRAPPER test_foreign_data_wrapper; " - create_objects=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d "$TARGET_DB" -wtAc "$request") - if [ $? -eq 0 ]; then - echo_info "PostgresDB objects created in schema public" - else - echo_error "ERROR: could not create grantable objects: $create_objects" - fi + pg_query "$TARGET_DB" "$request" > /dev/null + echo_info "PostgresDB objects created in schema public" } delete_grantable_objects() { @@ -80,19 +79,15 @@ delete_grantable_objects() { DROP SEQUENCE \"$TARGET_SCHEMA\".test_sequence_2; DROP TABLE \"$TARGET_SCHEMA\".test_table; " - drop_objects=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d "$TARGET_DB" -wtAc "$request") - if [ $? -eq 0 ]; then - echo_info "PostgresDB objects dropped from schema public" - else - echo_error "ERROR: could not delete grantable objects: $drop_objects" - fi + pg_query "$TARGET_DB" "$request" > /dev/null + echo_info "PostgresDB objects dropped from schema public" } setup_postgresdb_tests(){ # install provider resources echo_step "creating PostgresDB Database resource" # create DB - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/postgresql/database.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/database.yaml" echo_step "creating bring-your-own password secret for Role" # byo-password-role in role.yaml references this secret; without it that Role @@ -104,22 +99,22 @@ setup_postgresdb_tests(){ echo_step "creating PostgresDB Role resource" # create grant - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/postgresql/role.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/role.yaml" echo_step "creating PostgresDB Schema resources" # create grant - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/postgresql/schema.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/schema.yaml" echo_step "check if Role is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/role.yaml > /dev/null + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/postgresql/role.yaml" > /dev/null echo_step_completed echo_step "check if database is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/database.yaml > /dev/null + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/postgresql/database.yaml" > /dev/null echo_step_completed echo_step "check if schema is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/schema.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/postgresql/schema.yaml" echo_step_completed echo_step "create grantable objects" @@ -128,10 +123,10 @@ setup_postgresdb_tests(){ echo_step "creating PostgresDB Grant resource" # create grant - "${KUBECTL}" apply -f ${projectdir}/examples/${API_TYPE}/postgresql/grant.yaml + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/grant.yaml" echo_step "check if grant is ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f ${projectdir}/examples/${API_TYPE}/postgresql/grant.yaml + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/postgresql/grant.yaml" echo_step_completed } @@ -139,7 +134,7 @@ check_database_owner_ref() { echo_step "check if database created with ownerSelector has correct owner" local owner - owner=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d postgres -wtAc \ + owner=$(pg_query postgres \ "SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = 'db-owner-ref';") owner=$(echo "${owner}" | xargs) @@ -156,21 +151,8 @@ check_all_roles_privileges() { # check if granting mechanism is working properly echo_step "check if grant mechanism is working" - TARGET_DB='db1' - OWNER_ROLE='ownerrole' - USER_ROLE='example-role' - - # Define roles and their expected privileges - roles="$OWNER_ROLE $USER_ROLE" - privileges="CONNECT|CREATE|TEMP ||" - - # Iterate over roles and expected privileges - role_index=1 - for role in $roles; do - expected_privileges=$(echo "$privileges" | cut -d ' ' -f $role_index) - check_role_privileges "$role" "$expected_privileges" "${postgres_root_pw}" "$TARGET_DB" - role_index=$((role_index + 1)) - done + check_role_privileges ownerrole 'CONNECT|CREATE|TEMP' db1 + check_role_privileges example-role '||' db1 echo_step_completed } @@ -178,11 +160,11 @@ check_all_roles_privileges() { check_role_privileges() { local role=$1 local expected_privileges=$2 - local target_db=$4 + local target_db=$3 echo -n "Privileges for role: $role (expected: $expected_privileges)" - result=$(PGPASSWORD="$3" psql -h localhost -p 5432 -U postgres -d postgres -wtAc" SELECT CASE WHEN has_database_privilege('$role', '$target_db', 'CONNECT') THEN 'CONNECT' ELSE NULL END, CASE WHEN has_database_privilege('$role', '$target_db', 'CREATE') THEN 'CREATE' ELSE NULL END, CASE WHEN has_database_privilege('$role', '$target_db', 'TEMP') THEN 'TEMP' ELSE NULL END " | tr '\n' ',' | sed 's/,$//') + result=$(pg_query postgres "SELECT CASE WHEN has_database_privilege('$role', '$target_db', 'CONNECT') THEN 'CONNECT' ELSE NULL END, CASE WHEN has_database_privilege('$role', '$target_db', 'CREATE') THEN 'CREATE' ELSE NULL END, CASE WHEN has_database_privilege('$role', '$target_db', 'TEMP') THEN 'TEMP' ELSE NULL END " | tr '\n' ',' | sed 's/,$//') if [ "$result" = "$expected_privileges" ]; then echo " condition met" @@ -197,24 +179,8 @@ check_all_schema_privileges() { # check if schema privileges are set properly echo_step "check if schema privileges are set properly" - OWNER_ROLE='ownerrole' - USER_ROLE='no-grants-role' - - # Define roles and their expected privileges - roles="$OWNER_ROLE $USER_ROLE" - dbs="db1 example" - schemas="public my-schema" - privileges="USAGE|f,CREATE|f USAGE|t,CREATE|t" - - # Iterate over roles and expected privileges - role_index=1 - for role in $roles; do - expected_privileges=$(echo "$privileges" | cut -d ' ' -f $role_index) - target_db=$(echo "$dbs" | cut -d ' ' -f $role_index) - target_schema=$(echo "$schemas" | cut -d ' ' -f $role_index) - check_schema_privileges "$role" "$expected_privileges" "${postgres_root_pw}" "$target_db" "$target_schema" - role_index=$((role_index + 1)) - done + check_schema_privileges ownerrole 'USAGE|f,CREATE|f' db1 public + check_schema_privileges no-grants-role 'USAGE|t,CREATE|t' example my-schema echo_step_completed } @@ -227,7 +193,7 @@ check_privileges(){ local request=$5 echo -n "Privileges on $object for role: $role (expected: $expected)" - response=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d "$target_db" -wtAc "$request") + response=$(pg_query "$target_db" "$request") response=$(echo "$response" | xargs | tr ' ' ',') if [[ "$response" == "$expected" ]]; then @@ -242,12 +208,12 @@ check_privileges(){ check_schema_privileges(){ local role=$1 local expected_privileges=$2 - local target_db=$4 - local target_schema=$5 + local target_db=$3 + local target_schema=$4 request="select acl.privilege_type, acl.is_grantable from pg_namespace n, aclexplode(n.nspacl) acl INNER JOIN pg_roles s ON acl.grantee = s.oid where n.nspname = '$target_schema' and s.rolname='$role'" - check_privileges $target_db "schema $target_db.$target_schema" $role $expected_privileges "$request" + check_privileges "$target_db" "schema $target_db.$target_schema" "$role" "$expected_privileges" "$request" } check_table_privileges(){ @@ -259,7 +225,7 @@ check_table_privileges(){ request="select privilege_type, is_grantable from information_schema.role_table_grants where grantee = '$role' and table_schema = '$schema' and table_name='$table' order by privilege_type asc" - check_privileges $target_db "table $schema.$table" $role $expected_privileges "$request" + check_privileges "$target_db" "table $schema.$table" "$role" "$expected_privileges" "$request" } check_sequence_privileges(){ @@ -268,13 +234,10 @@ check_sequence_privileges(){ role='no-grants-role' expected_privileges='SELECT|f,UPDATE|f,USAGE|f' - sequence="test_sequence_1" - request="select acl.privilege_type, acl.is_grantable from pg_class c inner join pg_namespace n on c.relnamespace = n.oid, aclexplode(c.relacl) as acl inner join pg_roles s on acl.grantee = s.oid where c.relkind = 'S' and n.nspname = '$schema' and s.rolname='$role' and c.relname = '$sequence'" - check_privileges $target_db "sequence $schema.$sequence" $role $expected_privileges "$request" - - sequence="test_sequence_2" - request="select acl.privilege_type, acl.is_grantable from pg_class c inner join pg_namespace n on c.relnamespace = n.oid, aclexplode(c.relacl) as acl inner join pg_roles s on acl.grantee = s.oid where c.relkind = 'S' and n.nspname = '$schema' and s.rolname='$role' and c.relname = '$sequence'" - check_privileges $target_db "sequence $schema.$sequence" $role $expected_privileges "$request" + for sequence in test_sequence_1 test_sequence_2; do + request="select acl.privilege_type, acl.is_grantable from pg_class c inner join pg_namespace n on c.relnamespace = n.oid, aclexplode(c.relacl) as acl inner join pg_roles s on acl.grantee = s.oid where c.relkind = 'S' and n.nspname = '$schema' and s.rolname='$role' and c.relname = '$sequence'" + check_privileges "$target_db" "sequence $schema.$sequence" "$role" "$expected_privileges" "$request" + done } check_routine_privileges(){ @@ -286,7 +249,7 @@ check_routine_privileges(){ request="select privilege_type, is_grantable from information_schema.role_routine_grants where grantee = '$role' and routine_schema = '$schema' and routine_name='$routine' order by privilege_type asc" - check_privileges $target_db "routine $schema.$routine" $role $expected_privileges "$request" + check_privileges "$target_db" "routine $schema.$routine" "$role" "$expected_privileges" "$request" } check_column_privileges(){ @@ -299,7 +262,7 @@ check_column_privileges(){ request="select privilege_type, is_grantable from information_schema.role_column_grants where grantee = '$role' and table_schema = '$schema' and table_name='$table' and column_name='$column' order by privilege_type asc" - check_privileges $target_db "column $column on table $schema.$table" $role $expected_privileges "$request" + check_privileges "$target_db" "column $column on table $schema.$table" "$role" "$expected_privileges" "$request" } check_foreign_data_wrapper_privileges(){ @@ -310,7 +273,7 @@ check_foreign_data_wrapper_privileges(){ request="select privilege_type, is_grantable from information_schema.role_usage_grants where grantee = '$role' and object_type = 'FOREIGN DATA WRAPPER' and object_name='$foreign_data_wrapper' order by privilege_type asc" - check_privileges $target_db "foreign data wrapper $foreign_data_wrapper" $role $expected_privileges "$request" + check_privileges "$target_db" "foreign data wrapper $foreign_data_wrapper" "$role" "$expected_privileges" "$request" } check_foreign_server_privileges(){ @@ -321,7 +284,7 @@ check_foreign_server_privileges(){ request="select privilege_type, is_grantable from information_schema.role_usage_grants where grantee = '$role' and object_type = 'FOREIGN SERVER' and object_name='$foreign_server' order by privilege_type asc" - check_privileges $target_db "foreign server $foreign_server" $role $expected_privileges "$request" + check_privileges "$target_db" "foreign server $foreign_server" "$role" "$expected_privileges" "$request" } check_all_privileges_table_grant(){ @@ -334,7 +297,7 @@ check_all_privileges_table_grant(){ table="test_table" role='example-role' - pg_major=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc \ + pg_major=$(pg_query postgres \ "SELECT current_setting('server_version_num')::int / 10000;" | xargs) if [ "${pg_major}" -ge 17 ]; then @@ -345,14 +308,13 @@ check_all_privileges_table_grant(){ request="select acl.privilege_type, acl.is_grantable from pg_class c inner join pg_namespace n on c.relnamespace = n.oid, aclexplode(c.relacl) as acl inner join pg_roles s on acl.grantee = s.oid where c.relkind = 'r' and n.nspname = '$schema' and s.rolname='$role' and c.relname = '$table' order by acl.privilege_type asc" - check_privileges $target_db "ALL PRIVILEGES on table $schema.$table" $role $expected_privileges "$request" + check_privileges "$target_db" "ALL PRIVILEGES on table $schema.$table" "$role" "$expected_privileges" "$request" } setup_observe_only_database(){ echo_step "create pre-existing database for observe only" - local datname - datname="$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc "CREATE DATABASE \"db-observe\";")" + pg_query postgres "CREATE DATABASE \"db-observe\";" > /dev/null echo_step_completed } @@ -361,10 +323,10 @@ check_observe_only_database(){ echo_step "check if observe only database is preserved after deletion" # Delete the database kubernetes object, it should not delete the database - "${KUBECTL}" delete database.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io db-observe + "${KUBECTL}" delete "database.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io" db-observe local datname - datname="$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc "SELECT datname FROM pg_database WHERE datname = 'db-observe';")" + datname="$(pg_query postgres "SELECT datname FROM pg_database WHERE datname = 'db-observe';")" if [[ "$datname" == "db-observe" ]]; then echo "Database db-observe is still present" @@ -375,7 +337,7 @@ check_observe_only_database(){ fi # Clean up - PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -wtAc "DROP DATABASE \"db-observe\";" + pg_query postgres "DROP DATABASE \"db-observe\";" echo_step_completed } @@ -388,7 +350,7 @@ check_wildcard_privileges(){ schema="public" role='all-objects-role' - missing=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d "$target_db" -wtAc \ + missing=$(pg_query "$target_db" \ "select count(*) from pg_class c join pg_namespace n on c.relnamespace = n.oid where c.relkind in ('r','p','v','m','f') and n.nspname = '$schema' and not has_table_privilege('$role', c.oid, 'SELECT');" | xargs) @@ -400,7 +362,7 @@ check_wildcard_privileges(){ fi # Same for routines, which take EXECUTE rather than SELECT. - missing=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d "$target_db" -wtAc \ + missing=$(pg_query "$target_db" \ "select count(*) from pg_proc p join pg_namespace n on p.pronamespace = n.oid where n.nspname = '$schema' and not has_function_privilege('$role', p.oid, 'EXECUTE');" | xargs) @@ -433,18 +395,15 @@ delete_postgresdb_resources(){ # uninstall echo_step "uninstalling ${PROJECT_NAME}" - "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/grant.yaml" - "${KUBECTL}" delete --ignore-not-found=true -f "${projectdir}/examples/${API_TYPE}/postgresql/database.yaml" - "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/role.yaml" + "${KUBECTL}" delete -f "${EXAMPLES}/postgresql/grant.yaml" + "${KUBECTL}" delete --ignore-not-found=true -f "${EXAMPLES}/postgresql/database.yaml" + "${KUBECTL}" delete -f "${EXAMPLES}/postgresql/role.yaml" "${KUBECTL}" delete --ignore-not-found=true secret my-role-password -n default - "${KUBECTL}" delete -f "${projectdir}/examples/${API_TYPE}/postgresql/schema.yaml" - echo "${PROVIDER_CONFIG_POSTGRES_YAML}" | "${KUBECTL}" delete -f - + "${KUBECTL}" delete -f "${EXAMPLES}/postgresql/schema.yaml" + "${KUBECTL}" delete -f "${scriptdir}/postgres.providerconfig.${API_TYPE}.yaml" # ----------- cleaning postgres related resources - echo_step "kill port-forwarding" - kill $PORT_FORWARD_PID - echo_step "uninstalling secret and provider config for postgres" "${KUBECTL}" delete secret postgresdb-creds @@ -459,11 +418,11 @@ setup_extension_test() { # Apply extension resources echo_sub_step "Creating PostgreSQL extensions" - "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/postgresql/extension.yaml" + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/extension.yaml" # Wait for extensions to be ready echo_sub_step "Waiting for extensions to be ready" - "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${projectdir}/examples/${API_TYPE}/postgresql/extension.yaml" > /dev/null + "${KUBECTL}" wait --timeout 2m --for condition=Ready -f "${EXAMPLES}/postgresql/extension.yaml" > /dev/null echo_step_completed } @@ -472,8 +431,8 @@ check_extension_test() { # Check that extensions are ready echo_sub_step "Checking extension status" - hstore_status=$("${KUBECTL}" get extension.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/hstore-extension-db -n default -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') - ltree_status=$("${KUBECTL}" get extension.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/ltree-extension-db -n default -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') + hstore_status=$("${KUBECTL}" get "extension.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/hstore-extension-db" -n default -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') + ltree_status=$("${KUBECTL}" get "extension.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/ltree-extension-db" -n default -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') if [[ "$hstore_status" == "True" ]] && [[ "$ltree_status" == "True" ]]; then echo_info "Extensions are Ready as expected" @@ -483,8 +442,8 @@ check_extension_test() { # Verify extensions are installed in the database echo_sub_step "Checking extensions in database" - hstore_installed=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d example -wtAc "SELECT COUNT(*) FROM pg_extension WHERE extname = 'hstore';") - ltree_installed=$(PGPASSWORD="${postgres_root_pw}" psql -h localhost -p 5432 -U postgres -d example -wtAc "SELECT COUNT(*) FROM pg_extension WHERE extname = 'ltree';") + hstore_installed=$(pg_query example "SELECT COUNT(*) FROM pg_extension WHERE extname = 'hstore';") + ltree_installed=$(pg_query example "SELECT COUNT(*) FROM pg_extension WHERE extname = 'ltree';") if [[ "$hstore_installed" == "1" ]] && [[ "$ltree_installed" == "1" ]]; then echo_info "Extensions are installed in database as expected" @@ -497,7 +456,7 @@ check_extension_test() { delete_extension_test() { echo_step "Cleaning up PostgreSQL extensions" - "${KUBECTL}" delete --ignore-not-found=true -f "${projectdir}/examples/${API_TYPE}/postgresql/extension.yaml" + "${KUBECTL}" delete --ignore-not-found=true -f "${EXAMPLES}/postgresql/extension.yaml" echo_step_completed } @@ -586,7 +545,7 @@ check_role_passwords() { "${KUBECTL}" patch "role.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/rotating-role" --type merge \ -p '{"spec":{"managementPolicies":["Observe","Create","Update","LateInitialize"]}}' "${KUBECTL}" delete "role.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/rotating-role" - "${KUBECTL}" apply -f "${projectdir}/examples/${API_TYPE}/postgresql/role.yaml" + "${KUBECTL}" apply -f "${EXAMPLES}/postgresql/role.yaml" if ! "${KUBECTL}" wait --timeout 2m --for condition=Ready \ "role.postgresql.sql.${APIGROUP_SUFFIX}crossplane.io/rotating-role" > /dev/null; then echo_error "ERROR: rotating-role did not become Ready after Role object recreate (possible password-recovery regression)"