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

Commit 95f9342

Browse files
achimnolChrysan Angela Piarso
authored andcommitted
refactor: Make CLI commands consistent (#163)
* refactor: Make all cli.admin module names singular * refactor: Reorganize admin command hierarchy - Move individual "entity" and "entities" commands into "entity info" and "entity list" commands. - No longer duplicate arguments and options - Just "generate" the command functions multiple times and place it under different Click groups. - Avoid circular imports by making client.cli.main as a module when doing so. * wip: Add `client.cli.output` subpkg to provide common interface for output - All GraphQL/output fields are now defined as FieldSpec. - All CLI/functional API implementations refer the central FieldSet defined in the `ai.backend.client.output.fields` module. - All FieldSpec instances have their own formatters. * setup: Update backend.ai-cli * fix: remove support for servers with API ver older than v5.20191215 * maintenance: Remove Python 3.7 support and now require 3.8 or later. * fix: Improve handling of JSON-string CLI args better * Overrided the CLA check due to git client misconfiguration to specify author emails Co-authored-by: Chrysan Angela Piarso <[email protected]>
1 parent d5afaf9 commit 95f9342

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+3511
-2466
lines changed

.github/workflows/default.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: [3.7, 3.8, 3.9]
11+
python-version: [3.8, 3.9]
1212
steps:
1313
- uses: actions/checkout@v2
1414
- name: Set up Python ${{ matrix.python-version }}
@@ -73,7 +73,7 @@ jobs:
7373
strategy:
7474
matrix:
7575
os: [ubuntu-latest, macos-latest, windows-latest]
76-
python-version: [3.7, 3.8, 3.9]
76+
python-version: [3.8, 3.9]
7777
steps:
7878
- uses: actions/checkout@v2
7979
- name: Set up Python ${{ matrix.python-version }}

changes/163.breaking

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Multiple breaking changes related to pagination and CLI outputs:
2+
- 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.
3+
- 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.
4+
You may also define and pass your own `FieldSpec` and `OutputFormatter` objects as well.
5+
- It requires the Backend.AI Manager API v5.20191215 (release 20.03) or later. If used with older managers, it will show a warning.
6+
- It requires Python 3.8 or higher to run.

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ exclude = .git, .cache, .idea, __pycache__, venv, build, dist, docs
1010

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

setup.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
'rich~=10.5.0',
2323
'tabulate>=0.8.9',
2424
'tqdm>=4.61',
25-
'typing-extensions>=3.10.0',
2625
'yarl>=1.6.3',
2726
'backend.ai-cli~=0.5.0.post1',
2827
]
@@ -37,7 +36,6 @@
3736
'pytest-mock',
3837
'pytest-asyncio>=0.15.1',
3938
'aioresponses>=0.7.2',
40-
'asynctest>=0.13; python_version<"3.8"',
4139
'codecov',
4240
]
4341
lint_requires = [
@@ -90,7 +88,6 @@ def read_src_version():
9088
'Intended Audience :: Developers',
9189
'Programming Language :: Python',
9290
'Programming Language :: Python :: 3',
93-
'Programming Language :: Python :: 3.7',
9491
'Programming Language :: Python :: 3.8',
9592
'Programming Language :: Python :: 3.9',
9693
'Operating System :: POSIX',
@@ -102,7 +99,7 @@ def read_src_version():
10299
],
103100
package_dir={'': 'src'},
104101
packages=find_namespace_packages(where='src', include='ai.backend.*'),
105-
python_requires='>=3.7',
102+
python_requires='>=3.8',
106103
setup_requires=setup_requires,
107104
install_requires=install_requires,
108105
extras_require={
@@ -119,7 +116,7 @@ def read_src_version():
119116
},
120117
entry_points={
121118
'backendai_cli_v10': [
122-
'_ = ai.backend.client.cli:main',
119+
'_ = ai.backend.client.cli.main:main',
123120
]
124121
},
125122
)

