forktex-cloud 2.1.0__tar.gz → 2.3.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.
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/PKG-INFO +12 -11
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/README.md +9 -7
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/pyproject.toml +16 -5
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/__init__.py +7 -5
- forktex_cloud-2.3.0/src/forktex_cloud/bridge/__init__.py +25 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/bridge/local_compose.py +32 -9
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/bridge/loki.py +2 -2
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/bridge/persistence_defaults.py +17 -4
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/client/__init__.py +4 -3
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/client/client.py +316 -625
- forktex_cloud-2.3.0/src/forktex_cloud/client/facade.py +393 -0
- forktex_cloud-2.3.0/src/forktex_cloud/client/generated/__init__.py +2643 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/config.py +6 -2
- forktex_cloud-2.3.0/src/forktex_cloud/local.py +178 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/manifest/loader.py +3 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/manifest/schema.py +11 -2
- forktex_cloud-2.3.0/src/forktex_cloud/manifest/semantics.py +130 -0
- forktex_cloud-2.3.0/src/forktex_cloud/scaffold/__init__.py +5 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/scaffold/templates.py +1 -1
- forktex_cloud-2.3.0/src/forktex_cloud/secrets/__init__.py +14 -0
- forktex_cloud-2.1.0/src/forktex_cloud/bridge/__init__.py +0 -1
- forktex_cloud-2.1.0/src/forktex_cloud/client/generated/__init__.py +0 -1907
- forktex_cloud-2.1.0/src/forktex_cloud/scaffold/__init__.py +0 -1
- forktex_cloud-2.1.0/src/forktex_cloud/secrets/__init__.py +0 -1
- forktex_cloud-2.1.0/src/forktex_cloud/vpn/__init__.py +0 -23
- forktex_cloud-2.1.0/src/forktex_cloud/vpn/local.py +0 -86
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/LICENSE +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/bridge/log_formatter.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/manifest/__init__.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/manifest/errors.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/manifest/merge.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/secrets/base.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/secrets/factory.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/secrets/fernet.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/secrets/resolver.py +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/templates/observability/loki.yml +0 -0
- {forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/templates/observability/promtail.yml +0 -0
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forktex-cloud
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
7
7
|
Keywords: forktex,cloud,deployment,vps,hetzner,ansible,blue-green,iac,infrastructure-as-code,sdk
|
|
8
8
|
Author: ForkTex
|
|
9
9
|
Author-email: info@forktex.com
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.14,<4.0
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Intended Audience :: System Administrators
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
19
|
Classifier: Topic :: System :: Distributed Computing
|
|
21
20
|
Classifier: Topic :: System :: Installation/Setup
|
|
@@ -40,9 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
40
39
|
|
|
41
40
|
Standalone Python SDK for the [ForkTex Cloud](https://cloud.forktex.com) platform.
|
|
42
41
|
|
|
43
|
-
`forktex-cloud` is the typed `httpx` client + manifest plumbing
|
|
44
|
-
|
|
45
|
-
You can use it directly from any Python application — no `forktex` CLI required.
|
|
42
|
+
`forktex-cloud` is the typed `httpx` client + manifest plumbing for the ForkTex Cloud control plane: project provisioning, server management, deployments, vault, manifest validation, and the docker-compose / Hetzner / Ansible bridge. It was the library behind the retired `forktex cloud …` CLI; today it is used directly from Python applications, the platform's own runner, and CI.
|
|
46
43
|
|
|
47
44
|
## Install
|
|
48
45
|
|
|
@@ -50,7 +47,7 @@ You can use it directly from any Python application — no `forktex` CLI require
|
|
|
50
47
|
pip install forktex-cloud
|
|
51
48
|
```
|
|
52
49
|
|
|
53
|
-
Requires Python ≥ 3.
|
|
50
|
+
Requires Python ≥ 3.14 (`requires-python = ">=3.14,<4.0"`).
|
|
54
51
|
|
|
55
52
|
## Quick Start
|
|
56
53
|
|
|
@@ -93,6 +90,10 @@ client = Cloud(
|
|
|
93
90
|
account_key="ftx-dev-key-2026",
|
|
94
91
|
org_id="<your-org-uuid>",
|
|
95
92
|
)
|
|
93
|
+
|
|
94
|
+
For integration tests or proxying through a mock/live transport, pass the
|
|
95
|
+
optional `transport=` argument to `Cloud(...)` and provide any `httpx`
|
|
96
|
+
transport implementation.
|
|
96
97
|
```
|
|
97
98
|
|
|
98
99
|
### Trigger a deploy pipeline
|
|
@@ -155,8 +156,8 @@ client.vault_delete("POSTGRES_PASSWORD")
|
|
|
155
156
|
| `forktex_cloud.client` | Typed sync httpx client (`Cloud`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
|
|
156
157
|
| `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1), deep-merge for env overlays, `ManifestError` |
|
|
157
158
|
| `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
|
|
158
|
-
| `forktex_cloud.scaffold` | `forktex
|
|
159
|
-
| `forktex_cloud.bridge` | docker-compose generator (local mode
|
|
159
|
+
| `forktex_cloud.scaffold` | `forktex.json` template generator (formerly driven by the retired `forktex cloud init` CLI) |
|
|
160
|
+
| `forktex_cloud.bridge` | docker-compose generator (local mode: `local_compose_from_manifest`, used by the repo's `scripts/local_stack.py` / `make start`; formerly by the retired `forktex cloud apply --env local` CLI), Loki config, log formatters |
|
|
160
161
|
| `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
|
|
161
162
|
| `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) |
|
|
162
163
|
|
|
@@ -191,7 +192,7 @@ The SDK follows [SemVer](https://semver.org/). The client's response models are
|
|
|
191
192
|
This SDK lives inside the [`forktex/cloud`](https://github.com/forktex/cloud) monorepo alongside the API server (`api/`) and React Native client (`client/`). The SDK package is independently versioned and published to PyPI.
|
|
192
193
|
|
|
193
194
|
- Docs: [https://github.com/forktex/cloud/tree/master/docs](https://github.com/forktex/cloud/tree/master/docs)
|
|
194
|
-
-
|
|
195
|
+
- Operations guide: [operations.md](https://github.com/forktex/cloud/blob/master/docs/operations.md)
|
|
195
196
|
- Issues: [https://github.com/forktex/cloud/issues](https://github.com/forktex/cloud/issues)
|
|
196
197
|
|
|
197
198
|
## License
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Standalone Python SDK for the [ForkTex Cloud](https://cloud.forktex.com) platform.
|
|
8
8
|
|
|
9
|
-
`forktex-cloud` is the typed `httpx` client + manifest plumbing
|
|
10
|
-
|
|
11
|
-
You can use it directly from any Python application — no `forktex` CLI required.
|
|
9
|
+
`forktex-cloud` is the typed `httpx` client + manifest plumbing for the ForkTex Cloud control plane: project provisioning, server management, deployments, vault, manifest validation, and the docker-compose / Hetzner / Ansible bridge. It was the library behind the retired `forktex cloud …` CLI; today it is used directly from Python applications, the platform's own runner, and CI.
|
|
12
10
|
|
|
13
11
|
## Install
|
|
14
12
|
|
|
@@ -16,7 +14,7 @@ You can use it directly from any Python application — no `forktex` CLI require
|
|
|
16
14
|
pip install forktex-cloud
|
|
17
15
|
```
|
|
18
16
|
|
|
19
|
-
Requires Python ≥ 3.
|
|
17
|
+
Requires Python ≥ 3.14 (`requires-python = ">=3.14,<4.0"`).
|
|
20
18
|
|
|
21
19
|
## Quick Start
|
|
22
20
|
|
|
@@ -59,6 +57,10 @@ client = Cloud(
|
|
|
59
57
|
account_key="ftx-dev-key-2026",
|
|
60
58
|
org_id="<your-org-uuid>",
|
|
61
59
|
)
|
|
60
|
+
|
|
61
|
+
For integration tests or proxying through a mock/live transport, pass the
|
|
62
|
+
optional `transport=` argument to `Cloud(...)` and provide any `httpx`
|
|
63
|
+
transport implementation.
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
### Trigger a deploy pipeline
|
|
@@ -121,8 +123,8 @@ client.vault_delete("POSTGRES_PASSWORD")
|
|
|
121
123
|
| `forktex_cloud.client` | Typed sync httpx client (`Cloud`) + all OpenAPI-codegenned Pydantic models (`ServerRead`, `ProjectRead`, `EventRead`, `VaultGetResponse`, ...) |
|
|
122
124
|
| `forktex_cloud.manifest` | `Manifest` loader, discriminated-union schema (v1), deep-merge for env overlays, `ManifestError` |
|
|
123
125
|
| `forktex_cloud.config` | `CloudContext` — controller URL, JWT / account-key, current org + project keys |
|
|
124
|
-
| `forktex_cloud.scaffold` | `forktex
|
|
125
|
-
| `forktex_cloud.bridge` | docker-compose generator (local mode
|
|
126
|
+
| `forktex_cloud.scaffold` | `forktex.json` template generator (formerly driven by the retired `forktex cloud init` CLI) |
|
|
127
|
+
| `forktex_cloud.bridge` | docker-compose generator (local mode: `local_compose_from_manifest`, used by the repo's `scripts/local_stack.py` / `make start`; formerly by the retired `forktex cloud apply --env local` CLI), Loki config, log formatters |
|
|
126
128
|
| `forktex_cloud.secrets` | Fernet vault + `${vault:KEY}` resolver for compile-time secret injection |
|
|
127
129
|
| `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) |
|
|
128
130
|
|
|
@@ -157,7 +159,7 @@ The SDK follows [SemVer](https://semver.org/). The client's response models are
|
|
|
157
159
|
This SDK lives inside the [`forktex/cloud`](https://github.com/forktex/cloud) monorepo alongside the API server (`api/`) and React Native client (`client/`). The SDK package is independently versioned and published to PyPI.
|
|
158
160
|
|
|
159
161
|
- Docs: [https://github.com/forktex/cloud/tree/master/docs](https://github.com/forktex/cloud/tree/master/docs)
|
|
160
|
-
-
|
|
162
|
+
- Operations guide: [operations.md](https://github.com/forktex/cloud/blob/master/docs/operations.md)
|
|
161
163
|
- Issues: [https://github.com/forktex/cloud/issues](https://github.com/forktex/cloud/issues)
|
|
162
164
|
|
|
163
165
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "forktex-cloud"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.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"}
|
|
@@ -8,7 +8,7 @@ authors = [
|
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.14,<4.0"
|
|
12
12
|
keywords = [
|
|
13
13
|
"forktex",
|
|
14
14
|
"cloud",
|
|
@@ -28,8 +28,7 @@ classifiers = [
|
|
|
28
28
|
"Operating System :: OS Independent",
|
|
29
29
|
"Programming Language :: Python :: 3",
|
|
30
30
|
"Programming Language :: Python :: 3 :: Only",
|
|
31
|
-
"Programming Language :: Python :: 3.
|
|
32
|
-
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.14",
|
|
33
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
34
33
|
"Topic :: System :: Distributed Computing",
|
|
35
34
|
"Topic :: System :: Installation/Setup",
|
|
@@ -61,10 +60,15 @@ dev = [
|
|
|
61
60
|
"ruff (>=0.8.0)",
|
|
62
61
|
"pyright (>=1.1.0,<2.0.0)",
|
|
63
62
|
"mypy (>=1.11.0,<2.0.0)",
|
|
63
|
+
# Release tooling. `make build` and `make publish` shell out to these, so
|
|
64
|
+
# undeclared they turn a release into "No module named twine" at the moment
|
|
65
|
+
# you try to ship. Same pair, same versions, as ../forktex-py.
|
|
66
|
+
"build (>=1.2.0)",
|
|
67
|
+
"twine (>=5.0.0)",
|
|
64
68
|
]
|
|
65
69
|
|
|
66
70
|
[tool.ruff]
|
|
67
|
-
target-version = "
|
|
71
|
+
target-version = "py314"
|
|
68
72
|
line-length = 100
|
|
69
73
|
extend-exclude = [
|
|
70
74
|
# Auto-generated by OpenAPI codegen. Canonical import shape comes from
|
|
@@ -76,6 +80,13 @@ extend-exclude = [
|
|
|
76
80
|
[tool.ruff.lint]
|
|
77
81
|
select = ["E", "F", "I", "W"]
|
|
78
82
|
|
|
83
|
+
[tool.mypy]
|
|
84
|
+
plugins = ["pydantic.mypy"]
|
|
85
|
+
ignore_missing_imports = true
|
|
86
|
+
# Auto-generated from the OpenAPI spec — committed for drift-checking but not
|
|
87
|
+
# subject to hand-typing (mirrors the api codegen/build exclude).
|
|
88
|
+
exclude = ["client/generated"]
|
|
89
|
+
|
|
79
90
|
[tool.pytest.ini_options]
|
|
80
91
|
asyncio_mode = "auto"
|
|
81
92
|
testpaths = ["tests"]
|
|
@@ -41,8 +41,10 @@ __version__ = "2.1.0"
|
|
|
41
41
|
# model lookups hit the module-level cache populated by __getattr__.
|
|
42
42
|
_LAZY_ATTRS: dict[str, tuple[str, str]] = {
|
|
43
43
|
# Client (httpx-backed, heavy)
|
|
44
|
-
"Cloud": ("forktex_cloud.client.
|
|
44
|
+
"Cloud": ("forktex_cloud.client.facade", "Cloud"),
|
|
45
|
+
"CloudClient": ("forktex_cloud.client.client", "CloudClient"),
|
|
45
46
|
"CloudAPIError": ("forktex_cloud.client.client", "CloudAPIError"),
|
|
47
|
+
"DeployBlocked": ("forktex_cloud.client.client", "DeployBlocked"),
|
|
46
48
|
# Config
|
|
47
49
|
"CloudContext": ("forktex_cloud.config", "CloudContext"),
|
|
48
50
|
# Manifest
|
|
@@ -65,7 +67,6 @@ _LAZY_ATTRS: dict[str, tuple[str, str]] = {
|
|
|
65
67
|
"TokenResponse": ("forktex_cloud.client.generated", "TokenResponse"),
|
|
66
68
|
"UserRead": ("forktex_cloud.client.generated", "UserRead"),
|
|
67
69
|
"VaultGetResponse": ("forktex_cloud.client.generated", "VaultGetResponse"),
|
|
68
|
-
"WorkspaceRead": ("forktex_cloud.client.generated", "WorkspaceRead"),
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
|
|
@@ -93,7 +94,8 @@ def __dir__() -> list[str]:
|
|
|
93
94
|
# Static type-checker hint — declared as a TYPE_CHECKING block so it costs
|
|
94
95
|
# nothing at runtime but lets editors / mypy see the eventual symbol types.
|
|
95
96
|
if TYPE_CHECKING: # pragma: no cover
|
|
96
|
-
from forktex_cloud.client.client import
|
|
97
|
+
from forktex_cloud.client.client import CloudAPIError, CloudClient, DeployBlocked
|
|
98
|
+
from forktex_cloud.client.facade import Cloud
|
|
97
99
|
from forktex_cloud.client.generated import (
|
|
98
100
|
SPEC_HASH,
|
|
99
101
|
SPEC_VERSION,
|
|
@@ -111,7 +113,6 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
111
113
|
TokenResponse,
|
|
112
114
|
UserRead,
|
|
113
115
|
VaultGetResponse,
|
|
114
|
-
WorkspaceRead,
|
|
115
116
|
)
|
|
116
117
|
from forktex_cloud.config import CloudContext
|
|
117
118
|
from forktex_cloud.manifest.loader import Manifest, ManifestError
|
|
@@ -123,7 +124,9 @@ __all__ = [
|
|
|
123
124
|
"SPEC_HASH",
|
|
124
125
|
# Client — lazy
|
|
125
126
|
"Cloud",
|
|
127
|
+
"CloudClient",
|
|
126
128
|
"CloudAPIError",
|
|
129
|
+
"DeployBlocked",
|
|
127
130
|
# Config — lazy
|
|
128
131
|
"CloudContext",
|
|
129
132
|
# Manifest — lazy
|
|
@@ -144,5 +147,4 @@ __all__ = [
|
|
|
144
147
|
"TokenResponse",
|
|
145
148
|
"UserRead",
|
|
146
149
|
"VaultGetResponse",
|
|
147
|
-
"WorkspaceRead",
|
|
148
150
|
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Bridge modules: manifest → docker compose for local dev, plus log helpers.
|
|
2
|
+
|
|
3
|
+
``local_compose_from_manifest`` is the pure renderer the cloud repo's
|
|
4
|
+
``scripts/local_stack.py`` drives; it returns a compose dict and writes nothing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from forktex_cloud.bridge.local_compose import (
|
|
8
|
+
local_compose_from_manifest,
|
|
9
|
+
render_observability_configs,
|
|
10
|
+
)
|
|
11
|
+
from forktex_cloud.bridge.log_formatter import assign_colors, format_line
|
|
12
|
+
from forktex_cloud.bridge.loki import build_logql, loki_ready, query_range, tail
|
|
13
|
+
from forktex_cloud.bridge.persistence_defaults import detect_persistence_defaults
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"assign_colors",
|
|
17
|
+
"build_logql",
|
|
18
|
+
"detect_persistence_defaults",
|
|
19
|
+
"format_line",
|
|
20
|
+
"local_compose_from_manifest",
|
|
21
|
+
"loki_ready",
|
|
22
|
+
"query_range",
|
|
23
|
+
"render_observability_configs",
|
|
24
|
+
"tail",
|
|
25
|
+
]
|
|
@@ -194,6 +194,22 @@ def _add_observability_services(
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
|
|
197
|
+
def _observability_enabled(manifest: Manifest) -> bool:
|
|
198
|
+
"""Whether the manifest asks for the local Loki/Promtail pair.
|
|
199
|
+
|
|
200
|
+
The `observability` argument is the caller's veto, not the answer: a
|
|
201
|
+
manifest that sets `observability.enabled: false` — as a project whose
|
|
202
|
+
local stack runs beside another one does, to keep Loki's 3100 free — still
|
|
203
|
+
got both containers, because the flag defaulted to True and nothing
|
|
204
|
+
consulted the manifest.
|
|
205
|
+
"""
|
|
206
|
+
cloud = getattr(manifest, "cloud", None)
|
|
207
|
+
obs = getattr(cloud, "observability", None)
|
|
208
|
+
if obs is None:
|
|
209
|
+
return True
|
|
210
|
+
return bool(getattr(obs, "enabled", True))
|
|
211
|
+
|
|
212
|
+
|
|
197
213
|
def local_compose_from_manifest(
|
|
198
214
|
manifest: Manifest,
|
|
199
215
|
project_root: Path,
|
|
@@ -245,8 +261,8 @@ def local_compose_from_manifest(
|
|
|
245
261
|
# behind the initializer's start. We can't wait on `service_completed`
|
|
246
262
|
# for a true oneshot the way k8s init-containers do, but the
|
|
247
263
|
# ``service_started`` ordering closes the most common race (e.g.
|
|
248
|
-
#
|
|
249
|
-
#
|
|
264
|
+
# an initializer writes state the primary reads on its first call).
|
|
265
|
+
# The initializer itself self-polls its dependencies.
|
|
250
266
|
oneshot_ids: list[str] = [
|
|
251
267
|
svc_def["id"]
|
|
252
268
|
for svc_def in local_services
|
|
@@ -280,7 +296,7 @@ def local_compose_from_manifest(
|
|
|
280
296
|
build_entry["dockerfile"] = dockerfile
|
|
281
297
|
_require_dockerfile(ctx_dir, dockerfile, sid=sid, context=ctx)
|
|
282
298
|
svc["build"] = build_entry
|
|
283
|
-
elif svc_type
|
|
299
|
+
elif svc_type in ("compute", "daemon"):
|
|
284
300
|
found = _autodetect_dockerfile(project_root / sid, env_name)
|
|
285
301
|
if found is not None:
|
|
286
302
|
build_entry = {"context": _project_relative(f"./{sid}", root_prefix)}
|
|
@@ -291,14 +307,18 @@ def local_compose_from_manifest(
|
|
|
291
307
|
if sid in host_ports:
|
|
292
308
|
host_port = host_ports[sid]
|
|
293
309
|
svc["ports"] = [f"{host_port}:{port}"]
|
|
310
|
+
# Additional host:container mappings (e.g. mailhog UI 8025:8025).
|
|
311
|
+
extra_ports = svc_def.get("extraPorts") or []
|
|
312
|
+
if extra_ports:
|
|
313
|
+
svc.setdefault("ports", []).extend(str(p) for p in extra_ports)
|
|
294
314
|
|
|
295
|
-
# One-shot init services
|
|
315
|
+
# One-shot init services carry the
|
|
296
316
|
# `forktex.oneshot=true` label so the generator skips the implicit
|
|
297
317
|
# restart-always; they should exit cleanly after their seed step
|
|
298
318
|
# and stay exited. The label lives on the existing ServiceDef
|
|
299
319
|
# `labels` field — no schema/codegen change required.
|
|
300
320
|
is_oneshot = (svc_def.get("labels") or {}).get("forktex.oneshot") == "true"
|
|
301
|
-
if svc_type in ("persistence", "observability") and not is_oneshot:
|
|
321
|
+
if svc_type in ("persistence", "daemon", "observability") and not is_oneshot:
|
|
302
322
|
svc["restart"] = "always"
|
|
303
323
|
if svc_def.get("labels"):
|
|
304
324
|
svc["labels"] = dict(svc_def["labels"])
|
|
@@ -349,7 +369,7 @@ def local_compose_from_manifest(
|
|
|
349
369
|
if svc_type == "persistence":
|
|
350
370
|
# Bind-mount persistence data under .forktex/data/{sid}/ so it
|
|
351
371
|
# survives `docker compose down -v` and is visible on the host
|
|
352
|
-
# for inspection + backups.
|
|
372
|
+
# for inspection + backups.
|
|
353
373
|
defaults = detect_persistence_defaults(image)
|
|
354
374
|
if defaults and defaults.get("default_volume"):
|
|
355
375
|
target = defaults["default_volume"]
|
|
@@ -362,7 +382,7 @@ def local_compose_from_manifest(
|
|
|
362
382
|
if cmd:
|
|
363
383
|
svc["command"] = cmd
|
|
364
384
|
|
|
365
|
-
if svc_type
|
|
385
|
+
if svc_type in ("compute", "daemon"):
|
|
366
386
|
depends: dict[str, Any] = {}
|
|
367
387
|
for pid in persistence_ids:
|
|
368
388
|
healthy = persistence_has_healthcheck.get(pid)
|
|
@@ -371,7 +391,10 @@ def local_compose_from_manifest(
|
|
|
371
391
|
for iid in oneshot_ids:
|
|
372
392
|
# Initializers run side-by-side; we only need start-ordering,
|
|
373
393
|
# not completion. The initializer's contract is "write its
|
|
374
|
-
# output before the first compute request needs it."
|
|
394
|
+
# output before the first compute request needs it." A oneshot
|
|
395
|
+
# that is itself a daemon must not wait on itself.
|
|
396
|
+
if iid == sid:
|
|
397
|
+
continue
|
|
375
398
|
depends[iid] = {"condition": "service_started"}
|
|
376
399
|
if depends:
|
|
377
400
|
svc["depends_on"] = depends
|
|
@@ -383,7 +406,7 @@ def local_compose_from_manifest(
|
|
|
383
406
|
svc["networks"] = ["forktex"]
|
|
384
407
|
services[sid] = svc
|
|
385
408
|
|
|
386
|
-
if observability:
|
|
409
|
+
if observability and _observability_enabled(manifest):
|
|
387
410
|
_add_observability_services(services, named_volumes)
|
|
388
411
|
|
|
389
412
|
compose: dict[str, Any] = {"services": services}
|
|
@@ -16,7 +16,7 @@ def loki_ready(base_url: str = "http://localhost:3100") -> bool:
|
|
|
16
16
|
req = urllib.request.Request(f"{base_url}/ready", method="GET")
|
|
17
17
|
with urllib.request.urlopen(req, timeout=2) as resp:
|
|
18
18
|
return resp.status == 200
|
|
19
|
-
except
|
|
19
|
+
except urllib.error.URLError, OSError:
|
|
20
20
|
return False
|
|
21
21
|
|
|
22
22
|
|
|
@@ -77,7 +77,7 @@ def tail(
|
|
|
77
77
|
now_ns = int(time.time() * 1_000_000_000)
|
|
78
78
|
try:
|
|
79
79
|
entries = query_range(base_url, logql, last_ts + 1, now_ns)
|
|
80
|
-
except
|
|
80
|
+
except urllib.error.URLError, OSError:
|
|
81
81
|
return
|
|
82
82
|
for ts_ns, service, line in entries:
|
|
83
83
|
yield (ts_ns, service, line)
|
{forktex_cloud-2.1.0 → forktex_cloud-2.3.0}/src/forktex_cloud/bridge/persistence_defaults.py
RENAMED
|
@@ -60,11 +60,24 @@ PERSISTENCE_DEFAULTS: dict[str, dict[str, Any]] = {
|
|
|
60
60
|
"default_volume": "/data/db",
|
|
61
61
|
},
|
|
62
62
|
"qdrant": {
|
|
63
|
+
# The qdrant image ships no wget, curl or nc — only bash. So the probe
|
|
64
|
+
# speaks HTTP over /dev/tcp rather than shelling out to a client that is
|
|
65
|
+
# not there. It must be `CMD` + an explicit `bash -c`: /dev/tcp is a bash
|
|
66
|
+
# builtin, and `CMD-SHELL` runs /bin/sh, where it fails with "cannot
|
|
67
|
+
# create /dev/tcp/..." — indistinguishable from a down node.
|
|
68
|
+
# `/readyz` rather than a bare port open: a bound socket is not a node
|
|
69
|
+
# that is ready to serve.
|
|
63
70
|
"healthcheck": {
|
|
64
|
-
"test": [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
"test": [
|
|
72
|
+
"CMD",
|
|
73
|
+
"bash",
|
|
74
|
+
"-c",
|
|
75
|
+
'exec 3<>/dev/tcp/localhost/6333; printf "GET /readyz HTTP/1.0\\r\\n\\r\\n" >&3; '
|
|
76
|
+
'head -1 <&3 | grep -q "200 OK"',
|
|
77
|
+
],
|
|
78
|
+
"interval": "30s",
|
|
79
|
+
"timeout": "10s",
|
|
80
|
+
"retries": 5,
|
|
68
81
|
},
|
|
69
82
|
"default_port": 6333,
|
|
70
83
|
"default_volume": "/qdrant/storage",
|
|
@@ -6,7 +6,8 @@ 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
|
|
9
|
+
from forktex_cloud.client.client import CloudAPIError, CloudClient, DeployBlocked
|
|
10
|
+
from forktex_cloud.client.facade import Cloud
|
|
10
11
|
from forktex_cloud.client.generated import (
|
|
11
12
|
ApiKeyCreated,
|
|
12
13
|
ApiKeyRead,
|
|
@@ -31,12 +32,13 @@ from forktex_cloud.client.generated import (
|
|
|
31
32
|
TokenResponse,
|
|
32
33
|
UserRead,
|
|
33
34
|
VaultGetResponse,
|
|
34
|
-
WorkspaceRead,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
__all__ = [
|
|
38
38
|
"Cloud",
|
|
39
|
+
"CloudClient",
|
|
39
40
|
"CloudAPIError",
|
|
41
|
+
"DeployBlocked",
|
|
40
42
|
"ApiKeyCreated",
|
|
41
43
|
"ApiKeyRead",
|
|
42
44
|
"CapacityOrgLimits",
|
|
@@ -60,5 +62,4 @@ __all__ = [
|
|
|
60
62
|
"TokenResponse",
|
|
61
63
|
"UserRead",
|
|
62
64
|
"VaultGetResponse",
|
|
63
|
-
"WorkspaceRead",
|
|
64
65
|
]
|