AWS Spark NotebooksA VS Code / Cursor extension for running PySpark and Spark SQL in Features
Prerequisites
For EMR Serverless:
For Glue Interactive Sessions:
Installation (development)
Quick start
SidebarThe AWS Spark activity bar has three views. EMR ApplicationsTree structure: application → Livy sessions. Only applications with
Glue SessionsLists Livy-type Glue interactive sessions in the selected region.
ConfigAWS credentials and session presets for both backends:
Toolbar: Refresh, New Session Preset (EMR), New Glue Session Preset, Open Workspace Presets File (EMR and Glue). Connect notebooks via the kernel picker, sidebar attach actions, or Connect to EMR Serverless Session / Connect to Glue Session. Session presetsSaved session configurations for each backend. When creating a session, you pick a preset; Iceberg/Glue catalog conf from settings is merged on top for new sessions. EMR Serverless presetsSaved Livy
Example workspace file:
Preset fields: name, execution role ARN, driver/executor memory and cores, executor count, heartbeat timeout, optional TTL, and free-form Glue Interactive presetsSaved Glue
Preset fields: name, role ARN, Glue version, worker type and count, idle timeout, Python version, Status barWhen a notebook is connected, Spark UI appears on the left. Spark Help (right) opens the in-editor documentation. Kernel pickerEach notebook uses one of two controllers:
Connecting prompts you to choose EMR Serverless or Glue Interactive Sessions, then pick or create a session. Running cells while disconnected opens the same flow. Disconnect Notebook Session clears the notebook binding but leaves the remote session running. CommandsShared / EMR Serverless
Glue Interactive
SettingsSearch EMR Serverless or Glue Interactive in Settings ( EMR Serverless
Glue Interactive Sessions
Click AWS Profile or AWS Region in the Config sidebar to change credentials or region. Changing profile or region disconnects open notebook sessions. Notebook format (
|
| Magic | Supported |
|---|---|
%%sql |
Yes — set cell language to SQL or use %%sql at the top of a Python cell |
%pip install … |
Yes — runs python -m pip … on the session driver |
!pip install … |
Yes — same as %pip |
Limitations on EMR Serverless (unlike classic EMR Notebooks):
- Packages with native binaries may install on the driver but fail on executors — use a venv archive on S3 in session presets for production deps.
- Run
%pip installin one cell, thenimportin the next cell.
Do not rely on SparkSession.builder in notebook cells for catalog setup — the remote session already created spark with session conf; builder .config() for catalogs is ignored. The extension warns when a cell attempts this.
Iceberg catalogs (spark_catalog + glue_catalog)
Spark registers Iceberg catalogs at session creation only. The extension merges catalog conf into new session bodies (settings + presets):
| Catalog | Typical use | Config source |
|---|---|---|
spark_catalog |
EMR/Glue default (SparkSessionCatalog on Glue) | emrServerless.icebergCatalog.sessionConf |
glue_catalog |
Explicit GlueCatalog + warehouse | emrServerless.icebergCatalog.glueCatalog or preset Spark conf |
Then create a new session (attached sessions keep their original conf).
IAM permissions
Replace ACCOUNT_ID and role ARNs with your values. You only need the section for backends you use.
EMR Serverless
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"emr-serverless:ListApplications",
"emr-serverless:GetApplication",
"emr-serverless:StartApplication",
"emr-serverless:StopApplication",
"emr-serverless:AccessLivyEndpoints",
"emr-serverless:GetResourceDashboard",
"emr-serverless:GetDashboardForJobRun"
],
"Resource": "arn:aws:emr-serverless:*:ACCOUNT_ID:/applications/*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::ACCOUNT_ID:role/EMRServerlessExecutionRole",
"Condition": {
"StringLike": {
"iam:PassedToService": "emr-serverless.amazonaws.com"
}
}
}
]
}
Glue Interactive Sessions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"glue:CreateSession",
"glue:GetSession",
"glue:ListSessions",
"glue:StopSession",
"glue:DeleteSession",
"glue:RunStatement",
"glue:GetStatement",
"glue:CancelStatement",
"glue:GetDashboardUrl"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::ACCOUNT_ID:role/GlueInteractiveSessionRole",
"Condition": {
"StringLike": {
"iam:PassedToService": "glue.amazonaws.com"
}
}
}
]
}
Spark UI and driver logs
After connecting, the extension fetches a Spark UI URL from the active backend. Links appear in cell output, the status bar, and sidebar session rows. URLs expire after about one hour — use Refresh Spark UI Link (EMR) or re-open from the sidebar.
Driver logs: Spark UI → Executors tab → driver row → Logs.
Session isolation
AWS enforces session isolation per IAM principal. You can only attach to sessions created by the same credentials.
Development
Scripts
| Script | Description |
|---|---|
npm run build |
Bundle extension (dist/extension.js) and table renderer (dist/tableRenderer.js) |
npm run watch |
Rebuild on file changes |
npm run typecheck |
TypeScript check without emit |
npm run package |
Build and create a .vsix in releases/ |
npm run vscode:prepublish |
Pre-publish build hook |
Build uses esbuild (Node 18 target for the extension host, ESM for the notebook renderer webview).
Package for sharing (.vsix)
npm install
npm run package
This builds the extension and writes releases/emr-serverless-pyspark-<version>.vsix.
Options:
npm run package -- --out ./releases
npm run package -- --skip-build
npm run package -- --pre-release
Install the .vsix:
- VS Code:
code --install-extension releases/emr-serverless-pyspark-0.1.0.vsix - Cursor:
cursor --install-extension releases/emr-serverless-pyspark-0.1.0.vsix - Or: Extensions sidebar → ⋯ → Install from VSIX…
CI and merge requirements
Pull requests to main run:
| Check | Workflow job |
|---|---|
verify |
CI — typecheck, build, package smoke test |
semantic-pull-request |
PR Title — Conventional Commits PR title |
Block merge until checks pass — branch protection must be enabled on GitHub (not enforced by workflow files alone). After merging the repo-settings changes, run once:
./scripts/apply-github-settings.sh
Or add a repository secret GH_ADMIN_PAT (admin + Actions scope) and run the Apply Repo Settings workflow from the Actions tab.
This configures:
- Required status checks on
main(verify,semantic-pull-request) via repository ruleset - Repository Admin role bypass so release automation (via
GH_ADMIN_PAT) can push version/changelog commits tomain - Legacy branch protection removed to avoid duplicate required checks
- The least restrictive workflow approval policy available via API (
first_time_contributors_new_to_github)
The Release workflow needs repository secret GH_ADMIN_PAT (admin user PAT with contents write). The default GITHUB_TOKEN cannot bypass rulesets on personal repositories.
Workflow approval prompts: GitHub may still ask you to click Approve and run when a PR modifies files under .github/workflows/. That is a platform security control and cannot be fully disabled. Same-repo PRs from contributors who already have merged work should otherwise run automatically.
Releases (CI/CD)
Releases are automated with semantic-release on every push to main.
- Create a feature branch from
main. - Open a PR with a Conventional Commits title.
- CI runs typecheck, build, and a packaging smoke test.
- Merge to
main— the release workflow bumps the version, updatesCHANGELOG.md, builds a.vsix, publishes a GitHub Release, and publishes to the VS Code Marketplace.
Install from the marketplace:
code --install-extension denerops.emr-serverless-pyspark
Project layout
src/
extension.ts # Activation, command registration
platform/connectionHub.ts # Routes notebooks to EMR or Glue managers
aws/ # EMR Serverless SDK client, config, Iceberg helpers
livy/ # SigV4 Livy HTTP client, session, code transforms
glue/ # Glue Interactive Sessions SDK client and presets
emr/connectionManager.ts # Notebook ↔ EMR Livy session bindings
notebook/ # Serializer, controller, kernel manager, ipynb compat
browser/ # Sidebar tree providers and context-menu actions
session/ # EMR session presets and Livy body builder
ui/ # Status bar, kernel picker, connect wizard, preset editors
output/ # Result → notebook output mappers
renderer/ # DataFrame table webview renderer
media/ # Icons and renderer CSS
scripts/spike.mjs # Standalone AWS / Livy connectivity test
Validate AWS connectivity (spike)
node scripts/spike.mjs
EMR_APPLICATION_ID=00fxxxxxxxx EMR_EXECUTION_ROLE_ARN=arn:aws:iam::...:role/... node scripts/spike.mjs
The spike lists Livy-enabled EMR applications, optionally starts a test session, and prints dashboard URLs.
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ VS Code / Cursor UI │
│ ┌──────────────┐ ┌─────────────┐ ┌────────────────────────┐ │
│ │ Sidebar │ │ Notebook │ │ Status bar │ │
│ │ EMR + Glue │ │ controller │ │ │ │
│ └──────┬───────┘ └──────┬──────┘ └────────────────────────┘ │
└─────────┼─────────────────┼────────────────────────────────────┘
│ │
▼ ▼
NotebookConnectionHub ──► SparknbController
│
┌────┴────┐
▼ ▼
EMR Manager Glue Manager
│ │
▼ ▼
LivySession GlueSession
(SigV4 HTTP) (Glue SDK + Livy)
│ │
▼ ▼
EMR Serverless API AWS Glue Interactive Sessions API
per-app Livy endpoint per-session Livy endpoint
- EMR control plane:
@aws-sdk/client-emr-serverless— list/start/stop applications, dashboard URLs - EMR data plane: SigV4-signed HTTP to the per-application Livy endpoint
- Glue:
@aws-sdk/client-glue— CreateSession, RunStatement, GetDashboardUrl, etc. - Credentials:
@aws-sdk/credential-providers— default chain or explicit profile viaemrServerless.awsProfile - Table renderer: custom MIME type
application/vnd.emr-spark.table+jsonrendered in a notebook webview
License
MIT