ibee-cli 0.4.1__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.
- ibee_cli-0.5.0/PKG-INFO +142 -0
- ibee_cli-0.5.0/README.md +117 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/pyproject.toml +2 -2
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli/__init__.py +1 -1
- ibee_cli-0.5.0/src/ibee_cli/commands/billing.py +44 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/block_storage.py +186 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/buckets.py +222 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/cdn.py +243 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/compute.py +46 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/console.py +63 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/firewalls.py +220 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/gpus.py +8 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/load_balancers.py +238 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/networking.py +408 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/ops.py +53 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/reserved_ips.py +120 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/secrets.py +581 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/vm_lifecycle.py +849 -0
- ibee_cli-0.5.0/src/ibee_cli/commands/vms.py +8 -0
- ibee_cli-0.5.0/src/ibee_cli/context.py +120 -0
- ibee_cli-0.5.0/src/ibee_cli/helpers.py +238 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli/main.py +6 -13
- ibee_cli-0.5.0/src/ibee_cli/render.py +426 -0
- ibee_cli-0.5.0/src/ibee_cli.egg-info/PKG-INFO +142 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/SOURCES.txt +3 -11
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/requires.txt +1 -1
- ibee_cli-0.5.0/tests/test_cli_core.py +124 -0
- ibee_cli-0.5.0/tests/test_networking_commands.py +238 -0
- ibee_cli-0.5.0/tests/test_secret_store_commands.py +190 -0
- ibee_cli-0.5.0/tests/test_storage_commands.py +252 -0
- ibee_cli-0.5.0/tests/test_vm_commands.py +192 -0
- ibee_cli-0.4.1/PKG-INFO +0 -629
- ibee_cli-0.4.1/README.md +0 -604
- ibee_cli-0.4.1/src/ibee_cli/commands/billing.py +0 -117
- ibee_cli-0.4.1/src/ibee_cli/commands/block_storage.py +0 -657
- ibee_cli-0.4.1/src/ibee_cli/commands/buckets.py +0 -429
- ibee_cli-0.4.1/src/ibee_cli/commands/cdn.py +0 -497
- ibee_cli-0.4.1/src/ibee_cli/commands/compute.py +0 -73
- ibee_cli-0.4.1/src/ibee_cli/commands/console.py +0 -106
- ibee_cli-0.4.1/src/ibee_cli/commands/firewalls.py +0 -334
- ibee_cli-0.4.1/src/ibee_cli/commands/gpus.py +0 -325
- ibee_cli-0.4.1/src/ibee_cli/commands/load_balancers.py +0 -610
- ibee_cli-0.4.1/src/ibee_cli/commands/networking.py +0 -1017
- ibee_cli-0.4.1/src/ibee_cli/commands/ops.py +0 -98
- ibee_cli-0.4.1/src/ibee_cli/commands/reserved_ips.py +0 -280
- ibee_cli-0.4.1/src/ibee_cli/commands/secrets.py +0 -1409
- ibee_cli-0.4.1/src/ibee_cli/commands/vm_lifecycle.py +0 -1662
- ibee_cli-0.4.1/src/ibee_cli/commands/vms.py +0 -324
- ibee_cli-0.4.1/src/ibee_cli/context.py +0 -315
- ibee_cli-0.4.1/src/ibee_cli/helpers.py +0 -749
- ibee_cli-0.4.1/src/ibee_cli/render.py +0 -612
- ibee_cli-0.4.1/src/ibee_cli.egg-info/PKG-INFO +0 -629
- ibee_cli-0.4.1/tests/test_billing_preflight.py +0 -171
- ibee_cli-0.4.1/tests/test_block_storage_cdn_commands.py +0 -754
- ibee_cli-0.4.1/tests/test_cli.py +0 -401
- ibee_cli-0.4.1/tests/test_cloud_vm_commands.py +0 -335
- ibee_cli-0.4.1/tests/test_foundations.py +0 -796
- ibee_cli-0.4.1/tests/test_networking_commands.py +0 -198
- ibee_cli-0.4.1/tests/test_networking_parity_commands.py +0 -840
- ibee_cli-0.4.1/tests/test_object_storage_commands.py +0 -300
- ibee_cli-0.4.1/tests/test_parity_alignment.py +0 -204
- ibee_cli-0.4.1/tests/test_review_fixes.py +0 -340
- ibee_cli-0.4.1/tests/test_secret_store_commands.py +0 -684
- ibee_cli-0.4.1/tests/test_vm_lifecycle_commands.py +0 -260
- ibee_cli-0.4.1/tests/test_vm_parity_commands.py +0 -658
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/LICENSE +0 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/setup.cfg +0 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli/commands/__init__.py +0 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/dependency_links.txt +0 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/entry_points.txt +0 -0
- {ibee_cli-0.4.1 → ibee_cli-0.5.0}/src/ibee_cli.egg-info/top_level.txt +0 -0
ibee_cli-0.5.0/PKG-INFO
ADDED
|
@@ -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)
|
ibee_cli-0.5.0/README.md
ADDED
|
@@ -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.
|
|
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.
|
|
21
|
+
"ibee>=0.5.0,<0.6.0",
|
|
22
22
|
"httpx>=0.27",
|
|
23
23
|
"typer>=0.12",
|
|
24
24
|
"click>=8.1",
|
|
@@ -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)
|