Skip to content

Add CI that tests each Snowflake sample on Snowflake Next - #22

Merged
HarshCasper merged 23 commits into
mainfrom
ci/test-each-sample
Oct 14, 2026
Merged

HarshCasper merged 23 commits into
mainfrom
ci/test-each-sample

Conversation

@HarshCasper

@HarshCasper HarshCasper commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

The repo had no CI. This adds a workflow that runs all 10 samples in a matrix against the Snowflake Next emulator (localstack/snowflake-next), as requested in review and tracked in LAV-2294. Each sample gets a test.sh behind make test, plus make install and a start target.

How the samples start

  • Snowflake only (citi-bike, credit-card-fraud, credit-scoring, predicting-customer-spend, soda, streamlit): lstk start --type snowflake --image localstack/snowflake-next:latest (lstk 1.3.0+).
  • Snowflake + AWS (airflow-dbt, glue, lambda, multi-container): Next has no AWS services and lstk can't run both emulators at once. These samples therefore get a docker-compose.yml with LocalStack AWS on 4566 and Snowflake Next on a second port, on a shared network.
    • Containers that LocalStack starts (MWAA, the Glue job, the Lambda function) reach Next by its service name over plain http.
    • SF_HOSTNAMES lists that service name.
  • Fresh instance: Next starts with only the SNOWFLAKE database, as a new Snowflake account does. Each sample creates its database (init script or setup step) instead of relying on the old emulator creating it on connect.

What each test checks

Sample Test
airflow-dbt-transformation Creates the MWAA environment and checks the Airflow container can reach Next. Deploys the DAG and triggers a run: it succeeds and transform.PREPPED_DATA has 14 rows.
citi-bike-data-app Loads the full trips file (799,414 rows) and the weather data from the public S3 bucket. Starts the web server and checks that / and all three chart endpoints return data.
credit-card-fraud-detection-with-snowpark Runs the sample's Snowflake Cloud Snowpark scripts unchanged. Loads 11,033 transactions (590 frauds), 50 customers and 50 terminals, runs feature engineering, and checks the 10,927 feature rows and their 23 columns.
credit-scoring-with-snowpark Executes every notebook cell and checks that both CSVs were written to Snowflake in full.
glue-snowflake-integration Runs the Glue job. It opens a session with Next, reads 5 rows from src_glue and writes the 3 active rows to tgt_glue, which the test reads back.
lambda-snowpark-connector Deploys and invokes the function. It returns 200, its logs show the 3 rows and the timestamp, and the test reads the rows back from Next.
multi-container The AWS container logs in to Next by container name, and Next loads a CSV from LocalStack S3 through a stage.
predicting-customer-spend Executes every notebook cell, checks the customers in Snowflake and an R-squared above 0.9.
soda-data-quality-checks The scan reports the row count check failing (as written), and the NULL and schema checks passing.
streamlit-snowpark-dynamic-filters Starts the app and runs its filter logic and its sample(n=5) preview against Snowflake, checking the result against the same filter in SQL.

Sample fixes

  • airflow-dbt-transformation:
    • Ported to the Airflow 3 flow from the "Run Airflow and dbt pipelines on Snowflake locally" blog post.
    • dbt_project.yml configured models under my_project, but the project is test_dbt, so its schemas were never applied.
    • The DAG connects to snowflake:4566 over http.
  • citi-bike-data-app:
    • Seeds into test.public, and the Node connection passes the account and schema.
    • date_trunc('day', ...) instead of a quoted identifier.
  • credit-card-fraud-detection-with-snowpark:
    • The emulator-only scripts and checkpoint files are replaced by the Cloud scripts, run against Next.
    • The 10-row CSV, a single day that produced zero features, is replaced by a seeded, scaled-down simulation from the fraud detection handbook. Its generator is committed.
    • The image moves to Python 3.11, because Next rejects the decommissioned 3.8/3.9 UDF runtimes.
    • Model training is not in CI (see LAV-4520 below). The README says so.
  • glue-snowflake-integration:
    • The job connects to snowflake:4566.
    • Next advertises stage uploads on a dedicated host that is aliased to the Snowflake service, so the Spark connector's PUTs reach Next while the Glue script download still goes to LocalStack S3.
    • deploy.sh fails when the job fails.
  • lambda-snowpark-connector:
    • The zip never included handler.py, and the libraries are now built for the function's runtime.
    • The handler reads the Snowflake host, port and protocol from env vars.
  • multi-container:
    • The health check uses bash /dev/tcp, because the Next image has no curl or python.
    • SF_S3_ENDPOINT_EXTERNAL is gone: Next serves internal stages itself.
  • predicting-customer-spend:
    • The notebook used test.test, which does not exist. It now creates test and uses test.public.
    • corr(numeric_only=True) for pandas 2.
  • soda-data-quality-checks:
    • The schema-change check needs Soda Cloud, so it is replaced with a required-columns check.
    • snowflake-cli-labs was renamed to snowflake-cli.
  • streamlit-snowpark-dynamic-filters:
    • setup.sql used a PostgreSQL ::bit(32) cast.
    • pyarrow is pinned to 8.0.0, the version snowflake-connector-python 2.7.11 requires.
  • READMEs: every sample documents Next (lstk --image or Docker Compose) and make test. The top-level README explains the two setups.

