ibee-cli 0.4.2__tar.gz → 0.5.0__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 (71) hide show
  1. ibee_cli-0.5.0/PKG-INFO +142 -0
  2. ibee_cli-0.5.0/README.md +117 -0
  3. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/pyproject.toml +2 -2
  4. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli/__init__.py +1 -1
  5. ibee_cli-0.5.0/src/ibee_cli/commands/billing.py +44 -0
  6. ibee_cli-0.5.0/src/ibee_cli/commands/block_storage.py +186 -0
  7. ibee_cli-0.5.0/src/ibee_cli/commands/buckets.py +222 -0
  8. ibee_cli-0.5.0/src/ibee_cli/commands/cdn.py +243 -0
  9. ibee_cli-0.5.0/src/ibee_cli/commands/compute.py +46 -0
  10. ibee_cli-0.5.0/src/ibee_cli/commands/console.py +63 -0
  11. ibee_cli-0.5.0/src/ibee_cli/commands/firewalls.py +220 -0
  12. ibee_cli-0.5.0/src/ibee_cli/commands/gpus.py +8 -0
  13. ibee_cli-0.5.0/src/ibee_cli/commands/load_balancers.py +238 -0
  14. ibee_cli-0.5.0/src/ibee_cli/commands/networking.py +408 -0
  15. ibee_cli-0.5.0/src/ibee_cli/commands/ops.py +53 -0
  16. ibee_cli-0.5.0/src/ibee_cli/commands/reserved_ips.py +120 -0
  17. ibee_cli-0.5.0/src/ibee_cli/commands/secrets.py +581 -0
  18. ibee_cli-0.5.0/src/ibee_cli/commands/vm_lifecycle.py +849 -0
  19. ibee_cli-0.5.0/src/ibee_cli/commands/vms.py +8 -0
  20. ibee_cli-0.5.0/src/ibee_cli/context.py +120 -0
  21. ibee_cli-0.5.0/src/ibee_cli/helpers.py +238 -0
  22. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli/main.py +6 -13
  23. ibee_cli-0.5.0/src/ibee_cli/render.py +426 -0
  24. ibee_cli-0.5.0/src/ibee_cli.egg-info/PKG-INFO +142 -0
  25. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/SOURCES.txt +3 -11
  26. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/requires.txt +1 -1
  27. ibee_cli-0.5.0/tests/test_cli_core.py +124 -0
  28. ibee_cli-0.5.0/tests/test_networking_commands.py +238 -0
  29. ibee_cli-0.5.0/tests/test_secret_store_commands.py +190 -0
  30. ibee_cli-0.5.0/tests/test_storage_commands.py +252 -0
  31. ibee_cli-0.5.0/tests/test_vm_commands.py +192 -0
  32. ibee_cli-0.4.2/PKG-INFO +0 -618
  33. ibee_cli-0.4.2/README.md +0 -593
  34. ibee_cli-0.4.2/src/ibee_cli/commands/billing.py +0 -117
  35. ibee_cli-0.4.2/src/ibee_cli/commands/block_storage.py +0 -652
  36. ibee_cli-0.4.2/src/ibee_cli/commands/buckets.py +0 -429
  37. ibee_cli-0.4.2/src/ibee_cli/commands/cdn.py +0 -497
  38. ibee_cli-0.4.2/src/ibee_cli/commands/compute.py +0 -73
  39. ibee_cli-0.4.2/src/ibee_cli/commands/console.py +0 -106
  40. ibee_cli-0.4.2/src/ibee_cli/commands/firewalls.py +0 -334
  41. ibee_cli-0.4.2/src/ibee_cli/commands/gpus.py +0 -325
  42. ibee_cli-0.4.2/src/ibee_cli/commands/load_balancers.py +0 -610
  43. ibee_cli-0.4.2/src/ibee_cli/commands/networking.py +0 -1017
  44. ibee_cli-0.4.2/src/ibee_cli/commands/ops.py +0 -98
  45. ibee_cli-0.4.2/src/ibee_cli/commands/reserved_ips.py +0 -272
  46. ibee_cli-0.4.2/src/ibee_cli/commands/secrets.py +0 -1407
  47. ibee_cli-0.4.2/src/ibee_cli/commands/vm_lifecycle.py +0 -1662
  48. ibee_cli-0.4.2/src/ibee_cli/commands/vms.py +0 -324
  49. ibee_cli-0.4.2/src/ibee_cli/context.py +0 -315
  50. ibee_cli-0.4.2/src/ibee_cli/helpers.py +0 -705
  51. ibee_cli-0.4.2/src/ibee_cli/render.py +0 -612
  52. ibee_cli-0.4.2/src/ibee_cli.egg-info/PKG-INFO +0 -618
  53. ibee_cli-0.4.2/tests/test_billing_preflight.py +0 -202
  54. ibee_cli-0.4.2/tests/test_block_storage_cdn_commands.py +0 -753
  55. ibee_cli-0.4.2/tests/test_cli.py +0 -401
  56. ibee_cli-0.4.2/tests/test_cloud_vm_commands.py +0 -335
  57. ibee_cli-0.4.2/tests/test_foundations.py +0 -824
  58. ibee_cli-0.4.2/tests/test_networking_commands.py +0 -198
  59. ibee_cli-0.4.2/tests/test_networking_parity_commands.py +0 -841
  60. ibee_cli-0.4.2/tests/test_object_storage_commands.py +0 -300
  61. ibee_cli-0.4.2/tests/test_parity_alignment.py +0 -204
  62. ibee_cli-0.4.2/tests/test_review_fixes.py +0 -341
  63. ibee_cli-0.4.2/tests/test_secret_store_commands.py +0 -684
  64. ibee_cli-0.4.2/tests/test_vm_lifecycle_commands.py +0 -260
  65. ibee_cli-0.4.2/tests/test_vm_parity_commands.py +0 -658
  66. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/LICENSE +0 -0
  67. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/setup.cfg +0 -0
  68. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli/commands/__init__.py +0 -0
  69. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/dependency_links.txt +0 -0
  70. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/entry_points.txt +0 -0
  71. {ibee_cli-0.4.2 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: ibee-cli
3
+ Version: 0.5.0
4
+ Summary: Official command-line interface for the IBEE Solutions cloud platform
5
+ Author-email: IBEE Solutions <support@ibee.ai>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://ibee.ai
8
+ Project-URL: Documentation, https://ibee.ai/docs/api-reference
9
+ Project-URL: Repository, https://github.com/devs-ibee/ibee-cli
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Environment :: Console
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: ibee<0.6.0,>=0.5.0
17
+ Requires-Dist: httpx>=0.27
18
+ Requires-Dist: typer>=0.12
19
+ Requires-Dist: click>=8.1
20
+ Requires-Dist: rich>=13.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: build; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # IBEE Solutions CLI
27
+
28
+ Official command-line interface for the IBEE Solutions cloud platform, built on the
29
+ [`ibee` Python SDK](https://github.com/devs-ibee/ibee-python). Each command makes one
30
+ SDK call and prints the server's answer; the server decides prices, eligibility,
31
+ limits and states.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install ibee-cli
37
+ ```
38
+
39
+ Requires Python 3.10+.
40
+
41
+ ## Authentication
42
+
43
+ Create an API token in the portal under **Settings > API Tokens**, then:
44
+
45
+ ```bash
46
+ export IBEE_TOKEN="ibee_prod_key_xxxxxxxxxxxx"
47
+ export IBEE_WORKSPACE_ID="710995"
48
+ ```
49
+
50
+ Both can also be passed per command with `--token` and `--workspace`.
51
+
52
+ ## Usage
53
+
54
+ ```bash
55
+ ibee compute sites
56
+ ibee compute plans --vm-type cloud --site-id SITE_ID
57
+ ibee compute images --vm-type cloud --site-id SITE_ID
58
+
59
+ ibee vms create web-1 --site-id SITE_ID --plan-id PLAN_ID --template-id IMAGE_ID \
60
+ --ssh-key-file ~/.ssh/id_ed25519.pub --wait
61
+ ibee vms list
62
+ ibee vms get VM_ID
63
+ ibee vms stop VM_ID --wait
64
+ ibee vms delete VM_ID --yes
65
+
66
+ ibee ops wait OPERATION_ID
67
+ ibee vpcs list
68
+ ibee reserved-ips list
69
+ ibee firewalls list
70
+ ibee load-balancers list
71
+ ibee block-storage list
72
+ ibee buckets list
73
+ ibee cdn list
74
+ ibee secrets stores list
75
+ ibee billing eligibility --resource-type cpu_vm
76
+ ```
77
+
78
+ `ibee --help` and `ibee GROUP --help` list every command. `gpus` takes the same
79
+ commands as `vms`. Until the server resolves VM SKUs itself, `vms/gpus create` fills
80
+ CPU, RAM, disk and OS from the plan and the image and sends the plan's billing SKU for
81
+ `--billing-term` (see the SDK's `held.py`).
82
+
83
+ ## Global options
84
+
85
+ | Option | Environment variable | Meaning |
86
+ | --- | --- | --- |
87
+ | `--token` | `IBEE_TOKEN` (or `IBEE_API_TOKEN`) | API token |
88
+ | `--workspace`, `-w` | `IBEE_WORKSPACE_ID` | Workspace ID |
89
+ | `--dev` | `IBEE_ENV=dev` | Use the development environment |
90
+ | `--base-url` | `IBEE_BASE_URL`, `IBEE_ENDPOINT` | Custom API endpoint |
91
+ | `-o`, `--output` | `IBEE_OUTPUT` | `table`, `json`, `yaml` or `id` |
92
+ | `--json` | | Same as `-o json` |
93
+ | `--yes`, `-y` | `IBEE_ASSUME_YES=1` | Answer yes to every confirmation |
94
+
95
+ The endpoint is chosen in this order: `--base-url`, `IBEE_BASE_URL`, `IBEE_ENDPOINT`,
96
+ `--dev`, `IBEE_ENV`; the default is production, `https://api.ibee.ai/v1`. Without
97
+ `-o`, each command keeps its usual format (tables for most lists, JSON for single
98
+ resources).
99
+
100
+ ## Errors
101
+
102
+ Every error prints the HTTP status and the server's fields as sent:
103
+
104
+ ```text
105
+ Error: HTTP 402
106
+ code: billing_denied
107
+ reason: resource_limit_reached
108
+ message: -
109
+ request_id: 6f1c2e...
110
+ admission_context_id: adm_...
111
+ ```
112
+
113
+ With `-o json` the same fields, plus the raw `body`, are printed on stderr as
114
+ `{"error": {...}}`.
115
+
116
+ ## Exit codes
117
+
118
+ | Code | Meaning |
119
+ | --- | --- |
120
+ | 0 | Success |
121
+ | 1 | Failed, cancelled or timed-out operation (`--wait`), declined confirmation, or another error |
122
+ | 2 | Usage or configuration error (missing token or workspace, unsafe endpoint, token for the other environment); nothing was sent |
123
+ | 3 | `--wait` reached `--timeout` while the operation was still running; resume with `ibee ops wait OPERATION_ID` |
124
+ | 4 | Network error (no HTTP answer) |
125
+ | 10, 11, 12, 13, 14 | HTTP 400, 401, 402, 403, 404 |
126
+ | 15, 16, 17, 18 | HTTP 409, 422, 423, 429 |
127
+ | 19 | HTTP 5xx |
128
+
129
+ ## Retries and idempotency keys
130
+
131
+ The SDK retries reads on 429, 502, 503 and 504, and writes that carry an idempotency
132
+ key on 429 and 503, never on a billing or admission answer. Write commands that take
133
+ `--idempotency-key KEY` send it unchanged (without one the SDK sends a random key);
134
+ after a retryable failure the CLI prints `Retry safely with: --idempotency-key KEY` so
135
+ the command can be repeated without doing the write twice. `--wait` polls the
136
+ operation every `--poll-interval` seconds (default 5) for up to `--timeout` seconds
137
+ (default 1200).
138
+
139
+ ## Related
140
+
141
+ - [Python SDK](https://github.com/devs-ibee/ibee-python): `pip install ibee`
142
+ - [API documentation](https://ibee.ai/docs/api-reference)
@@ -0,0 +1,117 @@
1
+ # IBEE Solutions CLI
2
+
3
+ Official command-line interface for the IBEE Solutions cloud platform, built on the
4
+ [`ibee` Python SDK](https://github.com/devs-ibee/ibee-python). Each command makes one
5
+ SDK call and prints the server's answer; the server decides prices, eligibility,
6
+ limits and states.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install ibee-cli
12
+ ```
13
+
14
+ Requires Python 3.10+.
15
+
16
+ ## Authentication
17
+
18
+ Create an API token in the portal under **Settings > API Tokens**, then:
19
+
20
+ ```bash
21
+ export IBEE_TOKEN="ibee_prod_key_xxxxxxxxxxxx"
22
+ export IBEE_WORKSPACE_ID="710995"
23
+ ```
24
+
25
+ Both can also be passed per command with `--token` and `--workspace`.
26
+
27
+ ## Usage
28
+
29
+ ```bash
30
+ ibee compute sites
31
+ ibee compute plans --vm-type cloud --site-id SITE_ID
32
+ ibee compute images --vm-type cloud --site-id SITE_ID
33
+
34
+ ibee vms create web-1 --site-id SITE_ID --plan-id PLAN_ID --template-id IMAGE_ID \
35
+ --ssh-key-file ~/.ssh/id_ed25519.pub --wait
36
+ ibee vms list
37
+ ibee vms get VM_ID
38
+ ibee vms stop VM_ID --wait
39
+ ibee vms delete VM_ID --yes
40
+
41
+ ibee ops wait OPERATION_ID
42
+ ibee vpcs list
43
+ ibee reserved-ips list
44
+ ibee firewalls list
45
+ ibee load-balancers list
46
+ ibee block-storage list
47
+ ibee buckets list
48
+ ibee cdn list
49
+ ibee secrets stores list
50
+ ibee billing eligibility --resource-type cpu_vm
51
+ ```
52
+
53
+ `ibee --help` and `ibee GROUP --help` list every command. `gpus` takes the same
54
+ commands as `vms`. Until the server resolves VM SKUs itself, `vms/gpus create` fills
55
+ CPU, RAM, disk and OS from the plan and the image and sends the plan's billing SKU for
56
+ `--billing-term` (see the SDK's `held.py`).
57
+
58
+ ## Global options
59
+
60
+ | Option | Environment variable | Meaning |
61
+ | --- | --- | --- |
62
+ | `--token` | `IBEE_TOKEN` (or `IBEE_API_TOKEN`) | API token |
63
+ | `--workspace`, `-w` | `IBEE_WORKSPACE_ID` | Workspace ID |
64
+ | `--dev` | `IBEE_ENV=dev` | Use the development environment |
65
+ | `--base-url` | `IBEE_BASE_URL`, `IBEE_ENDPOINT` | Custom API endpoint |
66
+ | `-o`, `--output` | `IBEE_OUTPUT` | `table`, `json`, `yaml` or `id` |
67
+ | `--json` | | Same as `-o json` |
68
+ | `--yes`, `-y` | `IBEE_ASSUME_YES=1` | Answer yes to every confirmation |
69
+
70
+ The endpoint is chosen in this order: `--base-url`, `IBEE_BASE_URL`, `IBEE_ENDPOINT`,
71
+ `--dev`, `IBEE_ENV`; the default is production, `https://api.ibee.ai/v1`. Without
72
+ `-o`, each command keeps its usual format (tables for most lists, JSON for single
73
+ resources).
74
+
75
+ ## Errors
76
+
77
+ Every error prints the HTTP status and the server's fields as sent:
78
+
79
+ ```text
80
+ Error: HTTP 402
81
+ code: billing_denied
82
+ reason: resource_limit_reached
83
+ message: -
84
+ request_id: 6f1c2e...
85
+ admission_context_id: adm_...
86
+ ```
87
+
88
+ With `-o json` the same fields, plus the raw `body`, are printed on stderr as
89
+ `{"error": {...}}`.
90
+
91
+ ## Exit codes
92
+
93
+ | Code | Meaning |
94
+ | --- | --- |
95
+ | 0 | Success |
96
+ | 1 | Failed, cancelled or timed-out operation (`--wait`), declined confirmation, or another error |
97
+ | 2 | Usage or configuration error (missing token or workspace, unsafe endpoint, token for the other environment); nothing was sent |
98
+ | 3 | `--wait` reached `--timeout` while the operation was still running; resume with `ibee ops wait OPERATION_ID` |
99
+ | 4 | Network error (no HTTP answer) |
100
+ | 10, 11, 12, 13, 14 | HTTP 400, 401, 402, 403, 404 |
101
+ | 15, 16, 17, 18 | HTTP 409, 422, 423, 429 |
102
+ | 19 | HTTP 5xx |
103
+
104
+ ## Retries and idempotency keys
105
+
106
+ The SDK retries reads on 429, 502, 503 and 504, and writes that carry an idempotency
107
+ key on 429 and 503, never on a billing or admission answer. Write commands that take
108
+ `--idempotency-key KEY` send it unchanged (without one the SDK sends a random key);
109
+ after a retryable failure the CLI prints `Retry safely with: --idempotency-key KEY` so
110
+ the command can be repeated without doing the write twice. `--wait` polls the
111
+ operation every `--poll-interval` seconds (default 5) for up to `--timeout` seconds
112
+ (default 1200).
113
+
114
+ ## Related
115
+
116
+ - [Python SDK](https://github.com/devs-ibee/ibee-python): `pip install ibee`
117
+ - [API documentation](https://ibee.ai/docs/api-reference)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ibee-cli"
7
- version = "0.4.2"
7
+ version = "0.5.0"
8
8
  description = "Official command-line interface for the IBEE Solutions cloud platform"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -18,7 +18,7 @@ classifiers = [
18
18
  "Environment :: Console",
19
19
  ]
20
20
  dependencies = [
21
- "ibee>=0.4.2,<0.5.0",
21
+ "ibee>=0.5.0,<0.6.0",
22
22
  "httpx>=0.27",
23
23
  "typer>=0.12",
24
24
  "click>=8.1",
@@ -1,3 +1,3 @@
1
1
  """IBEE Solutions command-line interface."""
2
2
 
3
- __version__ = "0.4.2"
3
+ __version__ = "0.5.0"
@@ -0,0 +1,44 @@
1
+ """Billing eligibility (a diagnostic: the real request is decided by the server)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import typer
8
+
9
+ from ..context import get_client, get_settings
10
+ from ..helpers import compact_payload, field
11
+ from ..render import emit, handle_api_errors, to_data
12
+
13
+ app = typer.Typer(help="Billing resource eligibility", no_args_is_help=True)
14
+
15
+
16
+ @app.command("eligibility")
17
+ @handle_api_errors
18
+ def resource_eligibility(
19
+ ctx: typer.Context,
20
+ sku_code: Optional[str] = typer.Option(None, "--sku-code", help="Billing SKU from an IBEE product catalog"),
21
+ estimated_cost_minor: Optional[int] = typer.Option(None, "--estimated-cost-minor", help="Cost in the currency's minor unit"),
22
+ operation: Optional[str] = typer.Option(None, "--operation", help="Billing operation, for example CREATE_RESOURCE"),
23
+ resource_type: Optional[str] = typer.Option(None, "--resource-type", help="For example cpu_vm or block_volume"),
24
+ quantity: Optional[int] = typer.Option(None, "--quantity"),
25
+ size_gb: Optional[int] = typer.Option(None, "--size-gb"),
26
+ require: bool = typer.Option(False, "--require", help="Exit 1 unless the answer is allowed=true"),
27
+ ) -> None:
28
+ """Ask Billing whether the workspace may create a billable resource.
29
+
30
+ Prints Billing's decision as returned (JSON by default; -o table|yaml|id). Exits 0 for
31
+ any decision, unless --require is given and allowed is not true.
32
+ """
33
+ settings = get_settings(ctx)
34
+ decision = get_client(settings).billing.check_resource_eligibility(
35
+ **compact_payload(
36
+ sku_code=sku_code, estimated_cost_minor=estimated_cost_minor, operation=operation,
37
+ resource_type=resource_type, quantity=quantity, size_gb=size_gb,
38
+ )
39
+ )
40
+ data = to_data(decision)
41
+ rows = list(data.items()) if isinstance(data, dict) else []
42
+ emit(decision, settings=settings, default="json", title="Billing eligibility", headers=("Field", "Value"), rows=rows, id_field="organization_id")
43
+ if require and field(decision, "allowed") is not True:
44
+ raise typer.Exit(code=1)
@@ -0,0 +1,186 @@
1
+ """Standalone Block Storage volumes (``ibee block-storage``).
2
+
3
+ Each command makes one SDK call and prints the server's answer; the server checks
4
+ names, sizes, plans, states and billing. To attach a volume to a VM use
5
+ ``ibee vms|gpus volume-attach`` and ``volume-detach``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Optional
11
+
12
+ import typer
13
+
14
+ from ..context import get_client, get_settings
15
+ from ..helpers import compact_payload, confirm_destructive, field, idempotency_key_option
16
+ from ..render import cell, emit, handle_api_errors, print_json
17
+
18
+ app = typer.Typer(
19
+ help="Standalone Block Storage volumes. Attach them to VMs with 'ibee vms volume-attach'.",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+ ID_FIELD = "id"
24
+ LIST_HEADERS = ("ID", "Name", "Size (GB)", "State", "Site", "Attached to")
25
+ NODE_LEVEL_NOTE = (
26
+ "Advanced: records a storage-node attachment only. Use 'ibee vms volume-attach' / 'volume-detach' for VMs."
27
+ )
28
+
29
+
30
+ def _block_storage(ctx: typer.Context):
31
+ settings = get_settings(ctx)
32
+ return settings, get_client(settings).block_storage
33
+
34
+
35
+ def _first(item, *names):
36
+ """The first of ``names`` that has a value (display only)."""
37
+ return next((value for value in (cell(item, name) for name in names) if value not in (None, "")), None)
38
+
39
+
40
+ def _row(volume) -> tuple:
41
+ attached = ", ".join(str(_first(a, "vm_name", "vm_id", "node_name")) for a in cell(volume, "attachments") or [])
42
+ columns = ("id", "name", "size_gb", "state")
43
+ return (*(cell(volume, c) for c in columns), _first(volume, "site_name", "site_id"), attached or None)
44
+
45
+
46
+ @app.command("list")
47
+ @handle_api_errors
48
+ def list_volumes(ctx: typer.Context) -> None:
49
+ """List volumes in the workspace (-o table shows a summary table)."""
50
+ settings, storage = _block_storage(ctx)
51
+ volumes = list(storage.list_block_volumes() or [])
52
+ emit(volumes, settings=settings, default="json", headers=LIST_HEADERS, rows=[_row(v) for v in volumes],
53
+ title="Block Storage volumes", id_field=ID_FIELD)
54
+
55
+
56
+ @app.command("create")
57
+ @handle_api_errors
58
+ def create_volume(
59
+ ctx: typer.Context,
60
+ name: str = typer.Argument(..., help="Volume name"),
61
+ size_gb: int = typer.Option(..., "--size-gb", help="Size in GB"),
62
+ site_id: str = typer.Option(..., "--site-id", help="Placement site ID (see 'ibee compute sites')"),
63
+ site_name: Optional[str] = typer.Option(None, "--site-name", help="Site display name"),
64
+ sku_code: Optional[str] = typer.Option(None, "--sku-code", help="Block Storage SKU from the server's catalog"),
65
+ volume_class: Optional[str] = typer.Option(None, "--class", help="capacity, balanced or performance"),
66
+ replica_count: Optional[int] = typer.Option(None, "--replicas", help="Replica count"),
67
+ backup_enabled: Optional[bool] = typer.Option(None, "--backup-enabled/--no-backup", help="Volume backups"),
68
+ idempotency_key: Optional[str] = idempotency_key_option(),
69
+ ) -> None:
70
+ """Create a volume. The server resolves the plan and billing."""
71
+ settings, storage = _block_storage(ctx)
72
+ result = storage.create_block_volume(
73
+ name=name,
74
+ size_gb=size_gb,
75
+ site_id=site_id,
76
+ **compact_payload(
77
+ site_name=site_name,
78
+ sku_code=sku_code,
79
+ volume_class=volume_class,
80
+ replica_count=replica_count,
81
+ backup_enabled=backup_enabled,
82
+ idempotency_key=idempotency_key,
83
+ ),
84
+ )
85
+ if settings.output == "id":
86
+ typer.echo(str(field(field(result, "volume"), "id") or ""))
87
+ return
88
+ print_json(result)
89
+
90
+
91
+ @app.command("get")
92
+ @handle_api_errors
93
+ def get_volume(ctx: typer.Context, volume_id: str = typer.Argument(..., help="Volume ID")) -> None:
94
+ """Show a volume and its attachments."""
95
+ _settings, storage = _block_storage(ctx)
96
+ print_json(storage.get_block_volume(volume_id), id_field=ID_FIELD)
97
+
98
+
99
+ @app.command("operations")
100
+ @handle_api_errors
101
+ def list_operations(ctx: typer.Context, volume_id: str = typer.Argument(..., help="Volume ID")) -> None:
102
+ """List a volume's operations."""
103
+ _settings, storage = _block_storage(ctx)
104
+ print_json(storage.list_block_volume_operations(volume_id), id_field=ID_FIELD)
105
+
106
+
107
+ @app.command("attach", help=f"Attach a volume to a storage node. {NODE_LEVEL_NOTE}")
108
+ @handle_api_errors
109
+ def attach_volume(
110
+ ctx: typer.Context,
111
+ volume_id: str = typer.Argument(..., help="Volume ID"),
112
+ node_name: str = typer.Option(..., "--node-name", help="Storage node name"),
113
+ mode: Optional[str] = typer.Option(None, "--mode", help="single-writer or multi-writer"),
114
+ vm_id: Optional[str] = typer.Option(None, "--vm-id"),
115
+ vm_name: Optional[str] = typer.Option(None, "--vm-name"),
116
+ vm_state: Optional[str] = typer.Option(None, "--vm-state"),
117
+ vm_site_id: Optional[str] = typer.Option(None, "--vm-site-id"),
118
+ vm_type: Optional[str] = typer.Option(None, "--vm-type", help="cloud or gpu"),
119
+ idempotency_key: Optional[str] = idempotency_key_option(),
120
+ ) -> None:
121
+ _settings, storage = _block_storage(ctx)
122
+ print_json(storage.attach_block_volume(
123
+ volume_id,
124
+ node_name=node_name,
125
+ **compact_payload(mode=mode, vm_id=vm_id, vm_name=vm_name, vm_state=vm_state, vm_site_id=vm_site_id,
126
+ vm_type=vm_type, idempotency_key=idempotency_key),
127
+ ))
128
+
129
+
130
+ @app.command("detach", help=f"Detach a volume from a storage node. {NODE_LEVEL_NOTE}")
131
+ @handle_api_errors
132
+ def detach_volume(
133
+ ctx: typer.Context,
134
+ volume_id: str = typer.Argument(..., help="Volume ID"),
135
+ node_name: str = typer.Option(..., "--node-name", help="Storage node name"),
136
+ force: Optional[bool] = typer.Option(None, "--force", help="Ask the server to skip its unmount check"),
137
+ confirm_unmounted: Optional[bool] = typer.Option(
138
+ None, "--confirm-unmounted", help="I have unmounted this volume from the server"
139
+ ),
140
+ vm_state: Optional[str] = typer.Option(None, "--vm-state"),
141
+ vm_type: Optional[str] = typer.Option(None, "--vm-type", help="cloud or gpu"),
142
+ reason: Optional[str] = typer.Option(None, "--reason"),
143
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
144
+ idempotency_key: Optional[str] = idempotency_key_option(),
145
+ ) -> None:
146
+ settings, storage = _block_storage(ctx)
147
+ confirm_destructive(settings, f"Detach volume '{volume_id}' from node '{node_name}'?", yes)
148
+ print_json(storage.detach_block_volume(
149
+ volume_id,
150
+ node_name=node_name,
151
+ **compact_payload(force=force, confirm_unmounted=confirm_unmounted, vm_state=vm_state, vm_type=vm_type,
152
+ reason=reason, idempotency_key=idempotency_key),
153
+ ))
154
+
155
+
156
+ @app.command("resize")
157
+ @handle_api_errors
158
+ def resize_volume(
159
+ ctx: typer.Context,
160
+ volume_id: str = typer.Argument(..., help="Volume ID"),
161
+ new_size_gb: int = typer.Option(..., "--new-size-gb", help="New size in GB"),
162
+ allow_online: Optional[bool] = typer.Option(None, "--allow-online", help="Resize while attached to a running VM"),
163
+ vm_state: Optional[str] = typer.Option(None, "--vm-state", help="State of the attached VM"),
164
+ idempotency_key: Optional[str] = idempotency_key_option(),
165
+ ) -> None:
166
+ """Resize a volume. After growing it, extend the filesystem inside the VM."""
167
+ _settings, storage = _block_storage(ctx)
168
+ print_json(storage.resize_block_volume(
169
+ volume_id,
170
+ new_size_gb=new_size_gb,
171
+ **compact_payload(allow_online=allow_online, vm_state=vm_state, idempotency_key=idempotency_key),
172
+ ))
173
+
174
+
175
+ @app.command("delete")
176
+ @handle_api_errors
177
+ def delete_volume(
178
+ ctx: typer.Context,
179
+ volume_id: str = typer.Argument(..., help="Volume ID"),
180
+ force: Optional[bool] = typer.Option(None, "--force", help="Ask the server to detach and delete anyway"),
181
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
182
+ ) -> None:
183
+ """Delete a volume and its data."""
184
+ settings, storage = _block_storage(ctx)
185
+ confirm_destructive(settings, f"Delete Block Storage volume '{volume_id}'?", yes)
186
+ print_json(storage.delete_block_volume(volume_id, **compact_payload(force=force)), id_field=ID_FIELD)