ibee-cli 0.5.0__tar.gz → 0.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ibee_cli-0.5.0/src/ibee_cli.egg-info → ibee_cli-0.5.2}/PKG-INFO +8 -1
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/README.md +7 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/pyproject.toml +1 -1
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/__init__.py +1 -1
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/vm_lifecycle.py +2 -2
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/render.py +6 -2
- {ibee_cli-0.5.0 → ibee_cli-0.5.2/src/ibee_cli.egg-info}/PKG-INFO +8 -1
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_cli_core.py +1 -1
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_networking_commands.py +32 -2
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_vm_commands.py +13 -4
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/LICENSE +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/setup.cfg +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/__init__.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/billing.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/block_storage.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/buckets.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/cdn.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/compute.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/console.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/firewalls.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/gpus.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/load_balancers.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/networking.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/ops.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/reserved_ips.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/secrets.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/vms.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/context.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/helpers.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/main.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/SOURCES.txt +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/dependency_links.txt +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/entry_points.txt +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/requires.txt +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/top_level.txt +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_secret_store_commands.py +0 -0
- {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_storage_commands.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ibee-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Official command-line interface for the IBEE Solutions cloud platform
|
|
5
5
|
Author-email: IBEE Solutions <support@ibee.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -136,6 +136,13 @@ the command can be repeated without doing the write twice. `--wait` polls the
|
|
|
136
136
|
operation every `--poll-interval` seconds (default 5) for up to `--timeout` seconds
|
|
137
137
|
(default 1200).
|
|
138
138
|
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
`pip install -e ".[dev]" && pytest -q`. CI also runs `python scripts/check_sdk_calls.py`
|
|
142
|
+
against `ibee` from PyPI and from ibee-python `main`: every SDK method the CLI calls
|
|
143
|
+
(including the names the VM commands build from templates) must exist in the installed
|
|
144
|
+
SDK, and the CLI must make no raw HTTP calls.
|
|
145
|
+
|
|
139
146
|
## Related
|
|
140
147
|
|
|
141
148
|
- [Python SDK](https://github.com/devs-ibee/ibee-python): `pip install ibee`
|
|
@@ -111,6 +111,13 @@ the command can be repeated without doing the write twice. `--wait` polls the
|
|
|
111
111
|
operation every `--poll-interval` seconds (default 5) for up to `--timeout` seconds
|
|
112
112
|
(default 1200).
|
|
113
113
|
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
`pip install -e ".[dev]" && pytest -q`. CI also runs `python scripts/check_sdk_calls.py`
|
|
117
|
+
against `ibee` from PyPI and from ibee-python `main`: every SDK method the CLI calls
|
|
118
|
+
(including the names the VM commands build from templates) must exist in the installed
|
|
119
|
+
SDK, and the CLI must make no raw HTTP calls.
|
|
120
|
+
|
|
114
121
|
## Related
|
|
115
122
|
|
|
116
123
|
- [Python SDK](https://github.com/devs-ibee/ibee-python): `pip install ibee`
|
|
@@ -177,7 +177,7 @@ def register_vm_commands(app: typer.Typer, spec: VmCommandSpec) -> None:
|
|
|
177
177
|
def create_vm(
|
|
178
178
|
ctx: typer.Context,
|
|
179
179
|
name: str = typer.Argument(..., help="VM name (with --count, the base name)"),
|
|
180
|
-
site_id:
|
|
180
|
+
site_id: str = typer.Option(..., "--site-id", help="Site (required). See `ibee compute sites`."),
|
|
181
181
|
plan_id: str = typer.Option(..., "--plan-id", help="Plan. See `ibee compute plans --vm-type cloud`."),
|
|
182
182
|
template_id: str = typer.Option(..., "--template-id", help="Image. See `ibee compute images --vm-type cloud`."),
|
|
183
183
|
os_type: Optional[str] = typer.Option(None, "--os-type", help="Default: the image's"),
|
|
@@ -228,7 +228,7 @@ def register_vm_commands(app: typer.Typer, spec: VmCommandSpec) -> None:
|
|
|
228
228
|
def create_gpu(
|
|
229
229
|
ctx: typer.Context,
|
|
230
230
|
name: str = typer.Argument(..., help="VM name (with --count, the base name)"),
|
|
231
|
-
site_id:
|
|
231
|
+
site_id: str = typer.Option(..., "--site-id", help="Site (required). See `ibee compute sites`."),
|
|
232
232
|
plan_id: str = typer.Option(..., "--plan-id", help="Plan. See `ibee compute plans --vm-type gpu`."),
|
|
233
233
|
template_id: str = typer.Option(..., "--template-id", help="Image. See `ibee compute images --vm-type gpu`."),
|
|
234
234
|
os_type: Optional[str] = typer.Option(None, "--os-type", help="Default: the image's"),
|
|
@@ -216,10 +216,14 @@ def to_yaml(payload: Any) -> str:
|
|
|
216
216
|
# ---------------------------------------------------------------------------
|
|
217
217
|
|
|
218
218
|
|
|
219
|
-
def _items(data: Any) -> list[Any]:
|
|
219
|
+
def _items(data: Any, id_field: Optional[str] = None) -> list[Any]:
|
|
220
220
|
if isinstance(data, list):
|
|
221
221
|
return data
|
|
222
222
|
if isinstance(data, dict):
|
|
223
|
+
if id_field and data.get(id_field) not in (None, ""):
|
|
224
|
+
# One object that carries the command's id (a firewall group, a load
|
|
225
|
+
# balancer): its nested ``rules`` or ``subnets`` are not the result.
|
|
226
|
+
return [data]
|
|
223
227
|
for key in COLLECTION_KEYS:
|
|
224
228
|
if isinstance(data.get(key), list):
|
|
225
229
|
return data[key]
|
|
@@ -241,7 +245,7 @@ def _identifier(item: Any, id_field: Optional[str]) -> Optional[str]:
|
|
|
241
245
|
def identifiers(payload: Any, id_field: Optional[str] = None) -> list[str]:
|
|
242
246
|
"""Identifiers for ``-o id``: one per item of a collection, or one for an object."""
|
|
243
247
|
ids = []
|
|
244
|
-
for item in _items(to_data(payload)):
|
|
248
|
+
for item in _items(to_data(payload), id_field):
|
|
245
249
|
value = _identifier(item, id_field)
|
|
246
250
|
if value is not None:
|
|
247
251
|
ids.append(value)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ibee-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: Official command-line interface for the IBEE Solutions cloud platform
|
|
5
5
|
Author-email: IBEE Solutions <support@ibee.ai>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -136,6 +136,13 @@ the command can be repeated without doing the write twice. `--wait` polls the
|
|
|
136
136
|
operation every `--poll-interval` seconds (default 5) for up to `--timeout` seconds
|
|
137
137
|
(default 1200).
|
|
138
138
|
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
`pip install -e ".[dev]" && pytest -q`. CI also runs `python scripts/check_sdk_calls.py`
|
|
142
|
+
against `ibee` from PyPI and from ibee-python `main`: every SDK method the CLI calls
|
|
143
|
+
(including the names the VM commands build from templates) must exist in the installed
|
|
144
|
+
SDK, and the CLI must make no raw HTTP calls.
|
|
145
|
+
|
|
139
146
|
## Related
|
|
140
147
|
|
|
141
148
|
- [Python SDK](https://github.com/devs-ibee/ibee-python): `pip install ibee`
|
|
@@ -29,7 +29,7 @@ def test_help_and_version() -> None:
|
|
|
29
29
|
for group in ("billing", "block-storage", "buckets", "cdn", "secrets", "vms", "gpus", "console", "vpcs",
|
|
30
30
|
"reserved-ips", "firewalls", "load-balancers", "compute", "ops"):
|
|
31
31
|
assert group in result.output
|
|
32
|
-
assert runner.invoke(app, ["--version"]).output.strip() == f"ibee-cli {__version__}" == "ibee-cli 0.5.
|
|
32
|
+
assert runner.invoke(app, ["--version"]).output.strip() == f"ibee-cli {__version__}" == "ibee-cli 0.5.2"
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def test_missing_token_and_workspace_are_usage_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
@@ -10,9 +10,11 @@ import json
|
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
12
|
from click import unstyle
|
|
13
|
-
from _net_fixtures import
|
|
13
|
+
from _net_fixtures import (
|
|
14
|
+
FW, LB, NAT_CATALOG, RIP, RIP_CATALOG, V, WS, gateway_record, group, lb, pf_rule, plain, rip, run, subnet, vpc,
|
|
15
|
+
)
|
|
14
16
|
|
|
15
|
-
from ibee_cli.render import EXIT_CODES
|
|
17
|
+
from ibee_cli.render import EXIT_CODES, identifiers
|
|
16
18
|
|
|
17
19
|
BACKENDS = [{"type": "ip", "target": "10.0.0.5", "port": 80, "weight": 5}, {"target": "web.internal", "port": 8080}]
|
|
18
20
|
|
|
@@ -153,6 +155,34 @@ def test_load_balancer_create_sends_json_backends_unchanged(gw):
|
|
|
153
155
|
assert gw.last("POST", f"{LB}/l7").json == {"name": "web", "protocol": "https", "backends": BACKENDS}
|
|
154
156
|
|
|
155
157
|
|
|
158
|
+
L7_RULES = [{"priority": 1, "path_prefix": "/api", "backends": BACKENDS[:1]}]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@pytest.mark.parametrize(
|
|
162
|
+
"args, method, path, record, expected",
|
|
163
|
+
[
|
|
164
|
+
(["firewalls", "create", "web"], "POST", FW, group(), "fw-1"),
|
|
165
|
+
(["firewalls", "get", "fw-1"], "GET", f"{FW}/fw-1", group(), "fw-1"),
|
|
166
|
+
(["load-balancers", "create-l7", "web", "--backends", json.dumps(BACKENDS)], "POST", f"{LB}/l7",
|
|
167
|
+
lb(backends=BACKENDS, rules=L7_RULES), "lb-1"),
|
|
168
|
+
(["load-balancers", "create-l7", "web", "--backends", "[]"], "POST", f"{LB}/l7", lb(rules=[]), "lb-1"),
|
|
169
|
+
(["vpcs", "get", "vpc-1"], "GET", V, vpc(subnets=[subnet(), subnet(subnet_id="sub-2")]), "vpc-1"),
|
|
170
|
+
],
|
|
171
|
+
)
|
|
172
|
+
def test_id_output_prints_the_object_id_not_its_nested_list(gw, args, method, path, record, expected):
|
|
173
|
+
gw.on(method, path, (200, record))
|
|
174
|
+
|
|
175
|
+
result = run(["-o", "id", *args])
|
|
176
|
+
assert result.exit_code == 0, result.output
|
|
177
|
+
assert result.stdout.splitlines() == [expected]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_id_output_still_prints_one_id_per_collection_item():
|
|
181
|
+
groups = {"groups": [group(), group(firewall_group_id="fw-2")]}
|
|
182
|
+
assert identifiers(groups, "firewall_group_id") == ["fw-1", "fw-2"]
|
|
183
|
+
assert identifiers({"rules": group()["rules"]}) == ["sys-1", "r-1"]
|
|
184
|
+
|
|
185
|
+
|
|
156
186
|
def test_load_balancer_repeatable_backend_and_routing(gw):
|
|
157
187
|
gw.on("POST", f"{LB}/l4", (201, lb(layer="l4", protocol="tcp")))
|
|
158
188
|
|
|
@@ -57,16 +57,25 @@ def test_create_name_and_shape_are_sent_as_typed(gw) -> None:
|
|
|
57
57
|
"""No client name rule, no plan selectable check: the server answers."""
|
|
58
58
|
catalog(gw, selectable=False, pricing_status="unpriced")
|
|
59
59
|
gw.on("POST", "compute/cloud-vms", (422, {"detail": [{"loc": ["body", "name"], "msg": "invalid hostname"}]}))
|
|
60
|
-
result = run(["vms", "create", "Web_01!", "--plan-id", "p1", "--template-id", "t1"])
|
|
60
|
+
result = run(["vms", "create", "Web_01!", "--site-id", "s1", "--plan-id", "p1", "--template-id", "t1"])
|
|
61
61
|
assert result.exit_code == 16
|
|
62
62
|
assert gw.last("POST", "compute/cloud-vms").json["name"] == "Web_01!"
|
|
63
63
|
assert "Error: HTTP 422" in plain(result)
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
def test_create_requires_a_site(gw) -> None:
|
|
67
|
+
"""Contract 0.4.1: site_id is required on VM create; nothing is sent without it."""
|
|
68
|
+
for group in ("vms", "gpus"):
|
|
69
|
+
result = run([group, "create", "web", "--plan-id", "p1", "--template-id", "t1"])
|
|
70
|
+
assert result.exit_code == 2
|
|
71
|
+
assert "--site-id" in plain(result)
|
|
72
|
+
assert gw.requests("POST") == []
|
|
73
|
+
|
|
74
|
+
|
|
66
75
|
def test_count_creates_numbered_vms_with_numbered_keys(gw) -> None:
|
|
67
76
|
catalog(gw)
|
|
68
77
|
gw.on("POST", "compute/cloud-vms", (202, ACCEPTED))
|
|
69
|
-
result = run(["vms", "create", "web", "--plan-id", "p1", "--template-id", "t1", "--count", "3", "--idempotency-key", "k"])
|
|
78
|
+
result = run(["vms", "create", "web", "--site-id", "s1", "--plan-id", "p1", "--template-id", "t1", "--count", "3", "--idempotency-key", "k"])
|
|
70
79
|
assert result.exit_code == 0, result.output
|
|
71
80
|
creates = gw.requests("POST")
|
|
72
81
|
assert [c.json["name"] for c in creates] == ["web-1", "web-2", "web-3"]
|
|
@@ -76,7 +85,7 @@ def test_count_creates_numbered_vms_with_numbered_keys(gw) -> None:
|
|
|
76
85
|
def test_gpu_create_takes_the_gpu_shape_from_the_plan(gw) -> None:
|
|
77
86
|
catalog(gw, vm_type="gpu", gpu_count=1, gpu_model="L4")
|
|
78
87
|
gw.on("POST", "compute/gpu-vms", (202, ACCEPTED))
|
|
79
|
-
assert run(["gpus", "create", "g", "--plan-id", "p1", "--template-id", "t1"]).exit_code == 0
|
|
88
|
+
assert run(["gpus", "create", "g", "--site-id", "s1", "--plan-id", "p1", "--template-id", "t1"]).exit_code == 0
|
|
80
89
|
body = gw.last("POST", "compute/gpu-vms").json
|
|
81
90
|
assert (body["gpu_count"], body["gpu_model"]) == (1, "L4")
|
|
82
91
|
|
|
@@ -85,7 +94,7 @@ def test_create_with_wait_polls_the_operation(gw) -> None:
|
|
|
85
94
|
catalog(gw)
|
|
86
95
|
gw.on("POST", "compute/cloud-vms", (202, ACCEPTED))
|
|
87
96
|
gw.on("GET", "compute/operations/op-1", (200, operation("running")), (200, operation("succeeded")))
|
|
88
|
-
result = run(["vms", "create", "web", "--plan-id", "p1", "--template-id", "t1", "--wait"])
|
|
97
|
+
result = run(["vms", "create", "web", "--site-id", "s1", "--plan-id", "p1", "--template-id", "t1", "--wait"])
|
|
89
98
|
assert result.exit_code == 0, result.output
|
|
90
99
|
assert "Create succeeded for web" in plain(result)
|
|
91
100
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|