ballista-cli 0.1.0__py3-none-any.whl
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.
ballista_cli/__init__.py
ADDED
|
File without changes
|
ballista_cli/cli.py
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import prettytable
|
|
7
|
+
import typer
|
|
8
|
+
import yaml
|
|
9
|
+
from async_typer import AsyncTyper
|
|
10
|
+
from ballista_sdk.adapters import EnvironmentWithAdapter
|
|
11
|
+
from ballista_sdk.adapters.docker_compose import DockerComposeInfrastructureAdapter
|
|
12
|
+
from ballista_sdk.adapters.kubernetes import KubernetesAPIInfrastructureAdapter
|
|
13
|
+
from ballista_sdk.api.v1 import (
|
|
14
|
+
Bolt,
|
|
15
|
+
DefaultExecutionParameters,
|
|
16
|
+
Environment,
|
|
17
|
+
EnvironmentTier,
|
|
18
|
+
ExecutionParameters,
|
|
19
|
+
ExternalizedServiceParameters,
|
|
20
|
+
VolumeExecutionParameters,
|
|
21
|
+
)
|
|
22
|
+
from ballista_sdk.bolts.factory import BoltFactory
|
|
23
|
+
from ballista_sdk.bolts.v1 import BoltV1Factory
|
|
24
|
+
|
|
25
|
+
DOCKER_COMPOSE_LOCAL = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# TODO: Stuff to get a ballista-workspace.yaml file, or maybe a .ballista folder in a parent folder?
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_bolt_factory(api_version: str) -> BoltFactory:
|
|
32
|
+
if api_version == "v1":
|
|
33
|
+
return BoltV1Factory()
|
|
34
|
+
|
|
35
|
+
raise ValueError()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_local_bolt() -> Bolt:
|
|
39
|
+
"""Get a Bolt from local path."""
|
|
40
|
+
|
|
41
|
+
filename = "./ballista.yaml" if os.path.isfile("./ballista.yaml") else None
|
|
42
|
+
if not filename:
|
|
43
|
+
raise ValueError("NO BALLISTA.YAML")
|
|
44
|
+
|
|
45
|
+
with open(filename, "r") as f:
|
|
46
|
+
ballista_yaml = yaml.safe_load(f)
|
|
47
|
+
|
|
48
|
+
if ballista_yaml is None:
|
|
49
|
+
raise ValueError()
|
|
50
|
+
|
|
51
|
+
api_version = ballista_yaml.get("api_version")
|
|
52
|
+
|
|
53
|
+
factory = get_bolt_factory(api_version=api_version)
|
|
54
|
+
return factory.get_bolt(ballista_yaml)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_local_default_bolts() -> list[Bolt]:
|
|
58
|
+
"""Loads default Bolts from home folder."""
|
|
59
|
+
|
|
60
|
+
bolts = []
|
|
61
|
+
|
|
62
|
+
home = Path.home()
|
|
63
|
+
ballista_folder = home / ".ballista"
|
|
64
|
+
defaults_yaml = ballista_folder / "defaults.yaml"
|
|
65
|
+
if defaults_yaml.exists():
|
|
66
|
+
with open(defaults_yaml) as f:
|
|
67
|
+
for bolt_yaml in yaml.safe_load_all(f):
|
|
68
|
+
api_version = bolt_yaml.get("api_version")
|
|
69
|
+
|
|
70
|
+
factory = get_bolt_factory(api_version=api_version)
|
|
71
|
+
bolt = factory.get_bolt(bolt_yaml)
|
|
72
|
+
bolts.append(bolt)
|
|
73
|
+
|
|
74
|
+
return bolts
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_local_environment() -> EnvironmentWithAdapter:
|
|
78
|
+
"""Get a local Environment for development purposes."""
|
|
79
|
+
|
|
80
|
+
# TODO: When we have registry support, we won't need this.
|
|
81
|
+
# Load any local Bolts
|
|
82
|
+
local_bolts = get_local_default_bolts()
|
|
83
|
+
|
|
84
|
+
if DOCKER_COMPOSE_LOCAL:
|
|
85
|
+
adapter = DockerComposeInfrastructureAdapter(local_bolts)
|
|
86
|
+
else:
|
|
87
|
+
adapter = KubernetesAPIInfrastructureAdapter()
|
|
88
|
+
|
|
89
|
+
environment = Environment(name="local", tier=EnvironmentTier.DEVELOPMENT)
|
|
90
|
+
|
|
91
|
+
return environment, adapter
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# TODO: Improve this when there are more adapters
|
|
95
|
+
async def get_remote_environments() -> list[EnvironmentWithAdapter]:
|
|
96
|
+
# Read in remote Kubernetes environments
|
|
97
|
+
adapter = KubernetesAPIInfrastructureAdapter()
|
|
98
|
+
return [(e, adapter) for e in await adapter.list_environments()]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def get_available_environments(
|
|
102
|
+
include_remotes: bool = False, environment_names: list[str] | None = None
|
|
103
|
+
) -> list[EnvironmentWithAdapter]:
|
|
104
|
+
environments = [get_local_environment()]
|
|
105
|
+
|
|
106
|
+
if include_remotes:
|
|
107
|
+
environments.extend(await get_remote_environments())
|
|
108
|
+
|
|
109
|
+
if environment_names:
|
|
110
|
+
return [(environment, _) for environment, _ in environments if environment.name in environment_names]
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
return environments
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_local_execution_parameters() -> ExecutionParameters:
|
|
117
|
+
"""Get ExecutionParameters for the local development environment with the following settings:
|
|
118
|
+
|
|
119
|
+
- All services are externalized at `localhost`.
|
|
120
|
+
- Volumes are allowed at ???.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
return ExecutionParameters(
|
|
125
|
+
initial=DefaultExecutionParameters(
|
|
126
|
+
external_service=ExternalizedServiceParameters(host="localhost"),
|
|
127
|
+
volume=VolumeExecutionParameters(type="standard"),
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def get_dev_execution_parameters() -> ExecutionParameters:
|
|
133
|
+
return ExecutionParameters(
|
|
134
|
+
initial=DefaultExecutionParameters(
|
|
135
|
+
external_service=ExternalizedServiceParameters(host="dev.ballista.build", port=8123),
|
|
136
|
+
volume=VolumeExecutionParameters(type="standard"),
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
cli = AsyncTyper()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@cli.command(short_help="initialize a new ballista powered project")
|
|
145
|
+
def init(project: Annotated[str, typer.Argument(help="Name of new project.")]):
|
|
146
|
+
# TODO: Need to pick an api, so just use v1 for now. We'll probably want a default version with compatibility for old ones up to a certain date.
|
|
147
|
+
if True:
|
|
148
|
+
bolt_factory = BoltV1Factory()
|
|
149
|
+
|
|
150
|
+
# Check if that project (folder) already exists
|
|
151
|
+
if os.path.exists(project):
|
|
152
|
+
raise ValueError(f'Path "{project}" already exists.')
|
|
153
|
+
|
|
154
|
+
os.makedirs(project)
|
|
155
|
+
new_bolt = bolt_factory.create_bolt(project=project, version="0.1.0")
|
|
156
|
+
with open(os.path.join(project, "ballista.yaml"), "w") as f:
|
|
157
|
+
yaml.dump(new_bolt.to_dict(), f)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@cli.command(
|
|
161
|
+
short_help="Build artifacts defined in ballista.yaml",
|
|
162
|
+
help="""Build a path containing a ballista.yaml into a Launch-able Bolt.
|
|
163
|
+
|
|
164
|
+
Defined artifacts contained in a ballista.yaml will be built and ready to be Launched.
|
|
165
|
+
""",
|
|
166
|
+
)
|
|
167
|
+
def build(
|
|
168
|
+
artifacts: Annotated[list[str] | None, typer.Argument(help="List of artifacts to buid.")] = None,
|
|
169
|
+
artifact_types: Annotated[
|
|
170
|
+
list[str] | None,
|
|
171
|
+
typer.Option(help="List of specified Artifact Types to build."),
|
|
172
|
+
] = None,
|
|
173
|
+
):
|
|
174
|
+
ballista_bolt = get_local_bolt()
|
|
175
|
+
|
|
176
|
+
for artifact in ballista_bolt.buildable_artifacts:
|
|
177
|
+
build = artifact.build
|
|
178
|
+
image_name = f"{ballista_bolt.project}_{artifact.name}:{ballista_bolt.version}"
|
|
179
|
+
|
|
180
|
+
path = "."
|
|
181
|
+
dockerfile = build.dockerfile or "Dockerfile"
|
|
182
|
+
# Process possible path for the Dockerfile
|
|
183
|
+
dockerfile_pieces = dockerfile.rsplit("/", 1)
|
|
184
|
+
if len(dockerfile_pieces) > 1:
|
|
185
|
+
path, _ = dockerfile_pieces
|
|
186
|
+
|
|
187
|
+
cmd = f"docker build {path} -t {image_name} -f {dockerfile} --target {build.dockerfile_target}"
|
|
188
|
+
os.system(cmd)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@cli.command(short_help="Start local development environment.")
|
|
192
|
+
async def up(artifact_names: Annotated[list[str] | None, typer.Option("--artifact", "-a")] = None):
|
|
193
|
+
bolt = get_local_bolt()
|
|
194
|
+
environment, adapter = get_local_environment()
|
|
195
|
+
execution_parameters = get_local_execution_parameters()
|
|
196
|
+
|
|
197
|
+
executable_artifacts = bolt.executable_artifacts
|
|
198
|
+
if artifact_names and (found_artifacts := [a for a in executable_artifacts if a.name in artifact_names]):
|
|
199
|
+
executable_artifacts = found_artifacts
|
|
200
|
+
|
|
201
|
+
await adapter.deploy(
|
|
202
|
+
bolt=bolt,
|
|
203
|
+
artifacts=executable_artifacts,
|
|
204
|
+
environment=environment,
|
|
205
|
+
execution_parameters=execution_parameters,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@cli.command(short_help="Teardown local development environment.")
|
|
210
|
+
async def down():
|
|
211
|
+
bolt = get_local_bolt()
|
|
212
|
+
environment, adapter = get_local_environment()
|
|
213
|
+
execution_parameters = get_local_execution_parameters()
|
|
214
|
+
|
|
215
|
+
await adapter.teardown(
|
|
216
|
+
bolt=bolt,
|
|
217
|
+
artifacts=bolt.executable_artifacts,
|
|
218
|
+
environment=environment,
|
|
219
|
+
execution_parameters=execution_parameters,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@cli.command(short_help="Deploy to an environment.")
|
|
224
|
+
async def deploy(
|
|
225
|
+
environment_name: str, allow_unknown_environment: bool = False, environment_adapter_name: str | None = None
|
|
226
|
+
):
|
|
227
|
+
environments = await get_available_environments(include_remotes=True, environment_names=[environment_name])
|
|
228
|
+
|
|
229
|
+
if not environments:
|
|
230
|
+
print(f'Unknown environment "{environment_name}".')
|
|
231
|
+
if allow_unknown_environment:
|
|
232
|
+
environment = Environment(name=environment_name, tier=EnvironmentTier.DEVELOPMENT)
|
|
233
|
+
# TODO: Figure this out
|
|
234
|
+
adapter = KubernetesAPIInfrastructureAdapter()
|
|
235
|
+
else:
|
|
236
|
+
typer.Exit(1)
|
|
237
|
+
return
|
|
238
|
+
else:
|
|
239
|
+
environment, adapter = environments[0]
|
|
240
|
+
|
|
241
|
+
ballista_bolt = get_local_bolt()
|
|
242
|
+
|
|
243
|
+
# TODO: Get real execution parameters somehow!
|
|
244
|
+
execution_parameters = get_dev_execution_parameters()
|
|
245
|
+
await adapter.deploy(
|
|
246
|
+
bolt=ballista_bolt,
|
|
247
|
+
artifacts=ballista_bolt.executable_artifacts,
|
|
248
|
+
environment=environment,
|
|
249
|
+
execution_parameters=execution_parameters,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class GenerationTypes(StrEnum):
|
|
254
|
+
SETTINGS = "settings"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@cli.command(short_help="generate files from ballista dependencies")
|
|
258
|
+
def generate(type: GenerationTypes):
|
|
259
|
+
print("GENERATE")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@cli.command(short_help="Execute a command within an Artifact context.")
|
|
263
|
+
def run(artifact_name: str):
|
|
264
|
+
environment, adapter = get_local_environment()
|
|
265
|
+
bolt = get_local_bolt()
|
|
266
|
+
|
|
267
|
+
artifacts = [a for a in bolt.executable_artifacts if a.name == artifact_name]
|
|
268
|
+
if len(artifacts) == 0:
|
|
269
|
+
raise ValueError("artifact_name not found")
|
|
270
|
+
|
|
271
|
+
artifact = artifacts[0]
|
|
272
|
+
# TODO: Get environment the artifact needs
|
|
273
|
+
#
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# @cli.command(short_help="Environment information")
|
|
277
|
+
# def env():
|
|
278
|
+
# pass
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
list_cli = AsyncTyper(no_args_is_help=True)
|
|
282
|
+
cli.add_typer(list_cli, name="list", short_help="List stuff")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@list_cli.command(name="artifact_types")
|
|
286
|
+
async def list_artifact_types(
|
|
287
|
+
environment_names: Annotated[
|
|
288
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
289
|
+
] = None,
|
|
290
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
291
|
+
):
|
|
292
|
+
table = prettytable.PrettyTable()
|
|
293
|
+
table.align = "l"
|
|
294
|
+
table.field_names = ["Artifact Type", "Configs"]
|
|
295
|
+
|
|
296
|
+
environments = await get_available_environments(
|
|
297
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
298
|
+
)
|
|
299
|
+
for environment, adapter in environments:
|
|
300
|
+
for artifact_type in await adapter.list_artifact_types([environment]):
|
|
301
|
+
table.add_row(
|
|
302
|
+
[f"{artifact_type.title} ({artifact_type.name})", "NYI"],
|
|
303
|
+
divider=True,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
print(table.get_string())
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@list_cli.command("environments")
|
|
310
|
+
async def list_environments(
|
|
311
|
+
environment_names: Annotated[
|
|
312
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
313
|
+
] = None,
|
|
314
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
315
|
+
):
|
|
316
|
+
table = prettytable.PrettyTable()
|
|
317
|
+
table.align = "l"
|
|
318
|
+
table.field_names = ["Environment", "Tier", "Adapter"]
|
|
319
|
+
|
|
320
|
+
environments = await get_available_environments(
|
|
321
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
322
|
+
)
|
|
323
|
+
for environment, adapter in environments:
|
|
324
|
+
table.add_row([f"{environment.name} ({environment.name})", environment.tier, adapter.name])
|
|
325
|
+
|
|
326
|
+
print(table.get_string())
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@list_cli.command("executable_artifacts")
|
|
330
|
+
async def list_executable_artifacts(
|
|
331
|
+
project_names: Annotated[list[str] | None, typer.Option("--project", "-p", help="Specific project name.")] = None,
|
|
332
|
+
artifact_names: Annotated[
|
|
333
|
+
list[str] | None, typer.Option("--artifact", "-a", help="Specific artifact name.")
|
|
334
|
+
] = None,
|
|
335
|
+
environment_names: Annotated[
|
|
336
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
337
|
+
] = None,
|
|
338
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
339
|
+
):
|
|
340
|
+
table = prettytable.PrettyTable()
|
|
341
|
+
table.align = "l"
|
|
342
|
+
table.field_names = ["Project", "Artifact", "Version"]
|
|
343
|
+
|
|
344
|
+
environments = await get_available_environments(
|
|
345
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
346
|
+
)
|
|
347
|
+
for environment, adapter in environments:
|
|
348
|
+
for project_name, artifact_name, artifact_version in await adapter.list_executable_artifacts(
|
|
349
|
+
[environment], project_names=project_names
|
|
350
|
+
):
|
|
351
|
+
table.add_row([project_name, artifact_name, artifact_version])
|
|
352
|
+
|
|
353
|
+
print(table.get_string())
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@list_cli.command("provided_resources")
|
|
357
|
+
async def list_provided_resources(
|
|
358
|
+
project_names: Annotated[list[str] | None, typer.Option("--project", "-p", help="Specific project name.")] = None,
|
|
359
|
+
artifact_names: Annotated[
|
|
360
|
+
list[str] | None, typer.Option("--artifact", "-a", help="Specific artifact name.")
|
|
361
|
+
] = None,
|
|
362
|
+
environment_names: Annotated[
|
|
363
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
364
|
+
] = None,
|
|
365
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
366
|
+
):
|
|
367
|
+
verbose = False
|
|
368
|
+
|
|
369
|
+
table = prettytable.PrettyTable()
|
|
370
|
+
table.align = "l"
|
|
371
|
+
table.field_names = ["Resource", "Configs", "Secrets", "Requirements"]
|
|
372
|
+
|
|
373
|
+
environments = await get_available_environments(
|
|
374
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
375
|
+
)
|
|
376
|
+
for environment, adapter in environments:
|
|
377
|
+
for provided_resource, provider_artifact_reference in await adapter.list_provided_resources(
|
|
378
|
+
[environment], project_names=project_names, artifact_names=artifact_names
|
|
379
|
+
):
|
|
380
|
+
configs = None
|
|
381
|
+
if provided_resource.configs:
|
|
382
|
+
configs = prettytable.PrettyTable(align="l")
|
|
383
|
+
configs.set_style(prettytable.TableStyle.PLAIN_COLUMNS)
|
|
384
|
+
configs.header = False
|
|
385
|
+
if verbose:
|
|
386
|
+
configs.add_rows(
|
|
387
|
+
[[f"{c.title} ({c.name})", c.description, c.shared] for c in provided_resource.configs]
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
configs.add_rows([[f"{c.title} ({c.name})"] for c in provided_resource.configs])
|
|
391
|
+
|
|
392
|
+
secrets = None
|
|
393
|
+
if provided_resource.secrets:
|
|
394
|
+
secrets = prettytable.PrettyTable(align="l")
|
|
395
|
+
secrets.set_style(prettytable.TableStyle.PLAIN_COLUMNS)
|
|
396
|
+
secrets.header = False
|
|
397
|
+
if verbose:
|
|
398
|
+
secrets.add_rows(
|
|
399
|
+
[[f"{s.title} ({s.name})", s.description, s.shared] for s in provided_resource.secrets]
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
secrets.add_rows([[f"{s.title} ({s.name})"] for s in provided_resource.secrets])
|
|
403
|
+
|
|
404
|
+
requirements = None
|
|
405
|
+
if provided_resource.requirements and provided_resource.requirements.properties:
|
|
406
|
+
requirements = prettytable.PrettyTable(align="l")
|
|
407
|
+
requirements.set_style(prettytable.TableStyle.PLAIN_COLUMNS)
|
|
408
|
+
requirements.header = False
|
|
409
|
+
if verbose:
|
|
410
|
+
requirements.add_rows(
|
|
411
|
+
[
|
|
412
|
+
[f"{req_property.title or req_name} ({req_name})", req_property.description]
|
|
413
|
+
for req_name, req_property in provided_resource.requirements.properties.items()
|
|
414
|
+
]
|
|
415
|
+
)
|
|
416
|
+
else:
|
|
417
|
+
requirements.add_rows(
|
|
418
|
+
[
|
|
419
|
+
[f"{req_property.title or req_name} ({req_name})"]
|
|
420
|
+
for req_name, req_property in provided_resource.requirements.properties.items()
|
|
421
|
+
]
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
table.add_row(
|
|
425
|
+
[
|
|
426
|
+
f"{provided_resource.title} ({provider_artifact_reference.project_name}:{provided_resource.name})",
|
|
427
|
+
configs,
|
|
428
|
+
secrets,
|
|
429
|
+
requirements,
|
|
430
|
+
],
|
|
431
|
+
divider=True,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
print(table.get_string())
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@list_cli.command("provided_services", short_help="List all Provided Services.")
|
|
438
|
+
async def list_provided_services(
|
|
439
|
+
project_names: Annotated[list[str] | None, typer.Option("--project", "-p", help="Specific project name.")] = None,
|
|
440
|
+
artifact_names: Annotated[
|
|
441
|
+
list[str] | None, typer.Option("--artifact", "-a", help="Specific artifact name.")
|
|
442
|
+
] = None,
|
|
443
|
+
environment_names: Annotated[
|
|
444
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
445
|
+
] = None,
|
|
446
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
447
|
+
):
|
|
448
|
+
table = prettytable.PrettyTable()
|
|
449
|
+
table.align = "l"
|
|
450
|
+
table.field_names = ["Project", "Artifact", "Service", "Type", "Port"]
|
|
451
|
+
|
|
452
|
+
environments = await get_available_environments(
|
|
453
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
454
|
+
)
|
|
455
|
+
for environment, adapter in environments:
|
|
456
|
+
for provided_service, provider_artifact_reference in await adapter.list_provided_services(
|
|
457
|
+
[environment], project_names=project_names, artifact_names=artifact_names
|
|
458
|
+
):
|
|
459
|
+
if provided_service.grpc:
|
|
460
|
+
service_type = "grpc"
|
|
461
|
+
service_port = provided_service.grpc
|
|
462
|
+
elif provided_service.http:
|
|
463
|
+
service_type = "http"
|
|
464
|
+
service_port = provided_service.http
|
|
465
|
+
elif provided_service.tcp:
|
|
466
|
+
service_type = "tcp"
|
|
467
|
+
service_port = provided_service.tcp
|
|
468
|
+
|
|
469
|
+
table.add_row(
|
|
470
|
+
[
|
|
471
|
+
provider_artifact_reference.project_name,
|
|
472
|
+
provider_artifact_reference.artifact_name,
|
|
473
|
+
provided_service.name,
|
|
474
|
+
service_type,
|
|
475
|
+
service_port,
|
|
476
|
+
]
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
print(table.get_string())
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@list_cli.command("resources", short_help="List Resources and their statuses.")
|
|
483
|
+
async def list_resources(
|
|
484
|
+
project_names: Annotated[list[str] | None, typer.Option("--project", "-p", help="Specific project name.")] = None,
|
|
485
|
+
artifact_names: Annotated[
|
|
486
|
+
list[str] | None, typer.Option("--artifact", "-a", help="Specific artifact name.")
|
|
487
|
+
] = None,
|
|
488
|
+
environment_names: Annotated[
|
|
489
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
490
|
+
] = None,
|
|
491
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
492
|
+
):
|
|
493
|
+
table = prettytable.PrettyTable()
|
|
494
|
+
table.align = "l"
|
|
495
|
+
table.field_names = ["Project", "Artifact", "Resource", "Status"]
|
|
496
|
+
|
|
497
|
+
environments = await get_available_environments(
|
|
498
|
+
include_remotes=include_remotes, environment_names=environment_names
|
|
499
|
+
)
|
|
500
|
+
for environment, adapter in environments:
|
|
501
|
+
for artifact_reference in await adapter.list_executable_artifacts(
|
|
502
|
+
[environment], project_names=project_names, artifact_names=artifact_names
|
|
503
|
+
):
|
|
504
|
+
_, resolved_artifact = await adapter.resolve_artifact_reference(environment, artifact_reference)
|
|
505
|
+
if resolved_artifact.execution and resolved_artifact.execution.requires.resources:
|
|
506
|
+
for resource in resolved_artifact.execution.requires.resources:
|
|
507
|
+
table.add_row(
|
|
508
|
+
[
|
|
509
|
+
artifact_reference.project_name,
|
|
510
|
+
artifact_reference.artifact_name,
|
|
511
|
+
resource.resource_name,
|
|
512
|
+
"STATUS",
|
|
513
|
+
]
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
print(table.get_string())
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@list_cli.command("services")
|
|
520
|
+
async def list_services(
|
|
521
|
+
project_names: Annotated[list[str] | None, typer.Option("--project", "-p", help="Specific project name.")] = None,
|
|
522
|
+
artifact_names: Annotated[
|
|
523
|
+
list[str] | None, typer.Option("--artifact", "-a", help="Specific artifact name.")
|
|
524
|
+
] = None,
|
|
525
|
+
environment_names: Annotated[
|
|
526
|
+
list[str] | None, typer.Option("--environment", "-e", help="Specific environment name.")
|
|
527
|
+
] = None,
|
|
528
|
+
include_remotes: Annotated[bool, typer.Option(help="Include remote environments.")] = False,
|
|
529
|
+
):
|
|
530
|
+
table = prettytable.PrettyTable()
|
|
531
|
+
table.align = "l"
|
|
532
|
+
|
|
533
|
+
print(table.get_string())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ballista-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Ballista.build CLI
|
|
5
|
+
Author-email: August Bigelow <dev@atbigelow.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.12
|
|
15
|
+
Requires-Dist: async-typer>=0.2.1
|
|
16
|
+
Requires-Dist: ballista-sdk<0.2,>=0.1
|
|
17
|
+
Requires-Dist: prettytable
|
|
18
|
+
Requires-Dist: typer<0.26,>=0.25
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Ballista.build CLI
|
|
22
|
+
|
|
23
|
+
CLI for using Ballista.build
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- `init` - Create an initial ballista.yaml
|
|
28
|
+
- `build` - Build the artifacts contained in a ballista.yaml
|
|
29
|
+
- `up` - Execute project in local environment
|
|
30
|
+
- `down` - Teardown project in local environment
|
|
31
|
+
- `run` - Run a command with an Artifact context
|
|
32
|
+
- `deploy` - Deploy to an environment
|
|
33
|
+
- `list` - List various items from environments
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
`~/.ballista/defaults.yaml`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ballista_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
ballista_cli/cli.py,sha256=Sp8qeFs86L0lNcKtd9BCClWJgbVHLNq-beWrL-RSH2Q,19556
|
|
3
|
+
ballista_cli-0.1.0.dist-info/METADATA,sha256=CYMng0m6LrySxqCUSfjoAKCg9FWOJGHr6P27nAMMRpA,1102
|
|
4
|
+
ballista_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
5
|
+
ballista_cli-0.1.0.dist-info/entry_points.txt,sha256=FckQJh1fF4FC_3i7MNgUfKqaWYxn3pB-aDzMOFwBSew,50
|
|
6
|
+
ballista_cli-0.1.0.dist-info/licenses/LICENSE,sha256=6nehCbDq6UrbI_AojHmZOQhXgcyaIEZTQECloF-FfGo,1116
|
|
7
|
+
ballista_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 20236 August Trapper Bigelow and the ballista.build contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|