| name | openflow-bootstrap |
| description | Internal bootstrap skill shared by all OpenFlow demo skills. Scaffolds project files, resolves Snowflake connection, installs tools, and confirms runtime readiness. |
| parent_skill | openflow-gdrive-demo |
| internal | true |
OpenFlow Demo Bootstrap
Shared prerequisite gate for all OpenFlow demo skills. Scaffolds the user's project folder and verifies the environment is ready before any demo-specific logic runs.
When to Load
Called by demo skills (e.g., openflow-gdrive-demo) as their first gate. Not user-invocable.
Workflow
All operations target the user's current working directory ($PWD), referred to as PROJECT_DIR. The plugin install directory (PLUGIN_ROOT) is only used as a source for template files.
Step 1: Identify Paths
PLUGIN_ROOT = the directory containing this SKILL.md's grandparent (i.e., the repo root with .cortex-plugin/plugin.json)
PROJECT_DIR = the user's current working directory ($PWD)
Step 2: Scaffold Project Files
Copy from PLUGIN_ROOT to PROJECT_DIR (skip files that already exist):
.env.example
.sfutils/manifest.toml.example
.mise.toml
sql/setup.sql
sql/cleanup.sql
.gitignore
Step 3: Check mise
command -v mise
If missing: STOP. Tell user to install mise (https://mise.jdx.dev/getting-started.html).
Step 4: Resolve Snowflake Connection
Always confirm before proceeding -- env vars may be stale from a previous session.
Resolution order:
$PROJECT_DIR/.sfutils/manifest.toml -> [snowflake].connection (if manifest exists)
$PROJECT_DIR/.env -> SNOWFLAKE_DEFAULT_CONNECTION_NAME
snow connection list -> ask the user which connection to use
After resolution:
- Verify with
snow connection test -c <connection>
- Write the confirmed connection to both
$PROJECT_DIR/.env and $PROJECT_DIR/.sfutils/manifest.toml
Step 5: Create .env
If $PROJECT_DIR/.env does not exist:
- Copy from
$PROJECT_DIR/.env.example
- Set
SNOWFLAKE_DEFAULT_CONNECTION_NAME from Step 4
- Ask for demo-specific values (caller provides the list of required env vars)
- Write all values
Step 6: Create Manifest
If $PROJECT_DIR/.sfutils/manifest.toml does not exist:
- Copy from
$PROJECT_DIR/.sfutils/manifest.toml.example
- Fill
[snowflake].connection from Step 4
Step 7: Install Tools and Deps
Run from $PROJECT_DIR (where .mise.toml was copied):
mise trust
mise install
mise run setup
If any command fails: STOP and show the error.
Step 7b: Bootstrap Snowflake Objects
Run the SQL setup to create roles, database, and warehouse:
mise run sf-setup
This executes sql/setup.sql which creates OPENFLOW_ADMIN, OPENFLOW_DEMOS_ROLE, the database, warehouse, and all grants. Idempotent -- safe to run multiple times.
Step 7c: Copy Sample Documents (conditional)
Only run this step if the user selected "I need to set this up" in Gate 1. Skip entirely when the user confirmed their Shared Drive already has documents.
mise run sample-data
This sparse-checkouts sample documents from the demo content repo into $PROJECT_DIR/.content/. Tell the user:
Sample documents are available at .content/openflow-unstructured-data-pipeline-demo/sample-data/google-drive-docs/.
Upload these to your Google Shared Drive if you need test data.
Step 8: Create Service User and PAT
STOP. Invoke $sfutils:programmatic-access-token with these requirements:
- Service user name:
{PREFIX}_RUNNER (or GDRIVE_DEMO_RUNNER if no prefix)
- Service role name:
{PREFIX}_ACCESS (or GDRIVE_DEMO_ACCESS if no prefix)
- Role restriction:
OPENFLOW_ADMIN
- Database: value from
.env OPENFLOW_DATABASE (default: OPENFLOW_DEMOS)
- Network policy: yes (lock to current IP)
- Manifest path:
$PROJECT_DIR/.sfutils/manifest.toml
Wait for the skill to confirm completion. It will:
- Create the service user and role
- Create a network policy locked to the current IP
- Generate the PAT with OPENFLOW_ADMIN role restriction
- Store the PAT in OS keychain
- Grant OPENFLOW_ADMIN to the user and role
- Write results to manifest under
[pat.openflow-runner]
Do NOT write any SQL for this step. Do NOT proceed until the skill confirms success.
Step 9: Create Network Rule and EAI
STOP. Invoke $sfutils:network-rule with these requirements:
- Type: EGRESS
- Rule name:
{PREFIX}_EGRESS_RULE (or EGRESS_RULE if no prefix)
- Hosts:
googleapis.com, www.googleapis.com, oauth2.googleapis.com, accounts.google.com
- EAI name:
{PREFIX}_OPENFLOW_EAI (or OPENFLOW_EAI if no prefix)
- Database/Schema for rule:
OPENFLOW_DEMOS.NETWORKS
- Grant USAGE on EAI to role:
OPENFLOW_DEMOS_ROLE
- Manifest path:
$PROJECT_DIR/.sfutils/manifest.toml
Wait for the skill to confirm completion. It will:
- Create the EGRESS network rule
- Create the External Access Integration (EAI)
- Grant USAGE on the EAI to the specified role
- Write results to manifest under
[openflow]
Do NOT write any SQL for this step. Do NOT proceed until the skill confirms success.
After the skill completes, ask the user to attach the EAI to the runtime via Snowsight:
EAI has been created and USAGE granted.
Please attach it to your runtime:
- Go to Snowsight > Ingestion > OpenFlow > Deployments > your runtime
- Edit the runtime and add the EAI to External Access Integrations
- Save and confirm the runtime returns to ACTIVE
STOP: Wait for user to confirm EAI is attached.
Step 10: Discover OpenFlow Runtime and Create Profile
STOP. Invoke $openflow with "setup" intent.
The $openflow skill will discover runtimes and create a nipyapi profile. When it asks for a PAT or bearer token during its auth step, extract the token from keychain (created in Step 8) and provide it:
sfutils pat show-pat --user {SA_USER} --yes 2>/dev/null | grep -o 'eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*'
Provide the extracted JWT value as the answer to $openflow's PAT prompt. This ensures both skills use the same token.
The $openflow skill will:
- Discover deployments and runtimes
- Extract
server_url, execute_as_role, runtime_key
- Create the nipyapi profile using the provided PAT
- Verify connectivity
- Write results to
~/.snowflake/cortex/memory/openflow_infrastructure_<CONNECTION>.json
If $openflow fails (no runtimes found, permissions error):
- STOP. Guide user to create a runtime via Snowsight > Ingestion > OpenFlow > Deployments.
After $openflow completes, resolve execute_as_role:
Ask the user:
- Use
ask_user_question with type: "text", defaultValue: "OPENFLOW_DEMOS_ROLE"
- Question: "What role does the runtime execute as? (Check Snowsight > OpenFlow > Deployment settings)"
Write execute_as_role to manifest.
Do NOT proceed until $openflow confirms a working nipyapi profile.
Output
On success, the calling skill can assume:
$PROJECT_DIR/.env exists with a verified Snowflake connection
$PROJECT_DIR/.sfutils/manifest.toml exists with PAT, NW, and EAI tracked
mise, snow, uv, and nipyapi are available on PATH
- Snowflake objects exist (roles, database, warehouse, grants)
- PAT exists with OPENFLOW_ADMIN role restriction, stored in keychain
- EAI created and attached to runtime (user confirmed)
- nipyapi profile exists and connectivity to NiFi is verified
Stopping Points
- Step 3: mise not installed
- Step 4: no valid Snowflake connection
- Step 7: tool installation fails
- Step 8: PAT skill fails
- Step 9: network-rule skill fails
- Step 10: $openflow discovery fails or connectivity check fails