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.
Files changed (37) hide show
  1. {ibee_cli-0.5.0/src/ibee_cli.egg-info → ibee_cli-0.5.2}/PKG-INFO +8 -1
  2. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/README.md +7 -0
  3. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/pyproject.toml +1 -1
  4. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/__init__.py +1 -1
  5. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/vm_lifecycle.py +2 -2
  6. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/render.py +6 -2
  7. {ibee_cli-0.5.0 → ibee_cli-0.5.2/src/ibee_cli.egg-info}/PKG-INFO +8 -1
  8. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_cli_core.py +1 -1
  9. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_networking_commands.py +32 -2
  10. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_vm_commands.py +13 -4
  11. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/LICENSE +0 -0
  12. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/setup.cfg +0 -0
  13. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/__init__.py +0 -0
  14. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/billing.py +0 -0
  15. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/block_storage.py +0 -0
  16. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/buckets.py +0 -0
  17. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/cdn.py +0 -0
  18. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/compute.py +0 -0
  19. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/console.py +0 -0
  20. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/firewalls.py +0 -0
  21. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/gpus.py +0 -0
  22. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/load_balancers.py +0 -0
  23. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/networking.py +0 -0
  24. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/ops.py +0 -0
  25. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/reserved_ips.py +0 -0
  26. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/secrets.py +0 -0
  27. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/commands/vms.py +0 -0
  28. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/context.py +0 -0
  29. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/helpers.py +0 -0
  30. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli/main.py +0 -0
  31. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/SOURCES.txt +0 -0
  32. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/dependency_links.txt +0 -0
  33. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/entry_points.txt +0 -0
  34. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/requires.txt +0 -0
  35. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/src/ibee_cli.egg-info/top_level.txt +0 -0
  36. {ibee_cli-0.5.0 → ibee_cli-0.5.2}/tests/test_secret_store_commands.py +0 -0
  37. {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.0
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`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ibee-cli"
7
- version = "0.5.0"
7
+ version = "0.5.2"
8
8
  description = "Official command-line interface for the IBEE Solutions cloud platform"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """IBEE Solutions command-line interface."""
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.5.2"
@@ -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: Optional[str] = typer.Option(None, "--site-id", help="Site. See `ibee compute sites`."),
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: Optional[str] = typer.Option(None, "--site-id", help="Site. See `ibee compute sites`."),
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.0
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.0"
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 FW, LB, NAT_CATALOG, RIP, RIP_CATALOG, V, WS, gateway_record, group, lb, pf_rule, plain, rip, run, vpc
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