Skip to content

[CLI] New CLI tool for node operators #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion deployment/docker-build/pyaleph.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ RUN /opt/venv/bin/pip install --no-cache-dir -r /opt/build/requirements.txt
RUN rm /opt/build/requirements.txt

# === Install the CCN itself ===
COPY deployment/migrations /opt/pyaleph/migrations
COPY setup.py /opt/pyaleph/
COPY src /opt/pyaleph/src
# Git data is used to determine the version of the CCN
Expand Down
164 changes: 0 additions & 164 deletions deployment/migrations/config_updater.py

This file was deleted.

3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ install_requires =
sentry-sdk==1.5.11
setproctitle==1.2.2
substrate-interface==1.1.7
typer==0.4.1
ujson==5.1.0 # required by aiocache
urllib3==1.26.8
uvloop==0.16.0
Expand Down Expand Up @@ -96,6 +97,7 @@ testing =
pytest-aiohttp
pytest-asyncio
pytest-mock
types-pyyaml
types-requests
types-setuptools
nuls2 =
Expand All @@ -111,6 +113,7 @@ docs =
# Add here console scripts like:
console_scripts =
pyaleph = aleph.commands:run
ccn_cli = aleph.ccn_cli.main:app
# For example:
# console_scripts =
# fibonacci = pyaleph.skeleton:run
Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions src/aleph/ccn_cli/cli_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
Global configuration object for the CLI. Use the `get_cli_config()` method
to access and modify the configuration.
"""

from dataclasses import dataclass
from pathlib import Path


@dataclass
class CliConfig:
config_file_path: Path
key_dir: Path
verbose: bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default ?

Empty file.
46 changes: 46 additions & 0 deletions src/aleph/ccn_cli/commands/keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import typer

from aleph.ccn_cli.cli_config import CliConfig
from typing import cast
from aleph.ccn_cli.services.keys import generate_keypair, save_keys

keys_ns = typer.Typer()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This naming does not seem clear to me. The habit in Typer is app = typer.Typer(), so I would base the naming on that.

Suggested change
keys_ns = typer.Typer()
app = typer.Typer()



@keys_ns.command()
def generate(ctx: typer.Context):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace generate with the simpler create term ?

Suggested change
def generate(ctx: typer.Context):
def create(ctx: typer.Context):

"""
Generates a new set of private/public keys for the Core Channel Node.
The keys will be created in the key directory. You can modify the destination
by using the --key-dir option.
"""
cli_config = cast(CliConfig, ctx.obj)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This casting is not obvious. Would it be worth adding a comment to explain it ?

print(cli_config)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this print ?


typer.echo(f"Generating a key pair in {cli_config.key_dir.absolute()}...")
key_pair = generate_keypair()
save_keys(key_pair, str(cli_config.key_dir))
typer.echo("Done.")


@keys_ns.command()
def show(ctx: typer.Context):
"""
Prints the private key of the node.
"""
cli_config = cast(CliConfig, ctx.obj)

key_path = cli_config.key_dir / "node-secret.key"
if not key_path.exists():
typer.echo(
f"'{key_path.absolute()}' does not exist. Did you run 'keys generate'?",
err=True,
)
raise typer.Exit(code=1)

if not key_path.is_file():
typer.echo(f"'{key_path}' is not a file.", err=True)
raise typer.Exit(code=1)
Comment on lines +34 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if not key_path.exists():
typer.echo(
f"'{key_path.absolute()}' does not exist. Did you run 'keys generate'?",
err=True,
)
raise typer.Exit(code=1)
if not key_path.is_file():
typer.echo(f"'{key_path}' is not a file.", err=True)
raise typer.Exit(code=1)
if not key_path.is_file():
if key_path.exists():
typer.echo(f"'{key_path}' is not a file.", err=True)
else:
typer.echo(
f"'{key_path.absolute()}' does not exist. Did you run 'keys generate'?",
err=True,
)
raise typer.Exit(code=1)


with key_path.open() as f:
typer.echo(f.read())
87 changes: 87 additions & 0 deletions src/aleph/ccn_cli/commands/migrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import asyncio
from pathlib import Path
from traceback import format_exc
from typing import Optional
from typing import cast

import typer

from aleph.ccn_cli.cli_config import CliConfig
from .migration_runner import run_migrations

migrations_ns = typer.Typer()


FILTER_SCRIPTS_HELP = (
"A filter for migration scripts. If specified, only the files "
"matching the provided glob expression will be run."
)

KEY_FILE_HELP = (
"Path to the private key file, if any. "
"Only used to upgrade the key to the latest format."
)


def run_migration_command(
cli_config: CliConfig,
command: str,
filter_scripts: Optional[str],
key_file: Optional[Path],
):
try:
asyncio.run(
run_migrations(
cli_config=cli_config,
command=command,
filter_scripts=filter_scripts,
key_file=key_file,
)
)
except Exception as e:
typer.echo(f"{command} failed: {e}.", err=True)
if cli_config.verbose:
typer.echo(format_exc())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to only display the exception in verbose mode ? I am wondering if it would not make it easier that any user can copy paste the traceback of an error without running the command again 🤔

raise typer.Exit(code=1)


@migrations_ns.command()
def upgrade(
ctx: typer.Context,
filter_scripts: Optional[str] = typer.Option(
None,
help=FILTER_SCRIPTS_HELP,
),
key_file: Optional[Path] = typer.Option(
None,
help=KEY_FILE_HELP,
),
):
cli_config = cast(CliConfig, ctx.obj)
run_migration_command(
cli_config=cli_config,
command="upgrade",
filter_scripts=filter_scripts,
key_file=key_file,
)


@migrations_ns.command()
def downgrade(
ctx: typer.Context,
filter_scripts: Optional[str] = typer.Option(
None,
help=FILTER_SCRIPTS_HELP,
),
key_file: Optional[Path] = typer.Option(
None,
help=KEY_FILE_HELP,
),
):
cli_config = cast(CliConfig, ctx.obj)
run_migration_command(
cli_config=cli_config,
command="downgrade",
filter_scripts=filter_scripts,
key_file=key_file,
)
Loading