|
2 | 2 |
|
3 | 3 | import json |
4 | 4 | import logging |
| 5 | +import re |
5 | 6 | from base64 import b16decode, b32encode |
6 | 7 | from collections.abc import Mapping |
7 | 8 | from pathlib import Path |
8 | 9 | from typing import List, Optional, cast |
9 | 10 | from zipfile import BadZipFile |
10 | 11 |
|
| 12 | +import aiohttp |
11 | 13 | import typer |
12 | 14 | from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient |
13 | 15 | from aleph.sdk.account import _load_account |
@@ -171,14 +173,14 @@ async def upload( |
171 | 173 | hash_base32 = b32encode(b16decode(item_hash.upper())).strip(b"=").lower().decode() |
172 | 174 |
|
173 | 175 | if verbose: |
174 | | - typer.echo( |
175 | | - f"Your program has been uploaded on aleph.im\n\n" |
176 | | - "Available on:\n" |
177 | | - f" {settings.VM_URL_PATH.format(hash=item_hash)}\n" |
178 | | - f" {settings.VM_URL_HOST.format(hash_base32=hash_base32)}\n" |
179 | | - "Visualise on:\n https://explorer.aleph.im/address/" |
180 | | - f"{message.chain.value}/{message.sender}/message/PROGRAM/{item_hash}\n" |
181 | | - ) |
| 176 | + typer.echo( |
| 177 | + f"Your program has been uploaded on aleph.im\n\n" |
| 178 | + "Available on:\n" |
| 179 | + f" {settings.VM_URL_PATH.format(hash=item_hash)}\n" |
| 180 | + f" {settings.VM_URL_HOST.format(hash_base32=hash_base32)}\n" |
| 181 | + "Visualise on:\n https://explorer.aleph.im/address/" |
| 182 | + f"{message.chain.value}/{message.sender}/message/PROGRAM/{item_hash}\n" |
| 183 | + ) |
182 | 184 | return item_hash |
183 | 185 |
|
184 | 186 |
|
@@ -286,11 +288,11 @@ async def delete( |
286 | 288 | hashes=[ItemHash(code_volume.item_hash)], reason=f"Deletion of program {item_hash}" |
287 | 289 | ) |
288 | 290 | if verbose: |
289 | | - typer.echo(f"Code volume {code_volume.item_hash} has been deleted.") |
| 291 | + typer.echo(f"Code volume {code_volume.item_hash} has been deleted.") |
290 | 292 | if print_message: |
291 | 293 | typer.echo(f"{message.json(indent=4)}") |
292 | 294 | if verbose: |
293 | | - typer.echo(f"Program {item_hash} has been deleted.") |
| 295 | + typer.echo(f"Program {item_hash} has been deleted.") |
294 | 296 |
|
295 | 297 |
|
296 | 298 | @app.command(name="list") |
@@ -469,3 +471,80 @@ async def logs( |
469 | 471 | log_entries = await response.json() |
470 | 472 | for log in log_entries: |
471 | 473 | echo(f'{log["__REALTIME_TIMESTAMP"]}> {log["MESSAGE"]}') |
| 474 | + |
| 475 | + |
| 476 | +@app.command() |
| 477 | +async def runtime_checker( |
| 478 | + item_hash: str = typer.Argument(..., help="Item hash of the runtime to check"), |
| 479 | + private_key: Optional[str] = settings.PRIVATE_KEY_STRING, |
| 480 | + private_key_file: Optional[Path] = settings.PRIVATE_KEY_FILE, |
| 481 | + verbose: bool = False, |
| 482 | + debug: bool = False, |
| 483 | +): |
| 484 | + """Check versions used by a runtime (distribution, python, nodejs, etc)""" |
| 485 | + |
| 486 | + setup_logging(debug) |
| 487 | + |
| 488 | + echo("Deploy runtime checker program...") |
| 489 | + try: |
| 490 | + program_hash = await upload( |
| 491 | + path=Path(__file__).resolve().parent / "program_utils/runtime_checker.squashfs", |
| 492 | + entrypoint="main:app", |
| 493 | + channel=settings.DEFAULT_CHANNEL, |
| 494 | + memory=settings.DEFAULT_VM_MEMORY, |
| 495 | + vcpus=settings.DEFAULT_VM_VCPUS, |
| 496 | + timeout_seconds=settings.DEFAULT_VM_TIMEOUT, |
| 497 | + name="runtime_checker", |
| 498 | + runtime=item_hash, |
| 499 | + beta=False, |
| 500 | + persistent=False, |
| 501 | + skip_volume=True, |
| 502 | + private_key=private_key, |
| 503 | + private_key_file=private_key_file, |
| 504 | + print_messages=False, |
| 505 | + print_code_message=False, |
| 506 | + print_program_message=False, |
| 507 | + verbose=verbose, |
| 508 | + debug=debug, |
| 509 | + ) |
| 510 | + except Exception as e: |
| 511 | + echo(f"Failed to deploy the runtime checker program: {e}") |
| 512 | + raise typer.Exit(code=1) |
| 513 | + |
| 514 | + program_url = settings.VM_URL_PATH.format(hash=program_hash) |
| 515 | + versions: dict |
| 516 | + echo("Query runtime checker to retrieve versions...") |
| 517 | + try: |
| 518 | + timeout = aiohttp.ClientTimeout(total=settings.HTTP_REQUEST_TIMEOUT) |
| 519 | + async with aiohttp.ClientSession(timeout=timeout) as session: |
| 520 | + async with session.get(program_url) as resp: |
| 521 | + resp.raise_for_status() |
| 522 | + versions = await resp.json() |
| 523 | + except Exception as e: |
| 524 | + logger.debug(f"Unexpected error when calling {program_url}: {e}") |
| 525 | + raise typer.Exit(code=1) |
| 526 | + |
| 527 | + echo("Delete runtime checker...") |
| 528 | + try: |
| 529 | + await delete( |
| 530 | + item_hash=program_hash, |
| 531 | + reason="Automatic deletion of the runtime checker program", |
| 532 | + delete_code=True, |
| 533 | + private_key=private_key, |
| 534 | + private_key_file=private_key_file, |
| 535 | + print_message=False, |
| 536 | + verbose=verbose, |
| 537 | + debug=debug, |
| 538 | + ) |
| 539 | + except Exception as e: |
| 540 | + echo(f"Failed to delete the runtime checker program: {e}") |
| 541 | + raise typer.Exit(code=1) |
| 542 | + |
| 543 | + console = Console() |
| 544 | + infos = [Text.from_markup(f"[bold]Ref:[/bold] [bright_cyan]{item_hash}[/bright_cyan]")] |
| 545 | + for label, version in versions.items(): |
| 546 | + color = "green" if bool(re.search(r"\d", version)) else "red" |
| 547 | + infos.append(Text.from_markup(f"\n[bold]{label}:[/bold] [{color}]{version}[/{color}]")) |
| 548 | + console.print( |
| 549 | + Panel(Text.assemble(*infos), title="Runtime Infos", border_style="violet", expand=False, title_align="left") |
| 550 | + ) |
0 commit comments