pulse-railway 0.1.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.
- pulse_railway-0.1.0/PKG-INFO +131 -0
- pulse_railway-0.1.0/README.md +116 -0
- pulse_railway-0.1.0/pyproject.toml +25 -0
- pulse_railway-0.1.0/src/pulse_railway/__init__.py +61 -0
- pulse_railway-0.1.0/src/pulse_railway/auth.py +61 -0
- pulse_railway-0.1.0/src/pulse_railway/cli.py +321 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/__init__.py +1 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/common.py +116 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/deploy/__init__.py +10 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/deploy/cmd.py +44 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/deploy/common.py +203 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/deploy/image.py +15 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/deploy/source.py +17 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/init.py +220 -0
- pulse_railway-0.1.0/src/pulse_railway/commands/upgrade.py +32 -0
- pulse_railway-0.1.0/src/pulse_railway/config.py +97 -0
- pulse_railway-0.1.0/src/pulse_railway/constants.py +51 -0
- pulse_railway-0.1.0/src/pulse_railway/deployment.py +948 -0
- pulse_railway-0.1.0/src/pulse_railway/errors.py +8 -0
- pulse_railway-0.1.0/src/pulse_railway/images.py +140 -0
- pulse_railway-0.1.0/src/pulse_railway/janitor.py +299 -0
- pulse_railway-0.1.0/src/pulse_railway/kv.py +389 -0
- pulse_railway-0.1.0/src/pulse_railway/plugin.py +210 -0
- pulse_railway-0.1.0/src/pulse_railway/py.typed +1 -0
- pulse_railway-0.1.0/src/pulse_railway/railway.py +861 -0
- pulse_railway-0.1.0/src/pulse_railway/router.py +422 -0
- pulse_railway-0.1.0/src/pulse_railway/session.py +134 -0
- pulse_railway-0.1.0/src/pulse_railway/stack.py +1088 -0
- pulse_railway-0.1.0/src/pulse_railway/store.py +361 -0
- pulse_railway-0.1.0/src/pulse_railway/target.py +49 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pulse-railway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Railway deployment utilities for Pulse applications
|
|
5
|
+
Author: Erwin Kuhn
|
|
6
|
+
Author-email: Erwin Kuhn <erwin.kuhn@protonmail.com>
|
|
7
|
+
Requires-Dist: aiohttp>=3.12.15
|
|
8
|
+
Requires-Dist: fastapi>=0.116.1
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: pulse-framework
|
|
11
|
+
Requires-Dist: redis>=6.4.0
|
|
12
|
+
Requires-Dist: uvicorn>=0.35.0
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# pulse-railway
|
|
17
|
+
|
|
18
|
+
Railway deployment utilities for Pulse applications.
|
|
19
|
+
|
|
20
|
+
`pulse-railway` uses one stable public router service plus one Railway service per deployment. The router keeps older deployments alive and forwards HTTP and websocket traffic to the selected deployment based on `pulse_deployment`.
|
|
21
|
+
|
|
22
|
+
For local CLI usage, prefer `RAILWAY_API_TOKEN` when you are using a user/account/workspace token. Reserve `RAILWAY_TOKEN` for Railway project tokens, especially in CI.
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
set -a; source .env; set +a
|
|
28
|
+
|
|
29
|
+
uv run pulse-railway init \
|
|
30
|
+
--app-file examples/railway/main.py
|
|
31
|
+
|
|
32
|
+
uv run pulse-railway deploy \
|
|
33
|
+
--deployment-name prod \
|
|
34
|
+
--app-file examples/railway/main.py \
|
|
35
|
+
--web-root examples/railway/web \
|
|
36
|
+
--dockerfile examples/Dockerfile \
|
|
37
|
+
--context .
|
|
38
|
+
|
|
39
|
+
uv run pulse-railway deploy \
|
|
40
|
+
--deployment-name prod \
|
|
41
|
+
--app-file examples/railway/main.py \
|
|
42
|
+
--web-root examples/railway/web \
|
|
43
|
+
--dockerfile examples/Dockerfile \
|
|
44
|
+
--context . \
|
|
45
|
+
--image-repository ghcr.io/<org>/<name>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use the commands like this:
|
|
49
|
+
|
|
50
|
+
- `pulse-railway init` bootstraps the stable router, env, Redis, and janitor stack into an empty Railway project.
|
|
51
|
+
- `pulse-railway upgrade` is currently a no-op placeholder.
|
|
52
|
+
- `pulse-railway deploy` builds and rolls out a new backend service on top of an already-initialized stack.
|
|
53
|
+
By default it uploads source and uses `railway up` to build on Railway. Passing an image repository switches deploy to the local `docker buildx build --push` path.
|
|
54
|
+
|
|
55
|
+
`init` and `deploy` read the stable router, Redis, and janitor service names from `RailwayPlugin` on the target app. By default those are `pulse-router`, `pulse-redis`, and `pulse-janitor` without extra prefixing. The baseline also includes a stable env service named `pulse-env` that acts as the canonical source for user-managed deployment variables. Backend deployment services also default to no prefix, so their Railway service names match the generated deployment id unless you pass `--service-prefix`.
|
|
56
|
+
|
|
57
|
+
`pulse-railway init` is template-first and fresh-only. On an empty target it deploys the published `pulse-baseline` template so the router, janitor, and Redis land on the Railway canvas with a stable layout, creates the stable env service in the template group, then writes runtime images, variables, domains, cron, and healthchecks. Router and janitor services use the official GHCR images for the installed `pulse-railway` version unless you pass explicit image overrides: `ghcr.io/erwinkn/pulse-railway-router:<version>` and `ghcr.io/erwinkn/pulse-railway-janitor:<version>`. When you pass `--redis-url`, init removes the managed Redis service plus any template-created Redis references, then rewrites the baseline to use the external URL. If any baseline service already exists, including partial leftovers from a failed run, delete those services and rerun init. A future `pulse-railway update` command will handle explicit stack reconciliation.
|
|
58
|
+
|
|
59
|
+
If you omit `--project-id`, `pulse-railway init` creates a new Railway project first. That flow requires an account/workspace-capable Railway token plus `--workspace-id` (or `RAILWAY_WORKSPACE_ID`). The new project name defaults to the app file stem and can be overridden with `--project-name`.
|
|
60
|
+
|
|
61
|
+
You can also set `deployment_name` on `RailwayPlugin` to provide the default deploy name from app config. Precedence is: `--deployment-name`, then `PULSE_RAILWAY_DEPLOYMENT_NAME`, then `RailwayPlugin(deployment_name=...)`, then `prod`.
|
|
62
|
+
|
|
63
|
+
You can also set `image_repository` on `RailwayPlugin` to provide the default backend image registry from app config. Precedence is: `--image-repository`, then `PULSE_RAILWAY_IMAGE_REPOSITORY`, then `RailwayPlugin(image_repository=...)`. If none is set, deploy uses source mode.
|
|
64
|
+
|
|
65
|
+
If `--redis-url` is omitted, `pulse-railway init` creates the stable Redis service configured by `RailwayPlugin` in the Railway project.
|
|
66
|
+
|
|
67
|
+
`pulse-railway deploy` is now strict. It does not create or repair the stable baseline stack. If the router, env, Redis, or janitor baseline is missing or outdated, run `pulse-railway init` first.
|
|
68
|
+
|
|
69
|
+
User-managed app variables should live on the stable env service. Each new backend deployment references every non-Pulse-managed variable from `pulse-env`, so users can sync secrets into that service however they want: Railway UI, Shared Variables, Doppler, or another workflow.
|
|
70
|
+
|
|
71
|
+
By default, `pulse-railway deploy` uses source mode. Image deployments require `--image-repository ghcr.io/<org>/<name>`, `PULSE_RAILWAY_IMAGE_REPOSITORY`, or `RailwayPlugin(image_repository="ghcr.io/<org>/<name>")`.
|
|
72
|
+
|
|
73
|
+
## Model
|
|
74
|
+
|
|
75
|
+
- Stable Railway router service
|
|
76
|
+
- Stable Railway env service for user-managed deployment variables
|
|
77
|
+
- One Railway backend service per deployment
|
|
78
|
+
- Stable Railway Redis service unless you pass `--redis-url`
|
|
79
|
+
- Optional Railway cron job for janitor cleanup
|
|
80
|
+
- Active deployment stored in `PULSE_ACTIVE_DEPLOYMENT`
|
|
81
|
+
- Explicit affinity via `pulse_deployment` query param or `x-pulse-deployment` header
|
|
82
|
+
- Websockets proxied through the router to the selected backend service
|
|
83
|
+
- Draining and cleanup state stored in Redis
|
|
84
|
+
- Before janitor deletion, the backend broadcasts Pulse `reload` to connected clients
|
|
85
|
+
- Websocket reconnects with stale affinity fall back to the active deployment so the app can trigger a full-page reload
|
|
86
|
+
|
|
87
|
+
## Runtime
|
|
88
|
+
|
|
89
|
+
Backend services must set `PULSE_DEPLOYMENT_ID`. `RailwayPlugin` injects the affinity query into Pulse prerender and websocket directives and exposes `/_pulse/meta` for verification.
|
|
90
|
+
|
|
91
|
+
If your app opts into `pulse_railway.RailwaySessionStore()`:
|
|
92
|
+
|
|
93
|
+
- deploy injects `PULSE_RAILWAY_REDIS_URL` into the backend app
|
|
94
|
+
- the app session store uses that Redis for server-backed sessions
|
|
95
|
+
|
|
96
|
+
When the baseline stack has Redis configured:
|
|
97
|
+
|
|
98
|
+
- deploy marks the new deployment `active`
|
|
99
|
+
- previous active deployments become `draining`
|
|
100
|
+
- the router records HTTP activity and websocket leases in Redis
|
|
101
|
+
- the janitor signals connected browsers to reload before deleting a drained deployment
|
|
102
|
+
- the janitor cron job deletes drained deployments after they are idle
|
|
103
|
+
|
|
104
|
+
The janitor job runs as a Railway cron service, not a permanent always-on process. Use a cadence of 5 minutes or slower; Railway does not run cron jobs more frequently than that.
|
|
105
|
+
|
|
106
|
+
`pulse-railway janitor run` is for the deployed janitor service only. It probes `*.railway.internal` backends and now fails fast outside Railway.
|
|
107
|
+
|
|
108
|
+
If you need to trigger cleanup manually, run the command from inside the deployed janitor service:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pulse-railway janitor run
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
To remove a deployment by the original deployment name prefix, use:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pulse-railway remove \
|
|
118
|
+
--service pulse-router \
|
|
119
|
+
--deployment-name prod \
|
|
120
|
+
--project-id <project-id> \
|
|
121
|
+
--environment-id <environment-id> \
|
|
122
|
+
--token <project-token>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
If the name matches multiple generated deployment ids, the command fails and prints the matching ids so you can retry with `pulse-railway delete --deployment-id ...`.
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
- Backend services should run with `1` replica. Railway does not provide replica-level sticky routing, so deployment affinity alone is only safe with a single backend replica when sessions are stored in memory.
|
|
130
|
+
- The router can run with multiple replicas because routing state lives in the request query/header plus the active deployment variable.
|
|
131
|
+
- Healthchecks remain for crash recovery only. Deployment cleanup is handled by the janitor cron job, not by failing healthchecks on drained services.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# pulse-railway
|
|
2
|
+
|
|
3
|
+
Railway deployment utilities for Pulse applications.
|
|
4
|
+
|
|
5
|
+
`pulse-railway` uses one stable public router service plus one Railway service per deployment. The router keeps older deployments alive and forwards HTTP and websocket traffic to the selected deployment based on `pulse_deployment`.
|
|
6
|
+
|
|
7
|
+
For local CLI usage, prefer `RAILWAY_API_TOKEN` when you are using a user/account/workspace token. Reserve `RAILWAY_TOKEN` for Railway project tokens, especially in CI.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
set -a; source .env; set +a
|
|
13
|
+
|
|
14
|
+
uv run pulse-railway init \
|
|
15
|
+
--app-file examples/railway/main.py
|
|
16
|
+
|
|
17
|
+
uv run pulse-railway deploy \
|
|
18
|
+
--deployment-name prod \
|
|
19
|
+
--app-file examples/railway/main.py \
|
|
20
|
+
--web-root examples/railway/web \
|
|
21
|
+
--dockerfile examples/Dockerfile \
|
|
22
|
+
--context .
|
|
23
|
+
|
|
24
|
+
uv run pulse-railway deploy \
|
|
25
|
+
--deployment-name prod \
|
|
26
|
+
--app-file examples/railway/main.py \
|
|
27
|
+
--web-root examples/railway/web \
|
|
28
|
+
--dockerfile examples/Dockerfile \
|
|
29
|
+
--context . \
|
|
30
|
+
--image-repository ghcr.io/<org>/<name>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use the commands like this:
|
|
34
|
+
|
|
35
|
+
- `pulse-railway init` bootstraps the stable router, env, Redis, and janitor stack into an empty Railway project.
|
|
36
|
+
- `pulse-railway upgrade` is currently a no-op placeholder.
|
|
37
|
+
- `pulse-railway deploy` builds and rolls out a new backend service on top of an already-initialized stack.
|
|
38
|
+
By default it uploads source and uses `railway up` to build on Railway. Passing an image repository switches deploy to the local `docker buildx build --push` path.
|
|
39
|
+
|
|
40
|
+
`init` and `deploy` read the stable router, Redis, and janitor service names from `RailwayPlugin` on the target app. By default those are `pulse-router`, `pulse-redis`, and `pulse-janitor` without extra prefixing. The baseline also includes a stable env service named `pulse-env` that acts as the canonical source for user-managed deployment variables. Backend deployment services also default to no prefix, so their Railway service names match the generated deployment id unless you pass `--service-prefix`.
|
|
41
|
+
|
|
42
|
+
`pulse-railway init` is template-first and fresh-only. On an empty target it deploys the published `pulse-baseline` template so the router, janitor, and Redis land on the Railway canvas with a stable layout, creates the stable env service in the template group, then writes runtime images, variables, domains, cron, and healthchecks. Router and janitor services use the official GHCR images for the installed `pulse-railway` version unless you pass explicit image overrides: `ghcr.io/erwinkn/pulse-railway-router:<version>` and `ghcr.io/erwinkn/pulse-railway-janitor:<version>`. When you pass `--redis-url`, init removes the managed Redis service plus any template-created Redis references, then rewrites the baseline to use the external URL. If any baseline service already exists, including partial leftovers from a failed run, delete those services and rerun init. A future `pulse-railway update` command will handle explicit stack reconciliation.
|
|
43
|
+
|
|
44
|
+
If you omit `--project-id`, `pulse-railway init` creates a new Railway project first. That flow requires an account/workspace-capable Railway token plus `--workspace-id` (or `RAILWAY_WORKSPACE_ID`). The new project name defaults to the app file stem and can be overridden with `--project-name`.
|
|
45
|
+
|
|
46
|
+
You can also set `deployment_name` on `RailwayPlugin` to provide the default deploy name from app config. Precedence is: `--deployment-name`, then `PULSE_RAILWAY_DEPLOYMENT_NAME`, then `RailwayPlugin(deployment_name=...)`, then `prod`.
|
|
47
|
+
|
|
48
|
+
You can also set `image_repository` on `RailwayPlugin` to provide the default backend image registry from app config. Precedence is: `--image-repository`, then `PULSE_RAILWAY_IMAGE_REPOSITORY`, then `RailwayPlugin(image_repository=...)`. If none is set, deploy uses source mode.
|
|
49
|
+
|
|
50
|
+
If `--redis-url` is omitted, `pulse-railway init` creates the stable Redis service configured by `RailwayPlugin` in the Railway project.
|
|
51
|
+
|
|
52
|
+
`pulse-railway deploy` is now strict. It does not create or repair the stable baseline stack. If the router, env, Redis, or janitor baseline is missing or outdated, run `pulse-railway init` first.
|
|
53
|
+
|
|
54
|
+
User-managed app variables should live on the stable env service. Each new backend deployment references every non-Pulse-managed variable from `pulse-env`, so users can sync secrets into that service however they want: Railway UI, Shared Variables, Doppler, or another workflow.
|
|
55
|
+
|
|
56
|
+
By default, `pulse-railway deploy` uses source mode. Image deployments require `--image-repository ghcr.io/<org>/<name>`, `PULSE_RAILWAY_IMAGE_REPOSITORY`, or `RailwayPlugin(image_repository="ghcr.io/<org>/<name>")`.
|
|
57
|
+
|
|
58
|
+
## Model
|
|
59
|
+
|
|
60
|
+
- Stable Railway router service
|
|
61
|
+
- Stable Railway env service for user-managed deployment variables
|
|
62
|
+
- One Railway backend service per deployment
|
|
63
|
+
- Stable Railway Redis service unless you pass `--redis-url`
|
|
64
|
+
- Optional Railway cron job for janitor cleanup
|
|
65
|
+
- Active deployment stored in `PULSE_ACTIVE_DEPLOYMENT`
|
|
66
|
+
- Explicit affinity via `pulse_deployment` query param or `x-pulse-deployment` header
|
|
67
|
+
- Websockets proxied through the router to the selected backend service
|
|
68
|
+
- Draining and cleanup state stored in Redis
|
|
69
|
+
- Before janitor deletion, the backend broadcasts Pulse `reload` to connected clients
|
|
70
|
+
- Websocket reconnects with stale affinity fall back to the active deployment so the app can trigger a full-page reload
|
|
71
|
+
|
|
72
|
+
## Runtime
|
|
73
|
+
|
|
74
|
+
Backend services must set `PULSE_DEPLOYMENT_ID`. `RailwayPlugin` injects the affinity query into Pulse prerender and websocket directives and exposes `/_pulse/meta` for verification.
|
|
75
|
+
|
|
76
|
+
If your app opts into `pulse_railway.RailwaySessionStore()`:
|
|
77
|
+
|
|
78
|
+
- deploy injects `PULSE_RAILWAY_REDIS_URL` into the backend app
|
|
79
|
+
- the app session store uses that Redis for server-backed sessions
|
|
80
|
+
|
|
81
|
+
When the baseline stack has Redis configured:
|
|
82
|
+
|
|
83
|
+
- deploy marks the new deployment `active`
|
|
84
|
+
- previous active deployments become `draining`
|
|
85
|
+
- the router records HTTP activity and websocket leases in Redis
|
|
86
|
+
- the janitor signals connected browsers to reload before deleting a drained deployment
|
|
87
|
+
- the janitor cron job deletes drained deployments after they are idle
|
|
88
|
+
|
|
89
|
+
The janitor job runs as a Railway cron service, not a permanent always-on process. Use a cadence of 5 minutes or slower; Railway does not run cron jobs more frequently than that.
|
|
90
|
+
|
|
91
|
+
`pulse-railway janitor run` is for the deployed janitor service only. It probes `*.railway.internal` backends and now fails fast outside Railway.
|
|
92
|
+
|
|
93
|
+
If you need to trigger cleanup manually, run the command from inside the deployed janitor service:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pulse-railway janitor run
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To remove a deployment by the original deployment name prefix, use:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pulse-railway remove \
|
|
103
|
+
--service pulse-router \
|
|
104
|
+
--deployment-name prod \
|
|
105
|
+
--project-id <project-id> \
|
|
106
|
+
--environment-id <environment-id> \
|
|
107
|
+
--token <project-token>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If the name matches multiple generated deployment ids, the command fails and prints the matching ids so you can retry with `pulse-railway delete --deployment-id ...`.
|
|
111
|
+
|
|
112
|
+
## Notes
|
|
113
|
+
|
|
114
|
+
- Backend services should run with `1` replica. Railway does not provide replica-level sticky routing, so deployment affinity alone is only safe with a single backend replica when sessions are stored in memory.
|
|
115
|
+
- The router can run with multiple replicas because routing state lives in the request query/header plus the active deployment variable.
|
|
116
|
+
- Healthchecks remain for crash recovery only. Deployment cleanup is handled by the janitor cron job, not by failing healthchecks on drained services.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pulse-railway"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Railway deployment utilities for Pulse applications"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Erwin Kuhn", email = "erwin.kuhn@protonmail.com" }]
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"aiohttp>=3.12.15",
|
|
10
|
+
"fastapi>=0.116.1",
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"pulse-framework",
|
|
13
|
+
"redis>=6.4.0",
|
|
14
|
+
"uvicorn>=0.35.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
pulse-railway = "pulse_railway.cli:main"
|
|
19
|
+
|
|
20
|
+
[tool.uv]
|
|
21
|
+
package = true
|
|
22
|
+
|
|
23
|
+
[build-system]
|
|
24
|
+
requires = ["uv_build>=0.9.4,<0.10.0"]
|
|
25
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# ########################
|
|
2
|
+
# ##### NOTES ON IMPORT FORMAT
|
|
3
|
+
# ########################
|
|
4
|
+
|
|
5
|
+
from pulse_railway.config import (
|
|
6
|
+
DockerBuild as DockerBuild,
|
|
7
|
+
)
|
|
8
|
+
from pulse_railway.config import (
|
|
9
|
+
RailwayInternals as RailwayInternals,
|
|
10
|
+
)
|
|
11
|
+
from pulse_railway.config import (
|
|
12
|
+
RailwayProject as RailwayProject,
|
|
13
|
+
)
|
|
14
|
+
from pulse_railway.deployment import (
|
|
15
|
+
DeploymentError as DeploymentError,
|
|
16
|
+
)
|
|
17
|
+
from pulse_railway.deployment import (
|
|
18
|
+
DeployResult as DeployResult,
|
|
19
|
+
)
|
|
20
|
+
from pulse_railway.deployment import (
|
|
21
|
+
default_janitor_service_name as default_janitor_service_name,
|
|
22
|
+
)
|
|
23
|
+
from pulse_railway.deployment import (
|
|
24
|
+
delete_deployment as delete_deployment,
|
|
25
|
+
)
|
|
26
|
+
from pulse_railway.deployment import (
|
|
27
|
+
deploy as deploy,
|
|
28
|
+
)
|
|
29
|
+
from pulse_railway.deployment import (
|
|
30
|
+
resolve_deployment_id_by_name as resolve_deployment_id_by_name,
|
|
31
|
+
)
|
|
32
|
+
from pulse_railway.janitor import (
|
|
33
|
+
JanitorResult as JanitorResult,
|
|
34
|
+
)
|
|
35
|
+
from pulse_railway.janitor import (
|
|
36
|
+
run_janitor as run_janitor,
|
|
37
|
+
)
|
|
38
|
+
from pulse_railway.plugin import (
|
|
39
|
+
RailwayPlugin as RailwayPlugin,
|
|
40
|
+
)
|
|
41
|
+
from pulse_railway.session import (
|
|
42
|
+
RailwayRedisSessionStore as RailwayRedisSessionStore,
|
|
43
|
+
)
|
|
44
|
+
from pulse_railway.session import (
|
|
45
|
+
RailwaySessionStore as RailwaySessionStore,
|
|
46
|
+
)
|
|
47
|
+
from pulse_railway.store import (
|
|
48
|
+
RedisDeploymentStore as RedisDeploymentStore,
|
|
49
|
+
)
|
|
50
|
+
from pulse_railway.store import (
|
|
51
|
+
StoredDeployment as StoredDeployment,
|
|
52
|
+
)
|
|
53
|
+
from pulse_railway.target import (
|
|
54
|
+
RailwayDeployTarget as RailwayDeployTarget,
|
|
55
|
+
)
|
|
56
|
+
from pulse_railway.target import (
|
|
57
|
+
RailwayDeployTargetError as RailwayDeployTargetError,
|
|
58
|
+
)
|
|
59
|
+
from pulse_railway.target import (
|
|
60
|
+
railway_deploy_target_from_app as railway_deploy_target_from_app,
|
|
61
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from pulse_railway.constants import RAILWAY_API_TOKEN, RAILWAY_TOKEN
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class RailwayAccessToken:
|
|
11
|
+
value: str | None
|
|
12
|
+
env_name: str | None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_railway_access_token(token: str | None = None) -> RailwayAccessToken:
|
|
16
|
+
if token is not None:
|
|
17
|
+
return RailwayAccessToken(
|
|
18
|
+
value=token,
|
|
19
|
+
env_name=railway_access_token_name_for_value(token),
|
|
20
|
+
)
|
|
21
|
+
railway_token = os.environ.get(RAILWAY_TOKEN)
|
|
22
|
+
if railway_token is not None:
|
|
23
|
+
return RailwayAccessToken(value=railway_token, env_name=RAILWAY_TOKEN)
|
|
24
|
+
railway_api_token = os.environ.get(RAILWAY_API_TOKEN)
|
|
25
|
+
if railway_api_token is not None:
|
|
26
|
+
return RailwayAccessToken(value=railway_api_token, env_name=RAILWAY_API_TOKEN)
|
|
27
|
+
return RailwayAccessToken(value=None, env_name=None)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def railway_access_token() -> str | None:
|
|
31
|
+
return resolve_railway_access_token().value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def railway_access_token_name_for_value(token: str | None) -> str | None:
|
|
35
|
+
if token is None:
|
|
36
|
+
return None
|
|
37
|
+
if token == os.environ.get(RAILWAY_API_TOKEN):
|
|
38
|
+
return RAILWAY_API_TOKEN
|
|
39
|
+
if token == os.environ.get(RAILWAY_TOKEN):
|
|
40
|
+
return RAILWAY_TOKEN
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def railway_access_token_name() -> str | None:
|
|
45
|
+
return resolve_railway_access_token().env_name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def railway_cli_token_env_name_for_auth_mode(auth_mode: str) -> str:
|
|
49
|
+
if auth_mode == "bearer":
|
|
50
|
+
return RAILWAY_API_TOKEN
|
|
51
|
+
if auth_mode == "project-token":
|
|
52
|
+
return RAILWAY_TOKEN
|
|
53
|
+
raise ValueError(f"unsupported Railway auth mode: {auth_mode}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def railway_cli_token_env(token: str, *, env_name: str | None = None) -> dict[str, str]:
|
|
57
|
+
if env_name is None:
|
|
58
|
+
env_name = RAILWAY_TOKEN
|
|
59
|
+
if env_name not in {RAILWAY_API_TOKEN, RAILWAY_TOKEN}:
|
|
60
|
+
raise ValueError(f"unsupported Railway token env var: {env_name}")
|
|
61
|
+
return {env_name: token}
|