Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b6abb6f
Minor fixes: mypy + isort
philogicae Dec 21, 2024
77a3558
Improve instance/network.py
philogicae Dec 21, 2024
206b68f
Fix: needed delay for coco instance cmd
philogicae Dec 21, 2024
b77f119
Remove deprecated instance expire/erase
philogicae Dec 21, 2024
b7ba322
Fix GpuDevice model
philogicae Dec 21, 2024
499793f
Fix instance payment_chain: helpers + chain checks
philogicae Dec 21, 2024
8b4fc58
Replace label for instance/network/py
philogicae Dec 21, 2024
3baff90
Improve CRN checks
philogicae Dec 21, 2024
db603b2
Fix instance delete (after erase cmd deletion)
philogicae Dec 21, 2024
749e38e
Improve/fix instance list
philogicae Dec 21, 2024
e16ae2b
Add program list/delete cmds
philogicae Dec 21, 2024
9ef78ba
Add all missing instance tests (except coco) on Aleph testnet
philogicae Dec 21, 2024
11af272
Move find_sevctl_or_exit to utils + some fixes & cleanup
philogicae Dec 21, 2024
07339ba
Revamp instance cmds output
philogicae Dec 21, 2024
503a6e2
Fix wrong gpu conf in tests
philogicae Dec 21, 2024
c80a772
Update program list UX (revamp)
philogicae Dec 21, 2024
2440ca6
Fix instance chain and firmware_path helpers
philogicae Dec 21, 2024
c7860fc
Improve UX create/coco cmds
philogicae Dec 21, 2024
e8e5c85
Add coco cmds tests
philogicae Dec 21, 2024
9cf2f87
Fixes: chain/payment errors for instances
philogicae Jan 9, 2025
42ccd65
Refactor tests
philogicae Jan 9, 2025
5b3adc6
Add "runtime checker" program
philogicae Jan 9, 2025
3e6d78e
Update/fix programs
philogicae Jan 9, 2025
65e839b
Add program tests
philogicae Jan 9, 2025
439dd69
Remove duplicate safe_getattr (moved to sdk)
philogicae Jan 9, 2025
d2d40f9
Add file download --only-info + test
philogicae Jan 9, 2025
9ed0c10
Add crn hash to instance list cmd + minor fixes
philogicae Jan 9, 2025
db84d68
Fix/update gendocs, helpers, readme
philogicae Jan 10, 2025
9bf025d
Fix node_link hardcoded api host
philogicae Jan 10, 2025
f411ced
Fix missing try/catch on all get_message()
philogicae Jan 10, 2025
8041626
Fix missing try/catch on coco cmds
philogicae Jan 10, 2025
115676e
Add rootfs item hash on exceptions
philogicae Jan 13, 2025
74692bc
Fixes: keep_session prompting, catch measurement exception, increase …
philogicae Jan 14, 2025
2f74555
Add checks/exceptions for selected CRN on instance create
philogicae Jan 15, 2025
575c7bf
Improve account balance: UX + add details
philogicae Jan 15, 2025
e6714ea
aleph node compute: extra args + display crn urls
philogicae Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 7 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Documentation can be found on https://docs.aleph.im/tools/aleph-client/
Some cryptographic functionalities use curve secp256k1 and require
installing [libsecp256k1](https://github.com/bitcoin-core/secp256k1).

> apt-get install -y python3-pip libsecp256k1-dev
> apt-get install -y python3-pip libsecp256k1-dev squashfs-tools

### macOs

Expand All @@ -24,8 +24,7 @@ installing [libsecp256k1](https://github.com/bitcoin-core/secp256k1).

### Windows

The software is not tested on Windows, but should work using
the Windows Subsystem for Linux (WSL).
We recommend using [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) (Windows Subsystem for Linux).

## Installation

Expand Down Expand Up @@ -85,28 +84,15 @@ To install from source and still be able to modify the source code:

## Updating the User Documentation

The user documentation for Aleph is maintained in the `aleph-docs` repository. When releasing a new version, it's
important to update the documentation as part of the release process.

### Steps for Updating Documentation

Documentation is generated using the `typer` command.
The user documentation for Aleph is maintained in the [aleph-docs](https://github.com/aleph-im/aleph-docs) repository. The CLI page is generated using the `typer` command. When releasing a new version, it's important to update the documentation as part of the release process.

If you have the `aleph-docs` repository cloned as a sibling folder to your current directory, you can use the following
command to generate updated documentation:

```shell
./scripts/gendoc.py src/aleph_client/__main__.py docs --name aleph --title 'Aleph CLI Documentation' --output ../aleph-docs/docs/tools/aleph-client/usage.md
./scripts/gendoc.py src/aleph_client/__main__.py docs \
--name aleph --title 'Aleph CLI Documentation' \
--output ../aleph-docs/docs/tools/aleph-client/usage.md
```

After generating the documentation, you may need to update the path for the private key, as this depends on the user
configuration. This can be fixed manually using the `sed` command. For example:

```shell
sed -i 's#/home/olivier/.aleph-im/private-keys/sol2.key#~/.aleph-im/private-keys/ethereum.key#' ../aleph-docs/docs/tools/aleph-client/usage.md
```

This command replaces any hardcoded private key paths with the correct configuration path (
`~/.aleph-im/private-keys/ethereum.key`).

Once the documentation is updated, open a Pull Request (PR) on the `aleph-docs` repository with your changes.
Then, open a Pull Request (PR) on the [aleph-docs](https://github.com/aleph-im/aleph-docs/pulls) repository with your changes.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ pythonpath = [
testpaths = [
"tests",
]
asyncio_default_fixture_loop_scope = "function"

[tool.coverage.run]
branch = true
Expand Down
30 changes: 23 additions & 7 deletions scripts/gendoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
Copied from typer.cli.py to customise doc generation
"""

import click
import importlib.util
import os
import re
import sys
from pathlib import Path
from typing import Any, List, Optional

import click
import typer
import typer.core
from click import Command, Group
from pathlib import Path
from typing import Any, List, Optional

default_app_names = ("app", "cli", "main")
default_func_names = ("main", "cli", "app")
Expand Down Expand Up @@ -246,6 +248,19 @@ def get_docs_for_click(
return docs


def replace_local_values(text: str) -> str:
# Replace username
current_user = Path.home().owner()
text = text.replace(current_user, "$USER")

# Replace private key file path
pattern = r"[^/]+\.key"
replacement = r"ethereum.key"
text = re.sub(pattern, replacement, text)

return text


@utils_app.command()
def docs(
ctx: typer.Context,
Expand All @@ -269,13 +284,14 @@ def docs(
typer.echo("No Typer app found", err=True)
raise typer.Abort()
click_obj = typer.main.get_command(typer_obj)
docs = get_docs_for_click(obj=click_obj, ctx=ctx, name=name, title=title)
clean_docs = f"{docs.strip()}\n"
generated_docs = get_docs_for_click(obj=click_obj, ctx=ctx, name=name, title=title)
clean_docs = f"{generated_docs.strip()}\n"
fixed_docs = replace_local_values(clean_docs)
if output:
output.write_text(clean_docs)
output.write_text(fixed_docs)
typer.echo(f"Docs saved to: {output}")
else:
typer.echo(clean_docs)
typer.echo(fixed_docs)


utils_app()
17 changes: 8 additions & 9 deletions src/aleph_client/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@

app = AsyncTyper(no_args_is_help=True)

app.add_typer(account.app, name="account", help="Manage account")
app.add_typer(aggregate.app, name="aggregate", help="Manage aggregate messages on aleph.im")
app.add_typer(files.app, name="file", help="File uploading and pinning on IPFS and aleph.im")
app.add_typer(account.app, name="account", help="Manage accounts")
app.add_typer(
message.app,
name="message",
help="Post, amend, watch and forget messages on aleph.im",
help="Manage messages (post, amend, watch and forget) on aleph.im & twentysix.cloud",
)
app.add_typer(program.app, name="program", help="Upload and update programs on aleph.im VM")
app.add_typer(aggregate.app, name="aggregate", help="Manage aggregate messages on aleph.im & twentysix.cloud")
app.add_typer(files.app, name="file", help="Manage files (upload and pin on IPFS) on aleph.im & twentysix.cloud")
app.add_typer(program.app, name="program", help="Manage programs (micro-VMs) on aleph.im & twentysix.cloud")
app.add_typer(instance.app, name="instance", help="Manage instances (VMs) on aleph.im & twentysix.cloud")
app.add_typer(domain.app, name="domain", help="Manage custom domain (DNS) on aleph.im & twentysix.cloud")
app.add_typer(node.app, name="node", help="Get node info on aleph.im & twentysix.cloud")
app.add_typer(about.app, name="about", help="Display the informations of Aleph CLI")

app.add_typer(node.app, name="node", help="Get node info on aleph.im network")
app.add_typer(domain.app, name="domain", help="Manage custom Domain (dns) on aleph.im")
app.add_typer(instance.app, name="instance", help="Manage instances (VMs) on aleph.im network")

if __name__ == "__main__":
app()
51 changes: 41 additions & 10 deletions src/aleph_client/commands/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
from aleph.sdk.utils import bytes_from_hex
from aleph_message.models import Chain
from rich.console import Console
from rich.panel import Panel
from rich.prompt import Prompt
from rich.table import Table
from rich.text import Text
from typer.colors import GREEN, RED

from aleph_client.commands import help_strings
Expand Down Expand Up @@ -233,7 +235,7 @@
if not message:
message = input_multiline()

coroutine = account.sign_raw(message.encode())
coroutine = account.sign_raw(str(message).encode())
signature = asyncio.run(coroutine)
typer.echo("\nSignature: " + signature.hex())

Expand All @@ -259,15 +261,44 @@
if response.status == 200:
balance_data = await response.json()
balance_data["available_amount"] = balance_data["balance"] - balance_data["locked_amount"]
typer.echo(
"\n"
+ f"Address: {balance_data['address']}\n"
+ f"Balance: {balance_data['balance']:.2f}".rstrip("0").rstrip(".")
+ "\n"
+ f" - Locked: {balance_data['locked_amount']:.2f}".rstrip("0").rstrip(".")
+ "\n"
+ f" - Available: {balance_data['available_amount']:.2f}".rstrip("0").rstrip(".")
+ "\n"

infos = [
Text.from_markup(f"Address: [bright_cyan]{balance_data['address']}[/bright_cyan]"),
Text.from_markup(
f"\nBalance: [bright_cyan]{balance_data['balance']:.2f}".rstrip("0").rstrip(".")
+ "[/bright_cyan]"
),
]
details = balance_data.get("details")
if details:
infos += [Text("\n ↳ Details")]

Check warning on line 274 in src/aleph_client/commands/account.py

View check run for this annotation

Codecov / codecov/patch

src/aleph_client/commands/account.py#L274

Added line #L274 was not covered by tests
for chain, chain_balance in details.items():
infos += [

Check warning on line 276 in src/aleph_client/commands/account.py

View check run for this annotation

Codecov / codecov/patch

src/aleph_client/commands/account.py#L276

Added line #L276 was not covered by tests
Text.from_markup(
f"\n {chain}: [orange3]{chain_balance:.2f}".rstrip("0").rstrip(".") + "[/orange3]"
)
]
available_color = "bright_cyan" if balance_data["available_amount"] >= 0 else "red"
infos += [
Text.from_markup(
f"\n - Locked: [bright_cyan]{balance_data['locked_amount']:.2f}".rstrip("0").rstrip(".")
+ "[/bright_cyan]"
),
Text.from_markup(
f"\n - Available: [{available_color}]{balance_data['available_amount']:.2f}".rstrip("0").rstrip(
"."
)
+ f"[/{available_color}]"
),
]
console.print(
Panel(
Text.assemble(*infos),
title="Account Infos",
border_style="bright_cyan",
expand=False,
title_align="left",
)
)
else:
typer.echo(f"Failed to retrieve balance for address {address}. Status code: {response.status}")
Expand Down
47 changes: 31 additions & 16 deletions src/aleph_client/commands/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient
from aleph.sdk.account import _load_account
from aleph.sdk.conf import settings
from aleph.sdk.types import AccountFromPrivateKey, StorageEnum
from aleph_message.models import ItemHash, StoreMessage
from aleph.sdk.types import AccountFromPrivateKey, StorageEnum, StoredContent
from aleph.sdk.utils import safe_getattr
from aleph_message.models import ItemHash, ItemType, MessageType, StoreMessage
from aleph_message.status import MessageStatus
from pydantic import BaseModel, Field
from rich import box
Expand Down Expand Up @@ -101,28 +102,42 @@
output_path: Path = typer.Option(Path("."), help="Output directory path"),
file_name: str = typer.Option(None, help="Output file name (without extension)"),
file_extension: str = typer.Option(None, help="Output file extension"),
only_info: bool = False,
verbose: bool = True,
debug: bool = False,
):
"""Download a file on aleph.im."""
) -> Optional[StoredContent]:
"""Download a file from aleph.im or display related infos."""

setup_logging(debug)

output_path.mkdir(parents=True, exist_ok=True)
if not only_info:
output_path.mkdir(parents=True, exist_ok=True)

file_name = file_name if file_name else hash
file_extension = file_extension if file_extension else ""
file_name = file_name if file_name else hash
file_extension = file_extension if file_extension else ""

output_file_path = output_path / f"{file_name}{file_extension}"
output_file_path = output_path / f"{file_name}{file_extension}"

async with AlephHttpClient(api_server=settings.API_HOST) as client:
logger.info(f"Downloading {hash} ...")
with open(output_file_path, "wb") as fd:
if not use_ipfs:
await client.download_file_to_buffer(hash, fd)
else:
await client.download_file_ipfs_to_buffer(hash, fd)
async with AlephHttpClient(api_server=settings.API_HOST) as client:
logger.info(f"Downloading {hash} ...")
with open(output_file_path, "wb") as fd:
if not use_ipfs:
await client.download_file_to_buffer(hash, fd)
else:
await client.download_file_ipfs_to_buffer(hash, fd)

Check warning on line 127 in src/aleph_client/commands/files.py

View check run for this annotation

Codecov / codecov/patch

src/aleph_client/commands/files.py#L127

Added line #L127 was not covered by tests

logger.debug("File downloaded successfully.")
logger.debug("File downloaded successfully.")
else:
async with AlephHttpClient(api_server=settings.API_HOST) as client:
content = await client.get_stored_content(hash)
if verbose:
typer.echo(
f"Filename: {content.filename}\nHash: {content.hash}\nURL: {content.url}"
if safe_getattr(content, "url")
else safe_getattr(content, "error")
)
return content
return None


@app.command()
Expand Down
27 changes: 16 additions & 11 deletions src/aleph_client/commands/help_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@
CHANNEL = "Aleph.im network channel where the message is or will be broadcasted"
PRIVATE_KEY = "Your private key. Cannot be used with --private-key-file"
PRIVATE_KEY_FILE = "Path to your private key file"
REF = "Checkout https://aleph-im.gitbook.io/aleph-js/api-resources-reference/posts"
REF = "Item hash of the message to update"
SIGNABLE_MESSAGE = "Message to sign"
CUSTOM_DOMAIN_TARGET_TYPES = "IPFS|PROGRAM|INSTANCE"
CUSTOM_DOMAIN_OWNER_ADDRESS = "Owner address, default current account"
CUSTOM_DOMAIN_NAME = "Domain name. ex: aleph.im"
CUSTOM_DOMAIN_ITEM_HASH = "Item hash"
SKIP_VOLUME = "Skip prompt to attach more volumes"
PERSISTENT_VOLUME = """Persistent volumes are allocated on the host machine and are not deleted when the VM is stopped.\n
Requires at least "name", "persistence", "mount" and "size_mib". For more info, see the docs: https://docs.aleph.im/computing/volumes/persistent/\n
Example: --persistent_volume name=data,persistence=host,size_mib=100,mount=/opt/data"""
EPHEMERAL_VOLUME = """Ephemeral volumes are allocated on the host machine when the VM is started and deleted when the VM is stopped.\n
Requires at least "name", "mount" and "size_mib".\n
Example: --ephemeral-volume name=temp,size_mib=100,mount=/tmp/data"""
IMMUTABLE_VOLUME = """Immutable volumes are pinned on the network and can be used by multiple VMs at the same time. They are read-only and useful for setting up libraries or other dependencies.\n
Requires at least "name", "ref" (message hash) and "mount" path. "use_latest" is True by default, to use the latest version of the volume, if it has been amended. See the docs for more info: https://docs.aleph.im/computing/volumes/immutable/\n
Example: --immutable-volume name=libs,ref=25a393222692c2f73489dc6710ae87605a96742ceef7b91de4d7ec34bb688d94,mount=/lib/python3.8/site-packages"""
PERSISTENT_VOLUME = "Persistent volumes are allocated on the host machine and are not deleted when the VM is stopped.\nRequires at least `name`, `persistence`, `mount` and `size_mib`. For more info, see the docs: https://docs.aleph.im/computing/volumes/persistent/\nExample: --persistent_volume name=data,persistence=host,size_mib=100,mount=/opt/data"
EPHEMERAL_VOLUME = "Ephemeral volumes are allocated on the host machine when the VM is started and deleted when the VM is stopped.\nRequires at least `name`, `mount` and `size_mib`.\nExample: --ephemeral-volume name=temp,size_mib=100,mount=/tmp/data"
IMMUTABLE_VOLUME = "Immutable volumes are pinned on the network and can be used by multiple VMs at the same time. They are read-only and useful for setting up libraries or other dependencies.\nRequires at least `name`, `ref` (message hash) and `mount` path. `use_latest` is True by default, to use the latest version of the volume, if it has been amended. See the docs for more info: https://docs.aleph.im/computing/volumes/immutable/\nExample: --immutable-volume name=libs,ref=25a3...8d94,mount=/lib/python3.11/site-packages"
SKIP_ENV_VAR = "Skip prompt to set environment variables"
ENVIRONMENT_VARIABLES = "Environment variables to pass. They will be public and visible in the message, so don't include secrets. Must be a comma separated list. Example: `KEY=value` or `KEY=value,KEY=value`"
ASK_FOR_CONFIRMATION = "Prompt user for confirmation"
IPFS_CATCH_ALL_PATH = "Choose a relative path to catch all unmatched route or a 404 error"
PAYMENT_TYPE = "Payment method, either holding tokens, NFTs, or Pay-As-You-Go via token streaming"
Expand Down Expand Up @@ -46,7 +42,7 @@
VM_ID = "Item hash of your VM. If provided, skip the instance creation, else create a new one"
VM_NOT_READY = "VM not initialized/started"
VM_SCHEDULED = "VM scheduled but not available yet"
VM_NOT_AVAILABLE_YET = "VM not available yet"
CRN_UNKNOWN = "Unknown"
CRN_PENDING = "Pending..."
ALLOCATION_AUTO = "Auto - Scheduler"
ALLOCATION_MANUAL = "Manual - Selection"
Expand All @@ -56,3 +52,12 @@
ADDRESS_CHAIN = "Chain for the address"
CREATE_REPLACE = "Overwrites private key file if it already exists"
CREATE_ACTIVE = "Loads the new private key after creation"
PROMPT_CRN_URL = "URL of the CRN (Compute node) on which the instance is running"
PROMPT_PROGRAM_CRN_URL = "URL of the CRN (Compute node) on which the program is running"
PROGRAM_PATH = "Path to your source code. Can be a directory, a .squashfs file or a .zip archive"
PROGRAM_ENTRYPOINT = "Your program entrypoint. Example: `main:app` for Python programs, else `run.sh` for a script containing your launch command"
PROGRAM_RUNTIME = "Hash of the runtime to use for your program. You can also create your own runtime and pin it. Currently defaults to `{runtime_id}` (Use `aleph program runtime-checker` to inspect it)"
PROGRAM_BETA = "If true, you will be prompted to add message subscriptions to your program"
PROGRAM_UPDATABLE = "Allow program updates. By default, only the source code can be modified without requiring redeployement (same item hash). When enabled (set to True), this option allows to update any other field. However, such modifications will require a program redeployment (new item hash)"
PROGRAM_KEEP_CODE = "Keep the source code intact instead of deleting it"
PROGRAM_KEEP_PREV = "Keep the previous program intact instead of deleting it"
Loading
Loading