forktex-cloud 0.2.4__tar.gz → 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/PKG-INFO +22 -14
  2. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/README.md +21 -13
  3. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/pyproject.toml +1 -1
  4. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/__init__.py +16 -9
  5. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/bridge/local_compose.py +26 -22
  6. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/client/__init__.py +8 -4
  7. forktex_cloud-1.0.0/src/forktex_cloud/client/client.py +1308 -0
  8. forktex_cloud-1.0.0/src/forktex_cloud/client/generated/__init__.py +1894 -0
  9. forktex_cloud-1.0.0/src/forktex_cloud/manifest/__init__.py +20 -0
  10. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/manifest/loader.py +74 -43
  11. forktex_cloud-1.0.0/src/forktex_cloud/manifest/schema.py +27 -0
  12. forktex_cloud-1.0.0/src/forktex_cloud/scaffold/templates.py +73 -0
  13. forktex_cloud-1.0.0/src/forktex_cloud/vpn/__init__.py +23 -0
  14. forktex_cloud-1.0.0/src/forktex_cloud/vpn/local.py +86 -0
  15. forktex_cloud-0.2.4/src/forktex_cloud/client/client.py +0 -576
  16. forktex_cloud-0.2.4/src/forktex_cloud/client/generated/__init__.py +0 -1026
  17. forktex_cloud-0.2.4/src/forktex_cloud/manifest/__init__.py +0 -35
  18. forktex_cloud-0.2.4/src/forktex_cloud/manifest/schema.py +0 -65
  19. forktex_cloud-0.2.4/src/forktex_cloud/scaffold/templates.py +0 -120
  20. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/LICENSE +0 -0
  21. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/bridge/__init__.py +0 -0
  22. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/bridge/log_formatter.py +0 -0
  23. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/bridge/loki.py +0 -0
  24. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/bridge/persistence_defaults.py +0 -0
  25. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/config.py +0 -0
  26. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/manifest/errors.py +0 -0
  27. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/manifest/merge.py +0 -0
  28. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/paths.py +0 -0
  29. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/scaffold/__init__.py +0 -0
  30. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/secrets/__init__.py +0 -0
  31. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/secrets/base.py +0 -0
  32. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/secrets/factory.py +0 -0
  33. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/secrets/fernet.py +0 -0
  34. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/secrets/resolver.py +0 -0
  35. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/templates/observability/loki.yml +0 -0
  36. {forktex_cloud-0.2.4 → forktex_cloud-1.0.0}/src/forktex_cloud/templates/observability/promtail.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forktex-cloud
3
- Version: 0.2.4
3
+ Version: 1.0.0
4
4
  Summary: Typed Python SDK for the ForkTex Cloud platform — provision, deploy, and manage VPS-backed apps via a declarative manifest.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -57,10 +57,10 @@ Requires Python ≥ 3.11.
57
57
  ### Authenticated client
58
58
 
59
59
  ```python
60
- from forktex_cloud import ForktexCloudClient, CloudContext
60
+ from forktex_cloud import Cloud, CloudContext
61
61
 
62
62
  ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
63
- with ForktexCloudClient.from_context(ctx) as client:
63
+ with Cloud.from_context(ctx) as client:
64
64
  projects = client.list_projects()
65
65
  servers = client.list_servers()
66
66
  health = client.health()
@@ -69,14 +69,14 @@ with ForktexCloudClient.from_context(ctx) as client:
69
69
  ### Direct auth
70
70
 
71
71
  ```python
72
- from forktex_cloud import ForktexCloudClient
72
+ from forktex_cloud import Cloud
73
73
 
74
74
  # JWT bearer (user login)
75
- with ForktexCloudClient("https://cloud.forktex.com", access_token="eyJ...") as client:
75
+ with Cloud("https://cloud.forktex.com", access_token="eyJ...") as client:
76
76
  me = client.me()
77
77
 
78
78
  # Org-scoped API key (CI/CD)
