Skip to content

Latest commit

 

History

60 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FoundryBackend

The backend for Foundry (FastAPI app titled FOUNDRYFORGER) — a visual infrastructure builder.

A user drags AWS services onto a ReactFlow canvas in the frontend and draws arrows between them. This backend turns that canvas JSON into a CloudFormation template (via troposphere), deploys it to AWS, streams live deployment progress back over WebSocket, and then wires up a GitHub → CodeBuild → CodeDeploy pipeline so the user's app code auto-deploys onto the EC2 instance that was just created.

Built for ACM Projects (Oct–Dec 2025).


Table of contents

  1. What it actually does
  2. Architecture
  3. Repository map
  4. The canvas contract
  5. The deployment pipeline, step by step
  6. The CI/CD pipeline, step by step
  7. Resource naming and IAM
  8. Demo mode
  9. API reference
  10. Database schema
  11. Environment variables
  12. Running it locally
  13. Branch guide — where the working code lives
  14. Known issues and refactor candidates

1. What it actually does

Four AWS services are supported as canvas nodes: EC2, S3, RDS, and DynamoDB.

The interesting part is the edges. An edge from an S3 node to an EC2 node doesn't just draw a line — it causes the backend to:

  • generate an IAM role + instance profile scoped to exactly that bucket,
  • attach the profile to that EC2 instance,
  • and (intended, see §14) inject S3_BUCKET_NAME into the instance's environment.

So "draw an arrow from the bucket to the server" compiles down to least-privilege IAM plus service discovery. Same story for DynamoDB (IAM role + DYNAMODB_TABLE_NAME) and RDS (no IAM — it injects DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD / DB_ENGINE instead, since RDS auth is credential-based).

On top of that there is a second, largely independent subsystem: once a stack is up, the user can point a GitHub repo at their build. The backend registers a webhook, and every push triggers a full zip → S3 → CodeBuild → CodeDeploy → running-FastAPI-app cycle on the EC2 instance the canvas created.


2. Architecture

┌────────────────────────────────────────────────────────────────────────────┐
│  Frontend (ReactFlow canvas — separate repo)                               │
└───────────────┬─────────────────────────────────┬──────────────────────────┘
                │ POST /canvas/deploy             │ WS /canvas/deploy/track/{stack}
                │ (canvas JSON + buildId)         │ WS /github/ws/{build_id}
                ▼                                 ▼