Emulator bugs

Found on Next while porting, and filed:

  • LAV-4517: CTAS over staged files fails with 002043. Breaks Snowpark save_as_table from a stage. The fraud sample loads with copy_into_table.
  • LAV-4518: a windowed SUM() / n is described as NUMBER(38,0), so Snowpark save_as_table silently truncates the values.
  • LAV-4519: a scalar aggregate with ORDER BY on another column is rejected (002024). Snowpark 1.55 generates this by default. Needs a check against real Snowflake.
  • LAV-4520: Snowpark add_packages with pinned versions fails on a fresh emulator, so the fraud sample's model UDF can't be deployed.

The earlier bugs from the Python emulator don't apply on Next:

  • SAMPLE on a subquery (LAV-4420) works on Next.
  • A negative ROUND scale (LAV-4421) works on Next.
  • The samples no longer write into a missing schema (LAV-4422).

https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM

Run every sample against the LocalStack Snowflake emulator in a GitHub
Actions matrix, each with a test.sh behind `make test`, and fix the
setup steps that no longer work.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
- soda: match Soda's check output and use a schema check that works without Soda Cloud
- citi-bike: seed into test.public, where the app reads from
- fraud detection: run scripts through a make macro instead of goal arguments
- customer spend: Snowflake-style column names and a numeric-only correlation
- streamlit: replace the PostgreSQL BIT cast in setup.sql
- airflow: pin Airflow 2.10.3, the DAG uses Airflow 2 APIs
- multi-container: read the logs straight from both containers

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
- soda: Snowflake stores unquoted column names in upper case
- streamlit: run on bullseye, the pinned oscrypto cannot read OpenSSL 3
- citi-bike: pass the account and schema to the Node.js connection
- multi-container: IPv4 health checks and health details in the logs
- airflow, customer spend: diagnostics for the open failures

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
- citi-bike: date_trunc takes 'day' as a string, not a quoted identifier
- customer spend: write to the public schema, test.test does not exist
- streamlit: report the emulator's SAMPLE-on-subquery failure and check the preview with LIMIT
- airflow: relative DAG path and the requirements file, as MWAA expects
- multi-container: the AWS image has no curl, use Python for the checks
- READMEs: start LocalStack with lstk, SF_HOSTNAMES instead of SF_HOSTNAME_REGEX

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
… samples

- airflow: Airflow 3 compatible DAG (connection from AIRFLOW_CONN_SNOWFLAKE_LOCAL,
  schedule, one task at a time, query transform.PREPPED_DATA), upload the macros
  folder, requirements before the environment, restart Airflow after the sync
- citi-bike: group temperatures with round(x / 10) * 10, the emulator rejects round(x, -1)
- streamlit: pin pyarrow 8.0.0, the version snowflake-connector-python 2.7.11 needs

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
The models config sat under my_project, but the project is named test_dbt,
so dbt ignored it and built every view in PUBLIC instead of TRANSFORM and
ANALYSIS. Also print the query task's log when the DAG run fails.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM

@hovaesco hovaesco left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All sample apps should be updated to use the new Snowflake Next emulator https://linear.app/localstack/issue/LAV-2294/update-sample-apps-to-use-next-emulator

@hovaesco

Copy link
Copy Markdown
Contributor

Plus we won't address regressions reported against Snowflake Python emualtor.

