Skip to content
This repository has been archived by the owner on Sep 22, 2023. It is now read-only.

refactor: Make CLI commands consistent #163

Merged
merged 49 commits into from Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5db4357
refactor: Make all cli.admin module names singular
achimnol Jun 22, 2021
e7dc5de
refactor: Reorganize admin command hierarchy
achimnol Jun 22, 2021
cced9f5
setup: Update backend.ai-cli
achimnol Jun 22, 2021
ac5dafa
docs: Add news fragment
achimnol Jun 22, 2021
28fef3f
wip: Make aliases true aliases
achimnol Jul 2, 2021
8ccea73
feat: Reorganize aliases of session subcommands
achimnol Jul 5, 2021
5053315
Merge branch 'main' into feature/revamp-cli-consistency
achimnol Jul 10, 2021
d864561
Merge branch 'main' into feature/revamp-cli-consistency
achimnol Jul 30, 2021
b8c263a
fix: Add type annos to session create commands
achimnol Jul 30, 2021
a8737ae
fix: Unify vfolder list / admin vfolder list command impl.
achimnol Jul 30, 2021
c195473
fix: Minor code cleanup
achimnol Jul 30, 2021
d67d84c
fix: Python 3.7 compatibility
achimnol Jul 30, 2021
6cc8a25
wip: Add client.cli.output subpackage to provide common interface for…
achimnol Aug 2, 2021
fe401b4
fix: remove support for servers with API ver older than v5.20191215
achimnol Aug 2, 2021
f770e6d
fix: Elaborate what to do in the legacy server warning
achimnol Aug 2, 2021
61f1467
feat: Now we have a generalized output framework for console and json
achimnol Aug 3, 2021
46cb500
fix: circular imports, add test cases
achimnol Aug 3, 2021
9ff1c8c
feat: Implement nested object formatting
achimnol Aug 3, 2021
b7b20c5
fix: fine tuning
achimnol Aug 3, 2021
0f919af
fix: fine tuning with multiline cell
achimnol Aug 3, 2021
c09b34d
fix: another fine tuning
achimnol Aug 3, 2021
df6629d
feat: Update GQL pagination query schema to match with manager
achimnol Aug 4, 2021
344335b
feat: Add print_list() output handler to support non-paginated lists
achimnol Aug 4, 2021
8bde48a
fix: Update scaling-group queries to use the new output framework
achimnol Aug 4, 2021
f16fe3c
fix: lint/type errors
achimnol Aug 4, 2021
53f30f2
docs: Update news fragment
achimnol Aug 4, 2021
2b4accb
maintenance: Remove Python 3.7 support and now require 3.8 or later.
achimnol Aug 4, 2021
71bc88b
fix: UserDict generic for Python 3.8
achimnol Aug 4, 2021
118df3e
docs: Update news fragment
achimnol Aug 4, 2021
955dbee
test: Remove spurious pytest warning
achimnol Aug 4, 2021
252af1a
fix: Some mistakes in cli.admin.vfolder
achimnol Aug 4, 2021
da82927
update setup.py for rich PR
Aug 15, 2021
6c6a5c1
fetch upstream
Aug 16, 2021
9c4465b
refactor admin/domain.py and func/domain.py
Aug 19, 2021
02f4e2c
add more fixes to domain refactor
Aug 19, 2021
369add2
refactor group
Aug 19, 2021
902fe2e
refactor inage
Aug 19, 2021
f87a4a5
refactor a bit of manager
Aug 19, 2021
4e3561f
bug fix in install_requires
Aug 19, 2021
d04edd7
fix image issue
Aug 20, 2021
2bb97f3
refactor resource_policy
Aug 20, 2021
0656421
fix lint errors
Aug 20, 2021
c525c86
Merge branch 'main' into feature/revamp-cli-consistency
achimnol Aug 26, 2021
1c47263
fix: Improve handling of JSON-string CLI args better
achimnol Aug 27, 2021
4d0926b
implement review comments: remove news file, modify setup.py, apply r…
Aug 31, 2021
130ad4d
implement review comments: remove news file, modify setup.py, apply r…
nokchalatte Aug 31, 2021
59be82c
Merge branch 'feature/revamp-cli-consistency' of https://github.com/n…
nokchalatte Aug 31, 2021
f1feb8a
feat: Rich formatting (#174)
nokchalatte Aug 31, 2021
87271cb
Add byte formatter for size-related inquiries (#179)
nokchalatte Aug 31, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/default.yml
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.7, 3.8, 3.9]
python-version: [3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
6 changes: 6 additions & 0 deletions changes/163.breaking
@@ -0,0 +1,6 @@
Multiple breaking changes related to pagination and CLI outputs:
- The CLI command hierarchy and arguments are revamped to be much more consistent with enumeration and item queries, and to support global options such as `--output=json` and `-y`/`--yes` flags. This is not backward-compatible due to parsing ambiguity.
- When invoking the functional APIs to retrieve the details and lists, you need to change your `fields` arguments to be a list of `FieldSpec` objects instead of strings. You may refer `ai.backend.client.output.fields` for predefined field definitions and individual functional API modules for their default list/detail field sets.
You may also define and pass your own `FieldSpec` and `OutputFormatter` objects as well.
- It requires the Backend.AI Manager API v5.20191215 (release 20.03) or later. If used with older managers, it will show a warning.
- It requires Python 3.8 or higher to run.
1 change: 1 addition & 0 deletions changes/174.feature
@@ -0,0 +1 @@
Utilize Rich as a formatting library for server announcements
1 change: 0 additions & 1 deletion setup.cfg
Expand Up @@ -10,7 +10,6 @@ exclude = .git, .cache, .idea, __pycache__, venv, build, dist, docs

[tool:pytest]
norecursedirs = venv virtualenv .git
timeout = 5
markers =
integration: Test cases that require real manager (and agents) to be running on http://localhost:8081.

Expand Down
8 changes: 3 additions & 5 deletions setup.py
Expand Up @@ -19,9 +19,9 @@
'multidict>=5.1.0',
'python-dateutil>=2.8.2',
'PyYAML~=5.4.1',
'rich~=10.5.0',
'tabulate>=0.8.9',
'tqdm>=4.61',
'typing-extensions>=3.10.0',
'yarl>=1.6.3',
'backend.ai-cli~=0.5.0.post1',
]
Expand All @@ -36,7 +36,6 @@
'pytest-mock',
'pytest-asyncio>=0.15.1',
'aioresponses>=0.7.2',
'asynctest>=0.13; python_version<"3.8"',
'codecov',
]
lint_requires = [
Expand Down Expand Up @@ -89,7 +88,6 @@ def read_src_version():
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Operating System :: POSIX',
Expand All @@ -101,7 +99,7 @@ def read_src_version():
],
package_dir={'': 'src'},
packages=find_namespace_packages(where='src', include='ai.backend.*'),
python_requires='>=3.7',
python_requires='>=3.8',
setup_requires=setup_requires,
install_requires=install_requires,
extras_require={
Expand All @@ -118,7 +116,7 @@ def read_src_version():
},
entry_points={
'backendai_cli_v10': [
'_ = ai.backend.client.cli:main',
'_ = ai.backend.client.cli.main:main',
]
},
)
53 changes: 9 additions & 44 deletions src/ai/backend/client/cli/__init__.py
@@ -1,44 +1,9 @@
import warnings

