Skip to content

Commit b3b54bd

Browse files
Merge branch 'main' into SNOW-2043816-Kerberos-Proxy-Auth-Python
2 parents ddda565 + c1187c1 commit b3b54bd

File tree

17 files changed

+661
-51
lines changed

17 files changed

+661
-51
lines changed

DESCRIPTION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1010
- v3.16(TBD)
1111
- Bumped numpy dependency from <2.1.0 to <=2.2.4
1212
- Added Windows support for Python 3.13.
13+
- Add `bulk_upload_chunks` parameter to `write_pandas` function. Setting this parameter to True changes the behaviour of write_pandas function to first write all the data chunks to the local disk and then perform the wildcard upload of the chunks folder to the stage. In default behaviour the chunks are being saved, uploaded and deleted one by one.
14+
1315

1416
- v3.15.1(May 20, 2025)
1517
- Added basic arrow support for Interval types.

prober/Dockerfile

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
FROM alpine:3.18
2+
3+
# boilerplate labels required by validation when pushing to ACR, ECR & GCR
4+
LABEL org.opencontainers.image.source="https://github.com/snowflakedb/snowflake-connector-python"
5+
LABEL com.snowflake.owners.email="[email protected]"
6+
LABEL com.snowflake.owners.slack="triage-snow-drivers-warsaw-dl"
7+
LABEL com.snowflake.owners.team="Snow Drivers"
8+
LABEL com.snowflake.owners.jira_area="Developer Platform"
9+
LABEL com.snowflake.owners.jira_component="Python Driver"
10+
# fake layers label to pass the validation
11+
LABEL com.snowflake.ugcbi.layers="sha256:850959b749c07b254308a4d1a84686fd7c09fcb94aeae33cc5748aa07e5cb232,sha256:b79d3c4628a989cbb8bc6f0bf0940ff33a68da2dca9c1ffbf8cfb2a27ac8d133,sha256:1cbcc0411a84fbce85e7ee2956c8c1e67b8e0edc81746a33d9da48c852037c3e,sha256:07e89b796f91d37255c6eec926b066d6818f3f2edc344a584d1b9566f77e1c27,sha256:84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652,sha256:3ab72684daee4eea64c3ae78a43ea332b86358446b6f2904dca4b634712e1537"
12+
13+
RUN apk add --no-cache \
14+
bash \
15+
git \
16+
make \
17+
g++ \
18+
zlib-dev \
19+
openssl-dev \
20+
libffi-dev \
21+
jq
22+
23+
ENV HOME="/home/driveruser"
24+
25+
# Create a group with GID=1000 and a user with UID=1000
26+
RUN addgroup -g 1000 drivergroup && \
27+
adduser -u 1000 -G drivergroup -D driveruser
28+
29+
# Set permissions for the non-root user
30+
RUN mkdir -p ${HOME} && \
31+
chown -R driveruser:drivergroup ${HOME}
32+
33+
# Switch to the non-root user
34+
USER driveruser
35+
WORKDIR ${HOME}
36+
37+
# Set environment variables
38+
ENV PYENV_ROOT="${HOME}/.pyenv"
39+
ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
40+
41+
42+
# Install pyenv
43+
RUN git clone --depth=1 https://github.com/pyenv/pyenv.git ${PYENV_ROOT}
44+
45+
# Build arguments for Python versions and Snowflake connector versions
46+
ARG MATRIX_VERSION='{"3.10.17": ["3.14.0", "3.13.2", "3.13.1"], "3.9.22": ["3.14.0", "3.13.2", "3.13.1"], "3.8.20": ["3.14.0", "3.13.2", "3.13.1"]}'
47+
48+
49+
# Install Python versions from ARG MATRIX_VERSION
50+
RUN eval "$(pyenv init --path)" && \
51+
for python_version in $(echo $MATRIX_VERSION | jq -r 'keys[]'); do \
52+
pyenv install $python_version || echo "Failed to install Python $python_version"; \
53+
done
54+
55+
# Create virtual environments for each combination of Python and Snowflake connector versions
56+
RUN for python_version in $(echo $MATRIX_VERSION | jq -r 'keys[]'); do \
57+
for connector_version in $(echo $MATRIX_VERSION | jq -r ".\"${python_version}\"[]"); do \
58+
venv_path="${HOME}/venvs/python_${python_version}_connector_${connector_version}"; \
59+
$PYENV_ROOT/versions/$python_version/bin/python -m venv $venv_path && \
60+
$venv_path/bin/pip install --upgrade pip && \
61+
$venv_path/bin/pip install snowflake-connector-python==$connector_version; \
62+
done; \
63+
done
64+
65+
# Copy the prober script into the container
66+
RUN mkdir -p prober/probes/
67+
COPY __init__.py prober
68+
COPY setup.py prober
69+
COPY entrypoint.sh prober
70+
COPY probes/* prober/probes
71+
72+
# Install /prober in editable mode for each virtual environment
73+
RUN for venv in ${HOME}/venvs/*; do \
74+
source $venv/bin/activate && \
75+
pip install -e ${HOME}/prober && \
76+
deactivate; \
77+
done

prober/Jenkinsfile.groovy

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
pipeline {
2+
agent { label 'regular-memory-node' }
3+
4+
options {
5+
ansiColor('xterm')
6+
timestamps()
7+
}
8+
9+
environment {
10+
VAULT_CREDENTIALS = credentials('vault-jenkins')
11+
COMMIT_SHA_SHORT = sh(script: 'cd PythonConnector/prober && git rev-parse --short HEAD', returnStdout: true).trim()
12+
IMAGE_NAME = 'snowdrivers/python-driver-prober'
13+
TEAM_NAME = 'Snow Drivers'
14+
TEAM_JIRA_DL = 'triage-snow-drivers-warsaw-dl'
15+
TEAM_JIRA_AREA = 'Developer Platform'
16+
TEAM_JIRA_COMPONENT = 'Python Driver'
17+
}
18+
19+
stages {
20+
stage('Build Image') {
21+
steps {
22+
dir('./PythonConnector/prober') {
23+
sh """
24+
ls -l
25+
docker build \
26+
-t ${IMAGE_NAME}:${COMMIT_SHA_SHORT} \
27+
--label "org.opencontainers.image.revision=${COMMIT_SHA_SHORT}" \
28+
-f ./Dockerfile .
29+
"""
30+
}
31+
}
32+
}
33+
34+
stage('Checkout Jenkins Push Scripts') {
35+
steps {
36+
dir('k8sc-jenkins_scripts') {
37+
git branch: 'master',
38+
credentialsId: 'jenkins-snowflake-github-app-3',
39+
url: 'https://github.com/snowflakedb/k8sc-jenkins_scripts.git'
40+
}
41+
}
42+
}
43+
44+
stage('Push Image') {
45+
steps {
46+
sh """
47+
./k8sc-jenkins_scripts/jenkins_push.sh \
48+
-r "${VAULT_CREDENTIALS_USR}" \
49+
-s "${VAULT_CREDENTIALS_PSW}" \
50+
-i "${IMAGE_NAME}" \
51+
-v "${COMMIT_SHA_SHORT}" \
52+
-n "${TEAM_JIRA_DL}" \
53+
-a "${TEAM_JIRA_AREA}" \
54+
-C "${TEAM_JIRA_COMPONENT}"
55+
"""
56+
}
57+
}
58+
}
59+
60+
post {
61+
always {
62+
cleanWs()
63+
}
64+
}
65+
}

prober/entrypoint.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
3+
# Initialize an empty string to hold all parameters
4+
python_version=""
5+
connector_version=""
6+
params=""
7+
8+
# Parse command-line arguments
9+
while [[ "$#" -gt 0 ]]; do
10+
if [[ "$1" == "--python_version" ]]; then
11+
python_version="$2"
12+
shift 2
13+
elif [[ "$1" == "--connector_version" ]]; then
14+
connector_version="$2"
15+
shift 2
16+
else
17+
params+="$1 $2 "
18+
shift 2
19+
fi
20+
done
21+
22+
# Construct the virtual environment path
23+
venv_path="${HOME}/venvs/python_${python_version}_connector_${connector_version}"
24+
25+
# Check if the virtual environment exists
26+
if [[ ! -d "$venv_path" ]]; then
27+
echo "Error: Virtual environment not found at $venv_path"
28+
exit 1
29+
fi
30+
31+
# Run main.py with given venv
32+
echo "Running main.py with virtual environment: $venv_path"
33+
source "$venv_path/bin/activate"
34+
prober $params
35+
status=$?
36+
deactivate
37+
38+
# Check the exit status of prober
39+
if [[ $status -ne 0 ]]; then
40+
echo "Error: prober returned failure."
41+
exit 1
42+
else
43+
echo "Success: prober returned success."
44+
exit 0
45+
fi

prober/probes/login.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
from probes.logging_config import initialize_logger
24
from probes.registry import prober_function
35

@@ -29,12 +31,11 @@ def connect(connection_parameters: dict):
2931
database=connection_parameters["database"],
3032
schema=connection_parameters["schema"],
3133
role=connection_parameters["role"],
32-
authenticator="KEY_PAIR_AUTHENTICATOR",
34+
authenticator=connection_parameters["authenticator"],
3335
private_key=connection_parameters["private_key"],
3436
)
3537
return connection
3638
except Exception as e:
37-
logger.info({f"success_login={False}"})
3839
logger.error(f"Error connecting to Snowflake: {e}")
3940

4041

@@ -54,13 +55,22 @@ def perform_login(connection_parameters: dict):
5455
# Connect to Snowflake
5556
connection = connect(connection_parameters)
5657

58+
# Log the connection details
59+
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
60+
driver_version = snowflake.connector.__version__
61+
5762
# Perform a simple query to test the connection
5863
cursor = connection.cursor()
5964
cursor.execute("SELECT 1;")
6065
result = cursor.fetchone()
61-
logger.info(result)
6266
assert result == (1,)
63-
logger.info({f"success_login={True}"})
67+
print(
68+
f"cloudprober_driver_python_perform_login{{python_version={python_version}, driver_version={driver_version}}} 0"
69+
)
70+
sys.exit(0)
6471
except Exception as e:
65-
logger.info({f"success_login={False}"})
72+
print(
73+
f"cloudprober_driver_python_perform_login{{python_version={python_version}, driver_version={driver_version}}} 1"
74+
)
6675
logger.error(f"Error during login: {e}")
76+
sys.exit(1)

prober/probes/main.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import argparse
22
import logging
3+
import sys
34

5+
from probes import login, put_fetch_get # noqa
46
from probes.logging_config import initialize_logger
57
from probes.registry import PROBES_FUNCTIONS
68

@@ -19,30 +21,51 @@ def main():
1921
parser.add_argument("--account", required=True, help="Account")
2022
parser.add_argument("--schema", required=True, help="Schema")
2123
parser.add_argument("--warehouse", required=True, help="Warehouse")
24+
parser.add_argument("--database", required=True, help="Database")
2225
parser.add_argument("--user", required=True, help="Username")
23-
parser.add_argument("--private_key", required=True, help="Private key")
26+
parser.add_argument(
27+
"--authenticator",
28+
required=True,
29+
help="Authenticator (e.g., KEY_PAIR_AUTHENTICATOR)",
30+
)
31+
parser.add_argument(
32+
"--private_key_file",
33+
required=True,
34+
help="Private key file in DER format base64-encoded and '/' -> '_', '+' -> '-' replacements",
35+
)
2436

2537
# Parse arguments
2638
args = parser.parse_args()
2739

40+
private_key = (
41+
open(args.private_key_file).read().strip().replace("_", "/").replace("-", "+")
42+
)
43+
2844
connection_params = {
2945
"host": args.host,
3046
"port": args.port,
3147
"role": args.role,
3248
"account": args.account,
3349
"schema": args.schema,
3450
"warehouse": args.warehouse,
51+
"database": args.database,
3552
"user": args.user,
36-
"private_key": args.private_key,
53+
"authenticator": args.authenticator,
54+
"private_key": private_key,
3755
}
3856

39-
for function_name, function in PROBES_FUNCTIONS.items():
57+
if args.scope not in PROBES_FUNCTIONS:
58+
logging.error(
59+
f"Invalid scope: {args.scope}. Available scopes: {list(PROBES_FUNCTIONS.keys())}"
60+
)
61+
sys.exit(1)
62+
else:
63+
logging.info(f"Running probe for scope: {args.scope}")
4064
try:
41-
logging.info("BBB")
42-
logging.error(f"Running probe: {function_name}")
43-
function(connection_params)
65+
PROBES_FUNCTIONS[args.scope](connection_params)
4466
except Exception as e:
45-
logging.error(f"Error running probe {function_name}: {e}")
67+
logging.error(f"Error running probe {args.scope}: {e}")
68+
sys.exit(1)
4669

4770

4871
if __name__ == "__main__":

0 commit comments

Comments
 (0)