79
- with ForktexCloudClient(
79
+ with Cloud(
80
80
  "https://cloud.forktex.com",
81
81
  account_key="ftx-...",
82
82
  org_id="00000000-0000-0000-0000-000000000001",
@@ -88,7 +88,7 @@ with ForktexCloudClient(
88
88
  ### Local dev (point at your `make local` stack)
89
89
 
90
90
  ```python
91
- client = ForktexCloudClient(
91
+ client = Cloud(
92
92
  base_url="http://localhost:8000",
93
93
  account_key="ftx-dev-key-2026",
94
94
  org_id="<your-org-uuid>",
@@ -98,15 +98,23 @@ client = ForktexCloudClient(
98
98
  ### Trigger a deploy pipeline
99
99
 
100
100
  ```python
101
- # Full up (provision + bootstrap + deploy + DNS + SSL)
102
- job = client.up(project_dir=Path("./my-project")) # reads forktex.json
101
+ # Preview what an apply WOULD do (read-only, no mutation):
102
+ plan = client.plan(project_dir=Path("./my-project"), env="production")
103
+ print(plan.manifest_diff) # added / removed / changed paths
104
+ for role in plan.ansible_plan:
105
+ print(f" {role.role}: {role.changed} changed, {role.failed} failed")
106
+ for d in role.diffs:
107
+ print(f" {d.task}: {d.before_header}") # file-level diff hints
108
+
109
+ # Full apply (provision + bootstrap + deploy + DNS + SSL)
110
+ job = client.apply(project_dir=Path("./my-project")) # reads forktex.json
103
111
  print(job.job_id, job.deployment_id, job.status)
104
112
 
105
113
  # Code push to an existing server (no re-provision)
106
114
  job = client.deploy(server_id="...", service="api") # Ansible deploy tag
107
115
 
108
116
  # Tear down
109
- job = client.down(keep_dns=True) # preserves DNS record
117
+ job = client.destroy(keep_dns=True) # preserves DNS record
110
118
  ```
111
119
 
112
120
  ### Manifest loading + validation
@@ -144,11 +152,11 @@ client.vault_delete("POSTGRES_PASSWORD")
144
152
 
145
153
  | Module | Purpose |
146
154
  |---|---|
147
- | `forktex_cloud.client` | Typed sync httpx client (`ForktexCloudClient`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
148
- | `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1beta2), deep-merge for env overlays, `ManifestError` |
155
+ | `forktex_cloud.client` | Typed sync httpx client (`Cloud`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
156
+ | `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1), deep-merge for env overlays, `ManifestError` |
149
157
  | `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
150
158
  | `forktex_cloud.scaffold` | `forktex cloud init` template generator (ProjectDeployment / StaticSite / SingleContainer / NativeBuild) |
151
- | `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud up --env local` |
159
+ | `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud apply --env local` |
152
160
  | `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
153
161
  | `forktex_cloud.paths` | Cross-platform `.forktex/` + `~/.forktex/` filesystem spec (V1). See [docs/forktex-directory-spec.md](https://github.com/forktex/cloud/blob/master/docs/forktex-directory-spec.md) |
154
162
 
@@ -159,7 +167,7 @@ All response models come from the OpenAPI codegen pipeline — **one source of t
159
167
  ```python
160
168
  from forktex_cloud import (
161
169
  # Client
162
- ForktexCloudClient, CloudAPIError,
170
+ Cloud, CloudAPIError,
163
171
  # Config
164
172
  CloudContext,
165
173
  # Manifest
@@ -23,10 +23,10 @@ Requires Python ≥ 3.11.
23
23
  ### Authenticated client
24
24
 
25
25
  ```python
26
- from forktex_cloud import ForktexCloudClient, CloudContext
26
+ from forktex_cloud import Cloud, CloudContext
27
27
 
28
28
  ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
29
- with ForktexCloudClient.from_context(ctx) as client:
29
+ with Cloud.from_context(ctx) as client:
30
30
  projects = client.list_projects()
31
31
  servers = client.list_servers()
32
32
  health = client.health()
@@ -35,14 +35,14 @@ with ForktexCloudClient.from_context(ctx) as client:
35
35
  ### Direct auth
36
36
 
37
37
  ```python
38
- from forktex_cloud import ForktexCloudClient
38
+ from forktex_cloud import Cloud
39
39
 
40
40
  # JWT bearer (user login)
41
- with ForktexCloudClient("https://cloud.forktex.com", access_token="eyJ...") as client:
41
+ with Cloud("https://cloud.forktex.com", access_token="eyJ...") as client:
42
42
  me = client.me()
43
43
 
44
44
  # Org-scoped API key (CI/CD)
45
- with ForktexCloudClient(
45
+ with Cloud(
46
46
  "https://cloud.forktex.com",
47
47
  account_key="ftx-...",
48
48
  org_id="00000000-0000-0000-0000-000000000001",
@@ -54,7 +54,7 @@ with ForktexCloudClient(
54
54
  ### Local dev (point at your `make local` stack)
55
55
 
56
56
  ```python
57
- client = ForktexCloudClient(
57
+ client = Cloud(
58
58
  base_url="http://localhost:8000",
59
59
  account_key="ftx-dev-key-2026",
60
60
  org_id="<your-org-uuid>",
@@ -64,15 +64,23 @@ client = ForktexCloudClient(
64
64
  ### Trigger a deploy pipeline
65
65
 
66
66
  ```python
67
- # Full up (provision + bootstrap + deploy + DNS + SSL)
68
- job = client.up(project_dir=Path("./my-project")) # reads forktex.json
67
+ # Preview what an apply WOULD do (read-only, no mutation):
68
+ plan = client.plan(project_dir=Path("./my-project"), env="production")
69
+ print(plan.manifest_diff) # added / removed / changed paths
70
+ for role in plan.ansible_plan:
71
+ print(f" {role.role}: {role.changed} changed, {role.failed} failed")
72
+ for d in role.diffs:
73
+ print(f" {d.task}: {d.before_header}") # file-level diff hints
74
+
75
+ # Full apply (provision + bootstrap + deploy + DNS + SSL)
76
+ job = client.apply(project_dir=Path("./my-project")) # reads forktex.json
69
77
  print(job.job_id, job.deployment_id, job.status)
70
78
 
71
79
  # Code push to an existing server (no re-provision)
72
80
  job = client.deploy(server_id="...", service="api") # Ansible deploy tag
73
81
 
74
82
  # Tear down
75
- job = client.down(keep_dns=True) # preserves DNS record
83
+ job = client.destroy(keep_dns=True) # preserves DNS record
76
84
  ```
77
85
 
78
86
  ### Manifest loading + validation
@@ -110,11 +118,11 @@ client.vault_delete("POSTGRES_PASSWORD")
110
118
 
111
119
  | Module | Purpose |
112
120
  |---|---|
113
- | `forktex_cloud.client` | Typed sync httpx client (`ForktexCloudClient`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
114
- | `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1beta2), deep-merge for env overlays, `ManifestError` |
121
+ | `forktex_cloud.client` | Typed sync httpx client (`Cloud`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
122
+ | `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1), deep-merge for env overlays, `ManifestError` |
115
123
  | `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
116
124
  | `forktex_cloud.scaffold` | `forktex cloud init` template generator (ProjectDeployment / StaticSite / SingleContainer / NativeBuild) |
117
- | `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud up --env local` |
125
+ | `forktex_cloud.bridge` | docker-compose generator (local mode), Loki config, log formatters used by `forktex cloud apply --env local` |
118
126
  | `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
119
127
  | `forktex_cloud.paths` | Cross-platform `.forktex/` + `~/.forktex/` filesystem spec (V1). See [docs/forktex-directory-spec.md](https://github.com/forktex/cloud/blob/master/docs/forktex-directory-spec.md) |
120
128
 
@@ -125,7 +133,7 @@ All response models come from the OpenAPI codegen pipeline — **one source of t
125
133
  ```python
126
134
  from forktex_cloud import (
127
135
  # Client
128
- ForktexCloudClient, CloudAPIError,
136
+ Cloud, CloudAPIError,
129
137
  # Config
130
138
  CloudContext,
131
139
  # Manifest
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "forktex-cloud"
3
- version = "0.2.4"
3
+ version = "1.0.0"
4
4
  description = "Typed Python SDK for the ForkTex Cloud platform — provision, deploy, and manage VPS-backed apps via a declarative manifest."
5
5
  authors = [
6
6
  {name = "ForkTex", email = "info@forktex.com"}
@@ -2,25 +2,32 @@
2
2
 
3
3
  Usage::
4
4
 
5
- from forktex_cloud import ForktexCloudClient, CloudContext
5
+ from forktex_cloud import Cloud
6
+
7
+ with Cloud("https://cloud.forktex.com", account_key="ftx-...") as cloud:
8
+ projects = cloud.list_projects()
9
+ servers = cloud.list_servers()
10
+
11
+ Or, when you already hold a ``CloudContext``::
12
+
13
+ from forktex_cloud import Cloud, CloudContext
6
14
 
7
15
  ctx = CloudContext(controller="https://cloud.forktex.com", account_key="ftx-...")
8
- with ForktexCloudClient.from_context(ctx) as client:
9
- projects = client.list_projects()
10
- servers = client.list_servers()
16
+ with Cloud.from_context(ctx) as cloud:
17
+ ...
11
18
  """
12
19
 
13
- __version__ = "1.0.1"
20
+ __version__ = "1.0.0"
14
21
 
15
22
  from forktex_cloud import paths
16
- from forktex_cloud.client.client import CloudAPIError, ForktexCloudClient
23
+ from forktex_cloud.client.client import Cloud, CloudAPIError
17
24
  from forktex_cloud.client.generated import (
18
25
  SPEC_HASH,
19
26
  SPEC_VERSION,
20
27
  ApiKeyCreated,
21
28
  ApiKeyRead,
29
+ AuditEventRead,
22
30
  EnvironmentRead,
23
- EventRead,
24
31
  HealthRead,
25
32
  JobResponse,
26
33
  MeResponse,
@@ -43,7 +50,7 @@ __all__ = [
43
50
  "SPEC_VERSION",
44
51
  "SPEC_HASH",
45
52
  # Client
46
- "ForktexCloudClient",
53
+ "Cloud",
47
54
  "CloudAPIError",
48
55
  # Config
49
56
  "CloudContext",
@@ -53,8 +60,8 @@ __all__ = [
53
60
  # Models (from OpenAPI codegen — the single source of truth)
54
61
  "ApiKeyCreated",
55
62
  "ApiKeyRead",
63
+ "AuditEventRead",
56
64
  "EnvironmentRead",
57
- "EventRead",
58
65
  "HealthRead",
59
66
  "JobResponse",
60
67
  "MeResponse",
@@ -142,24 +142,23 @@ def local_compose_from_manifest(
142
142
 
143
143
  svc: dict[str, Any] = {"image": image}
144
144
 
145
- if svc_type == "compute":
146
- build_cfg = svc_def.get("build")
147
- if build_cfg and isinstance(build_cfg, dict):
148
- # Use explicit build config from manifest overlay
149
- build_entry: dict[str, str] = {}
150
- ctx = build_cfg.get("context", f"./{sid}")
151
- # Rewrite relative context to be relative to .forktex/ dir
152
- if ctx.startswith("./"):
153
- build_entry["context"] = f"../{ctx[2:]}"
154
- else:
155
- build_entry["context"] = ctx
156
- if build_cfg.get("dockerfile"):
157
- build_entry["dockerfile"] = build_cfg["dockerfile"]
158
- svc["build"] = build_entry
159
- else:
160
- dockerfile = project_root / sid / "Dockerfile"
161
- if dockerfile.is_file():
162
- svc["build"] = {"context": f"../{sid}"}
145
+ # Build context — explicit overlay first, else auto-detect a sibling
146
+ # Dockerfile for compute services. Persistence services only opt in
147
+ # when the manifest explicitly declares `build` (zot is the canonical
148
+ # case persistence-typed but first-party).
149
+ build_cfg = svc_def.get("build")
150
+ if build_cfg and isinstance(build_cfg, dict):
151
+ build_entry: dict[str, str] = {}
152
+ ctx = build_cfg.get("context", f"./{sid}")
153
+ # Rewrite relative context to be relative to .forktex/ dir
154
+ build_entry["context"] = f"../{ctx.removeprefix('./')}" if ctx.startswith("./") else ctx
155
+ if build_cfg.get("dockerfile"):
156
+ build_entry["dockerfile"] = build_cfg["dockerfile"]
157
+ svc["build"] = build_entry
158
+ elif svc_type == "compute":
159
+ dockerfile = project_root / sid / "Dockerfile"
160
+ if dockerfile.is_file():
161
+ svc["build"] = {"context": f"../{sid}"}
163
162
 
164
163
  if sid in host_ports:
165
164
  host_port = host_ports[sid]
@@ -207,12 +206,17 @@ def local_compose_from_manifest(
207
206
  else:
208
207
  rewritten.append(v)
209
208
  svc["volumes"] = rewritten
210
- elif svc_type == "persistence":
209
+ if svc_type == "persistence":
210
+ # Bind-mount persistence data under .forktex/data/{sid}/ so it
211
+ # survives `docker compose down -v` and is visible on the host
212
+ # for inspection + backups. Mirrors api/src/bridge/local_compose.py.
211
213
  defaults = detect_persistence_defaults(image)
212
214
  if defaults and defaults.get("default_volume"):
213
- vol_name = f"{sid}-data"
214
- svc["volumes"] = [f"{vol_name}:{defaults['default_volume']}"]
215
- named_volumes[vol_name] = None
215
+ target = defaults["default_volume"]
216
+ existing = svc.get("volumes", [])
217
+ if not any(isinstance(v, str) and v.endswith(f":{target}") for v in existing):
218
+ existing.append(f"./data/{sid}:{target}")
219
+ svc["volumes"] = existing
216
220
 
217
221
  cmd = svc_def.get("command")
218
222
  if cmd:
@@ -6,7 +6,7 @@ commonly used ones so call sites can ``from forktex_cloud.client import X``
6
6
  without touching the ``generated`` namespace directly.
7
7
  """
8
8
 
9
- from forktex_cloud.client.client import CloudAPIError, ForktexCloudClient
9
+ from forktex_cloud.client.client import Cloud, CloudAPIError
10
10
  from forktex_cloud.client.generated import (
11
11
  ApiKeyCreated,
12
12
  ApiKeyRead,
@@ -14,13 +14,15 @@ from forktex_cloud.client.generated import (
14
14
  CapacityReport,
15
15
  CapacityServers,
16
16
  EnvironmentRead,
17
- EventRead,
18
17
  HealthRead,
19
18
  JobResponse,
20
19
  MeResponse,
21
20
  OrgBrief,
22
21
  OrgRead,
23
22
  ProjectRead,
23
+ RegistryCredentialCreate,
24
+ RegistryCredentialRead,
25
+ RegistryVerifyResponse,
24
26
  ServerDnsRead,
25
27
  ServerRead,
26
28
  ServerServiceRead,
@@ -33,7 +35,7 @@ from forktex_cloud.client.generated import (
33
35
  )
34
36
 
35
37
  __all__ = [
36
- "ForktexCloudClient",
38
+ "Cloud",
37
39
  "CloudAPIError",
38
40
  "ApiKeyCreated",
39
41
  "ApiKeyRead",
@@ -41,13 +43,15 @@ __all__ = [
41
43
  "CapacityReport",
42
44
  "CapacityServers",
43
45
  "EnvironmentRead",
44
- "EventRead",
45
46
  "HealthRead",
46
47
  "JobResponse",
47
48
  "MeResponse",
48
49
  "OrgBrief",
49
50
  "OrgRead",
50
51
  "ProjectRead",
52
+ "RegistryCredentialCreate",
53
+ "RegistryCredentialRead",
54
+ "RegistryVerifyResponse",
51
55
  "ServerDnsRead",
52
56
  "ServerRead",
53
57
  "ServerServiceRead",