import click

from .. import __version__
from ..config import APIConfig, set_config
from ai.backend.cli.extensions import ExtendedCommandGroup


@click.group(
cls=ExtendedCommandGroup,
context_settings={
'help_option_names': ['-h', '--help'],
},
)
@click.option('--skip-sslcert-validation',
help='Skip SSL certificate validation for all API requests.',
is_flag=True)
@click.version_option(version=__version__)
def main(skip_sslcert_validation):
"""
Backend.AI command line interface.
"""
from .announcement import announce
config = APIConfig(
skip_sslcert_validation=skip_sslcert_validation,
announcement_handler=announce,
)
set_config(config)

from .pretty import show_warning
warnings.showwarning = show_warning


def _attach_command():
from . import admin, config, app, files, logs, manager, proxy, ps, run # noqa
from . import ssh # noqa
from . import vfolder # noqa
from . import session_template # noqa
from . import dotfile # noqa
from . import server_log # noqa


_attach_command()
from . import main # noqa
from . import config # noqa
from . import session # noqa
from . import session_template # noqa
from . import vfolder # noqa
from . import dotfile # noqa
from . import server_log # noqa
from . import admin # noqa
from . import app, logs, proxy, run # noqa
2 changes: 1 addition & 1 deletion src/ai/backend/client/cli/__main__.py
@@ -1,4 +1,4 @@
from . import main
from .main import main


main()
45 changes: 21 additions & 24 deletions src/ai/backend/client/cli/admin/__init__.py
@@ -1,30 +1,27 @@
from .. import main
from ..main import main