src/ai/backend/client/cli/__init__.py

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,9 @@
1-
import warnings
2-
3-
import click
4-
5-
from .. import __version__
6-
from ..config import APIConfig, set_config
7-
from ai.backend.cli.extensions import ExtendedCommandGroup
8-
9-
10-
@click.group(
11-
cls=ExtendedCommandGroup,
12-
context_settings={
13-
'help_option_names': ['-h', '--help'],
14-
},
15-
)
16-
@click.option('--skip-sslcert-validation',
17-
help='Skip SSL certificate validation for all API requests.',
18-
is_flag=True)
19-
@click.version_option(version=__version__)
20-
def main(skip_sslcert_validation):
21-
"""
22-
Backend.AI command line interface.
23-
"""
24-
from .announcement import announce
25-
config = APIConfig(
26-
skip_sslcert_validation=skip_sslcert_validation,
27-
announcement_handler=announce,
28-
)
29-
set_config(config)
30-
31-
from .pretty import show_warning
32-
warnings.showwarning = show_warning
33-
34-
35-
def _attach_command():
36-
from . import admin, config, app, files, logs, manager, proxy, ps, run # noqa
37-
from . import ssh # noqa
38-
from . import vfolder # noqa
39-
from . import session_template # noqa
40-
from . import dotfile # noqa
41-
from . import server_log # noqa
42-
43-
44-
_attach_command()
1+
from . import main # noqa
2+
from . import config # noqa
3+
from . import session # noqa
4+
from . import session_template # noqa
5+
from . import vfolder # noqa
6+
from . import dotfile # noqa
7+
from . import server_log # noqa
8+
from . import admin # noqa
9+
from . import app, logs, proxy, run # noqa

src/ai/backend/client/cli/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from . import main
1+
from .main import main
22

33

44
main()
Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
1-
from .. import main
1+
from ..main import main
22

33

44
@main.group()
55
def admin():
6-
'''
7-
Provides the admin API access.
8-
'''
6+
"""
7+
Administrative command set
8+
"""
99

1010

