From 56e0493b0059c29008242597a02542a6e4ded145 Mon Sep 17 00:00:00 2001 From: aliel Date: Fri, 29 Sep 2023 10:22:28 +0200 Subject: [PATCH 01/13] WIP add custom domain support --- src/aleph_client/__main__.py | 19 ++---- src/aleph_client/commands/domain.py | 71 +++++++++++++++++++++++ src/aleph_client/commands/help_strings.py | 4 ++ src/aleph_client/commands/loader.py | 41 +++++++++++++ 4 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 src/aleph_client/commands/domain.py create mode 100644 src/aleph_client/commands/loader.py diff --git a/src/aleph_client/__main__.py b/src/aleph_client/__main__.py index 7a29bdeb..d2370d5d 100644 --- a/src/aleph_client/__main__.py +++ b/src/aleph_client/__main__.py @@ -5,23 +5,11 @@ import typer from aleph_client.utils import AsyncTyper -from .commands import about, account, aggregate, files, message, program, node +from .commands import about, account, aggregate, domain, files, message, program, node app = AsyncTyper() - -@app.callback() -def common( - ctx: typer.Context, - version: bool = typer.Option( - None, "--version", callback=about.get_version, help="Show Aleph CLI Version" - ), - v: bool = typer.Option( - None, "-v", callback=about.get_version, help="Show Aleph CLI Version" - ), -): - pass - +app = typer.Typer() app.add_typer(account.app, name="account", help="Manage account") app.add_typer( @@ -41,6 +29,9 @@ def common( 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="Add and link a Custom Domain on aleph.im VM" +) if __name__ == "__main__": app() diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py new file mode 100644 index 00000000..c1a5ad2c --- /dev/null +++ b/src/aleph_client/commands/domain.py @@ -0,0 +1,71 @@ +import asyncio +import logging +from pathlib import Path +from time import sleep +from typing import Optional + +import typer +from aleph.sdk.account import _load_account +from aleph.sdk.conf import settings as sdk_settings +from aleph.sdk.domain import AlephDNS +from aleph.sdk.exceptions import DomainConfigurationError +from aleph_client.commands import help_strings +from aleph_client.commands.loader import Loader +from pydantic import BaseModel +from typer.colors import GREEN, RED + +app = typer.Typer() + +@app.command() +def add( + private_key: Optional[str] = typer.Option( + sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + domain_name: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), + item_hash: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), + target_type: Optional[str] = typer.Option("program", help=help_strings.CUSTOM_DOMAIN_TARGET_TYPE), + owner: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_OWNER_ADDRESS) +): + """Add and link a Custom Domain.""" + account: AccountFromPrivateKey = _load_account(private_key, private_key_file) + print("<<<<<<") + + loop = asyncio.new_event_loop() + aleph_dns = AlephDNS(loop) + + dns_rules = aleph_dns.get_required_dns_rules(domain_name, target_type) + + typer.secho(dns_rules[0]["info"], fg=typer.colors.YELLOW) + input("press any key to continue") + + max_retries = 10 + while max_retries >= 0: + domain_check = check_configuration(aleph_dns, domain_name, target_type) + if domain_check is True: + break + sleep(5) + max_retries -= 1 + #if max_retries == 8: + typer.secho(domain_check[0], fg=RED) + + if domain_check is not True: + typer.echo(domain_check[0]) +# print("res:", domain_check) + # check if item hash exists + #message = + + # check domain and follow steps + + + +def check_configuration(aleph_dns, domain_name, target_type): + try: + with Loader("Domain configuration check"): + sleep(2) + domain_status = asyncio.run(aleph_dns.check_domain(domain_name, target_type)) + return True + except DomainConfigurationError as error: + return error.args[0] diff --git a/src/aleph_client/commands/help_strings.py b/src/aleph_client/commands/help_strings.py index f8617806..6d911db4 100644 --- a/src/aleph_client/commands/help_strings.py +++ b/src/aleph_client/commands/help_strings.py @@ -4,3 +4,7 @@ PRIVATE_KEY_FILE = "Path to your private key file" REF = "Checkout https://aleph-im.gitbook.io/aleph-js/api-resources-reference/posts" SIGNABLE_MESSAGE = "Message to sign" +CUSTOM_DOMAIN_TARGET_TYPE = "Target type: IPFS|PROGRAM" +CUSTOM_DOMAIN_OWNER_ADDRESS = "Owner address, default current account" +CUSTOM_DOMAIN_NAME = "Domain name. ex: aleph.im" +CUSTOM_DOMAIN_ITEM_HASH = "Item hash" diff --git a/src/aleph_client/commands/loader.py b/src/aleph_client/commands/loader.py new file mode 100644 index 00000000..e1a39281 --- /dev/null +++ b/src/aleph_client/commands/loader.py @@ -0,0 +1,41 @@ +import shutil +import threading +from itertools import cycle +from time import sleep + + +class Loader: + def __init__(self, desc="Loading...", end="Done!"): + self.desc = desc + self.end = end + self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"] + self.done = False + self._thread = None + + def start(self): + if not self._thread: + self._thread = threading.Thread(target=self._animate, daemon=True) + self._thread.start() + return self + + def _animate(self): + for c in cycle(self.steps): + if self.done: + break + print(f"\r{self.desc} {c}", flush=True, end="") + sleep(0.1) + + def stop(self): + self.done = True + cols = shutil.get_terminal_size().columns + print("\r" + " " * cols, end="", flush=True) + print(f"\r{self.end}", flush=True) + if self._thread: + self._thread.join() + self._thread = None + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_value, traceback): + self.stop From 1fbe21fbd53329d4863b9bab2f38d8f14a74dcad Mon Sep 17 00:00:00 2001 From: aliel Date: Mon, 2 Oct 2023 10:09:45 +0200 Subject: [PATCH 02/13] WIP --- src/aleph_client/commands/domain.py | 53 +++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index c1a5ad2c..7d86e49f 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -1,5 +1,6 @@ import asyncio import logging +from functools import wraps from pathlib import Path from time import sleep from typing import Optional @@ -7,13 +8,22 @@ import typer from aleph.sdk.account import _load_account from aleph.sdk.conf import settings as sdk_settings -from aleph.sdk.domain import AlephDNS +from aleph.sdk.domain import DomainValidator from aleph.sdk.exceptions import DomainConfigurationError from aleph_client.commands import help_strings from aleph_client.commands.loader import Loader from pydantic import BaseModel from typer.colors import GREEN, RED + +def coro(f): + @wraps(f) + def wrapper(*args, **kwargs): + return asyncio.run(f(*args, **kwargs)) + + return wrapper + + app = typer.Typer() @app.command() @@ -33,8 +43,8 @@ def add( account: AccountFromPrivateKey = _load_account(private_key, private_key_file) print("<<<<<<") - loop = asyncio.new_event_loop() - aleph_dns = AlephDNS(loop) + #loop = asyncio.new_event_loop() + domain_validator = DomainValidator() dns_rules = aleph_dns.get_required_dns_rules(domain_name, target_type) @@ -69,3 +79,40 @@ def check_configuration(aleph_dns, domain_name, target_type): return True except DomainConfigurationError as error: return error.args[0] + +@app.command() +@coro +async def info( + domain_name: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) +): + domain_validator = DomainValidator() + target = None + try: + res = await domain_validator.resolver.query(domain_name, "CNAME") + cname_value = res.cname + if sdk_settings.DNS_IPFS_DOMAIN in cname_value: + target = "ipfs" + elif sdk_settings.DNS_PROGRAM_DOMAIN in cname_value: + target = "program" + elif sdk_settings.DNS_INSTANCE_DOMAIN in cname_value: + target = "instance" + except Exception: + typer.echo(f"Domain: {domain_name} not configured") + raise typer.Exit() + + print(target) + if target is not None: + try: + status = await domain_validator.check_domain(domain_name, target) + print(status) + if target == "ipfs": + pass + elif target == "program": + pass + if target == "instance": + ipv6 = domain_validator.get_ipv6_addresses(domain_name) + typer.echo() + except Exception: + typer.Exit() + else: + raise typer.Exit() From 4edf6f3106e92a6a874f897e6999c702039fc46a Mon Sep 17 00:00:00 2001 From: aliel Date: Mon, 9 Oct 2023 12:33:19 +0200 Subject: [PATCH 03/13] Support custom domain management --- src/aleph_client/__main__.py | 2 +- src/aleph_client/commands/domain.py | 227 +++++++++++++++++----- src/aleph_client/commands/help_strings.py | 2 +- src/aleph_client/commands/loader.py | 41 ---- 4 files changed, 179 insertions(+), 93 deletions(-) delete mode 100644 src/aleph_client/commands/loader.py diff --git a/src/aleph_client/__main__.py b/src/aleph_client/__main__.py index d2370d5d..edd90840 100644 --- a/src/aleph_client/__main__.py +++ b/src/aleph_client/__main__.py @@ -30,7 +30,7 @@ app.add_typer(node.app, name="node", help="Get node info on aleph.im network") app.add_typer( - domain.app, name="domain", help="Add and link a Custom Domain on aleph.im VM" + domain.app, name="domain", help="Manage custom Domain (dns) on aleph.im" ) if __name__ == "__main__": diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index 7d86e49f..e3507eef 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -1,94 +1,221 @@ -import asyncio import logging -from functools import wraps from pathlib import Path from time import sleep -from typing import Optional +from typing import Optional, cast import typer from aleph.sdk.account import _load_account +from aleph.sdk.client import AlephClient, AuthenticatedAlephClient from aleph.sdk.conf import settings as sdk_settings -from aleph.sdk.domain import DomainValidator +from aleph.sdk.domain import DomainValidator, Hostname, TargetType, hostname_from_url from aleph.sdk.exceptions import DomainConfigurationError +from aleph.sdk.types import AccountFromPrivateKey from aleph_client.commands import help_strings -from aleph_client.commands.loader import Loader +from aleph_client.commands.utils import coro +from aleph_message.models import AggregateMessage, MessageType from pydantic import BaseModel +from rich.console import Console +from rich.prompt import Confirm, Prompt +from rich.table import Table from typer.colors import GREEN, RED +app = typer.Typer() -def coro(f): - @wraps(f) - def wrapper(*args, **kwargs): - return asyncio.run(f(*args, **kwargs)) - return wrapper +async def get_aggregate_domain_info(account, fqdn): + async with AlephClient(api_server=sdk_settings.API_HOST) as client: + aggregates = await client.get_messages( + addresses=[str(account.get_address())], + message_type=MessageType.aggregate, + page=1, + pagination=1000 + ) + + for message in aggregates.messages: + aggregate = cast(AggregateMessage, message) + if aggregate.content.key == "domains": + for domain, info in aggregate.content.content.items(): + print("===", domain, fqdn) + if domain == fqdn: + return { + "timestamp": aggregate.content.time, + "info": info + } + return None + + +async def check_domain_records(fqdn, target, owner): + domain_validator = DomainValidator() + try: + status = await domain_validator.check_domain(fqdn, target, owner) + except DomainConfigurationError as msg: + help_, err, status = msg.args[0] + return status + + +async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None): + domain_info = await get_aggregate_domain_info(account, fqdn) + console = Console() + + while not item_hash: + item_hash = Prompt.ask("Enter Hash reference of the resource to attach") + + table = Table(title=f"Attach resource to: {fqdn}") + table.add_column("Current resource", justify="right", style="red", no_wrap=True) + table.add_column("New resource", justify="right", style="green", no_wrap=True) + table.add_column("Resource type", style="magenta") + + current_resource = None + resource_type = None + print(domain_info) + if domain_info is not None: + current_resource = domain_info["info"]["message_id"] + resource_type = TargetType(domain_info["info"]["type"].lower()) + else: + domain_validator = DomainValidator() + resource_type = await domain_validator.get_target_type(fqdn) + + table.add_row(f"{current_resource[:16]}...{current_resource[-16:]}", f"{item_hash[:16]}...{item_hash[-16:]}", resource_type) + + console.print(table) + + if Confirm.ask("Continue"): + """Create aggregate message""" + + async with AuthenticatedAlephClient( + account=account, api_server=sdk_settings.API_HOST + ) as client: + aggregate_content = { + fqdn: { + "message_id": item_hash, + "type": resource_type, + # console page compatibility + "programType": resource_type + } + } + + aggregate_message, message_status = await client.create_aggregate( + key="domains", + content=aggregate_content, + channel="ALEPH-CLOUDSOLUTIONS" + ) + + console.log(f"[green bold]Aleph message created!") + console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") -app = typer.Typer() @app.command() -def add( +@coro +async def add( private_key: Optional[str] = typer.Option( sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY ), private_key_file: Optional[Path] = typer.Option( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), - domain_name: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), - item_hash: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), - target_type: Optional[str] = typer.Option("program", help=help_strings.CUSTOM_DOMAIN_TARGET_TYPE), + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME + ), + target: Optional[TargetType] = typer.Option(None, + help=help_strings.CUSTOM_DOMAIN_TARGET_TYPES), + item_hash: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), owner: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_OWNER_ADDRESS) ): """Add and link a Custom Domain.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) - print("<<<<<<") - #loop = asyncio.new_event_loop() + console = Console() domain_validator = DomainValidator() + fqdn = hostname_from_url(fqdn) - dns_rules = aleph_dns.get_required_dns_rules(domain_name, target_type) + if target is None: + target = Prompt.ask("Select a resource type", choices={i.name.lower(): i.value.lower() for i in TargetType}) - typer.secho(dns_rules[0]["info"], fg=typer.colors.YELLOW) - input("press any key to continue") + table = Table(title=f"Required DNS entries for: {fqdn}") - max_retries = 10 - while max_retries >= 0: - domain_check = check_configuration(aleph_dns, domain_name, target_type) - if domain_check is True: - break - sleep(5) - max_retries -= 1 - #if max_retries == 8: - typer.secho(domain_check[0], fg=RED) + table.add_column("RECORD ID", justify="right", style="cyan", no_wrap=True) + table.add_column("DNS TYPE", justify="right", style="cyan", no_wrap=True) + table.add_column("DNS NAME", style="magenta") + table.add_column("DNS VALUE", justify="right", style="green") - if domain_check is not True: - typer.echo(domain_check[0]) -# print("res:", domain_check) - # check if item hash exists - #message = + owner = account.get_address() + dns_rules = domain_validator.get_required_dns_rules(fqdn, target, owner) + for rule_id, rule in enumerate(dns_rules): + table.add_row(str(rule_id), rule["dns"]["type"], rule["dns"]["name"], rule["dns"]["value"]) - # check domain and follow steps + console.print(table) + msg_status = "[bold green]Detecting dns..." + with console.status(msg_status) as status: + max_retries = 5 + while dns_rules: + rule = dns_rules[0] + """Get rules check status""" + checks = await check_domain_records(fqdn, target, owner) + completed_rules = [] + for index, rule in enumerate(dns_rules): + if checks[rule["rule_name"]] is True: + """Pass configured rules""" + completed_rules.append(rule) + console.log(f"record: {index} [bold green] OK") -def check_configuration(aleph_dns, domain_name, target_type): - try: - with Loader("Domain configuration check"): - sleep(2) - domain_status = asyncio.run(aleph_dns.check_domain(domain_name, target_type)) - return True - except DomainConfigurationError as error: - return error.args[0] + for _rule in completed_rules: + dns_rules.remove(_rule) + completed_rules = [] + + if dns_rules: + rule = dns_rules[0] + console.log(f"[green]{rule['info']}") + status.update(f"{msg_status} [bold red]{rule['on_error']}") + + max_retries -= 1 + sleep(10) + + if max_retries == 0: + status.stop() + continue_ = Confirm.ask("Continue?") + if continue_: + status.start() + max_retries = 5 + else: + raise typer.Exit() + + """Attach option""" + if Confirm.ask(f"Attach ressource to [bold green]{fqdn}"): + await attach_resource(account, fqdn, item_hash) + + raise typer.Exit() + + +@app.command() +@coro +async def attach( + private_key: Optional[str] = typer.Option( + sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME + ), + item_hash: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), +): + """Attach resource to a Custom Domain.""" + account: AccountFromPrivateKey = _load_account(private_key, private_key_file) + + await attach_resource(account, fqdn, item_hash) + raise typer.Exit() @app.command() @coro async def info( - domain_name: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) ): domain_validator = DomainValidator() target = None try: - res = await domain_validator.resolver.query(domain_name, "CNAME") + res = await domain_validator.resolver.query(fqdn, "CNAME") cname_value = res.cname if sdk_settings.DNS_IPFS_DOMAIN in cname_value: target = "ipfs" @@ -97,22 +224,22 @@ async def info( elif sdk_settings.DNS_INSTANCE_DOMAIN in cname_value: target = "instance" except Exception: - typer.echo(f"Domain: {domain_name} not configured") + typer.echo(f"Domain: {fqdn} not configured") raise typer.Exit() print(target) if target is not None: try: - status = await domain_validator.check_domain(domain_name, target) + status = await domain_validator.check_domain(fqdn, target) print(status) if target == "ipfs": pass elif target == "program": pass if target == "instance": - ipv6 = domain_validator.get_ipv6_addresses(domain_name) - typer.echo() + ipv6 = await domain_validator.get_ipv6_addresses(fqdn) + typer.echo(ipv6) except Exception: - typer.Exit() + raise typer.Exit() else: raise typer.Exit() diff --git a/src/aleph_client/commands/help_strings.py b/src/aleph_client/commands/help_strings.py index 6d911db4..a4c14c40 100644 --- a/src/aleph_client/commands/help_strings.py +++ b/src/aleph_client/commands/help_strings.py @@ -4,7 +4,7 @@ PRIVATE_KEY_FILE = "Path to your private key file" REF = "Checkout https://aleph-im.gitbook.io/aleph-js/api-resources-reference/posts" SIGNABLE_MESSAGE = "Message to sign" -CUSTOM_DOMAIN_TARGET_TYPE = "Target type: IPFS|PROGRAM" +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" diff --git a/src/aleph_client/commands/loader.py b/src/aleph_client/commands/loader.py deleted file mode 100644 index e1a39281..00000000 --- a/src/aleph_client/commands/loader.py +++ /dev/null @@ -1,41 +0,0 @@ -import shutil -import threading -from itertools import cycle -from time import sleep - - -class Loader: - def __init__(self, desc="Loading...", end="Done!"): - self.desc = desc - self.end = end - self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"] - self.done = False - self._thread = None - - def start(self): - if not self._thread: - self._thread = threading.Thread(target=self._animate, daemon=True) - self._thread.start() - return self - - def _animate(self): - for c in cycle(self.steps): - if self.done: - break - print(f"\r{self.desc} {c}", flush=True, end="") - sleep(0.1) - - def stop(self): - self.done = True - cols = shutil.get_terminal_size().columns - print("\r" + " " * cols, end="", flush=True) - print(f"\r{self.end}", flush=True) - if self._thread: - self._thread.join() - self._thread = None - - def __enter__(self): - self.start() - - def __exit__(self, exc_type, exc_value, traceback): - self.stop From bf244d1ca4000c702c723e7635ca295760719e91 Mon Sep 17 00:00:00 2001 From: aliel Date: Mon, 9 Oct 2023 13:39:13 +0200 Subject: [PATCH 04/13] add aiodns for tests --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 55d26ee2..7225fcff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,6 +73,7 @@ testing = types-requests aleph-pytezos==0.1.0 types-setuptools + aiodns mqtt = aiomqtt certifi From 0536249b5e24d57965b59ee63734ef4cb8fe61ef Mon Sep 17 00:00:00 2001 From: aliel Date: Mon, 9 Oct 2023 14:50:15 +0200 Subject: [PATCH 05/13] fix types --- src/aleph_client/commands/domain.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index e3507eef..2c7ae564 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -1,4 +1,3 @@ -import logging from pathlib import Path from time import sleep from typing import Optional, cast @@ -13,11 +12,9 @@ from aleph_client.commands import help_strings from aleph_client.commands.utils import coro from aleph_message.models import AggregateMessage, MessageType -from pydantic import BaseModel from rich.console import Console from rich.prompt import Confirm, Prompt from rich.table import Table -from typer.colors import GREEN, RED app = typer.Typer() @@ -66,8 +63,9 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h table.add_column("New resource", justify="right", style="green", no_wrap=True) table.add_column("Resource type", style="magenta") - current_resource = None - resource_type = None + current_resource = "" + resource_type: TargetType + print(domain_info) if domain_info is not None: current_resource = domain_info["info"]["message_id"] From b44b97c20a3dabb0719c220e6089917d8d3655f3 Mon Sep 17 00:00:00 2001 From: aliel Date: Thu, 12 Oct 2023 11:36:27 +0200 Subject: [PATCH 06/13] add domain detach and info command --- src/aleph_client/commands/domain.py | 127 +++++++++++++++++----------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index 2c7ae564..1268782d 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -51,11 +51,11 @@ async def check_domain_records(fqdn, target, owner): return status -async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None): +async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None, detach: Optional[bool] = False): domain_info = await get_aggregate_domain_info(account, fqdn) console = Console() - while not item_hash: + while not item_hash and detach is False: item_hash = Prompt.ask("Enter Hash reference of the resource to attach") table = Table(title=f"Attach resource to: {fqdn}") @@ -63,16 +63,16 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h table.add_column("New resource", justify="right", style="green", no_wrap=True) table.add_column("Resource type", style="magenta") - current_resource = "" - resource_type: TargetType + current_resource = "null" + domain_validator = DomainValidator() + + """ + Detect target type on the fly to be able to switch to another type + """ + resource_type = await domain_validator.get_target_type(fqdn) - print(domain_info) - if domain_info is not None: + if domain_info is not None and domain_info.get("info"): current_resource = domain_info["info"]["message_id"] - resource_type = TargetType(domain_info["info"]["type"].lower()) - else: - domain_validator = DomainValidator() - resource_type = await domain_validator.get_target_type(fqdn) table.add_row(f"{current_resource[:16]}...{current_resource[-16:]}", f"{item_hash[:16]}...{item_hash[-16:]}", resource_type) @@ -84,14 +84,19 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h async with AuthenticatedAlephClient( account=account, api_server=sdk_settings.API_HOST ) as client: - aggregate_content = { - fqdn: { - "message_id": item_hash, - "type": resource_type, - # console page compatibility - "programType": resource_type + if detach: + aggregate_content = { + fqdn: None + } + else: + aggregate_content = { + fqdn: { + "message_id": item_hash, + "type": resource_type, + # console page compatibility + "programType": resource_type + } } - } aggregate_message, message_status = await client.create_aggregate( key="domains", @@ -99,7 +104,7 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h channel="ALEPH-CLOUDSOLUTIONS" ) - console.log(f"[green bold]Aleph message created!") + console.log("[green bold]Aleph message created!") console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") @@ -195,8 +200,7 @@ async def attach( private_key_file: Optional[Path] = typer.Option( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), - fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME - ), + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), item_hash: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), ): """Attach resource to a Custom Domain.""" @@ -205,39 +209,66 @@ async def attach( await attach_resource(account, fqdn, item_hash) raise typer.Exit() +@app.command() +@coro +async def detach( + private_key: Optional[str] = typer.Option( + sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) +): + """Unlink Custom Domain.""" + account: AccountFromPrivateKey = _load_account(private_key, private_key_file) + + await attach_resource(account, fqdn, None, True) + raise typer.Exit() + + @app.command() @coro async def info( - fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) + private_key: Optional[str] = typer.Option( + sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY + ), + private_key_file: Optional[Path] = typer.Option( + sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE + ), + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) ): + """Show Custom Domain Details.""" + account: AccountFromPrivateKey = _load_account(private_key, private_key_file) + + console = Console() domain_validator = DomainValidator() - target = None + + domain_info = await get_aggregate_domain_info(account, fqdn) + if domain_info is None or domain_info.get("info") is None: + console.log(f"Domain: {fqdn} not configured") + raise typer.Exit() + + table = Table(title=f"Domain info: {fqdn}") + table.add_column("Resource type", justify="right", style="cyan", no_wrap=True) + table.add_column("Attached resource", justify="right", style="cyan", no_wrap=True) + table.add_column("Final resource", justify="right", style="cyan", no_wrap=True) + + resource_type = TargetType(domain_info["info"]["type"]) + final_resource = "Unknown" + try: - res = await domain_validator.resolver.query(fqdn, "CNAME") - cname_value = res.cname - if sdk_settings.DNS_IPFS_DOMAIN in cname_value: - target = "ipfs" - elif sdk_settings.DNS_PROGRAM_DOMAIN in cname_value: - target = "program" - elif sdk_settings.DNS_INSTANCE_DOMAIN in cname_value: - target = "instance" + if resource_type == TargetType.IPFS: + final_resource = "" + elif resource_type == TargetType.PROGRAM: + final_resource = domain_info["info"]["message_id"] + if resource_type == TargetType.INSTANCE: + ips = await domain_validator.get_ipv6_addresses(fqdn) + final_resource = ",".join(ips) except Exception: - typer.echo(f"Domain: {fqdn} not configured") - raise typer.Exit() + pass - print(target) - if target is not None: - try: - status = await domain_validator.check_domain(fqdn, target) - print(status) - if target == "ipfs": - pass - elif target == "program": - pass - if target == "instance": - ipv6 = await domain_validator.get_ipv6_addresses(fqdn) - typer.echo(ipv6) - except Exception: - raise typer.Exit() - else: - raise typer.Exit() + table.add_row(resource_type, domain_info["info"]["message_id"], final_resource) + + console.print(table) + raise typer.Exit() From 6a5e953f9265ab4fd1518553a05b81296b9d549a Mon Sep 17 00:00:00 2001 From: aliel Date: Thu, 12 Oct 2023 11:51:14 +0200 Subject: [PATCH 07/13] fix mypy, display correct messages --- src/aleph_client/commands/domain.py | 66 ++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index 1268782d..2f07e2d1 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -51,11 +51,50 @@ async def check_domain_records(fqdn, target, owner): return status -async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None, detach: Optional[bool] = False): +async def detach_resource(account: AccountFromPrivateKey, fqdn: Hostname): domain_info = await get_aggregate_domain_info(account, fqdn) console = Console() - while not item_hash and detach is False: + table = Table(title=f"Detach resource of: {fqdn}") + table.add_column("Current resource", justify="right", style="red", no_wrap=True) + table.add_column("New resource", justify="right", style="green", no_wrap=True) + table.add_column("Resource type", style="magenta") + + domain_validator = DomainValidator() + + if domain_info is not None and domain_info.get("info"): + current_resource = domain_info["info"]["message_id"] + + resource_type = await domain_validator.get_target_type(fqdn) + table.add_row(f"{current_resource[:16]}...{current_resource[-16:]}", "", resource_type) + + console.print(table) + + if Confirm.ask("Continue"): + """Update aggregate message""" + + async with AuthenticatedAlephClient( + account=account, api_server=sdk_settings.API_HOST + ) as client: + aggregate_content = { + fqdn: None + } + + aggregate_message, message_status = await client.create_aggregate( + key="domains", + content=aggregate_content, + channel="ALEPH-CLOUDSOLUTIONS" + ) + + console.log("[green bold]Resource detached!") + console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") + + +async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None): + domain_info = await get_aggregate_domain_info(account, fqdn) + console = Console() + + while not item_hash: item_hash = Prompt.ask("Enter Hash reference of the resource to attach") table = Table(title=f"Attach resource to: {fqdn}") @@ -84,19 +123,14 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h async with AuthenticatedAlephClient( account=account, api_server=sdk_settings.API_HOST ) as client: - if detach: - aggregate_content = { - fqdn: None - } - else: - aggregate_content = { - fqdn: { - "message_id": item_hash, - "type": resource_type, - # console page compatibility - "programType": resource_type - } + aggregate_content = { + fqdn: { + "message_id": item_hash, + "type": resource_type, + # console page compatibility + "programType": resource_type } + } aggregate_message, message_status = await client.create_aggregate( key="domains", @@ -104,7 +138,7 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h channel="ALEPH-CLOUDSOLUTIONS" ) - console.log("[green bold]Aleph message created!") + console.log("[green bold]Resource attached!") console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") @@ -223,7 +257,7 @@ async def detach( """Unlink Custom Domain.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) - await attach_resource(account, fqdn, None, True) + await detach_resource(account, fqdn) raise typer.Exit() From ef63c94296f090593b116802a3514d10bb3fac40 Mon Sep 17 00:00:00 2001 From: aliel Date: Thu, 12 Oct 2023 14:45:27 +0200 Subject: [PATCH 08/13] fix typo --- src/aleph_client/commands/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index 2f07e2d1..0aa7729c 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -286,7 +286,7 @@ async def info( table = Table(title=f"Domain info: {fqdn}") table.add_column("Resource type", justify="right", style="cyan", no_wrap=True) table.add_column("Attached resource", justify="right", style="cyan", no_wrap=True) - table.add_column("Final resource", justify="right", style="cyan", no_wrap=True) + table.add_column("Target resource", justify="right", style="cyan", no_wrap=True) resource_type = TargetType(domain_info["info"]["type"]) final_resource = "Unknown" From 9f533827b58a5858798f2f06ce735cd43478d14a Mon Sep 17 00:00:00 2001 From: aliel Date: Thu, 12 Oct 2023 14:49:14 +0200 Subject: [PATCH 09/13] clean code --- src/aleph_client/commands/domain.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index 0aa7729c..e76fa423 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -32,7 +32,6 @@ async def get_aggregate_domain_info(account, fqdn): aggregate = cast(AggregateMessage, message) if aggregate.content.key == "domains": for domain, info in aggregate.content.content.items(): - print("===", domain, fqdn) if domain == fqdn: return { "timestamp": aggregate.content.time, From 94e95895b8a046fac69eb8e7e3a087b8c8f5b23a Mon Sep 17 00:00:00 2001 From: mhh Date: Tue, 21 Nov 2023 12:04:54 +0100 Subject: [PATCH 10/13] reformat; fix types and update code to reflect changes in SDK --- src/aleph_client/__main__.py | 7 +- src/aleph_client/commands/aggregate.py | 4 - src/aleph_client/commands/domain.py | 157 ++++++++++++++----------- src/aleph_client/commands/message.py | 15 ++- src/aleph_client/commands/program.py | 20 ++-- src/aleph_client/utils.py | 2 +- tests/unit/test_init.py | 2 +- 7 files changed, 111 insertions(+), 96 deletions(-) diff --git a/src/aleph_client/__main__.py b/src/aleph_client/__main__.py index edd90840..cc4f7344 100644 --- a/src/aleph_client/__main__.py +++ b/src/aleph_client/__main__.py @@ -5,7 +5,8 @@ import typer from aleph_client.utils import AsyncTyper -from .commands import about, account, aggregate, domain, files, message, program, node + +from .commands import about, account, aggregate, domain, files, message, node, program app = AsyncTyper() @@ -29,9 +30,7 @@ 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(domain.app, name="domain", help="Manage custom Domain (dns) on aleph.im") if __name__ == "__main__": app() diff --git a/src/aleph_client/commands/aggregate.py b/src/aleph_client/commands/aggregate.py index f15374d5..efe149e8 100644 --- a/src/aleph_client/commands/aggregate.py +++ b/src/aleph_client/commands/aggregate.py @@ -17,8 +17,6 @@ app = AsyncTyper() -from aleph_client.commands.utils import colorful_message_json - @app.command() async def forget( @@ -131,5 +129,3 @@ async def get( typer.echo(json.dumps(aggregates, indent=4, default=extended_json_encoder)) else: typer.echo("No aggregates found for the given key and content.") - - diff --git a/src/aleph_client/commands/domain.py b/src/aleph_client/commands/domain.py index e76fa423..e899ac2e 100644 --- a/src/aleph_client/commands/domain.py +++ b/src/aleph_client/commands/domain.py @@ -4,28 +4,38 @@ import typer from aleph.sdk.account import _load_account -from aleph.sdk.client import AlephClient, AuthenticatedAlephClient +from aleph.sdk.client import AlephHttpClient, AuthenticatedAlephHttpClient from aleph.sdk.conf import settings as sdk_settings -from aleph.sdk.domain import DomainValidator, Hostname, TargetType, hostname_from_url +from aleph.sdk.domain import ( + DomainValidator, + Hostname, + TargetType, + get_target_type, + hostname_from_url, +) from aleph.sdk.exceptions import DomainConfigurationError +from aleph.sdk.query.filters import MessageFilter from aleph.sdk.types import AccountFromPrivateKey -from aleph_client.commands import help_strings -from aleph_client.commands.utils import coro from aleph_message.models import AggregateMessage, MessageType from rich.console import Console from rich.prompt import Confirm, Prompt from rich.table import Table -app = typer.Typer() +from aleph_client.commands import help_strings +from aleph_client.utils import AsyncTyper + +app = AsyncTyper() async def get_aggregate_domain_info(account, fqdn): - async with AlephClient(api_server=sdk_settings.API_HOST) as client: + async with AlephHttpClient(api_server=sdk_settings.API_HOST) as client: aggregates = await client.get_messages( - addresses=[str(account.get_address())], - message_type=MessageType.aggregate, + message_filter=MessageFilter( + addresses=[str(account.get_address())], + message_types=[MessageType.aggregate], + ), page=1, - pagination=1000 + page_size=1000, ) for message in aggregates.messages: @@ -33,10 +43,7 @@ async def get_aggregate_domain_info(account, fqdn): if aggregate.content.key == "domains": for domain, info in aggregate.content.content.items(): if domain == fqdn: - return { - "timestamp": aggregate.content.time, - "info": info - } + return {"timestamp": aggregate.content.time, "info": info} return None @@ -59,37 +66,39 @@ async def detach_resource(account: AccountFromPrivateKey, fqdn: Hostname): table.add_column("New resource", justify="right", style="green", no_wrap=True) table.add_column("Resource type", style="magenta") - domain_validator = DomainValidator() - if domain_info is not None and domain_info.get("info"): current_resource = domain_info["info"]["message_id"] + else: + current_resource = "null" - resource_type = await domain_validator.get_target_type(fqdn) - table.add_row(f"{current_resource[:16]}...{current_resource[-16:]}", "", resource_type) + resource_type = await get_target_type(fqdn) + table.add_row( + f"{current_resource[:16]}...{current_resource[-16:]}", "", resource_type + ) console.print(table) if Confirm.ask("Continue"): """Update aggregate message""" - async with AuthenticatedAlephClient( - account=account, api_server=sdk_settings.API_HOST + async with AuthenticatedAlephHttpClient( + account=account, api_server=sdk_settings.API_HOST ) as client: - aggregate_content = { - fqdn: None - } + aggregate_content = {fqdn: None} aggregate_message, message_status = await client.create_aggregate( - key="domains", - content=aggregate_content, - channel="ALEPH-CLOUDSOLUTIONS" + key="domains", content=aggregate_content, channel="ALEPH-CLOUDSOLUTIONS" ) console.log("[green bold]Resource detached!") - console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") + console.log( + f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}" + ) -async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None): +async def attach_resource( + account: AccountFromPrivateKey, fqdn: Hostname, item_hash: Optional[str] = None +): domain_info = await get_aggregate_domain_info(account, fqdn) console = Console() @@ -101,48 +110,50 @@ async def attach_resource(account: AccountFromPrivateKey, fqdn: Hostname, item_h table.add_column("New resource", justify="right", style="green", no_wrap=True) table.add_column("Resource type", style="magenta") - current_resource = "null" - domain_validator = DomainValidator() - """ Detect target type on the fly to be able to switch to another type """ - resource_type = await domain_validator.get_target_type(fqdn) + resource_type = await get_target_type(fqdn) if domain_info is not None and domain_info.get("info"): current_resource = domain_info["info"]["message_id"] + else: + current_resource = "null" - table.add_row(f"{current_resource[:16]}...{current_resource[-16:]}", f"{item_hash[:16]}...{item_hash[-16:]}", resource_type) + table.add_row( + f"{current_resource[:16]}...{current_resource[-16:]}", + f"{item_hash[:16]}...{item_hash[-16:]}", + resource_type, + ) console.print(table) if Confirm.ask("Continue"): """Create aggregate message""" - async with AuthenticatedAlephClient( - account=account, api_server=sdk_settings.API_HOST + async with AuthenticatedAlephHttpClient( + account=account, api_server=sdk_settings.API_HOST ) as client: aggregate_content = { fqdn: { "message_id": item_hash, "type": resource_type, # console page compatibility - "programType": resource_type + "programType": resource_type, } } aggregate_message, message_status = await client.create_aggregate( - key="domains", - content=aggregate_content, - channel="ALEPH-CLOUDSOLUTIONS" + key="domains", content=aggregate_content, channel="ALEPH-CLOUDSOLUTIONS" ) console.log("[green bold]Resource attached!") - console.log(f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}") + console.log( + f"Visualise on: https://explorer.aleph.im/address/ETH/{account.get_address()}/message/AGGREGATE/{aggregate_message.item_hash}" + ) @app.command() -@coro async def add( private_key: Optional[str] = typer.Option( sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY @@ -150,12 +161,16 @@ async def add( private_key_file: Optional[Path] = typer.Option( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), - fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME - ), - target: Optional[TargetType] = typer.Option(None, - help=help_strings.CUSTOM_DOMAIN_TARGET_TYPES), - item_hash: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), - owner: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_OWNER_ADDRESS) + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), + target: Optional[TargetType] = typer.Option( + None, help=help_strings.CUSTOM_DOMAIN_TARGET_TYPES + ), + item_hash: Optional[str] = typer.Option( + None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH + ), + owner: Optional[str] = typer.Option( + None, help=help_strings.CUSTOM_DOMAIN_OWNER_ADDRESS + ), ): """Add and link a Custom Domain.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) @@ -165,7 +180,10 @@ async def add( fqdn = hostname_from_url(fqdn) if target is None: - target = Prompt.ask("Select a resource type", choices={i.name.lower(): i.value.lower() for i in TargetType}) + target = Prompt.ask( + "Select a target resource type", + choices=[TargetType.IPFS, TargetType.PROGRAM, TargetType.INSTANCE], + ) table = Table(title=f"Required DNS entries for: {fqdn}") @@ -174,10 +192,12 @@ async def add( table.add_column("DNS NAME", style="magenta") table.add_column("DNS VALUE", justify="right", style="green") - owner = account.get_address() + owner = owner or account.get_address() dns_rules = domain_validator.get_required_dns_rules(fqdn, target, owner) for rule_id, rule in enumerate(dns_rules): - table.add_row(str(rule_id), rule["dns"]["type"], rule["dns"]["name"], rule["dns"]["value"]) + table.add_row( + str(rule_id), rule.dns["type"], rule.dns["name"], rule.dns["value"] + ) console.print(table) @@ -191,7 +211,7 @@ async def add( checks = await check_domain_records(fqdn, target, owner) completed_rules = [] for index, rule in enumerate(dns_rules): - if checks[rule["rule_name"]] is True: + if checks[rule.name] is True: """Pass configured rules""" completed_rules.append(rule) console.log(f"record: {index} [bold green] OK") @@ -202,8 +222,8 @@ async def add( if dns_rules: rule = dns_rules[0] - console.log(f"[green]{rule['info']}") - status.update(f"{msg_status} [bold red]{rule['on_error']}") + console.log(f"[green]{rule.info}") + status.update(f"{msg_status} [bold red]{rule.on_error}") max_retries -= 1 sleep(10) @@ -225,7 +245,6 @@ async def add( @app.command() -@coro async def attach( private_key: Optional[str] = typer.Option( sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY @@ -234,16 +253,18 @@ async def attach( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), - item_hash: Optional[str] = typer.Option(None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH), + item_hash: Optional[str] = typer.Option( + None, help=help_strings.CUSTOM_DOMAIN_ITEM_HASH + ), ): """Attach resource to a Custom Domain.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) - await attach_resource(account, fqdn, item_hash) + await attach_resource(account, Hostname(fqdn), item_hash) raise typer.Exit() + @app.command() -@coro async def detach( private_key: Optional[str] = typer.Option( sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY @@ -251,17 +272,16 @@ async def detach( private_key_file: Optional[Path] = typer.Option( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), - fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), ): """Unlink Custom Domain.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) - await detach_resource(account, fqdn) + await detach_resource(account, Hostname(fqdn)) raise typer.Exit() @app.command() -@coro async def info( private_key: Optional[str] = typer.Option( sdk_settings.PRIVATE_KEY_STRING, help=help_strings.PRIVATE_KEY @@ -269,7 +289,7 @@ async def info( private_key_file: Optional[Path] = typer.Option( sdk_settings.PRIVATE_KEY_FILE, help=help_strings.PRIVATE_KEY_FILE ), - fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME) + fqdn: str = typer.Argument(..., help=help_strings.CUSTOM_DOMAIN_NAME), ): """Show Custom Domain Details.""" account: AccountFromPrivateKey = _load_account(private_key, private_key_file) @@ -290,16 +310,13 @@ async def info( resource_type = TargetType(domain_info["info"]["type"]) final_resource = "Unknown" - try: - if resource_type == TargetType.IPFS: - final_resource = "" - elif resource_type == TargetType.PROGRAM: - final_resource = domain_info["info"]["message_id"] - if resource_type == TargetType.INSTANCE: - ips = await domain_validator.get_ipv6_addresses(fqdn) - final_resource = ",".join(ips) - except Exception: - pass + if resource_type == TargetType.IPFS: + final_resource = "" + elif resource_type == TargetType.PROGRAM: + final_resource = domain_info["info"]["message_id"] + if resource_type == TargetType.INSTANCE: + ips = await domain_validator.get_ipv6_addresses(Hostname(fqdn)) + final_resource = ",".join([str(ip) for ip in ips]) table.add_row(resource_type, domain_info["info"]["message_id"], final_resource) diff --git a/src/aleph_client/commands/message.py b/src/aleph_client/commands/message.py index 617963a4..385cdeab 100644 --- a/src/aleph_client/commands/message.py +++ b/src/aleph_client/commands/message.py @@ -11,8 +11,8 @@ from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient from aleph.sdk.account import _load_account from aleph.sdk.conf import settings as sdk_settings -from aleph.sdk.query.responses import MessagesResponse from aleph.sdk.query.filters import MessageFilter +from aleph.sdk.query.responses import MessagesResponse from aleph.sdk.types import AccountFromPrivateKey, StorageEnum from aleph.sdk.utils import extended_json_encoder from aleph_message.models import AlephMessage, ItemHash, MessageType, ProgramMessage @@ -66,9 +66,11 @@ async def find( parsed_channels = channels.split(",") if channels else None parsed_chains = chains.split(",") if chains else None - message_types = [ - MessageType(message_type) for message_type in parsed_message_types - ] if parsed_message_types else None + message_types = ( + [MessageType(message_type) for message_type in parsed_message_types] + if parsed_message_types + else None + ) start_time = str_to_datetime(start_date) end_time = str_to_datetime(end_date) @@ -259,8 +261,9 @@ async def watch( original: AlephMessage = await client.get_message(item_hash=ref) async for message in client.watch_messages( message_filter=MessageFilter( - refs=[ref], addresses=[original.content.address] - )): + refs=[ref], addresses=[original.content.address] + ) + ): typer.echo(f"{message.json(indent=indent)}") diff --git a/src/aleph_client/commands/program.py b/src/aleph_client/commands/program.py index d7650427..68336860 100644 --- a/src/aleph_client/commands/program.py +++ b/src/aleph_client/commands/program.py @@ -28,7 +28,7 @@ yes_no_input, ) from aleph_client.conf import settings -from aleph_client.utils import create_archive, AsyncTyper +from aleph_client.utils import AsyncTyper, create_archive logger = logging.getLogger(__name__) app = AsyncTyper() @@ -67,24 +67,24 @@ def upload( persistent: bool = False, persistent_volume: Optional[List[str]] = typer.Option( None, - help="""Takes 3 parameters - A persistent volume is allocated on the host machine at any time - eg: Use , to seperate the parameters and no spaces + help="""Takes 3 parameters + A persistent volume is allocated on the host machine at any time + eg: Use , to seperate the parameters and no spaces --persistent_volume persistence=host,name=my-volume,size=100 ./my-program main:app """, ), ephemeral_volume: Optional[List[str]] = typer.Option( None, - help="""Takes 1 parameter Only - Ephemeral volumes can move and be removed by the host,Garbage collected basically, when the VM isn't running - eg: Use , to seperate the parameters and no spaces + help="""Takes 1 parameter Only + Ephemeral volumes can move and be removed by the host,Garbage collected basically, when the VM isn't running + eg: Use , to seperate the parameters and no spaces --ephemeral-volume size_mib=100 ./my-program main:app """, ), immutable_volume: Optional[List[str]] = typer.Option( None, - help="""Takes 3 parameters - Immutable volume is one whose contents do not change - eg: Use , to seperate the parameters and no spaces + help="""Takes 3 parameters + Immutable volume is one whose contents do not change + eg: Use , to seperate the parameters and no spaces --immutable-volume ref=25a393222692c2f73489dc6710ae87605a96742ceef7b91de4d7ec34bb688d94,use_latest=true,mount=/mnt/volume ./my-program main:app """, ), diff --git a/src/aleph_client/utils.py b/src/aleph_client/utils.py index 21dbc044..5d367f7c 100644 --- a/src/aleph_client/utils.py +++ b/src/aleph_client/utils.py @@ -2,7 +2,7 @@ import inspect import logging import os -from functools import wraps, partial +from functools import partial, wraps from pathlib import Path from shutil import make_archive from typing import Tuple, Type diff --git a/tests/unit/test_init.py b/tests/unit/test_init.py index 28561a1d..8422fb81 100644 --- a/tests/unit/test_init.py +++ b/tests/unit/test_init.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ != "" \ No newline at end of file + assert __version__ != "" From 2e7ba862a7510ec34a6f4a5ceaf2115a6e83ae61 Mon Sep 17 00:00:00 2001 From: mhh Date: Tue, 21 Nov 2023 12:09:38 +0100 Subject: [PATCH 11/13] add missing dependency --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 7225fcff..46468000 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,6 +42,7 @@ install_requires = python-magic pygments rich + aiodns # The usage of test_requires is discouraged, see `Dependency Management` docs # tests_require = pytest; pytest-cov # Require a specific Python version, e.g. Python 2.7 or >= 3.4 From d2a5c25a856b76d8c2f7261098a4a3ef54bb476d Mon Sep 17 00:00:00 2001 From: mhh Date: Tue, 21 Nov 2023 12:13:02 +0100 Subject: [PATCH 12/13] fix double assignment --- src/aleph_client/__main__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/aleph_client/__main__.py b/src/aleph_client/__main__.py index cc4f7344..aa11dcab 100644 --- a/src/aleph_client/__main__.py +++ b/src/aleph_client/__main__.py @@ -10,8 +10,6 @@ app = AsyncTyper() -app = typer.Typer() - app.add_typer(account.app, name="account", help="Manage account") app.add_typer( aggregate.app, name="aggregate", help="Manage aggregate messages on aleph.im" From 237283ee3dce1f394d75c2cecfb6ee1a4df99321 Mon Sep 17 00:00:00 2001 From: Mike Hukiewitz <70762838+MHHukiewitz@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:22:38 +0100 Subject: [PATCH 13/13] Update setup.cfg --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 46468000..9ff3d051 100644 --- a/setup.cfg +++ b/setup.cfg @@ -74,7 +74,6 @@ testing = types-requests aleph-pytezos==0.1.0 types-setuptools - aiodns mqtt = aiomqtt certifi