Skip to content
Merged
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
27 changes: 26 additions & 1 deletion .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ concurrency:
# older builds for the same pull request numer or branch should be cancelled
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build
Expand Down Expand Up @@ -170,3 +169,29 @@ jobs:
# without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557
token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }}
fail_ci_if_error: true

test-rockylinux9:
needs: build
name: ${{ matrix.cloud }} Node.js ${{ matrix.nodeVersion }} on Rocky Linux 9
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
cloud: ['AWS', 'AZURE', 'GCP']
nodeVersion: ['18', '20', '22']
steps:
- uses: actions/checkout@v4
- name: Test
shell: bash
env:
PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}
CLOUD_PROVIDER: ${{ matrix.cloud }}
GITHUB_ACTIONS: true
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF: ${{ github.ref }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_NAME: ${{ github.event_name }}
RUNNER_TRACKING_ID: ${{ runner.name }}
run: ./ci/test_rhel9_docker.sh ${{ matrix.nodeVersion }}
135 changes: 135 additions & 0 deletions ci/container/test_rhel9_component.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/bin/bash -e
#
# Test NodeJS driver in RHEL 9
# NOTES:
# - Node.js major version MUST be passed in as the first argument, e.g: "18", "20", "22"
# - This is the script that test_rhel9_docker.sh runs inside of the docker container

if [[ -z "${1}" ]]; then
echo "[ERROR] Node.js major version is required as first argument (e.g., '18', '20', '22')"
echo "Usage: $0 <node_major_version>"
exit 1
fi

NODE_VERSION="${1}"
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# CONNECTOR_DIR is the repo root (parent of ci directory)
CONNECTOR_DIR="$( dirname "$( dirname "${THIS_DIR}")")"

# Validate prerequisites - parameters.json should be decrypted before container runs
if [[ ! -f "${CONNECTOR_DIR}/parameters.json" ]]; then
echo "[ERROR] parameters.json not found at ${CONNECTOR_DIR}/parameters.json"
echo "[ERROR] Parameters must be decrypted before running the container"
exit 1
fi

# Setup locale explicitly to ensure UTF-8 encoding (critical for bind tests)
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# Ensure Node.js uses UTF-8 encoding for all operations
export NODE_OPTIONS="--max-old-space-size=4096"
echo "[INFO] Locale settings: LANG=${LANG}, LC_ALL=${LC_ALL}"
locale
echo "[INFO] Verifying locale is available:"
locale -a | grep -i "en_us.utf" || echo "[WARN] en_US.UTF-8 locale not found in available locales"

# Setup Node.js environment
echo "[INFO] Using Node.js major version ${NODE_VERSION}"

# Verify Node.js is available (should be installed directly in Dockerfile)
if ! command -v node &> /dev/null; then
echo "[ERROR] Node.js not found in PATH!"
exit 1
fi

# Verify Node.js major version matches
INSTALLED_NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
if [[ "${INSTALLED_NODE_VERSION}" != "${NODE_VERSION}" ]]; then
echo "[WARN] Installed Node.js major version (${INSTALLED_NODE_VERSION}) does not match requested version (${NODE_VERSION})"
echo "[INFO] Continuing with installed version: ${INSTALLED_NODE_VERSION}"
fi

# Verify Node.js and npm versions
echo "[INFO] Node.js version: $(node --version)"
echo "[INFO] npm version: $(npm --version)"

cd $CONNECTOR_DIR

echo "[INFO] Installing npm dependencies"
npm install

# Load connection parameters (same pattern as test_component.sh)
echo "[INFO] Setting test parameters"
PARAMETER_FILE=${CONNECTOR_DIR}/parameters.json
if [[ ! -f "$PARAMETER_FILE" ]]; then
echo "[ERROR] parameters.json not found at $PARAMETER_FILE"
exit 1
fi
eval $(jq -r '.testconnection | to_entries | map("export \(.key)=\(.value|tostring)")|.[]' $PARAMETER_FILE)

# Sanitize RUNNER_TRACKING_ID to handle spaces (e.g., "GitHub Actions" -> "GitHub_Actions")
# This ensures schema names are SQL-safe when used by both shell and Python scripts
if [[ -n "$RUNNER_TRACKING_ID" ]]; then
export RUNNER_TRACKING_ID=$(echo "$RUNNER_TRACKING_ID" | tr ' ' '_' | tr -cd '[:alnum:]_')
fi

export TARGET_SCHEMA_NAME=${RUNNER_TRACKING_ID//-/_}_${GITHUB_SHA}
export DRIVER_NAME=nodejs
export TIMEOUT=180000
export SF_OCSP_TEST_OCSP_RESPONDER_TIMEOUT=1000

function finish() {
pushd ${CONNECTOR_DIR}/ci/container >& /dev/null
echo "[INFO] Drop schema $TARGET_SCHEMA_NAME"
python3 drop_schema.py
popd >& /dev/null
}
trap finish EXIT

pushd ${CONNECTOR_DIR}/ci/container >& /dev/null
echo "[INFO] Create schema $TARGET_SCHEMA_NAME"
if python3 create_schema.py; then
export SNOWFLAKE_TEST_SCHEMA=$TARGET_SCHEMA_NAME
else
echo "[WARN] SNOWFLAKE_TEST_SCHEMA: $SNOWFLAKE_TEST_SCHEMA"
fi
popd >& /dev/null

env | grep SNOWFLAKE_ | grep -v PASS

[[ -n "$PROXY_IP" ]] && echo "[INFO] SNOWFLAKE_TEST_PROXY_HOST=$PROXY_IP" && export SNOWFLAKE_TEST_PROXY_HOST=$PROXY_IP
[[ -n "$PROXY_PORT" ]] && echo "[INFO] SNOWFLAKE_TEST_PROXY_PORT=$PROXY_PORT" && export SNOWFLAKE_TEST_PROXY_PORT=$PROXY_PORT

echo "[INFO] Starting hang_webserver.py 12345"
python3 ${CONNECTOR_DIR}/ci/container/hang_webserver.py 12345 > hang_webserver.out 2>&1 &

# Configure Wiremock for RHEL9 environment
# Verify Java is accessible before tests
if command -v java &> /dev/null; then
echo "[INFO] Pre-warming Java JVM for faster Wiremock startup"
echo "[INFO] Java version:"
java -version 2>&1 || echo "[WARN] Java version check failed"
echo "[INFO] JAVA_HOME: ${JAVA_HOME:-'not set'}"
echo "[INFO] JAVA_OPTS: ${JAVA_OPTS:-'not set'}"
echo "[INFO] Java executable: $(which java)"
else
echo "[ERROR] Java not found in PATH! Wiremock will not work."
echo "[ERROR] PATH: $PATH"
exit 1
fi

# Set environment variables for optimized Wiremock startup on RHEL9
export WIREMOCK_STARTUP_TIMEOUT_MS=60000
echo "[INFO] Wiremock startup timeout set to ${WIREMOCK_STARTUP_TIMEOUT_MS}ms for RHEL9"

# Run tests using npm test:ci (unit and integration tests)
cd ${CONNECTOR_DIR}
echo "[INFO] Running Tests"
npm run test:ci

# Restore original wiremockRunner.js if backup exists
if [[ -f "${WIREMOCK_RUNNER}.bak" ]]; then
echo "[INFO] Restoring original wiremockRunner.js"
mv "${WIREMOCK_RUNNER}.bak" "$WIREMOCK_RUNNER" 2>/dev/null || true
fi

66 changes: 66 additions & 0 deletions ci/image/Dockerfile.rhel9
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
ARG BASE_IMAGE=rockylinux:9
FROM $BASE_IMAGE

ARG TARGETARCH

# Install minimal required packages + dependencies for Node.js and testing
# Note: Node.js will be installed via dnf module system
# Java is required for Wiremock tests
# glibc-langpack-en for UTF-8 locale support
RUN dnf install -y --allowerasing \
python3 \
python3-pip \
jq \
gpg \
git \
java-11-openjdk \
glibc-langpack-en \
&& dnf clean all

# Set UTF-8 locale to ensure proper string/character handling in tests
# This is critical for array bind and bind variable tests
# Generate locale as root before switching to user
RUN localedef -i en_US -f UTF-8 en_US.UTF-8 2>&1 || \
(echo "Locale generation note (may already exist)" && locale -a | grep -i en_us || true)
ENV LANG=en_US.UTF-8
ENV LC_ALL=en_US.UTF-8

# Install snowflake-connector-python for schema management
RUN pip3 install -U snowflake-connector-python

# Set Java 11 as the default using environment variables
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk
ENV PATH="${JAVA_HOME}/bin:${PATH}"
# Optimize Java startup for faster Wiremock initialization
# Use server VM for better performance, reduce initial heap to start faster
ENV JAVA_OPTS="-server -Xms64m -Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication"

# Accept user ID as build argument to match host permissions
ARG USER_ID=1001
ARG GROUP_ID=1001

# Create user for proper permission testing
RUN groupadd -g ${GROUP_ID} user && \
useradd -u ${USER_ID} -g ${GROUP_ID} -m -s /bin/bash user && \
mkdir -p /home/user/snowflake-connector-nodejs && \
chown -R user:user /home/user

# Accept Node.js major version as build argument (e.g., NODE_VERSION=18, 20, 22)
ARG NODE_VERSION

# Install Node.js via dnf module system (Rocky Linux 9 uses AppStream modules)
# Note: In RHEL 9/Rocky Linux 9, Node.js is delivered through AppStream modules.
# NODE_VERSION should be the major version (18, 20, 22, etc.)
RUN echo "Installing Node.js ${NODE_VERSION} via dnf..." && \
# Enable the Node.js module stream first
dnf module enable -y nodejs:${NODE_VERSION} && \
# Install Node.js and npm from the enabled module (common profile includes both)
dnf install -y nodejs npm && \
# Verify installation
node --version && \
npm --version && \
dnf clean all

USER user
WORKDIR /home/user/snowflake-connector-nodejs

93 changes: 93 additions & 0 deletions ci/test_rhel9_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/bin/bash -e
#
# Test NodeJS driver in Rocky Linux 9 Docker
# NOTES:
# - Node.js major version MUST be specified as first argument
# - Usage: ./test_rhel9_docker.sh "18"

set -o pipefail

if [[ -z "${1}" ]]; then
echo "[ERROR] Node.js major version is required as first argument (e.g., '18', '20', '22')"
echo "Usage: $0 <node_major_version>"
exit 1
fi

NODE_VERSION=${1}

# Set constants
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
CONNECTOR_DIR="$( dirname "${THIS_DIR}")"
WORKSPACE=${WORKSPACE:-${CONNECTOR_DIR}}

cd ${THIS_DIR}/image

CONTAINER_NAME=test_nodejs_rockylinux9

echo "[INFO] Building docker image for Rocky Linux 9 with Node.js ${NODE_VERSION} (major version)"

# Get current user/group IDs to match host permissions
USER_ID=$(id -u)
GROUP_ID=$(id -g)

docker build --pull -t ${CONTAINER_NAME}:1.0 \
--build-arg BASE_IMAGE=rockylinux:9 \
--build-arg NODE_VERSION=$NODE_VERSION \
--build-arg USER_ID=$USER_ID \
--build-arg GROUP_ID=$GROUP_ID \
. -f Dockerfile.rhel9

# Setup connection parameters - decrypt parameters.json before running container
if [[ -n "$PARAMETERS_SECRET" && -n "$CLOUD_PROVIDER" ]]; then
if [[ "$CLOUD_PROVIDER" == "AZURE" ]]; then
ENCODED_PARAMETERS_FILE="${CONNECTOR_DIR}/.github/workflows/parameters_azure.json.gpg"
elif [[ "$CLOUD_PROVIDER" == "GCP" ]]; then
ENCODED_PARAMETERS_FILE="${CONNECTOR_DIR}/.github/workflows/parameters_gcp.json.gpg"
elif [[ "$CLOUD_PROVIDER" == "AWS" ]]; then
ENCODED_PARAMETERS_FILE="${CONNECTOR_DIR}/.github/workflows/parameters_aws.json.gpg"
else
echo "[ERROR] Unknown cloud provider: $CLOUD_PROVIDER (must be AWS, AZURE, or GCP)"
exit 1
fi

if [[ ! -f "$ENCODED_PARAMETERS_FILE" ]]; then
echo "[ERROR] Encrypted parameters file not found: $ENCODED_PARAMETERS_FILE"
exit 1
fi

echo "[INFO] Decrypting connection parameters for $CLOUD_PROVIDER"
gpg --quiet --batch --yes --decrypt \
--passphrase="$PARAMETERS_SECRET" \
--output "${CONNECTOR_DIR}/parameters.json" \
"$ENCODED_PARAMETERS_FILE"

if [[ ! -f "${CONNECTOR_DIR}/parameters.json" ]]; then
echo "[ERROR] Failed to decrypt parameters.json"
exit 1
fi
echo "[INFO] Successfully decrypted parameters.json"
elif [[ ! -f "${CONNECTOR_DIR}/parameters.json" ]]; then
echo "[ERROR] parameters.json not found and PARAMETERS_SECRET/CLOUD_PROVIDER not provided"
echo "[ERROR] Either provide PARAMETERS_SECRET and CLOUD_PROVIDER to decrypt, or manually decrypt parameters.json"
exit 1
else
echo "[INFO] Using existing parameters.json"
fi

# Run the container
# Mount source directory directly - permissions work because container user matches host user IDs
docker run --network=host \
-e TERM=vt102 \
-e JENKINS_HOME \
-e GITHUB_ACTIONS \
-e GITHUB_SHA \
-e GITHUB_REF \
-e GITHUB_REPOSITORY \
-e GITHUB_EVENT_NAME \
-e RUNNER_TRACKING_ID \
-e CLOUD_PROVIDER \
-e PARAMETERS_SECRET \
--mount type=bind,source="${CONNECTOR_DIR}",target=/home/user/snowflake-connector-nodejs,readonly=false \
${CONTAINER_NAME}:1.0 \
bash -c "cd /home/user/snowflake-connector-nodejs && ci/container/test_rhel9_component.sh ${NODE_VERSION}"

2 changes: 1 addition & 1 deletion test/authentication/connectionParameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const oauthPATOnWiremock = {
accessUrl: null,
username: 'MOCK_USERNAME',
account: 'MOCK_ACCOUNT_NAME',
host: 'localhost',
host: '127.0.0.1',
protocol: 'http',
authenticator: 'PROGRAMMATIC_ACCESS_TOKEN',
};
Expand Down
2 changes: 1 addition & 1 deletion test/integration/wiremock/testWiremockRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ if (os.platform !== 'win32') {
}
const mappings = await wireMock.mappings.getAllMappings();
assert.strictEqual(mappings.mappings.length, 2);
const response = await axios.get(`http://localhost:${port}/test/authorize.html`);
const response = await axios.get(`http://127.0.0.1:${port}/test/authorize.html`);
assert.strictEqual(response.status, 200);
});
});
Expand Down
Loading
Loading