@main.group()
def admin():
'''
Provides the admin API access.
'''
"""
Administrative command set
"""


def _attach_command():
from . import ( # noqa
agents,
domains,
etcd,
groups,
images,
keypairs,
license,
resources,
resource_policies,
scaling_groups,
sessions,
storage,
users,
vfolders,
)


_attach_command()
from . import ( # noqa
agent,
domain,
etcd,
group,
image,
keypair,
manager,
license,
resource,
resource_policy,
scaling_group,
session,
storage,
user,
vfolder,
)
192 changes: 192 additions & 0 deletions src/ai/backend/client/cli/admin/agent.py
@@ -0,0 +1,192 @@
from __future__ import annotations

import sys

import click

from ai.backend.client.session import Session
from ai.backend.client.output.fields import agent_fields
from ..types import CLIContext
from . import admin


@admin.group()
def agent():
"""
Agent administration commands.
"""


@agent.command()
@click.pass_obj
@click.argument('agent_id')
def info(ctx: CLIContext, agent_id: str) -> None:
"""
Show the information about the given agent.
"""
fields = [
agent_fields['id'],
agent_fields['status'],
agent_fields['region'],
agent_fields['first_contact'],
agent_fields['cpu_cur_pct'],
agent_fields['available_slots'],
agent_fields['occupied_slots'],
agent_fields['hardware_metadata'],
agent_fields['live_stat'],
]
with Session() as session:
try:
item = session.Agent.detail(agent_id=agent_id, fields=fields)
ctx.output.print_item(item, fields)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)


@agent.command()
@click.pass_obj
@click.option('-s', '--status', type=str, default='ALIVE',
help='Filter agents by the given status.')
@click.option('--scaling-group', '--sgroup', type=str, default=None,
help='Filter agents by the scaling group.')
@click.option('--filter', 'filter_', default=None,
help='Set the query filter expression.')
@click.option('--order', default=None,
help='Set the query ordering expression.')
@click.option('--offset', default=0,
help='The index of the current page start for pagination.')
@click.option('--limit', default=None,
help='The page size for pagination.')
def list(
ctx: CLIContext,
status: str,
scaling_group: str | None,
filter_: str | None,
order: str | None,
offset: int,
limit: int | None,
) -> None:
"""
List agents.
(super-admin privilege required)
"""
fields = [
agent_fields['id'],
agent_fields['status'],
agent_fields['scaling_group'],
agent_fields['region'],
agent_fields['first_contact'],
agent_fields['cpu_cur_pct'],
agent_fields['mem_cur_bytes'],
agent_fields['available_slots'],
agent_fields['occupied_slots'],
]
try:
with Session() as session:
fetch_func = lambda pg_offset, pg_size: session.Agent.paginated_list(
status,
scaling_group,
fields=fields,
page_offset=pg_offset,
page_size=pg_size,
filter=filter_,
order=order,
)
ctx.output.print_paginated_list(
fetch_func,
initial_page_offset=offset,
page_size=limit,
)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)


@admin.group()
def watcher():
"""
Agent watcher commands.

Available only for Linux-based agents.
"""


@watcher.command()
@click.pass_obj
@click.argument('agent', type=str)
def status(ctx: CLIContext, agent: str) -> None:
"""
Get agent and watcher status.
(superadmin privilege required)

\b
AGENT: Agent id.
"""
with Session() as session:
try:
status = session.AgentWatcher.get_status(agent)
print(status)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)


@watcher.command()
@click.pass_obj
@click.argument('agent', type=str)
def agent_start(ctx: CLIContext, agent: str) -> None:
"""
Start agent service.
(superadmin privilege required)

\b
AGENT: Agent id.
"""
with Session() as session:
try:
status = session.AgentWatcher.agent_start(agent)
print(status)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)


@watcher.command()
@click.pass_obj
@click.argument('agent', type=str)
def agent_stop(ctx: CLIContext, agent: str) -> None:
"""
Stop agent service.
(superadmin privilege required)

\b
AGENT: Agent id.
"""
with Session() as session:
try:
status = session.AgentWatcher.agent_stop(agent)
print(status)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)


@watcher.command()
@click.pass_obj
@click.argument('agent', type=str)
def agent_restart(ctx: CLIContext, agent: str) -> None:
"""
Restart agent service.
(superadmin privilege required)

\b
AGENT: Agent id.
"""
with Session() as session:
try:
status = session.AgentWatcher.agent_restart(agent)
print(status)
except Exception as e:
ctx.output.print_error(e)
sys.exit(1)