11-
def _attach_command():
12-
from . import ( # noqa
13-
agents,
14-
domains,
15-
etcd,
16-
groups,
17-
images,
18-
keypairs,
19-
license,
20-
resources,
21-
resource_policies,
22-
scaling_groups,
23-
sessions,
24-
storage,
25-
users,
26-
vfolders,
27-
)
28-
29-
30-
_attach_command()
11+
from . import ( # noqa
12+
agent,
13+
domain,
14+
etcd,
15+
group,
16+
image,
17+
keypair,
18+
manager,
19+
license,
20+
resource,
21+
resource_policy,
22+
scaling_group,
23+
session,
24+
storage,
25+
user,
26+
vfolder,
27+
)
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from __future__ import annotations
2+
3+
import sys
4+
5+
import click
6+
7+
from ai.backend.client.session import Session
8+
from ai.backend.client.output.fields import agent_fields
9+
from ..types import CLIContext
10+
from . import admin
11+
12+
13+
@admin.group()
14+
def agent():
15+
"""
16+
Agent administration commands.
17+
"""
18+
19+
20+
@agent.command()
21+
@click.pass_obj
22+
@click.argument('agent_id')
23+
def info(ctx: CLIContext, agent_id: str) -> None:
24+
"""
25+
Show the information about the given agent.
26+
"""
27+
fields = [
28+
agent_fields['id'],
29+
agent_fields['status'],
30+
agent_fields['region'],
31+
agent_fields['first_contact'],
32+
agent_fields['cpu_cur_pct'],
33+
agent_fields['available_slots'],
34+
agent_fields['occupied_slots'],
35+
agent_fields['hardware_metadata'],
36+
agent_fields['live_stat'],
37+
]
38+
with Session() as session:
39+
try:
40+
item = session.Agent.detail(agent_id=agent_id, fields=fields)
41+
ctx.output.print_item(item, fields)
42+
except Exception as e:
43+
ctx.output.print_error(e)
44+
sys.exit(1)
45+
46+
47+
@agent.command()
48+
@click.pass_obj
49+
@click.option('-s', '--status', type=str, default='ALIVE',
50+
help='Filter agents by the given status.')
51+
@click.option('--scaling-group', '--sgroup', type=str, default=None,
52+
help='Filter agents by the scaling group.')
53+
@click.option('--filter', 'filter_', default=None,
54+
help='Set the query filter expression.')
55+
@click.option('--order', default=None,
56+
help='Set the query ordering expression.')
57+
@click.option('--offset', default=0,
58+
help='The index of the current page start for pagination.')
59+
@click.option('--limit', default=None,
60+
help='The page size for pagination.')
61+
def list(
62+
ctx: CLIContext,
63+
status: str,
64+
scaling_group: str | None,
65+
filter_: str | None,
66+
order: str | None,
67+
offset: int,
68+
limit: int | None,
69+
) -> None:
70+
"""
71+
List agents.
72+
(super-admin privilege required)
73+
"""
74+
fields = [
75+
agent_fields['id'],
76+
agent_fields['status'],
77+
agent_fields['scaling_group'],
78+
agent_fields['region'],
79+
agent_fields['first_contact'],
80+
agent_fields['cpu_cur_pct'],
81+
agent_fields['mem_cur_bytes'],
82+
agent_fields['available_slots'],
83+
agent_fields['occupied_slots'],
84+
]
85+
try:
86+
with Session() as session:
87+
fetch_func = lambda pg_offset, pg_size: session.Agent.paginated_list(
88+
status,
89+
scaling_group,
90+
fields=fields,
91+
page_offset=pg_offset,
92+
page_size=pg_size,
93+
filter=filter_,
94+
order=order,
95+
)
96+
ctx.output.print_paginated_list(
97+
fetch_func,
98+
initial_page_offset=offset,
99+
page_size=limit,
100+
)
101+
except Exception as e:
102+
ctx.output.print_error(e)
103+
sys.exit(1)
104+
105+
106+
@admin.group()
107+
def watcher():
108+
"""
109+
Agent watcher commands.
110+
111+
Available only for Linux-based agents.
112+
"""
113+
114+
115+
@watcher.command()
116+
@click.pass_obj
117+
@click.argument('agent', type=str)
118+
def status(ctx: CLIContext, agent: str) -> None:
119+
"""
120+
Get agent and watcher status.
121+
(superadmin privilege required)
122+
123+
\b
124+
AGENT: Agent id.
125+
"""
126+
with Session() as session:
127+
try:
128+
status = session.AgentWatcher.get_status(agent)
129+
print(status)
130+
except Exception as e:
131+
ctx.output.print_error(e)
132+
sys.exit(1)
133+
134+
135+
@watcher.command()
136+
@click.pass_obj
137+
@click.argument('agent', type=str)
138+
def agent_start(ctx: CLIContext, agent: str) -> None:
139+
"""
140+
Start agent service.
141+
(superadmin privilege required)
142+
143+
\b
144+
AGENT: Agent id.
145+
"""
146+
with Session() as session:
147+
try:
148+
status = session.AgentWatcher.agent_start(agent)
149+
print(status)
150+
except Exception as e:
151+
ctx.output.print_error(e)
152+
sys.exit(1)
153+
154+
155+
@watcher.command()
156+
@click.pass_obj
157+
@click.argument('agent', type=str)
158+
def agent_stop(ctx: CLIContext, agent: str) -> None:
159+
"""
160+
Stop agent service.
161+
(superadmin privilege required)
162+
163+
\b
164+
AGENT: Agent id.
165+
"""
166+
with Session() as session:
167+
try:
168+
status = session.AgentWatcher.agent_stop(agent)
169+
print(status)
170+
except Exception as e:
171+
ctx.output.print_error(e)
172+
sys.exit(1)
173+
174+
175+
@watcher.command()
176+
@click.pass_obj
177+
@click.argument('agent', type=str)
178+
def agent_restart(ctx: CLIContext, agent: str) -> None:
179+
"""
180+
Restart agent service.
181+
(superadmin privilege required)
182+
183+
\b
184+
AGENT: Agent id.
185+
"""
186+
with Session() as session:
187+
try:
188+
status = session.AgentWatcher.agent_restart(agent)
189+
print(status)
190+
except Exception as e:
191+
ctx.output.print_error(e)
192+
sys.exit(1)

0 commit comments

Comments
 (0)