┌────────────────────────────────────────────────────────────────────────────┐
│  app.py — FastAPI, CORS wide open                                          │
│    routers/canvas.py        → /canvas/*  and  /builds/*                    │
│    routers/github_webhook.py→ /github/*                                    │
└───────────────┬────────────────────────────────────┬───────────────────────┘
                │                                    │
     ┌──────────▼────────────┐            ┌──────────▼─────────────┐
     │  CFCreators/          │            │  CICD/                 │
     │  "canvas → CF → AWS"  │            │  "git push → EC2"      │
     │                       │            │                        │
     │  CFCreator.py         │            │  addYamlZip.py         │
     │   orchestrator        │            │   inject buildspec/    │
     │  template_composer.py │            │   appspec into repo zip│
     │   canvas → troposphere│            │  deploymentScripts.py  │
     │  singleServiceCreator/│            │   inject start/stop/   │
     │   EC2/S3/RDS/Dynamo/  │            │   install .sh          │
     │   IAM builders        │            │  upload_s3.py          │
     │  key_pair_manager.py  │            │  trigger_codebuild.py  │
     │   SSH keypairs        │            │  code_Deploy.py        │
     │  aws_deployer.py      │            │  add_webhook.py        │
     │   boto3 CFN calls     │            └──────────┬─────────────┘
     │  deploymentModal/     │                       │
     │   poll CFN events →WS │                       │
     └──────────┬────────────┘                       │
                │                                    │
     ┌──────────▼────────────┐  ┌──────────────┐  ┌──▼──────────────────────┐
     │ costs/  logs/         │  │ database.py  │  │ AWS: CodeBuild,         │
     │ CloudWatch + pricing  │  │ Postgres/RDS │  │ CodeDeploy, S3          │
     └───────────────────────┘  └──────────────┘  └─────────────────────────┘

Two DB access styles coexist: database.py uses psycopg2 (sync, with a get_db_connection context manager), while many endpoints in routers/canvas.py open asyncpg connections inline. See §14.


3. Repository map

Entry point

File Role
app.py FastAPI app. Mounts canvas.router (/canvas), canvas.builds (/builds), github_router (/github). CORS is allow_origins=["*"].

routers/ — HTTP surface

File Role
canvas.py (1051 lines) The main API. Deploy / update / delete stacks, change sets, build CRUD, GitHub repo listing, invites, project settings, costs, logs, S3 file upload, and the deployment-tracking WebSocket. This is the file most in need of splitting.
github_webhook.py Webhook registration + the push handler that runs the whole CI/CD chain. Also hosts a per-build_id WebSocket used to stream CodeBuild/CodeDeploy status strings.

CFCreators/ — canvas → CloudFormation → AWS

File Role
CFCreator.py Public façade. createGeneration() (canvas → template, saves a copy to allJSONs/createdCFs/), deployToAWS() (5-step pipeline), getStackStatus(), deleteStack().
template_composer.py The core translation logic. Parses edges into a dependency map, creates non-EC2 resources first (phase 2), then creates EC2s with the IAM roles / env vars their edges imply (phase 3).
aws_deployer.py CloudFormationDeployer — thin boto3 wrapper. Auto-discovers the default VPC/subnet/SG, creates a DB subnet group when RDS is present, create_stack, change-set create/execute/delete, status.
key_pair_manager.py Creates one EC2 SSH key pair per EC2 node before the stack goes up; returns private key material to the caller (it's only retrievable once). Also bulk-deletes key pairs on stack teardown.
singleServiceCreator/EC2_creation.py Builds AWS::EC2::Instance. AMI friendly-name → SSM parameter resolution, block devices, tags, instance profile, key name, UserData.
singleServiceCreator/S3_creation.py Builds AWS::S3::Bucket. Encryption, public-access block, and BucketOwnerEnforced are hardcoded on.
singleServiceCreator/RDS_creation.py Builds AWS::RDS::DBInstance. db.t4g.micro, 20 GB gp3, encrypted, single-AZ, private, 7-day backups — all hardcoded.
singleServiceCreator/DynamoDB_creation.py Builds AWS::DynamoDB::Table. PAY_PER_REQUEST, SSE on, PITR on — hardcoded.
singleServiceCreator/IAM_creation.py Three role factories: S3-only, DynamoDB-only, and multi-service. Only the multi-service one is actually called.
deploymentModal/event_tracker.py Polls describe_stack_events, dedupes by EventId, maintains a per-resource status cache and progress percentage.
deploymentModal/websocket_handler.py DeploymentWebSocketManager — fan-out to multiple clients per stack, one 3-second polling task per stack, cancels the task when the last client leaves. Exposed as the module-global deployment_ws_manager.
deploymentModal/deployment_formatter.py Pure formatting functions → the JSON message shapes the frontend consumes.
allJSONs/createdCFs/ Every generated template, saved as CF_<build_id>.json. Gitignored on paper, ~190 files committed in practice.
allJSONs/templateConfigurationExamples/ Self-documenting per-service canvas JSON examples (field types, validation rules, choices). Good reference for the frontend contract.
NAMING_CONVENTION.md, IAM_ROLE_ARCHITECTURE.md, SSH_KEY_PAIR_GUIDE.md Design docs written alongside the code.

CICD/ — git push → running app

File Role
addYamlZip.py Holds the buildspec.yml and appspec.yml templates and injects them into the downloaded repo zip.
deploymentScripts.py Holds start.sh / stop.sh / install.sh (venv + pip install -r requirements.txt + nohup uvicorn main:app --port 8000) and injects them under scripts/.
upload_s3.py Uploads the rewritten zip to the CodeBuild source bucket.
trigger_codebuild.py start_build with S3 source override, then polls batch_get_builds every 3 s, emitting status over the build's WebSocket.
code_Deploy.py Creates/updates the CodeDeploy application and deployment group (targeted by the BuildId EC2 tag), starts the deployment, polls to completion, emits status.
add_webhook.py POST /repos/{owner}/{repo}/hooks with the user's OAuth token.
test.py, testCodeDeploy.py Standalone scratch scripts from development.

Supporting modules

File Role
database.py psycopg2 helpers: save_build (8-digit random ID with collision retry), get_build, update_build_canvas_and_template, get_builds_by_owner, log_activity, get_access_token_for_owner.
create_schema.py One-shot DDL for all tables.
costs/s3.py Finds the stack's buckets by CFN tag, reads BucketSizeBytes from CloudWatch, multiplies by $0.023/GB.
costs/ec2.py Finds instances by BuildId tag, computes hours since launch × a hardcoded per-type rate table.
costs/dynamo.py Fully commented out.
logs/logs.py Last hour of CPUUtilization per instance, as time series points.
settings/get_user.py asyncpg query listing user emails.
github_webhook_test/ An earlier standalone FastAPI app used to prototype the webhook flow. Superseded by routers/github_webhook.py.
test_*.py, check_tables.py, verify_build_table.py, demo_stack_update.py Root-level manual/integration scripts (not pytest suites — most are print-driven walkthroughs run by hand).

4. The canvas contract (frontend → backend)

The canvas is standard ReactFlow JSON. Only nodes[].id, nodes[].type, nodes[].data, and edges[].source/edges[].target are read; position is ignored by the backend.

{
  "nodes": [
    {
      "id": "ec2-1",
      "type": "EC2",                        // "EC2" | "S3" | "RDS" | "DynamoDB"
      "data": {
        "name": "api-server",               // required
        "imageId": "Ubuntu",                // friendly name or "ami-xxxx"
        "instanceType": "t3.micro",         // required
        "keyName": "optional-existing-key",
        "userData": "#!/bin/bash ...",
        "storage": {
          "rootVolumeSizeGiB": 20,          // default 20
          "rootVolumeType": "gp3",          // default gp3
          "deleteOnTermination": true
        }
      }
    },
    { "id": "s3-1",  "type": "S3",       "data": { "bucketName": "my-app-storage" } },
    { "id": "ddb-1", "type": "DynamoDB", "data": {
        "tableName": "users", "partitionKey": "id", "partitionKeyType": "S",
        "sortKey": "", "sortKeyType": "S" } },
    { "id": "rds-1", "type": "RDS",      "data": {
        "dbName": "appdb", "engine": "postgres",
        "masterUsername": "admin", "masterUserPassword": "..." } }
  ],
  "edges": [
    { "source": "s3-1", "target": "ec2-1" }   // direction matters: resource → EC2
  ]
}

Edge semantics. source is the resource being consumed, target is the EC2 doing the consuming. Edges whose target isn't an EC2 node are silently ignored (template_composer.py:92).

imageId friendly names resolve to AWS-maintained SSM parameters that always point at the latest AMI ({{resolve:ssm:/aws/service/...}}): Amazon Linux, Ubuntu, Windows. macOS is deliberately unsupported (needs dedicated hosts). Anything starting with ami- passes through untouched.


5. The deployment pipeline, step by step

POST /canvas/deployCFCreator.deployToAWS():

  1. Key pairscreate_key_pairs_for_deployment() walks the canvas for EC2 nodes and calls ec2:CreateKeyPair for each, naming them {build_id}-{node_id[:6]}-{instance_name}-key. The PEM private key comes back in the API response and is never stored server-side — the frontend must hand it to the user immediately.
  2. Template generationcreateGeneration()template_composer.make_stack_template():
    • Adds VpcId / SubnetId / SecurityGroupId parameters (plus DBSubnetGroupName if any RDS node exists).
    • Phase 1: walk edges, build ec2_dependencies = {ec2_id: {s3: [...], dynamodb: [...], rds: [...]}}.
    • Phase 2: create S3 / RDS / DynamoDB resources, recording each in resource_refs keyed by node id.
    • Phase 3: for each EC2, look up its dependencies, create the IAM role + instance profile (or use the demo profile), assemble the environment-variable dict, and add the instance.
    • The JSON is also written to CFCreators/allJSONs/createdCFs/CF_<build_id>.json.
  3. Deployer initCloudFormationDeployer(region) creates cloudformation/ec2/rds boto3 clients.
  4. VPC discovery — finds the default VPC, its first subnet, and its default security group. If RDS is present, it also gets-or-creates foundry-db-subnet-group-{vpc_id} spanning two AZs.
  5. Deploycreate_stack with Capabilities=['CAPABILITY_NAMED_IAM'] (needed because roles get explicit names) and OnFailure='ROLLBACK'. Stack name is foundry-stack-{build_id}.

Then back in the router: the canvas + template are persisted to the build row, and an activity_log entry is written. A DB failure here is caught and logged but does not fail the response — the stack is already up, so the deploy is reported as successful.

Live tracking. In parallel the frontend opens WS /canvas/deploy/track/{stack_name}. DeploymentWebSocketManager starts one polling task per stack (3 s interval), diffs describe_stack_events against seen event IDs, and broadcasts resource_update messages with a progress percentage, finishing with a stack_complete message carrying stack outputs and a human-readable duration.

Updates (POST /canvas/deploy/update) go through change sets, not blind updates: generate the new template, create_change_set, return the formatted list of adds/modifies/removes (including whether a change forces resource replacement) for the user to review, then either auto_execute or wait for POST /canvas/deploy/execute-changeset / DELETE /canvas/deploy/changeset. The "no changes" case is detected by inspecting the failed waiter's StatusReason and returned cleanly rather than as an error.

Deletes (POST /canvas/deploy/delete) call delete_stack then sweep key pairs matching the stack name.


6. The CI/CD pipeline, step by step

  1. POST /github/add_webhook with {owner, repo, build_id}. The user's GitHub OAuth token is looked up from the account table, a push webhook is registered against the ngrok URL, and (owner, repo) → build_id is recorded in the in-process dict build_id_store.
  2. GitHub sends push to POST /github/webhook. The HMAC-SHA256 signature is verified against GITHUB_WEBHOOK_SECRET (only when both the secret and the header are present).
  3. The repo is downloaded as a zip from the GitHub zipball API.
  4. buildspec.yml, appspec.yml, and scripts/{start,stop,install}.sh are injected into the zip — each injector rewrites the archive into a temp file and moves it over the original. The user's repo doesn't need to contain any AWS config.
  5. Zip → s3://foundry-codebuild-zip/{owner}/{repo}-{ref}.zip.
  6. trigger_codebuild() starts the foundryCICD CodeBuild project with an S3 source override and polls until terminal, streaming each status over the build's WebSocket.
  7. On SUCCEEDED, codeDeploy() creates/updates a CodeDeploy application and a deployment group whose EC2 tag filter is BuildId = {build_id}this is the join between the two subsystems: the tag that EC2_creation.py stamps on every instance is what CodeDeploy uses to find the target.
  8. The instance's public IP is read back via describe_instances filtered on the same tag, and http://{ip}:8000 is written to build.endpoint.

7. Resource naming and IAM

Everything is named {build_id}-{...}-{user_supplied_name} so resources from one build are greppable and never collide with another build's.

Resource Pattern Sanitizer
Stack foundry-stack-{build_id}
EC2 Name tag {build_id}-{name} sanitize_ec2_name (alnum, _, -)
S3 bucket {build_id}-{bucketName}, ≤63 chars sanitize_bucket_name_part (lowercase, alnum, -)
RDS identifier {build_id}-{dbName} sanitize_rds_identifier (lowercase, must start with a letter)
DynamoDB table {build_id}-{tableName} sanitize_dynamodb_name (alnum, _, -, .)
IAM role {build_id}-{node_id[:6]}-ec2-multi-service-role sanitize_iam_name
Key pair {build_id}-{node_id[:6]}-{name}-key inline replace

CloudFormation logical IDs are separate: they're {Type}{node_id with -, :, _ stripped}, since logical IDs must be alphanumeric.

Every resource also carries the tags Name, OriginalName (what the user actually typed), ManagedBy, and BuildId. The BuildId tag is load-bearing — CodeDeploy targeting, cost lookup, and log lookup all key off it.

IAM roles are per-EC2 and per-resource-ARN, not shared: an EC2 wired to one bucket gets a policy naming that bucket's ARN and {ARN}/*, and nothing else.


8. Demo mode

Live IAM role creation costs 30–90 seconds of AWS eventual-consistency waiting, which was too slow for a 2-minute demo. Setting DEMO_MODE=true makes two substitutions:

  • template_composer.py skips creating IAM resources entirely and attaches the pre-existing instance profile named by DEMO_IAM_INSTANCE_PROFILE_NAME (default foundry-demo-ec2-profile). This is why EC2_creation.py accepts an instance profile that is either a string (demo) or a troposphere object (production).
  • resolve_image_id() returns a hardcoded pre-baked Ubuntu AMI (ami-002382dae3b809f72 on hola) that already has the CodeDeploy agent and Python deps installed, instead of the stock SSM-resolved Ubuntu image.

USE_DEFAULT_BUILD_ID=true is a companion flag that forces every resource to use the literal string default as its build ID prefix, so demo resource names are predictable.

The setup script for this lives on the liveDemo branch, not hola — see §13.


9. API reference

Stacks — /canvas

Method Path Notes
GET /canvas/health Liveness.
POST /canvas/deploy Body: {buildId, canvas, owner_id?, region?}. Returns stack id/name/status, outputs, and keyPairs including PEM private keys.
GET /canvas/deploy/status/{stack_name} ?region= optional.
POST /canvas/deploy/update Body: {build_id, stack_name, canvas, auto_execute?}. Creates a change set.
POST /canvas/deploy/execute-changeset Query params, not a body: stack_name, change_set_name, build_id.
DELETE /canvas/deploy/changeset Query params: stack_name, change_set_name.
POST /canvas/deploy/delete Body: {stack_name, build_id?, cleanup_key_pairs?}.
WS /canvas/deploy/track/{stack_name} Streams initial_state / resource_update / stack_complete / error.

Builds — /builds

Method Path Notes
GET /builds/new?id={owner_id} Creates an empty build row, returns {build_id}. Must be called before /canvas/deploy.
GET /builds/?id={owner_id} All builds for an owner.
GET /builds/invitations?id={user_id}
POST /builds/invitations/accept · /builds/invitations/decline Body {id}.

Project data — /canvas

Method Path Notes
GET /canvas/ Lists the caller's GitHub repos. Requires Authorization: Bearer <github-token>.
GET /canvas/users All users.
GET/POST /canvas/settings Read/write project name + description.
POST /canvas/invite Body {invite_id[], build_id, owner_id, project_name, description}.
POST /canvas/deployments Marks build.status = true.
GET /canvas/endpoint/?build_id= The deployed app's URL.
GET /canvas/costs?build_id= {s3, ec2} estimates.
GET /canvas/logs?build_id= CPU time series per instance.
POST /canvas/s3/upload Multipart: file, bucket_name, node_id.

CI/CD — /github

Method Path Notes
POST /github/add_webhook Body {owner, repo, build_id}.
POST /github/webhook GitHub's endpoint. HMAC-verified.
WS /github/ws/{build_id} Streams raw CodeBuild/CodeDeploy status strings.

10. Database schema

PostgreSQL on RDS. create_schema.py creates:

Table Purpose
users id, name, username, email.
account GitHub OAuth linkage. Read at runtime via github_logingithub_access_token.
build id (8-digit random, not the SERIAL the DDL declares), owner_id, canvas JSONB, cf_template JSONB, created_at.
build_access Per-build role grants.
activity_log build_id, user_id, change — free-text audit trail.
deployments Stack tracking table. Declared but not written to by any current code path.

⚠️ create_schema.py has drifted from reality. Columns the running code reads or writes that the DDL doesn't declare: build.project_name, build.description, build.status, build.endpoint; the whole invites table; and account.github_login / account.github_access_token (the DDL declares user_id + token instead). Treat the live database as the source of truth and regard create_schema.py as stale.


11. Environment variables

Read from .env (gitignored; database.py loads it with override=True).

Variable Purpose
DATABASE_URL asyncpg DSN, used by the inline-async endpoints.
RDS_HOST / RDS_PORT / RDS_DATABASE / RDS_USER / RDS_PASSWORD psycopg2 config in database.py. SSL is forced (sslmode=require).
GITHUB_WEBHOOK_SECRET HMAC secret for webhook signature verification.
DEMO_MODE true → skip IAM creation, use pre-baked AMI.
DEMO_IAM_ROLE_NAME / DEMO_IAM_INSTANCE_PROFILE_NAME Names of the pre-created demo role/profile.
USE_DEFAULT_BUILD_ID true → force every resource prefix to the literal default.

AWS credentials come from the standard boto3 chain (~/.aws/credentials, env vars, or instance role) — they are not read from .env.


12. Running it locally

Prerequisites: Python 3.12, AWS credentials with CloudFormation/EC2/S3/RDS/DynamoDB/IAM/CodeBuild/CodeDeploy permissions, and network access to the Postgres instance.

pip install -r requirements.txt
uvicorn app:app --reload --port 8000

Interactive docs at http://localhost:8000/docs. Sanity-check the DB with:

python database.py

For the GitHub webhook you need a public URL. The handler currently has an ngrok URL hardcoded at routers/github_webhook.py:104 — update it to your own tunnel:

ngrok http 8000

Typical end-to-end flow: GET /builds/new?id=1POST /canvas/deploy with the returned buildId → open WS /canvas/deploy/track/foundry-stack-{buildId}POST /github/add_webhookgit push.


13. Branch guide — where the working code lives

The repo has 12 remote branches. Development happened by branching per person/feature and merging sideways rather than through main, so main is not the most current code — it was last updated by PR #3 (deployment_modal) and is missing 22 commits of subsequent work.

✅ Use hola

origin/hola (last commit f55797e, 2025-12-03) is the most complete and most recent branch. git branch -r --merged origin/hola confirms that enaya, greetings, efrain, ak2, StackUpdates, deployment_modal, and webhook are all ancestors of it — their work is already in. It has:

  • the full CI/CD chain in routers/github_webhook.py, including the corrected trigger_codebuild argument order that older branches got wrong,
  • the costs/, logs/, and /canvas/s3/upload features,
  • all of liveDemo's demo-mode logic (BRANCH_SYNC_SUMMARY.md documents that merge, and diffing template_composer.py between the two confirms they're identical),
  • the newest demo AMI (ami-002382dae3b809f72, newer than liveDemo's ami-0650b7c7445670128),
  • a clean requirements.txt (liveDemo's was overwritten by a conda export containing broken file:///home/conda/... local paths and is missing asyncpg, websockets, and python-multipart).

Your local working tree is already on hola and is clean apart from a stray .pyc and an untracked nume-config.yml.

Two things exist only on liveDemo

setup_demo_iam_role.py and DEMO_MODE_SETUP.md — the script that creates the demo IAM role and profile that DEMO_MODE=true expects to already exist. Without them, demo mode fails at deploy time unless the role was created by hand. These are worth cherry-picking onto hola:

git checkout origin/liveDemo -- setup_demo_iam_role.py DEMO_MODE_SETUP.md

The rest

Branch Last commit Status
hola 2025-12-03 ✅ Most current. Work here.
hola2 2025-12-03 hola minus 2 commits, plus one demo tweak that drops the build_id prefix from DynamoDB table names (so a demo app could hardcode the table name). Not wanted generally.
liveDemo 2025-12-02 Demo-day snapshot. Only setup_demo_iam_role.py + DEMO_MODE_SETUP.md are worth keeping.
enaya 2025-12-02 Fully contained in hola.
greetings 2025-12-01 Fully contained in hola.
efrain 2025-11-19 The costs/logs work; already merged into hola.
main 2025-12-05 Behind by 22 commits despite the newer timestamp (the timestamp is a merge commit).
deployment_modal, StackUpdates, ak2, webhook, efrain_2 Nov 2025 Older feature branches, all merged in.

Recommendation: treat hola as trunk. Cherry-pick the two liveDemo files, then fast-forward main to hola so the default branch stops lying about the state of the project.


14. Known issues and refactor candidates

Catalogued from reading the code, ordered by impact. Nothing here has been changed.

Real bugs

  1. EC2 environment-variable injection is dead code. EC2_creation.py:153-173 carefully builds combined_user_data with all the S3_BUCKET_NAME / DYNAMODB_TABLE_NAME / DB_* exports — and then line 225 unconditionally overwrites props["UserData"] with a hardcoded CodeDeploy-agent install script. combined_user_data is never read. Deployed instances never receive the env vars the canvas edges imply, even though template_composer.py computes them correctly and the docs promise them. This is the single highest-value fix in the repo.

  2. S3 buckets ignore build_id. template_composer.py:139 calls S3_creation.add_s3_bucket(t, node, logical_id=logical_id) without passing build_id, so the parameter falls back to its "default" default and every bucket from every build is named default-<name> — which means the second build with the same bucket name fails on S3's global uniqueness. This is also why /canvas/s3/upload hardcodes second_name = f"default-{bucket_name}" (routers/canvas.py:1033); fix the composer and that hack goes away. RDS and DynamoDB do pass build_id correctly.

  3. Key-pair cleanup never matches. Keys are named {build_id}-...-key but cleanup_key_pairs_for_stack() matches against the stack name foundry-stack-{build_id}, so stack_name in key_name is always false. Deleting a stack reports 0 key pairs deleted and leaks every key pair. Match on the build ID, or better, pass the known key names through from the delete request.

  4. addBuildSpec returns None on the common path. Its return target_path sits inside the else branch (addYamlZip.py:103), so when the repo already has a buildspec.yml and overWrite=True, it returns None. That None becomes buildspecOverride in trigger_codebuild, silently changing which buildspec CodeBuild uses. Move the return out of the conditional.

  5. time.sleep(3) inside an async def. trigger_codebuild.py:43 blocks the whole event loop for the entire duration of a CodeBuild run — every other request, including all live WebSockets, stalls. code_Deploy.py gets this right with await asyncio.sleep(2); make them consistent.

  6. build_id_store is in-process memory. The (owner, repo) → build_id mapping in github_webhook.py:20 is lost on restart and not shared across workers, so a push after a restart deploys with build_id = None. It belongs in the database.

  7. /canvas/deploy/update drops build_id during template generation. It calls createGeneration(request.canvas) with no build_id, so the regenerated template falls back to a timestamp-based ID and every resource gets a new name — change sets will show spurious replacements.

  8. The webhook handler returns nothing when a build fails. github_webhook.py:193 only handles build_status == "SUCCEEDED"; there is no else, so a failed CodeBuild falls off the end of the function and FastAPI returns 200 null. The frontend can't distinguish a failure from a success. (An earlier version on efrain did return {"message": "Build failed, skipping deploy"} — the branch was lost in a later rewrite.) The try/except around the endpoint-recording block has the same shape: it prints and returns None.

Redundancy

  1. Copy-pasted zip injectors. addBuildSpec, addAppSpec, addStartScript, addStopScript, and addInstallScript are five near-identical ~45-line functions differing only in target_path. They collapse into one inject_into_zip(zip_path, target_suffix, content, overwrite=True) — about 200 lines → about 30. (Several still print "Injected start.sh" regardless of what they wrote, betraying the copy-paste.)

  2. get_access_token_for_owner is defined twice, byte-identical, at database.py:238 and :263, and imported twice on consecutive lines in github_webhook.py:16-17.

  3. Duplicated blocks in the webhook handler. github_webhook.py extracts owner/repo/build_id twice (lines 72-97), repeats the ping/ref guard twice (lines 138-161), and declares both build_id_store and sockets twice each.

  4. Three IAM role factories, one caller. create_ec2_s3_role and create_ec2_dynamodb_role are exported but never invoked — create_ec2_multi_service_role handles both cases. Delete the two dead ones (~200 lines), or keep them and delete the branch in the multi-service function.

  5. Env-var assembly duplicated inside template_composer.py. The S3/DynamoDB env-var loops appear once in the production branch (lines 226-242) and again in the "still add environment variables even in demo mode" block (lines 252-265), guarded by an if not environment_variables check that only works by accident. Hoist it above the demo/production split.

  6. Four hand-rolled sanitizers. sanitize_ec2_name, sanitize_bucket_name_part, sanitize_rds_identifier, sanitize_dynamodb_name, and sanitize_iam_name are the same algorithm with different allowed-character sets. One parameterized sanitize(name, allowed, lowercase=False, must_start_alpha=False) covers all five.

  7. The USE_DEFAULT_BUILD_ID check appears in six places (template_composer, all four service creators, two of three IAM factories — and create_ec2_s3_role is missing it, an inconsistency). Resolve it once in make_stack_template and pass the result down.

  8. Two database access patterns. database.py (psycopg2, pooled through a context manager) versus ~10 endpoints in canvas.py that call await asyncpg.connect(...) inline — opening a fresh connection per request and, in most cases, never closing it. Several of these also swallow exceptions with a bare print and return None, which FastAPI serialises as null with a 200 status, so the frontend can't tell success from failure. Consolidate on one driver behind database.py.

  9. Imports scattered mid-function. canvas.py re-imports get_build, log_activity, CloudFormationDeployer, and createGeneration inside handler bodies even though several are already imported at module top.

Hygiene and security

  1. Hardcoded credentials in create_schema.py:8-14 — a real database host, user, and password committed to git. Rotate the password and move the config to .env. (database.py:19 also carries the same host as a default.)
  2. CORS is allow_origins=["*"] with allow_credentials=True (app.py:14-20) — an invalid and unsafe combination. Pin the frontend origin.
  3. No authentication anywhere. Every endpoint takes owner_id as a plain request field defaulting to 1; the # TODO: Replace with actual auth comments say as much. Any caller can read or delete any build.
  4. SSH private keys are returned in the deploy HTTP response and printed to logs on the way through. Acceptable for a demo, not for anything real.
  5. Hardcoded infrastructure identifiers scattered through the code: the CodeDeploy service role ARN including the AWS account number (code_Deploy.py:11), bucket names foundry-codebuild-zip / foundry-artifacts-bucket, the project name foundryCICD, the ngrok URL, and region='us-east-1' in six modules despite the API accepting a region parameter. All belong in config.
  6. The founryCICD typo (missing d) is baked into artifact names in both trigger_codebuild.py and code_Deploy.py. They agree, so it works — just note that fixing one without the other breaks deploys.
  7. .pyc files and ~190 generated createdCFs/*.json are committed despite being listed in .gitignore (they were added before the ignore rules). git rm -r --cached them.
  8. Root directory clutter. 9 test_*.py scripts, check_tables.py, verify_build_table.py, demo_stack_update.py, tempfile.txt (empty), fastAPIserver-main.zip, and 4 status-report markdown files sit at the top level. The test_* files are manual walkthroughs, not a pytest suite — there is no automated test coverage of the template composer, which is the piece most worth testing.
  9. github_webhook_test/ duplicates routers/github_webhook.py in an obsolete standalone app. Delete it.
  10. costs/dynamo.py is entirely commented out, and its import in canvas.py:30 is commented out to match.

Suggested refactor order

If/when we do the refactor, this sequencing keeps things working at every step:

  1. Fix bugs 1–3 first (they're behavioural and independently verifiable).
  2. Collapse the zip injectors (9) and sanitizers (14) — pure, mechanical, easy to test.
  3. Delete duplicates 10, 11, 12, 26, 27.
  4. Unify the DB layer (16), which also fixes the leaked connections and swallowed errors.
  5. Extract config (22) and split canvas.py into deploy/builds/project/observability routers.
  6. Then auth (20) and CORS (19), which change the API contract and need frontend coordination.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages