-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.py
More file actions
189 lines (159 loc) · 7.33 KB
/
Copy pathdeploy.py
File metadata and controls
189 lines (159 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""
Developable Deploy — phase 2 of the Backend Engineer pipeline.
Reads the .developable/config.json written by main.py, generates Terraform
IaC files, provisions cloud infrastructure, and writes deploy.yml.
Usage
─────
python deploy.py --out ./output --deploy-to aws
python deploy.py --out ./output --deploy-to gcp --gcp-project my-project
python deploy.py --out ./output --deploy-to heroku
"""
import os
import subprocess
import sys
from pathlib import Path
from core.parser import PrismaParser
from core.project_config import ProjectConfig
from core.cli_helpers import print_deploy_eta
def _push_terraform_to_github(out_dir: Path, provider: str) -> None:
"""Commit terraform/ and push to origin main. Non-fatal — warns on failure."""
if not (out_dir / ".git").exists():
return
rc = subprocess.run(
["git", "remote", "get-url", "origin"],
cwd=out_dir, capture_output=True,
).returncode
if rc != 0:
print(" [Terraform] No git remote — skipping GitHub push of terraform/")
return
def git(*cmd: str) -> tuple[int, str]:
r = subprocess.run(["git", *cmd], cwd=out_dir, capture_output=True, text=True)
return r.returncode, (r.stdout + r.stderr).strip()
git("add", "terraform/")
rc, _ = git("diff", "--cached", "--quiet")
if rc == 0:
print(" [Terraform] terraform/ unchanged — nothing to push to GitHub")
return
rc, out = git("commit", "-m",
f"Add Terraform IaC files for {provider.upper()} deployment\n\n"
"Generated by Developable deploy.py.")
if rc != 0:
print(f" [Terraform] Warning: git commit failed — {out}", file=sys.stderr)
return
print(f" [Terraform] Pushing terraform/ to GitHub...")
rc, out = git("push", "origin", "main")
if rc != 0:
print(
f" [Terraform] Warning: push failed — committed locally but not on GitHub.\n"
f" Push manually: cd {out_dir} && git push origin main\n ({out})",
file=sys.stderr,
)
return
print(f" [Terraform] terraform/ pushed to GitHub ✓")
def main():
import argparse
parser = argparse.ArgumentParser(
description="Developable Deploy — provision cloud infrastructure for a generated API"
)
parser.add_argument("--out", default="./output",
help="Output directory produced by main.py (default: ./output)")
parser.add_argument("--deploy-to", required=True, metavar="PROVIDER",
choices=["aws", "heroku", "gcp"],
help="Cloud provider to deploy to: aws | heroku | gcp")
parser.add_argument("--schema", default=None, metavar="PATH",
help="Path to schema.prisma (resolved from config.json when omitted)")
parser.add_argument("--aws-region", default=None, metavar="REGION")
parser.add_argument("--heroku-app", default=None, metavar="NAME")
parser.add_argument("--gcp-project", default=None, metavar="PROJECT_ID")
parser.add_argument("--gcp-region", default=None, metavar="REGION")
parser.add_argument("--gcp-sa-path", default=None, metavar="PATH",
help="Path to GCP service account JSON key (leave unset to use ADC)")
parser.add_argument("--github-token", default=None, metavar="TOKEN")
args = parser.parse_args()
out_dir = Path(args.out)
if not out_dir.exists():
print(f"Error: output directory {out_dir} not found. Run main.py first.", file=sys.stderr)
sys.exit(1)
cfg = ProjectConfig(out_dir).load()
schema_path: Path | None = None
if args.schema:
schema_path = Path(args.schema)
elif cfg.get("schema_path"):
schema_path = Path(cfg["schema_path"])
else:
candidate = out_dir / "schema.prisma"
if candidate.exists():
schema_path = candidate
if schema_path is None or not schema_path.exists():
print(
"Error: could not locate schema.prisma. "
"Pass --schema <path> or ensure main.py wrote .developable/config.json.",
file=sys.stderr,
)
sys.exit(1)
tests_dir: Path | None = None
for candidate_path in (cfg.get("tests_dir"), str(out_dir / "tests")):
if candidate_path:
p = Path(candidate_path)
if p.is_dir():
tests_dir = p
break
if args.gcp_sa_path:
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = args.gcp_sa_path
github_token = args.github_token or os.environ.get("GITHUB_TOKEN", "") or ""
if not github_token:
print(
"\n Note: no GitHub token found. deploy.yml will be pushed but GitHub Actions\n"
" secrets cannot be set automatically.\n"
" Re-run with GITHUB_TOKEN=<token> or --github-token <token> to fix.\n",
file=sys.stderr,
)
print(f"Parsing schema from {schema_path}...")
spec = PrismaParser().parse(schema_path)
project_name = spec["entities"][0]["name_lower"] + "-api" if spec.get("entities") else "unknown"
_SEP = "─" * 58
provider_detail = {
"aws": f"AWS ({args.aws_region or 'us-east-1 (default)'})",
"gcp": f"GCP ({args.gcp_project or 'project not set'} / {args.gcp_region or 'us-central1'})",
"heroku": f"Heroku ({args.heroku_app or project_name})",
}.get(args.deploy_to, args.deploy_to)
print(f"\n{_SEP}\n Developable Deploy\n{_SEP}")
print(f" Entities : {', '.join(e['name'] for e in spec['entities'])}")
print(f" Project : {project_name}")
print(f" Output : {out_dir.resolve()}")
print(f" Provider : {provider_detail}")
print(f" Tests dir : {tests_dir or 'none detected'}")
print(f"{_SEP}\n")
# ── Terraform agent ────────────────────────────────────────────────────────
from agents.terraform import TerraformAgent
tf_config: dict = {}
if args.deploy_to == "aws":
tf_config = {"aws_region": args.aws_region or "us-east-1"}
elif args.deploy_to == "gcp":
tf_config = {"gcp_project": args.gcp_project or "", "gcp_region": args.gcp_region or "us-central1"}
print(f"\n[Terraform] Generating IaC files for {args.deploy_to.upper()}...")
TerraformAgent(out_dir, args.deploy_to, tf_config).generate(spec)
print(f" Terraform files written to {out_dir}/terraform/")
_push_terraform_to_github(out_dir, args.deploy_to)
# ── Deployment agent ───────────────────────────────────────────────────────
from agents.deployment import Deployment
print_deploy_eta(args.deploy_to)
print(f"[Deployment] Deploying to {args.deploy_to.upper()}...")
record = Deployment(
out_dir=out_dir,
provider=args.deploy_to,
tests_dir=tests_dir,
github_token=github_token,
aws_region=args.aws_region,
heroku_app=args.heroku_app,
gcp_project=args.gcp_project,
gcp_region=args.gcp_region,
).deploy(spec, {})
print(f"\n✓ Deployment complete!")
print(f" Endpoint : {record['endpoint']}")
if record.get("region"):
print(f" Region : {record['region']}")
print(f" Provider : {record['provider']}")
print(f" State : {out_dir}/.developable/state.json")
if __name__ == "__main__":
main()