Start localstack/snowflake-next with lstk, create the test database before
seeding (a fresh Next instance has none), drop SF_CSV_IMPORT_MAX_ROWS and
SF_LOG=trace, and restore round(temp_avg_f, -1), which Next supports.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
Start localstack/snowflake-next through lstk --image. A fresh Next
instance only has the SNOWFLAKE database, so the customer spend
notebook now creates its database before writing to it.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
Start localstack/snowflake-next through lstk --image. Soda's init now
creates the test database, since Next starts with only SNOWFLAKE. Next
supports SAMPLE on a subquery, so the streamlit smoke test checks the
app's .sample(n=5) preview directly again.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
Switch the Snowflake service to localstack/snowflake-next. The image has
no python3, so the health check uses bash /dev/tcp. SF_HOSTNAMES now
replaces the built-in host list, so the default hostname comes first.
The old POST /session probe returns 401 on Next, so the reachability
check logs in by container name instead. SF_S3_ENDPOINT_EXTERNAL is
dropped: internal stages are served by the emulator itself.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
A new session on the Next emulator has no current database, as in
Snowflake, so CREATE TABLE failed with error 090105.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
snowflake-next is Snowflake only, so docker compose starts LocalStack for
AWS next to it. The Lambda container joins the compose network and reaches
the emulator by its service name over http, and init.sf.sql creates the
database and schema the function connects to, since snowflake-next starts
with only the SNOWFLAKE database. make test also reads the rows back from
the emulator on host port 4567.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
LocalStack for AWS (MWAA, S3) and snowflake-next run as two Compose
containers on a shared network. MAIN_DOCKER_NETWORK puts the MWAA Airflow
container on that network, so the DAG and dbt reach the emulator at
http://snowflake:4566. init.sf.sql creates the test database at startup.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
Start localstack/snowflake-next with lstk and run the Snowpark scripts
written for Snowflake Cloud against it, which replaces the emulator
workarounds and the data checkpoints they needed. The bundled data is now
a scaled-down simulation from the fraud detection handbook, so feature
engineering has several months of transactions to work with, and make test
checks the features table as well as the loaded tables.

The image moves to Python 3.11, since Snowflake no longer runs Python 3.8
UDFs, and the training script pins the UDF packages to the local versions.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
Glue and S3 run in LocalStack AWS and Snowflake in snowflake-next, as two
compose containers on one network. The Glue job now reads src_glue, keeps
the active rows and writes them to tgt_glue, and the test checks the rows.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
The localstack-pro image has no curl, so check health with python3.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
The Spark connector writes through the internal stage at
stages.s3.localhost.localstack.cloud. LocalStack's DNS answered that name
with its own S3, so resolve it upstream with Docker's DNS, where it is an
alias of the Snowflake emulator.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
LocalStack fetches the Glue script through s3.localhost.localstack.cloud,
so only stages.s3.localhost.localstack.cloud may go to the emulator. Print
the job error and the LocalStack log tail when the job fails.

Claude-Session: https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM
@HarshCasper HarshCasper changed the title Add CI that tests each Snowflake sample Add CI that tests each Snowflake sample on Snowflake Next Oct 14, 2026
@HarshCasper

Copy link
Copy Markdown
Member Author

@hovaesco thanks, all 10 samples now run on localstack/snowflake-next:latest and pass: https://github.com/localstack-samples/localstack-snowflake-samples/actions/runs/38553101332

  • The 6 Snowflake-only samples start Next with lstk start --type snowflake --image localstack/snowflake-next:latest.
  • airflow-dbt, glue, lambda and multi-container need AWS too, so they use Docker Compose with LocalStack AWS and Next side by side. The PR description explains how the MWAA, Glue and Lambda containers reach Next.
  • The Python emulator workarounds are gone. SAMPLE on a subquery and a negative ROUND scale both work on Next.
  • I filed four Next issues found along the way: LAV-4517 (CTAS from a stage), LAV-4518 (windowed division scale in describe), LAV-4519 (scalar aggregate with ORDER BY, needs a Cloud check) and LAV-4520 (pinned Snowpark packages). LAV-4520 is why the fraud sample's model training isn't in CI yet.

https://claude.ai/code/session_01DZfDmzUBPqNAu3yFKyfCNM

@HarshCasper
HarshCasper merged commit 24dbbc7 into main Oct 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants