flowstash-cli 0.8.3__tar.gz → 0.9.2__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.
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/PKG-INFO +2 -2
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/pyproject.toml +2 -2
- flowstash_cli-0.9.2/src/flowstash/cli/commands/deploy.py +479 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/main.py +6 -74
- flowstash_cli-0.8.3/src/flowstash/cli/commands/deploy.py +0 -178
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/apikey.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/auth.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/build.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/client.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/project.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/run.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/commands/webhook.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/api_client.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/auth_server.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/builder.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/config.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/docker_utils.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/core/patcher.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/AGENTS.md +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/README.md +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_.dockerignore +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_.flowstash +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_api_main.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/[env]/(backend-asyncio)/backend.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/[env]/(backend-dramatiq)/backend.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/[env]/(backend-managed)/backend.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/[env]/(observability-logfile)/observability.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/[env]/(observability-managed)/observability.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/shared/backend.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/shared/clients/demoClient.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/shared/clients.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_deployment/[env]/(backend-asyncio)/docker-compose.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_deployment/[env]/(backend-dramatiq)/docker-compose.yaml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_deployment/shared/api.Dockerfile +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_deployment/shared/worker.Dockerfile +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_pyproject.toml +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_api/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_api/_routes/webhooks.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_shared/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_shared/clients/client.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_shared/models/models.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_shared/tasks/sharedTasks.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_worker/__init__.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_worker/tasks/tasks.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_worker_main.py +0 -0
- {flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/ui/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: flowstash-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: CLI for the flowstash Managed Platform
|
|
5
5
|
Author: juraj.bezdek@gmail.com
|
|
6
6
|
Author-email: juraj.bezdek@gmail.com
|
|
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
-
Requires-Dist: flowstash-runtime (>=0.
|
|
12
|
+
Requires-Dist: flowstash-runtime (>=0.9.2,<0.10.0)
|
|
13
13
|
Requires-Dist: httpx (>=0.27.0)
|
|
14
14
|
Requires-Dist: keyring (>=25.0.0)
|
|
15
15
|
Requires-Dist: libcst (>=1.1.0)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flowstash-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.2"
|
|
4
4
|
description = "CLI for the flowstash Managed Platform"
|
|
5
5
|
authors = [{name = "juraj.bezdek@gmail.com", email = "juraj.bezdek@gmail.com"}]
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"flowstash-runtime>=0.
|
|
8
|
+
"flowstash-runtime>=0.9.2,<0.10.0",
|
|
9
9
|
"typer[all]>=0.12.0",
|
|
10
10
|
"httpx>=0.27.0",
|
|
11
11
|
"pyyaml>=6.0.1",
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
import asyncio
|
|
3
|
+
import typer
|
|
4
|
+
import click
|
|
5
|
+
from typer.core import TyperGroup
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
|
+
from rich.prompt import Confirm
|
|
9
|
+
import questionary
|
|
10
|
+
from ..core.api_client import APIClient
|
|
11
|
+
from ..core.config import load_project_config, resolve_credentials
|
|
12
|
+
from .build import run_build_flow
|
|
13
|
+
import flowstash.runtime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DefaultCommandGroup(TyperGroup):
|
|
17
|
+
"""Typer group that falls back to a default subcommand.
|
|
18
|
+
|
|
19
|
+
Keeps ``flowstash deploy``, ``flowstash deploy <env>`` and
|
|
20
|
+
``flowstash deploy --artifact X`` working (they route to ``run``) while
|
|
21
|
+
still allowing real subcommands such as ``configure``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
DEFAULT_CMD = "run"
|
|
25
|
+
|
|
26
|
+
def parse_args(self, ctx, args):
|
|
27
|
+
if not args:
|
|
28
|
+
args = [self.DEFAULT_CMD]
|
|
29
|
+
elif args[0] not in self.commands and args[0] != "--help":
|
|
30
|
+
args = [self.DEFAULT_CMD, *args]
|
|
31
|
+
return super().parse_args(ctx, args)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(cls=DefaultCommandGroup)
|
|
35
|
+
console = Console()
|
|
36
|
+
|
|
37
|
+
# Status labels shown to the user while polling
|
|
38
|
+
_STATUS_LABELS = {
|
|
39
|
+
"QUEUED": "Queued, waiting for deployment to start...",
|
|
40
|
+
"VALIDATING": "Validating container images...",
|
|
41
|
+
"DEPLOYING": "Deploying services...",
|
|
42
|
+
"HEALTH_CHECK": "Health-checking API and Worker...",
|
|
43
|
+
"SYNCING_SCHEDULES": "Waiting for deployment verification...",
|
|
44
|
+
"PROFILE_UPDATE": "Applying profile update...",
|
|
45
|
+
"DEPLOYED": "Deployed successfully ✓",
|
|
46
|
+
"FAILED": "Deployment failed.",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_TERMINAL_STATUSES = {"DEPLOYED", "FAILED"}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def _poll_deploy_status(api: APIClient, deploy_id: str, progress, task):
|
|
53
|
+
"""Poll deploy status until terminal. Returns the final status payload.
|
|
54
|
+
|
|
55
|
+
On FAILED, prints the backend error and raises typer.Exit(1).
|
|
56
|
+
"""
|
|
57
|
+
last_status = None
|
|
58
|
+
while True:
|
|
59
|
+
status_data = await api.get(f"/v1/deploy/{deploy_id}/status")
|
|
60
|
+
current_status = status_data["status"]
|
|
61
|
+
|
|
62
|
+
if current_status != last_status:
|
|
63
|
+
label = _STATUS_LABELS.get(current_status, f"Status: {current_status}")
|
|
64
|
+
progress.update(task, description=label)
|
|
65
|
+
last_status = current_status
|
|
66
|
+
|
|
67
|
+
if current_status == "DEPLOYED":
|
|
68
|
+
return status_data
|
|
69
|
+
elif current_status == "FAILED":
|
|
70
|
+
error = status_data.get("error_message", "Unknown error")
|
|
71
|
+
console.print(f"[red]Deployment failed: {error}[/red]")
|
|
72
|
+
raise typer.Exit(code=1)
|
|
73
|
+
|
|
74
|
+
await asyncio.sleep(5)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def run_deploy_flow(
|
|
78
|
+
env: str, artifact_id: Optional[str] = None, user: Optional[str] = None
|
|
79
|
+
):
|
|
80
|
+
project_config = load_project_config()
|
|
81
|
+
if not project_config:
|
|
82
|
+
console.print(
|
|
83
|
+
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
84
|
+
)
|
|
85
|
+
raise typer.Exit(code=1)
|
|
86
|
+
|
|
87
|
+
project_id = project_config.project_id
|
|
88
|
+
if not project_id:
|
|
89
|
+
console.print("[red]project_id not found in .flowstash[/red]")
|
|
90
|
+
raise typer.Exit(code=1)
|
|
91
|
+
|
|
92
|
+
token = resolve_credentials(user=user)
|
|
93
|
+
if not token:
|
|
94
|
+
console.print("[red]Not logged in. Run 'flowstash login' first.[/red]")
|
|
95
|
+
raise typer.Exit(code=1)
|
|
96
|
+
|
|
97
|
+
api = APIClient(token=token)
|
|
98
|
+
|
|
99
|
+
# 1. If artifact_id is not provided, run build first
|
|
100
|
+
if not artifact_id:
|
|
101
|
+
console.print("No artifact ID provided. Building first...")
|
|
102
|
+
build_result = await run_build_flow(user=user)
|
|
103
|
+
artifact_id = build_result["artifact_id"]
|
|
104
|
+
console.print(f"Build finished. Deploying artifact: [bold]{artifact_id}[/bold]")
|
|
105
|
+
|
|
106
|
+
with Progress(
|
|
107
|
+
SpinnerColumn(),
|
|
108
|
+
TextColumn("[progress.description]{task.description}"),
|
|
109
|
+
transient=True,
|
|
110
|
+
) as progress:
|
|
111
|
+
# 2. Trigger deployment
|
|
112
|
+
task = progress.add_task(description="Triggering deployment...", total=None)
|
|
113
|
+
deploy_data = await api.post(
|
|
114
|
+
"/v1/deploy",
|
|
115
|
+
json={
|
|
116
|
+
"project_id": project_id,
|
|
117
|
+
"artifact_id": artifact_id,
|
|
118
|
+
"env_vars": {"ENVIRONMENT": env},
|
|
119
|
+
"flowstash_runtime_version": flowstash.runtime.__version__,
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
deploy_id = deploy_data["deploy_id"]
|
|
123
|
+
progress.update(task, description=f"Deployment triggered (ID: {deploy_id}).")
|
|
124
|
+
|
|
125
|
+
# 3. Poll status until terminal
|
|
126
|
+
status_data = await _poll_deploy_status(api, deploy_id, progress, task)
|
|
127
|
+
|
|
128
|
+
return status_data
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _resolve_project_and_token(user: Optional[str]):
|
|
132
|
+
"""Load .flowstash project_id and resolve an access token, or exit."""
|
|
133
|
+
project_config = load_project_config()
|
|
134
|
+
if not project_config:
|
|
135
|
+
console.print(
|
|
136
|
+
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
137
|
+
)
|
|
138
|
+
raise typer.Exit(code=1)
|
|
139
|
+
|
|
140
|
+
project_id = project_config.project_id
|
|
141
|
+
if not project_id:
|
|
142
|
+
console.print(
|
|
143
|
+
"[red]project_id not found in .flowstash. Use 'flowstash init' or link to a project.[/red]"
|
|
144
|
+
)
|
|
145
|
+
raise typer.Exit(code=1)
|
|
146
|
+
|
|
147
|
+
token = resolve_credentials(user=user)
|
|
148
|
+
if not token:
|
|
149
|
+
console.print("[red]Not logged in. Run 'flowstash login' first.[/red]")
|
|
150
|
+
raise typer.Exit(code=1)
|
|
151
|
+
|
|
152
|
+
return project_config, project_id, token
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _spec_summary(spec: dict) -> str:
|
|
156
|
+
"""Render a compact one-line summary of a deployment-profile spec."""
|
|
157
|
+
if not isinstance(spec, dict):
|
|
158
|
+
return ""
|
|
159
|
+
parts = []
|
|
160
|
+
worker = spec.get("worker_service") or {}
|
|
161
|
+
if worker:
|
|
162
|
+
parts.append(
|
|
163
|
+
f"worker cpu={worker.get('cpu')} mem={worker.get('memory')} "
|
|
164
|
+
f"max={worker.get('max_instances')}"
|
|
165
|
+
)
|
|
166
|
+
api_svc = spec.get("api_service") or {}
|
|
167
|
+
if api_svc:
|
|
168
|
+
parts.append(f"api cpu={api_svc.get('cpu')} mem={api_svc.get('memory')}")
|
|
169
|
+
return " / ".join(parts)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.command("run")
|
|
173
|
+
def deploy_run(
|
|
174
|
+
ctx: typer.Context,
|
|
175
|
+
env: str = typer.Argument("prod", help="Environment to deploy to (default: prod)"),
|
|
176
|
+
artifact: Optional[str] = typer.Option(
|
|
177
|
+
None, "--artifact", "-a", help="Artifact ID to deploy"
|
|
178
|
+
),
|
|
179
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts"),
|
|
180
|
+
user: Optional[str] = typer.Option(
|
|
181
|
+
None, "--user", "-u", help="Account to use (default: project-linked or current)"
|
|
182
|
+
),
|
|
183
|
+
):
|
|
184
|
+
"""
|
|
185
|
+
[bold cyan]Deploy[/bold cyan] your project to the flowstash Managed Platform.
|
|
186
|
+
|
|
187
|
+
Defaults to the 'prod' environment. If 'prod' is missing, it will prompt you to set it up.
|
|
188
|
+
|
|
189
|
+
[yellow]Note:[/yellow] To deploy to a specific environment, use: [bold]flowstash deploy <env_name>[/bold]
|
|
190
|
+
"""
|
|
191
|
+
from .project import add_environment, _link_project
|
|
192
|
+
|
|
193
|
+
if ctx.get_parameter_source("env") == click.core.ParameterSource.DEFAULT:
|
|
194
|
+
console.print(
|
|
195
|
+
f"[dim]Env argument not specified... using [bold]{env}[/bold] as default[/dim]"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
project_config = load_project_config()
|
|
199
|
+
if not project_config:
|
|
200
|
+
console.print(
|
|
201
|
+
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
202
|
+
)
|
|
203
|
+
raise typer.Exit(code=1)
|
|
204
|
+
|
|
205
|
+
# Find the requested environment, offering to scaffold 'prod' if missing
|
|
206
|
+
env_mode = next((e for e in project_config.environments if e.name == env), None)
|
|
207
|
+
if not env_mode:
|
|
208
|
+
if env == "prod":
|
|
209
|
+
if yes or Confirm.ask(
|
|
210
|
+
f"Environment '{env}' not found. Would you like to set it up now?"
|
|
211
|
+
):
|
|
212
|
+
add_environment(project_config, env_name=env)
|
|
213
|
+
project_config = load_project_config()
|
|
214
|
+
env_mode = next(
|
|
215
|
+
(e for e in project_config.environments if e.name == env), None
|
|
216
|
+
)
|
|
217
|
+
if not env_mode:
|
|
218
|
+
console.print(
|
|
219
|
+
f"[red]Environment '{env}' was not created. Aborting.[/red]"
|
|
220
|
+
)
|
|
221
|
+
raise typer.Exit(code=1)
|
|
222
|
+
else:
|
|
223
|
+
console.print(
|
|
224
|
+
"[red]Aborting. Use 'flowstash env add' to create environments manually.[/red]"
|
|
225
|
+
)
|
|
226
|
+
raise typer.Exit(code=1)
|
|
227
|
+
else:
|
|
228
|
+
console.print(f"[red]Environment '{env}' not found.[/red]")
|
|
229
|
+
console.print(
|
|
230
|
+
f"[yellow]Available environments: {', '.join(e.name for e in project_config.environments)}[/yellow]"
|
|
231
|
+
)
|
|
232
|
+
console.print(
|
|
233
|
+
"To deploy to a specific environment, use: [bold]flowstash deploy <env_name>[/bold]"
|
|
234
|
+
)
|
|
235
|
+
raise typer.Exit(code=1)
|
|
236
|
+
|
|
237
|
+
# Ask for confirmation unless non-interactive is provided
|
|
238
|
+
if not yes:
|
|
239
|
+
if not Confirm.ask(f"Are you sure you want to deploy to '{env}'?"):
|
|
240
|
+
console.print("Deployment cancelled.")
|
|
241
|
+
raise typer.Exit(code=0)
|
|
242
|
+
|
|
243
|
+
# Check if env is managed
|
|
244
|
+
if not env_mode.managed:
|
|
245
|
+
console.print(
|
|
246
|
+
f"[red]Environment '{env}' is not managed. Deployment is only supported for managed environments.[/red]"
|
|
247
|
+
)
|
|
248
|
+
console.print(
|
|
249
|
+
"[yellow]Update your .flowstash environments if this is incorrect.[/yellow]"
|
|
250
|
+
)
|
|
251
|
+
raise typer.Exit(code=1)
|
|
252
|
+
|
|
253
|
+
project_id = project_config.project_id
|
|
254
|
+
if not project_id:
|
|
255
|
+
if not yes and Confirm.ask(
|
|
256
|
+
"Project ID not found. Would you like to link to a managed project now?"
|
|
257
|
+
):
|
|
258
|
+
_link_project(project_config, user=user)
|
|
259
|
+
project_id = project_config.project_id
|
|
260
|
+
|
|
261
|
+
if not project_id:
|
|
262
|
+
console.print(
|
|
263
|
+
"[red]project_id not found in .flowstash. Use 'flowstash init' or link to a project.[/red]"
|
|
264
|
+
)
|
|
265
|
+
raise typer.Exit(code=1)
|
|
266
|
+
|
|
267
|
+
result = asyncio.run(run_deploy_flow(env, artifact, user=user))
|
|
268
|
+
|
|
269
|
+
api_url = result.get("api_url", "")
|
|
270
|
+
console.print("[green]✓ Deployment complete![/green]")
|
|
271
|
+
console.print(f" Deployment ID : [bold]{result['deploy_id']}[/bold]")
|
|
272
|
+
if api_url:
|
|
273
|
+
console.print(f" API URL : [bold]{api_url}[/bold]")
|
|
274
|
+
|
|
275
|
+
scheduled_tasks = result.get("scheduled_tasks")
|
|
276
|
+
if scheduled_tasks:
|
|
277
|
+
console.print(" Scheduled Tasks:")
|
|
278
|
+
for task in scheduled_tasks:
|
|
279
|
+
console.print(
|
|
280
|
+
f" - [cyan]{task['task_name']}[/cyan] : [yellow]{task['cron']}[/yellow]"
|
|
281
|
+
)
|
|
282
|
+
elif scheduled_tasks is not None:
|
|
283
|
+
console.print(" Scheduled Tasks: [dim]None[/dim]")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@app.command("configure")
|
|
287
|
+
def deploy_configure(
|
|
288
|
+
env: Optional[str] = typer.Option(
|
|
289
|
+
None, "--env", "-e", help="Environment to configure"
|
|
290
|
+
),
|
|
291
|
+
profile: Optional[str] = typer.Option(
|
|
292
|
+
None, "--profile", "-p", help="Deployment profile name"
|
|
293
|
+
),
|
|
294
|
+
always_on: Optional[bool] = typer.Option(
|
|
295
|
+
None,
|
|
296
|
+
"--always-on/--no-always-on",
|
|
297
|
+
help="Keep one worker warm (min_worker_instances 1) vs scale to zero (0)",
|
|
298
|
+
),
|
|
299
|
+
apply: Optional[bool] = typer.Option(
|
|
300
|
+
None,
|
|
301
|
+
"--apply/--no-apply",
|
|
302
|
+
help="Apply to running services immediately (skips the prompt)",
|
|
303
|
+
),
|
|
304
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts"),
|
|
305
|
+
user: Optional[str] = typer.Option(
|
|
306
|
+
None, "--user", "-u", help="Account to use (default: project-linked or current)"
|
|
307
|
+
),
|
|
308
|
+
):
|
|
309
|
+
"""
|
|
310
|
+
[bold cyan]Configure[/bold cyan] the deployment profile and scaling for an environment.
|
|
311
|
+
|
|
312
|
+
Interactively pick the environment, deployment profile, and whether the worker
|
|
313
|
+
stays always-on. Provide [bold]--env[/bold], [bold]--profile[/bold] and
|
|
314
|
+
[bold]--always-on/--no-always-on[/bold] to skip the prompts.
|
|
315
|
+
"""
|
|
316
|
+
project_config, project_id, token = _resolve_project_and_token(user)
|
|
317
|
+
api = APIClient(token=token)
|
|
318
|
+
|
|
319
|
+
# 1. Environment selection (restricted to managed environments)
|
|
320
|
+
managed_envs = [e for e in project_config.environments if e.managed]
|
|
321
|
+
if env is None:
|
|
322
|
+
if not managed_envs:
|
|
323
|
+
console.print(
|
|
324
|
+
"[red]No managed environments found in .flowstash. "
|
|
325
|
+
"Add one with 'flowstash env add'.[/red]"
|
|
326
|
+
)
|
|
327
|
+
raise typer.Exit(code=1)
|
|
328
|
+
env = questionary.select(
|
|
329
|
+
"Environment to configure:",
|
|
330
|
+
choices=[e.name for e in managed_envs],
|
|
331
|
+
).ask()
|
|
332
|
+
if not env:
|
|
333
|
+
console.print("Cancelled.")
|
|
334
|
+
raise typer.Exit(code=0)
|
|
335
|
+
else:
|
|
336
|
+
env_mode = next((e for e in project_config.environments if e.name == env), None)
|
|
337
|
+
if env_mode is not None and not env_mode.managed:
|
|
338
|
+
console.print(
|
|
339
|
+
f"[red]Environment '{env}' is not managed. Deployment configuration "
|
|
340
|
+
"is only supported for managed environments.[/red]"
|
|
341
|
+
)
|
|
342
|
+
raise typer.Exit(code=1)
|
|
343
|
+
|
|
344
|
+
# 2. Load available profiles + current config from the API
|
|
345
|
+
async def _load():
|
|
346
|
+
profiles = await api.get(
|
|
347
|
+
"/v1/deployment-profiles", params={"project_id": project_id}
|
|
348
|
+
)
|
|
349
|
+
try:
|
|
350
|
+
current = await api.get(
|
|
351
|
+
f"/v1/environments/{project_id}/{env}/deployment-config"
|
|
352
|
+
)
|
|
353
|
+
except Exception:
|
|
354
|
+
# First-time configuration: no stored config yet.
|
|
355
|
+
current = {}
|
|
356
|
+
return profiles, current
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
profiles, current = asyncio.run(_load())
|
|
360
|
+
except Exception as e:
|
|
361
|
+
console.print(f"[red]Failed to load deployment profiles: {e}[/red]")
|
|
362
|
+
raise typer.Exit(code=1)
|
|
363
|
+
|
|
364
|
+
profile_names = [p.get("name") for p in (profiles or []) if p.get("name")]
|
|
365
|
+
current_profile = (current or {}).get("deployment_profile")
|
|
366
|
+
current_min = (current or {}).get("min_worker_instances") or 0
|
|
367
|
+
|
|
368
|
+
# 3. Profile selection
|
|
369
|
+
if profile is None:
|
|
370
|
+
if not profile_names:
|
|
371
|
+
console.print("[red]No deployment profiles available.[/red]")
|
|
372
|
+
raise typer.Exit(code=1)
|
|
373
|
+
spec_by_name = {p.get("name"): p.get("spec", {}) for p in profiles}
|
|
374
|
+
choices = []
|
|
375
|
+
for name in profile_names:
|
|
376
|
+
summary = _spec_summary(spec_by_name.get(name, {}))
|
|
377
|
+
label = f"{name} — {summary}" if summary else name
|
|
378
|
+
choices.append(questionary.Choice(label, value=name))
|
|
379
|
+
profile = questionary.select(
|
|
380
|
+
"Deployment profile:",
|
|
381
|
+
choices=choices,
|
|
382
|
+
default=current_profile if current_profile in profile_names else None,
|
|
383
|
+
).ask()
|
|
384
|
+
if not profile:
|
|
385
|
+
console.print("Cancelled.")
|
|
386
|
+
raise typer.Exit(code=0)
|
|
387
|
+
elif profile_names and profile not in profile_names:
|
|
388
|
+
console.print(
|
|
389
|
+
f"[yellow]Warning: profile '{profile}' is not in the known list "
|
|
390
|
+
f"({', '.join(profile_names)}). Sending anyway.[/yellow]"
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# 4. Always-on flag -> min_worker_instances (boolean: 1 / 0)
|
|
394
|
+
if always_on is None:
|
|
395
|
+
always_on = Confirm.ask(
|
|
396
|
+
"Keep a worker always on (no cold starts)?", default=current_min > 0
|
|
397
|
+
)
|
|
398
|
+
min_worker_instances = 1 if always_on else 0
|
|
399
|
+
|
|
400
|
+
# 5. Summary + confirmation
|
|
401
|
+
console.print()
|
|
402
|
+
console.print(f" Environment : [bold]{env}[/bold]")
|
|
403
|
+
console.print(f" Profile : [bold]{profile}[/bold]")
|
|
404
|
+
console.print(
|
|
405
|
+
f" Always on : [bold]{'yes' if always_on else 'no'}[/bold] "
|
|
406
|
+
f"(min_worker_instances={min_worker_instances})"
|
|
407
|
+
)
|
|
408
|
+
if not yes and not Confirm.ask("Save this deployment configuration?", default=True):
|
|
409
|
+
console.print("Cancelled.")
|
|
410
|
+
raise typer.Exit(code=0)
|
|
411
|
+
|
|
412
|
+
# 6. Persist server-side
|
|
413
|
+
async def _save():
|
|
414
|
+
return await api.request(
|
|
415
|
+
"PUT",
|
|
416
|
+
f"/v1/environments/{project_id}/{env}/deployment-config",
|
|
417
|
+
json={
|
|
418
|
+
"deployment_profile": profile,
|
|
419
|
+
"min_worker_instances": min_worker_instances,
|
|
420
|
+
},
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
asyncio.run(_save())
|
|
425
|
+
except Exception as e:
|
|
426
|
+
console.print(f"[red]Failed to save deployment configuration: {e}[/red]")
|
|
427
|
+
raise typer.Exit(code=1)
|
|
428
|
+
|
|
429
|
+
console.print(f"[green]✓ Deployment configuration saved for '{env}'.[/green]")
|
|
430
|
+
|
|
431
|
+
# 7. Offer to apply to running services
|
|
432
|
+
if apply is None:
|
|
433
|
+
apply = Confirm.ask("Apply to running services now?", default=False)
|
|
434
|
+
if not apply:
|
|
435
|
+
console.print(
|
|
436
|
+
"[dim]Changes will take effect on the next 'flowstash deploy'.[/dim]"
|
|
437
|
+
)
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
asyncio.run(_apply_profile(api, project_id, env))
|
|
442
|
+
except typer.Exit:
|
|
443
|
+
raise
|
|
444
|
+
except Exception as e:
|
|
445
|
+
console.print(f"[red]Failed to apply profile: {e}[/red]")
|
|
446
|
+
raise typer.Exit(code=1)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
async def _apply_profile(api: APIClient, project_id: str, env: str):
|
|
450
|
+
"""POST apply-profile and poll the resulting deploy, if any."""
|
|
451
|
+
res = await api.request(
|
|
452
|
+
"POST", f"/v1/environments/{project_id}/{env}/apply-profile"
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if res.get("no_change"):
|
|
456
|
+
console.print(
|
|
457
|
+
f"[green]✓ {res.get('message', 'Profile configuration already applied.')}[/green]"
|
|
458
|
+
)
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
deploy_id = res.get("deploy_id")
|
|
462
|
+
if not deploy_id:
|
|
463
|
+
console.print(
|
|
464
|
+
f"[green]✓ {res.get('message', 'Profile update queued.')}[/green]"
|
|
465
|
+
)
|
|
466
|
+
return
|
|
467
|
+
|
|
468
|
+
with Progress(
|
|
469
|
+
SpinnerColumn(),
|
|
470
|
+
TextColumn("[progress.description]{task.description}"),
|
|
471
|
+
transient=True,
|
|
472
|
+
) as progress:
|
|
473
|
+
task = progress.add_task(
|
|
474
|
+
description="Applying profile update...", total=None
|
|
475
|
+
)
|
|
476
|
+
result = await _poll_deploy_status(api, deploy_id, progress, task)
|
|
477
|
+
|
|
478
|
+
console.print("[green]✓ Profile update applied.[/green]")
|
|
479
|
+
console.print(f" Deployment ID : [bold]{result.get('deploy_id', deploy_id)}[/bold]")
|
|
@@ -71,6 +71,12 @@ app.add_typer(
|
|
|
71
71
|
no_args_is_help=True,
|
|
72
72
|
)
|
|
73
73
|
|
|
74
|
+
app.add_typer(
|
|
75
|
+
deploy_cmds.app,
|
|
76
|
+
name="deploy",
|
|
77
|
+
help="Deploy your project, or configure deployment ('configure')",
|
|
78
|
+
)
|
|
79
|
+
|
|
74
80
|
|
|
75
81
|
@app.command()
|
|
76
82
|
def help(ctx: typer.Context):
|
|
@@ -282,80 +288,6 @@ def build(
|
|
|
282
288
|
build_cmds.build(env=env, tag=tag, user=user)
|
|
283
289
|
|
|
284
290
|
|
|
285
|
-
@app.command()
|
|
286
|
-
def deploy(
|
|
287
|
-
ctx: typer.Context,
|
|
288
|
-
env: str = typer.Argument("prod", help="Environment to deploy to (default: prod)"),
|
|
289
|
-
artifact: Optional[str] = typer.Option(
|
|
290
|
-
None, "--artifact", "-a", help="Artifact ID to deploy"
|
|
291
|
-
),
|
|
292
|
-
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompts"),
|
|
293
|
-
user: Optional[str] = typer.Option(
|
|
294
|
-
None, "--user", "-u", help="Account to use (default: project-linked or current)"
|
|
295
|
-
),
|
|
296
|
-
):
|
|
297
|
-
"""
|
|
298
|
-
[bold cyan]Deploy[/bold cyan] your project to the flowstash Managed Platform.
|
|
299
|
-
|
|
300
|
-
Defaults to the 'prod' environment. If 'prod' is missing, it will prompt you to set it up.
|
|
301
|
-
|
|
302
|
-
[yellow]Note:[/yellow] To deploy to a specific environment, use: [bold]flowstash deploy <env_name>[/bold]
|
|
303
|
-
"""
|
|
304
|
-
from .core.config import load_project_config
|
|
305
|
-
from .commands.project import add_environment
|
|
306
|
-
from rich.prompt import Confirm
|
|
307
|
-
|
|
308
|
-
if ctx.get_parameter_source("env") == click.core.ParameterSource.DEFAULT:
|
|
309
|
-
console.print(
|
|
310
|
-
f"[dim]Env argument not specified... using [bold]{env}[/bold] as default[/dim]"
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
project_config = load_project_config()
|
|
314
|
-
if not project_config:
|
|
315
|
-
console.print(
|
|
316
|
-
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
317
|
-
)
|
|
318
|
-
raise typer.Exit(code=1)
|
|
319
|
-
|
|
320
|
-
# Find the requested environment
|
|
321
|
-
env_mode = next((e for e in project_config.environments if e.name == env), None)
|
|
322
|
-
|
|
323
|
-
if not env_mode:
|
|
324
|
-
if env == "prod":
|
|
325
|
-
if yes or Confirm.ask(
|
|
326
|
-
f"Environment '{env}' not found. Would you like to set it up now?"
|
|
327
|
-
):
|
|
328
|
-
# We need to pass the actual project_config object to add_environment
|
|
329
|
-
# But project_cmds is a module, we should be careful about cyclic imports or just use it.
|
|
330
|
-
project_cmds.add_environment(project_config, env_name=env)
|
|
331
|
-
# Reload or refresh check
|
|
332
|
-
project_config = load_project_config()
|
|
333
|
-
env_mode = next(
|
|
334
|
-
(e for e in project_config.environments if e.name == env), None
|
|
335
|
-
)
|
|
336
|
-
if not env_mode:
|
|
337
|
-
console.print(
|
|
338
|
-
f"[red]Environment '{env}' was not created. Aborting.[/red]"
|
|
339
|
-
)
|
|
340
|
-
raise typer.Exit(code=1)
|
|
341
|
-
else:
|
|
342
|
-
console.print(
|
|
343
|
-
f"[red]Aborting. Use 'flowstash env add' to create environments manually.[/red]"
|
|
344
|
-
)
|
|
345
|
-
raise typer.Exit(code=1)
|
|
346
|
-
else:
|
|
347
|
-
console.print(f"[red]Environment '{env}' not found.[/red]")
|
|
348
|
-
console.print(
|
|
349
|
-
f"[yellow]Available environments: {', '.join(e.name for e in project_config.environments)}[/yellow]"
|
|
350
|
-
)
|
|
351
|
-
console.print(
|
|
352
|
-
f"To deploy to a specific environment, use: [bold]flowstash deploy <env_name>[/bold]"
|
|
353
|
-
)
|
|
354
|
-
raise typer.Exit(code=1)
|
|
355
|
-
|
|
356
|
-
deploy_cmds.deploy(env=env, artifact=artifact, yes=yes, user=user)
|
|
357
|
-
|
|
358
|
-
|
|
359
291
|
@app.command()
|
|
360
292
|
def whoami(
|
|
361
293
|
user: Optional[str] = typer.Option(
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
import asyncio
|
|
3
|
-
import typer
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from rich.console import Console
|
|
6
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
7
|
-
from ..core.api_client import APIClient
|
|
8
|
-
from ..core.config import load_project_config, resolve_credentials
|
|
9
|
-
from .build import run_build_flow
|
|
10
|
-
import flowstash.runtime
|
|
11
|
-
|
|
12
|
-
app = typer.Typer()
|
|
13
|
-
console = Console()
|
|
14
|
-
|
|
15
|
-
# Status labels shown to the user while polling
|
|
16
|
-
_STATUS_LABELS = {
|
|
17
|
-
"QUEUED": "Queued, waiting for deployment to start...",
|
|
18
|
-
"VALIDATING": "Validating container images...",
|
|
19
|
-
"DEPLOYING": "Deploying services...",
|
|
20
|
-
"HEALTH_CHECK": "Health-checking API and Worker...",
|
|
21
|
-
"SYNCING_SCHEDULES": "Waiting for deployment verification...",
|
|
22
|
-
"DEPLOYED": "Deployed successfully ✓",
|
|
23
|
-
"FAILED": "Deployment failed.",
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
_TERMINAL_STATUSES = {"DEPLOYED", "FAILED"}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
async def run_deploy_flow(
|
|
30
|
-
env: str, artifact_id: Optional[str] = None, user: Optional[str] = None
|
|
31
|
-
):
|
|
32
|
-
project_config = load_project_config()
|
|
33
|
-
if not project_config:
|
|
34
|
-
console.print(
|
|
35
|
-
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
36
|
-
)
|
|
37
|
-
raise typer.Exit(code=1)
|
|
38
|
-
|
|
39
|
-
project_id = project_config.project_id
|
|
40
|
-
if not project_id:
|
|
41
|
-
console.print("[red]project_id not found in .flowstash[/red]")
|
|
42
|
-
raise typer.Exit(code=1)
|
|
43
|
-
|
|
44
|
-
token = resolve_credentials(user=user)
|
|
45
|
-
if not token:
|
|
46
|
-
console.print("[red]Not logged in. Run 'flowstash login' first.[/red]")
|
|
47
|
-
raise typer.Exit(code=1)
|
|
48
|
-
|
|
49
|
-
api = APIClient(token=token)
|
|
50
|
-
|
|
51
|
-
# 1. If artifact_id is not provided, run build first
|
|
52
|
-
if not artifact_id:
|
|
53
|
-
console.print("No artifact ID provided. Building first...")
|
|
54
|
-
build_result = await run_build_flow(user=user)
|
|
55
|
-
artifact_id = build_result["artifact_id"]
|
|
56
|
-
console.print(f"Build finished. Deploying artifact: [bold]{artifact_id}[/bold]")
|
|
57
|
-
|
|
58
|
-
with Progress(
|
|
59
|
-
SpinnerColumn(),
|
|
60
|
-
TextColumn("[progress.description]{task.description}"),
|
|
61
|
-
transient=True,
|
|
62
|
-
) as progress:
|
|
63
|
-
# 2. Trigger deployment
|
|
64
|
-
task = progress.add_task(description="Triggering deployment...", total=None)
|
|
65
|
-
deploy_data = await api.post(
|
|
66
|
-
"/v1/deploy",
|
|
67
|
-
json={
|
|
68
|
-
"project_id": project_id,
|
|
69
|
-
"artifact_id": artifact_id,
|
|
70
|
-
"env_vars": {"ENVIRONMENT": env},
|
|
71
|
-
"flowstash_runtime_version": flowstash.runtime.__version__,
|
|
72
|
-
},
|
|
73
|
-
)
|
|
74
|
-
deploy_id = deploy_data["deploy_id"]
|
|
75
|
-
progress.update(task, description=f"Deployment triggered (ID: {deploy_id}).")
|
|
76
|
-
|
|
77
|
-
# 3. Poll status until terminal
|
|
78
|
-
last_status = None
|
|
79
|
-
while True:
|
|
80
|
-
status_data = await api.get(f"/v1/deploy/{deploy_id}/status")
|
|
81
|
-
current_status = status_data["status"]
|
|
82
|
-
|
|
83
|
-
if current_status != last_status:
|
|
84
|
-
label = _STATUS_LABELS.get(current_status, f"Status: {current_status}")
|
|
85
|
-
progress.update(task, description=label)
|
|
86
|
-
last_status = current_status
|
|
87
|
-
|
|
88
|
-
if current_status == "DEPLOYED":
|
|
89
|
-
break
|
|
90
|
-
elif current_status == "FAILED":
|
|
91
|
-
error = status_data.get("error_message", "Unknown error")
|
|
92
|
-
console.print(f"[red]Deployment failed: {error}[/red]")
|
|
93
|
-
raise typer.Exit(code=1)
|
|
94
|
-
|
|
95
|
-
await asyncio.sleep(5)
|
|
96
|
-
|
|
97
|
-
return status_data
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
@app.command()
|
|
101
|
-
def deploy(
|
|
102
|
-
env: str = typer.Argument(..., help="Environment to deploy to"),
|
|
103
|
-
artifact: Optional[str] = typer.Option(
|
|
104
|
-
None, "--artifact", "-a", help="Artifact ID to deploy"
|
|
105
|
-
),
|
|
106
|
-
yes: bool = typer.Option(False, "--yes", "-y", help="Do not ask for confirmation"),
|
|
107
|
-
user: Optional[str] = typer.Option(
|
|
108
|
-
None, "--user", "-u", help="Account to use (default: project-linked or current)"
|
|
109
|
-
),
|
|
110
|
-
):
|
|
111
|
-
"""Deploy an artifact to the managed platform for a specified environment."""
|
|
112
|
-
project_config = load_project_config()
|
|
113
|
-
if not project_config:
|
|
114
|
-
console.print(
|
|
115
|
-
"[red]No .flowstash found. Please run 'flowstash init' first.[/red]"
|
|
116
|
-
)
|
|
117
|
-
raise typer.Exit(code=1)
|
|
118
|
-
|
|
119
|
-
# Ask for confirmation unless non-interactive is provided
|
|
120
|
-
if not yes:
|
|
121
|
-
from rich.prompt import Confirm
|
|
122
|
-
|
|
123
|
-
if not Confirm.ask(f"Are you sure you want to deploy to '{env}'?"):
|
|
124
|
-
console.print("Deployment cancelled.")
|
|
125
|
-
raise typer.Exit(code=0)
|
|
126
|
-
|
|
127
|
-
# Check if env is managed
|
|
128
|
-
is_managed = False
|
|
129
|
-
for em in project_config.environments:
|
|
130
|
-
if em.name == env:
|
|
131
|
-
is_managed = em.managed
|
|
132
|
-
break
|
|
133
|
-
|
|
134
|
-
if not is_managed:
|
|
135
|
-
console.print(
|
|
136
|
-
f"[red]Environment '{env}' is not managed. Deployment is only supported for managed environments.[/red]"
|
|
137
|
-
)
|
|
138
|
-
console.print(
|
|
139
|
-
"[yellow]Update your .flowstash environments if this is incorrect.[/yellow]"
|
|
140
|
-
)
|
|
141
|
-
raise typer.Exit(code=1)
|
|
142
|
-
|
|
143
|
-
project_id = project_config.project_id
|
|
144
|
-
if not project_id:
|
|
145
|
-
if not yes:
|
|
146
|
-
from rich.prompt import Confirm
|
|
147
|
-
|
|
148
|
-
if Confirm.ask(
|
|
149
|
-
"Project ID not found. Would you like to link to a managed project now?"
|
|
150
|
-
):
|
|
151
|
-
from .project import _link_project
|
|
152
|
-
|
|
153
|
-
_link_project(project_config, user=user)
|
|
154
|
-
project_id = project_config.project_id
|
|
155
|
-
|
|
156
|
-
if not project_id:
|
|
157
|
-
console.print(
|
|
158
|
-
"[red]project_id not found in .flowstash. Use 'flowstash init' or link to a project.[/red]"
|
|
159
|
-
)
|
|
160
|
-
raise typer.Exit(code=1)
|
|
161
|
-
|
|
162
|
-
result = asyncio.run(run_deploy_flow(env, artifact, user=user))
|
|
163
|
-
|
|
164
|
-
api_url = result.get("api_url", "")
|
|
165
|
-
console.print(f"[green]✓ Deployment complete![/green]")
|
|
166
|
-
console.print(f" Deployment ID : [bold]{result['deploy_id']}[/bold]")
|
|
167
|
-
if api_url:
|
|
168
|
-
console.print(f" API URL : [bold]{api_url}[/bold]")
|
|
169
|
-
|
|
170
|
-
scheduled_tasks = result.get("scheduled_tasks")
|
|
171
|
-
if scheduled_tasks:
|
|
172
|
-
console.print(" Scheduled Tasks:")
|
|
173
|
-
for task in scheduled_tasks:
|
|
174
|
-
console.print(
|
|
175
|
-
f" - [cyan]{task['task_name']}[/cyan] : [yellow]{task['cron']}[/yellow]"
|
|
176
|
-
)
|
|
177
|
-
elif scheduled_tasks is not None:
|
|
178
|
-
console.print(" Scheduled Tasks: [dim]None[/dim]")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/shared/backend.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_config/shared/clients.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_api/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_shared/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_worker/__init__.py
RENAMED
|
File without changes
|
{flowstash_cli-0.8.3 → flowstash_cli-0.9.2}/src/flowstash/cli/templates/_src/_worker/tasks/tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|