mtn-cloud 0.3.1__tar.gz → 0.3.3__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.
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/PKG-INFO +30 -17
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/README.md +27 -12
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/advanced-cookbook.md +15 -4
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-overview.md +5 -4
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/client.md +17 -3
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/instances.md +25 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/index.md +8 -6
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/instances.md +35 -6
- mtn_cloud-0.3.3/docs/recipes.md +110 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/pyproject.toml +3 -4
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/_version.py +1 -1
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/client.py +9 -8
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/config.py +28 -8
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/http.py +177 -41
- mtn_cloud-0.3.3/src/mtn_cloud/py.typed +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/instances.py +185 -1
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_client.py +35 -2
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_config.py +40 -4
- mtn_cloud-0.3.3/tests/test_http.py +335 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_instances.py +110 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/.gitignore +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/LICENSE +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/archive-buckets.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/backups.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/clouds.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/groups.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/index.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/instance-types.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/networks.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/plans.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/security-groups.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/storage-buckets.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/api-reference/virtual-images.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/backups.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/networking.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/quickstart.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/security-groups.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/storage.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/stylesheets/extra.css +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/docs/virtual-images.md +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/__init__.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/exceptions.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/__init__.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/archive.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/backup.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/base.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/cloud.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/group.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/instance.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/instance_type.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/network.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/plan.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/resource_pool.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/security_group.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/snapshot.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/storage_bucket.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/user.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/virtual_image.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/models/volume.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/__init__.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/archive_buckets.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/backups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/base.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/clouds.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/groups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/instance_types.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/networks.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/plans.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/security_groups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/storage_buckets.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/src/mtn_cloud/resources/virtual_images.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/__init__.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/conftest.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_archive_buckets.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_backups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_clouds.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_exceptions.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_groups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_instance_types.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_networks.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_plans.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_security_groups.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_storage_buckets.py +0 -0
- {mtn_cloud-0.3.1 → mtn_cloud-0.3.3}/tests/test_virtual_images.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: mtn-cloud
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Community Python SDK for MTN Cloud - Deploy and manage cloud resources with ease
|
|
5
5
|
Project-URL: Homepage, https://github.com/mahveotm/mtn-cloud-python
|
|
6
6
|
Project-URL: Documentation, https://github.com/mahveotm/mtn-cloud-python#readme
|
|
@@ -10,7 +10,7 @@ Author-email: Marvellous Osuolale <m@rvellous.com>
|
|
|
10
10
|
Maintainer-email: Marvellous Osuolale <m@rvellous.com>
|
|
11
11
|
License: MIT
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Keywords: api,cloud,devops,infrastructure,mtn,mtn-cloud,sdk
|
|
13
|
+
Keywords: api,cloud,cloud-management,devops,infrastructure,mtn,mtn-cloud,mtn-cloud-python,sdk
|
|
14
14
|
Classifier: Development Status :: 4 - Beta
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -31,10 +31,8 @@ Requires-Dist: typing-extensions>=4.5.0
|
|
|
31
31
|
Requires-Dist: urllib3>=2.0.0
|
|
32
32
|
Provides-Extra: dev
|
|
33
33
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
34
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
35
34
|
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
36
35
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
37
|
-
Requires-Dist: respx>=0.20.0; extra == 'dev'
|
|
38
36
|
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
39
37
|
Requires-Dist: types-requests>=2.28.0; extra == 'dev'
|
|
40
38
|
Provides-Extra: docs
|
|
@@ -150,33 +148,36 @@ for plan in cloud.instances.list_service_plans(
|
|
|
150
148
|
print(plan["id"], plan["name"])
|
|
151
149
|
```
|
|
152
150
|
|
|
153
|
-
A **resource pool is required** to create an instance — it's where the VM is hosted.
|
|
151
|
+
A **resource pool is required** to create an instance — it's where the VM is hosted. `provision()` (below) discovers it for you; or fetch one yourself with `cloud.instances.get_resource_pool("my-project", group="MTNNG_CLOUD_AZ_1")`.
|
|
152
|
+
|
|
153
|
+
### 2. Create an Instance
|
|
154
|
+
|
|
155
|
+
The guided `provision()` resolves layout, plan, and resource pool from names — the fastest path:
|
|
154
156
|
|
|
155
157
|
```python
|
|
156
|
-
|
|
157
|
-
"
|
|
158
|
-
|
|
158
|
+
instance = cloud.instances.provision(
|
|
159
|
+
name="web-01",
|
|
160
|
+
type="MTN-CS10", # type code
|
|
161
|
+
group="MTNNG_CLOUD_AZ_1", # also used as the cloud/zone
|
|
162
|
+
plan="G2S4", # plan name (or numeric id)
|
|
163
|
+
resource_pool="my-project", # pool name; auto-selected if the group has only one
|
|
159
164
|
)
|
|
160
|
-
print(
|
|
165
|
+
print(instance.id, instance.status, instance.primary_ip)
|
|
161
166
|
```
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
For full control over every ID, use `create()` directly:
|
|
164
169
|
|
|
165
170
|
```python
|
|
166
|
-
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
167
|
-
|
|
168
171
|
instance = cloud.instances.create(
|
|
169
|
-
name="
|
|
172
|
+
name="web-01",
|
|
170
173
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
171
174
|
type="MTN-CS10",
|
|
172
175
|
group="MTNNG_CLOUD_AZ_1",
|
|
173
176
|
layout=327,
|
|
174
177
|
plan=6776,
|
|
175
|
-
resource_pool_id=
|
|
178
|
+
resource_pool_id="pool-214", # or the numeric ID 214
|
|
176
179
|
labels=["production", "web"],
|
|
177
180
|
)
|
|
178
|
-
|
|
179
|
-
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
180
181
|
```
|
|
181
182
|
|
|
182
183
|
### 3. Manage an Instance
|
|
@@ -404,6 +405,8 @@ Environment variables:
|
|
|
404
405
|
| `MTN_CLOUD_MAX_RETRIES` | Maximum retry attempts | `3` |
|
|
405
406
|
| `MTN_CLOUD_RETRY_DELAY` | Retry backoff factor | `1.0` |
|
|
406
407
|
| `MTN_CLOUD_VERIFY_SSL` | Enable SSL verification | `true` |
|
|
408
|
+
| `MTN_CLOUD_USER_AGENT` | Application identity appended to the SDK user agent | - |
|
|
409
|
+
| `MTN_CLOUD_DEBUG` | Enable bounded, secret-redacted HTTP debug logs | `false` |
|
|
407
410
|
|
|
408
411
|
Programmatic configuration:
|
|
409
412
|
|
|
@@ -416,11 +419,21 @@ config = MTNCloudConfig(
|
|
|
416
419
|
max_retries=5,
|
|
417
420
|
retry_delay=1.5,
|
|
418
421
|
verify_ssl=True,
|
|
422
|
+
user_agent="my-automation/1.0", # appended after the required SDK identity
|
|
419
423
|
)
|
|
420
424
|
|
|
421
425
|
cloud = MTNCloud(config=config)
|
|
422
426
|
```
|
|
423
427
|
|
|
428
|
+
Tokens and passwords are stored as masked Pydantic secret values, and debug
|
|
429
|
+
logging recursively redacts credential-shaped fields. The transport retries
|
|
430
|
+
transient failures only for safe read methods (`GET`, `HEAD`, and `OPTIONS`);
|
|
431
|
+
create, update, action, and delete requests are never status-retried automatically.
|
|
432
|
+
|
|
433
|
+
The default `mtn-cloud-python/<version>` user-agent prefix is required by the
|
|
434
|
+
MTN Cloud API edge. A configured `user_agent` is treated as an application
|
|
435
|
+
suffix so the SDK identity is preserved.
|
|
436
|
+
|
|
424
437
|
## Examples
|
|
425
438
|
|
|
426
439
|
| Script | What it demonstrates |
|
|
@@ -106,33 +106,36 @@ for plan in cloud.instances.list_service_plans(
|
|
|
106
106
|
print(plan["id"], plan["name"])
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
A **resource pool is required** to create an instance — it's where the VM is hosted.
|
|
109
|
+
A **resource pool is required** to create an instance — it's where the VM is hosted. `provision()` (below) discovers it for you; or fetch one yourself with `cloud.instances.get_resource_pool("my-project", group="MTNNG_CLOUD_AZ_1")`.
|
|
110
|
+
|
|
111
|
+
### 2. Create an Instance
|
|
112
|
+
|
|
113
|
+
The guided `provision()` resolves layout, plan, and resource pool from names — the fastest path:
|
|
110
114
|
|
|
111
115
|
```python
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
|
|
116
|
+
instance = cloud.instances.provision(
|
|
117
|
+
name="web-01",
|
|
118
|
+
type="MTN-CS10", # type code
|
|
119
|
+
group="MTNNG_CLOUD_AZ_1", # also used as the cloud/zone
|
|
120
|
+
plan="G2S4", # plan name (or numeric id)
|
|
121
|
+
resource_pool="my-project", # pool name; auto-selected if the group has only one
|
|
115
122
|
)
|
|
116
|
-
print(
|
|
123
|
+
print(instance.id, instance.status, instance.primary_ip)
|
|
117
124
|
```
|
|
118
125
|
|
|
119
|
-
|
|
126
|
+
For full control over every ID, use `create()` directly:
|
|
120
127
|
|
|
121
128
|
```python
|
|
122
|
-
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
123
|
-
|
|
124
129
|
instance = cloud.instances.create(
|
|
125
|
-
name="
|
|
130
|
+
name="web-01",
|
|
126
131
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
127
132
|
type="MTN-CS10",
|
|
128
133
|
group="MTNNG_CLOUD_AZ_1",
|
|
129
134
|
layout=327,
|
|
130
135
|
plan=6776,
|
|
131
|
-
resource_pool_id=
|
|
136
|
+
resource_pool_id="pool-214", # or the numeric ID 214
|
|
132
137
|
labels=["production", "web"],
|
|
133
138
|
)
|
|
134
|
-
|
|
135
|
-
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
136
139
|
```
|
|
137
140
|
|
|
138
141
|
### 3. Manage an Instance
|
|
@@ -360,6 +363,8 @@ Environment variables:
|
|
|
360
363
|
| `MTN_CLOUD_MAX_RETRIES` | Maximum retry attempts | `3` |
|
|
361
364
|
| `MTN_CLOUD_RETRY_DELAY` | Retry backoff factor | `1.0` |
|
|
362
365
|
| `MTN_CLOUD_VERIFY_SSL` | Enable SSL verification | `true` |
|
|
366
|
+
| `MTN_CLOUD_USER_AGENT` | Application identity appended to the SDK user agent | - |
|
|
367
|
+
| `MTN_CLOUD_DEBUG` | Enable bounded, secret-redacted HTTP debug logs | `false` |
|
|
363
368
|
|
|
364
369
|
Programmatic configuration:
|
|
365
370
|
|
|
@@ -372,11 +377,21 @@ config = MTNCloudConfig(
|
|
|
372
377
|
max_retries=5,
|
|
373
378
|
retry_delay=1.5,
|
|
374
379
|
verify_ssl=True,
|
|
380
|
+
user_agent="my-automation/1.0", # appended after the required SDK identity
|
|
375
381
|
)
|
|
376
382
|
|
|
377
383
|
cloud = MTNCloud(config=config)
|
|
378
384
|
```
|
|
379
385
|
|
|
386
|
+
Tokens and passwords are stored as masked Pydantic secret values, and debug
|
|
387
|
+
logging recursively redacts credential-shaped fields. The transport retries
|
|
388
|
+
transient failures only for safe read methods (`GET`, `HEAD`, and `OPTIONS`);
|
|
389
|
+
create, update, action, and delete requests are never status-retried automatically.
|
|
390
|
+
|
|
391
|
+
The default `mtn-cloud-python/<version>` user-agent prefix is required by the
|
|
392
|
+
MTN Cloud API edge. A configured `user_agent` is treated as an application
|
|
393
|
+
suffix so the SDK identity is preserved.
|
|
394
|
+
|
|
380
395
|
## Examples
|
|
381
396
|
|
|
382
397
|
| Script | What it demonstrates |
|
|
@@ -5,14 +5,16 @@ Production-friendly patterns for robust automation.
|
|
|
5
5
|
## Configure a Resilient Client
|
|
6
6
|
|
|
7
7
|
```python
|
|
8
|
-
from mtn_cloud import MTNCloud
|
|
8
|
+
from mtn_cloud import MTNCloud, MTNCloudConfig
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
config = MTNCloudConfig(
|
|
11
11
|
token="your-api-token",
|
|
12
12
|
timeout=45,
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
max_retries=3,
|
|
14
|
+
retry_delay=1.0,
|
|
15
|
+
user_agent="my-automation/1.0",
|
|
15
16
|
)
|
|
17
|
+
cloud = MTNCloud(config=config)
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
Use environment variables for deployment:
|
|
@@ -24,6 +26,15 @@ export MTN_CLOUD_MAX_RETRIES="5"
|
|
|
24
26
|
export MTN_CLOUD_RETRY_DELAY="1.5"
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
Retries apply only to safe reads (`GET`, `HEAD`, and `OPTIONS`). The SDK does
|
|
30
|
+
not automatically retry provisioning, actions, updates, or deletes because a
|
|
31
|
+
server or proxy error does not prove that a write was rejected before execution.
|
|
32
|
+
|
|
33
|
+
`user_agent` adds an application identity after the SDK's required
|
|
34
|
+
`mtn-cloud-python/<version>` prefix. Debug logging is bounded and recursively
|
|
35
|
+
redacts tokens, passwords, access keys, secret keys, cookies, authorization
|
|
36
|
+
values, and similar credential fields.
|
|
37
|
+
|
|
27
38
|
## Pattern: Idempotent "Get or Create"
|
|
28
39
|
|
|
29
40
|
```python
|
|
@@ -42,10 +42,11 @@ cloud.instance_types.get_by_code("MTN-CS10")
|
|
|
42
42
|
## Common Write Operations
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
# Guided: resolves layout, plan, and resource pool from names
|
|
46
|
+
cloud.instances.provision(name="web-01", type="MTN-CS10",
|
|
47
|
+
group="MTNNG_CLOUD_AZ_1", plan="G2S4", resource_pool="my-project")
|
|
48
|
+
# Explicit: full control over each ID
|
|
49
|
+
cloud.instances.create(..., resource_pool_id="pool-214")
|
|
49
50
|
cloud.instances.update(instance_id=123, name="new-name")
|
|
50
51
|
cloud.instances.delete(123)
|
|
51
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Client: `MTNCloud`
|
|
2
2
|
|
|
3
|
-
### `MTNCloud(token=None, username=None, password=None, url=None, timeout=None, verify_ssl=
|
|
3
|
+
### `MTNCloud(token=None, username=None, password=None, url=None, timeout=None, verify_ssl=None, config=None)`
|
|
4
4
|
|
|
5
5
|
Creates an SDK client and lazy-loads resource managers.
|
|
6
6
|
|
|
@@ -10,12 +10,27 @@ Creates an SDK client and lazy-loads resource managers.
|
|
|
10
10
|
- `username`, `password`: OAuth password-grant alternative
|
|
11
11
|
- `url`: MTN Cloud base console URL, no `/api` needed
|
|
12
12
|
- `timeout`: request timeout in seconds
|
|
13
|
-
- `verify_ssl`: enable/disable TLS certificate validation
|
|
13
|
+
- `verify_ssl`: enable/disable TLS certificate validation; when omitted,
|
|
14
|
+
`MTN_CLOUD_VERIFY_SSL` or the configuration default is used
|
|
14
15
|
- `config`: explicit `MTNCloudConfig` object (overrides other args)
|
|
15
16
|
- Returns: `MTNCloud`
|
|
16
17
|
- Raises:
|
|
17
18
|
- `pydantic.ValidationError` if config values violate `MTNCloudConfig` constraints
|
|
18
19
|
|
|
20
|
+
Tokens and passwords are stored as masked secret values. Use
|
|
21
|
+
`config.get_token_value()` or `config.get_password_value()` only when an explicit
|
|
22
|
+
integration needs the underlying value; normal SDK usage never needs to reveal
|
|
23
|
+
them.
|
|
24
|
+
|
|
25
|
+
The transport:
|
|
26
|
+
|
|
27
|
+
- preserves the `mtn-cloud-python/<version>` user-agent identity required by the
|
|
28
|
+
MTN API edge;
|
|
29
|
+
- recursively redacts secrets from debug logs and raised error response payloads;
|
|
30
|
+
- retries 429 and transient 5xx responses only for `GET`, `HEAD`, and `OPTIONS`;
|
|
31
|
+
- does not automatically status-retry `POST`, `PUT`, `PATCH`, or `DELETE` calls;
|
|
32
|
+
- handles both delta-seconds and HTTP-date `Retry-After` headers.
|
|
33
|
+
|
|
19
34
|
### `whoami() -> User`
|
|
20
35
|
|
|
21
36
|
- Endpoint: `GET /api/whoami`
|
|
@@ -41,4 +56,3 @@ Connectivity/auth convenience check.
|
|
|
41
56
|
- Parameters: none
|
|
42
57
|
- Returns: `None`
|
|
43
58
|
- Raises: none
|
|
44
|
-
|
|
@@ -61,6 +61,31 @@
|
|
|
61
61
|
- common API exceptions
|
|
62
62
|
- `NotFoundError` when `group` cannot be resolved
|
|
63
63
|
|
|
64
|
+
### `provision(name: str, *, type: str, group: str, plan: str | int, resource_pool=None, cloud=None, availability_zone=None, security_group="default", wait=True, timeout=600, dry_run=False, **create_kwargs) -> Instance | dict`
|
|
65
|
+
|
|
66
|
+
Guided wrapper over `create()` that resolves IDs from names.
|
|
67
|
+
|
|
68
|
+
- Endpoint sequence:
|
|
69
|
+
- `GET /api/groups?name=<group>` (resolve group → `cloud_ids`)
|
|
70
|
+
- `GET /api/instance-types?code=<type>` (resolve `layout` via `default_layout_id`)
|
|
71
|
+
- `GET /api/instances/service-plans` (resolve `plan` name → ID, unless `plan` is an int)
|
|
72
|
+
- `GET /api/options/zonePools` (resolve/auto-select `resource_pool`, unless given as int)
|
|
73
|
+
- `POST /api/instances`, then polls `GET /api/instances/{id}` when `wait=True`
|
|
74
|
+
- Parameters:
|
|
75
|
+
- `type`: instance type code; its `default_layout_id` becomes `layout`
|
|
76
|
+
- `group`: group name; also used as `cloud` unless `cloud` is given
|
|
77
|
+
- `plan`: plan name (resolved against the live list) or numeric ID
|
|
78
|
+
- `resource_pool`: pool name, code, or numeric ID. If omitted, the group's single pool is used; if it has several, a `ValidationError` lists them
|
|
79
|
+
- `cloud`: cloud/zone name; defaults to `group`
|
|
80
|
+
- `wait`: block until running (default `True`)
|
|
81
|
+
- `timeout`: max seconds to wait when `wait=True`
|
|
82
|
+
- `dry_run`: return the resolved config dict instead of creating
|
|
83
|
+
- `**create_kwargs`: forwarded to `create()` (labels, tags, volumes, network_interfaces, ports, …)
|
|
84
|
+
- Returns: the created `Instance` (running if `wait=True`), or a config `dict` when `dry_run=True`
|
|
85
|
+
- Raises:
|
|
86
|
+
- `NotFoundError` when the group or instance type can't be resolved
|
|
87
|
+
- `ValidationError` when the plan or resource pool can't be resolved
|
|
88
|
+
|
|
64
89
|
### `update(instance_id: int, name=None, description=None, labels=None) -> Instance`
|
|
65
90
|
|
|
66
91
|
- Endpoint: `PUT /api/instances/{instance_id}`
|
|
@@ -15,9 +15,10 @@ Use this guide in two ways:
|
|
|
15
15
|
5. [Security Groups](./security-groups.md)
|
|
16
16
|
6. [Backups](./backups.md)
|
|
17
17
|
7. [Virtual Images](./virtual-images.md)
|
|
18
|
-
8. [
|
|
19
|
-
9. [
|
|
20
|
-
10. [API
|
|
18
|
+
8. [Recipes](./recipes.md)
|
|
19
|
+
9. [Advanced Cookbook](./advanced-cookbook.md)
|
|
20
|
+
10. [API Overview](./api-overview.md)
|
|
21
|
+
11. [API Reference](./api-reference/index.md)
|
|
21
22
|
|
|
22
23
|
## Who This Is For
|
|
23
24
|
|
|
@@ -41,6 +42,7 @@ Use this guide in two ways:
|
|
|
41
42
|
5. Security Groups
|
|
42
43
|
6. Backups
|
|
43
44
|
7. Virtual Images
|
|
44
|
-
8.
|
|
45
|
-
9. API
|
|
46
|
-
10.
|
|
45
|
+
8. Recipes
|
|
46
|
+
9. API Overview
|
|
47
|
+
10. API Reference
|
|
48
|
+
11. Advanced Cookbook
|
|
@@ -106,15 +106,44 @@ If you already know the cloud and group IDs, pass them explicitly to skip the gr
|
|
|
106
106
|
pools = cloud.instances.list_resource_pools(cloud_id=4, group_id=621)
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
##
|
|
109
|
+
## Provision an Instance (guided)
|
|
110
|
+
|
|
111
|
+
`provision()` is the fastest way to create an instance: it takes the names you already know and resolves the numeric IDs for you — the layout from the type code, the plan from its name, and the resource pool from its name (or auto-selects it when the group has exactly one).
|
|
110
112
|
|
|
111
113
|
```python
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
group="MTNNG_CLOUD_AZ_1",
|
|
114
|
+
instance = cloud.instances.provision(
|
|
115
|
+
name="app-server-01",
|
|
116
|
+
type="MTN-CS10", # type code -> resolves the layout
|
|
117
|
+
group="MTNNG_CLOUD_AZ_1", # also used as the cloud/zone
|
|
118
|
+
plan="G2S4", # plan name (or pass a numeric plan ID)
|
|
119
|
+
resource_pool="my-project", # pool name; omit if the group has one pool
|
|
120
|
+
availability_zone="Lagos-AZ-1-fd1",
|
|
121
|
+
security_group="default",
|
|
116
122
|
)
|
|
117
123
|
|
|
124
|
+
# wait=True by default, so the instance is already running here
|
|
125
|
+
print(instance.id, instance.name, instance.status, instance.primary_ip)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If a name can't be resolved, the error tells you what's available (e.g. the valid plan names, or the pools to choose from). Set `wait=False` to return immediately, or `dry_run=True` to get the resolved config back without creating anything:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
config = cloud.instances.provision(
|
|
132
|
+
name="app-server-01", type="MTN-CS10", group="MTNNG_CLOUD_AZ_1",
|
|
133
|
+
plan="G2S4", resource_pool="my-project", dry_run=True,
|
|
134
|
+
)
|
|
135
|
+
# {"layout": 327, "plan": 6776, "resource_pool_id": "pool-214", ...}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Any extra keyword (labels, tags, volumes, network_interfaces, ports, …) passes straight through to `create()`.
|
|
139
|
+
|
|
140
|
+
## Create an Instance (explicit)
|
|
141
|
+
|
|
142
|
+
When you want full control over each ID, call `create()` directly. Resolve the inputs yourself first (see [Discover Required IDs](#discover-required-ids-and-codes)):
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
pool = cloud.instances.get_resource_pool("my-project", group="MTNNG_CLOUD_AZ_1")
|
|
146
|
+
|
|
118
147
|
instance = cloud.instances.create(
|
|
119
148
|
name="app-server-01",
|
|
120
149
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
@@ -124,7 +153,7 @@ instance = cloud.instances.create(
|
|
|
124
153
|
plan=6776, # use a plan ID from list_service_plans()
|
|
125
154
|
resource_pool_id=pool.code, # e.g. "pool-214" (or pass 214 directly)
|
|
126
155
|
availability_zone="Lagos-AZ-1-fd1",
|
|
127
|
-
security_group="default",
|
|
156
|
+
security_group="default",
|
|
128
157
|
)
|
|
129
158
|
|
|
130
159
|
print(instance.id, instance.name, instance.status, instance.primary_ip)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Recipes
|
|
2
|
+
|
|
3
|
+
Three complete, real-world workflows. Each assumes an authenticated client:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from mtn_cloud import MTNCloud
|
|
7
|
+
|
|
8
|
+
cloud = MTNCloud(token="your-api-token")
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For isolated patterns (retries, pagination, idempotency), see the [Advanced Cookbook](./advanced-cookbook.md).
|
|
12
|
+
|
|
13
|
+
## Provision a Linux VM with SSH
|
|
14
|
+
|
|
15
|
+
Discover the inputs, open SSH in a security group, provision, and wait until it's reachable.
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
19
|
+
itype = cloud.instance_types.get_by_code("MTN-CS10")
|
|
20
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
21
|
+
plans = cloud.instances.list_service_plans(
|
|
22
|
+
zone_id=group.cloud_ids[0], layout_id=itype.default_layout_id, group_id=group.id,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
sg = cloud.security_groups.create(name="linux-ssh", description="SSH access")
|
|
26
|
+
cloud.security_groups.create_rule(
|
|
27
|
+
sg.id, name="allow-ssh", direction="ingress", protocol="tcp",
|
|
28
|
+
port_range="22", source_type="cidr", source="200.200.113.15/32",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
instance = cloud.instances.create(
|
|
32
|
+
name="linux-web-01",
|
|
33
|
+
cloud=group.name,
|
|
34
|
+
type=itype.code,
|
|
35
|
+
group=group.name,
|
|
36
|
+
layout=itype.default_layout_id,
|
|
37
|
+
plan=plans[0]["id"],
|
|
38
|
+
resource_pool_id=pool.code,
|
|
39
|
+
availability_zone="Lagos-AZ-1-fd1",
|
|
40
|
+
security_group=sg.name,
|
|
41
|
+
create_user=True,
|
|
42
|
+
)
|
|
43
|
+
instance = cloud.instances.wait_until_running(instance.id, timeout=600)
|
|
44
|
+
print("ready:", instance.primary_ip)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
SSH **keys** are uploaded once in the console (**User Settings → SSH Keys**); the SDK controls the firewall rule and user creation. Then: `ssh <your-user>@<instance.primary_ip>`.
|
|
48
|
+
|
|
49
|
+
## Back up a folder to MTN Object Storage
|
|
50
|
+
|
|
51
|
+
Register the provider and bucket idempotently, preview with a dry run, then upload.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from mtn_cloud import NotFoundError
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
storage = cloud.storage_buckets.get_by_name("my-s3-storage")
|
|
58
|
+
except NotFoundError:
|
|
59
|
+
storage = cloud.storage_buckets.create_s3(
|
|
60
|
+
name="my-s3-storage",
|
|
61
|
+
bucket_name="my-app-objects",
|
|
62
|
+
access_key="your-access-key", # from MOS email
|
|
63
|
+
secret_key="your-secret-key", # from MOS email
|
|
64
|
+
endpoint="https://ps1csp-s3.ict.mtn.com.ng:9021",
|
|
65
|
+
create_bucket=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
archive = cloud.archive_buckets.get_by_name("my-app-archives")
|
|
70
|
+
except NotFoundError:
|
|
71
|
+
archive = cloud.archive_buckets.create(
|
|
72
|
+
name="my-app-archives", storage_provider_id=storage.id, visibility="private",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
preview = cloud.archive_buckets.upload_directory(
|
|
76
|
+
bucket_name=archive.name, remote_path="/backups/",
|
|
77
|
+
local_directory="./data", recursive=True, dry_run=True,
|
|
78
|
+
)
|
|
79
|
+
if preview.skipped_count:
|
|
80
|
+
raise RuntimeError(f"{preview.skipped_count} files would be skipped")
|
|
81
|
+
|
|
82
|
+
result = cloud.archive_buckets.upload_directory(
|
|
83
|
+
bucket_name=archive.name, remote_path="/backups/",
|
|
84
|
+
local_directory="./data", recursive=True, strict=True,
|
|
85
|
+
)
|
|
86
|
+
print(f"uploaded={result.uploaded_count} failed={result.failed_count}")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Tear down demo resources
|
|
90
|
+
|
|
91
|
+
Delete in dependency order — instances first, then the firewall and network they used. Deletes are best-effort so one failure doesn't block the rest.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from mtn_cloud import MTNCloudError
|
|
95
|
+
|
|
96
|
+
def cleanup(manager, items, label):
|
|
97
|
+
for item in items:
|
|
98
|
+
if (item.name or "").startswith("demo-"):
|
|
99
|
+
try:
|
|
100
|
+
manager.delete(item.id)
|
|
101
|
+
print("deleted", label, item.id)
|
|
102
|
+
except MTNCloudError as exc:
|
|
103
|
+
print("skip", label, item.id, exc)
|
|
104
|
+
|
|
105
|
+
cleanup(cloud.instances, cloud.instances.list(max_results=100), "instance")
|
|
106
|
+
cleanup(cloud.security_groups, cloud.security_groups.list(), "security-group")
|
|
107
|
+
cleanup(cloud.networks, cloud.networks.list(), "network")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A network or security group can't be deleted while an instance still references it — always remove instances first and let them finish.
|
|
@@ -21,7 +21,9 @@ keywords = [
|
|
|
21
21
|
"sdk",
|
|
22
22
|
"api",
|
|
23
23
|
"infrastructure",
|
|
24
|
-
"devops"
|
|
24
|
+
"devops",
|
|
25
|
+
"cloud-management",
|
|
26
|
+
"mtn-cloud-python"
|
|
25
27
|
]
|
|
26
28
|
classifiers = [
|
|
27
29
|
"Development Status :: 4 - Beta",
|
|
@@ -50,8 +52,6 @@ dependencies = [
|
|
|
50
52
|
dev = [
|
|
51
53
|
"pytest>=7.0.0",
|
|
52
54
|
"pytest-cov>=4.0.0",
|
|
53
|
-
"pytest-asyncio>=0.21.0",
|
|
54
|
-
"respx>=0.20.0",
|
|
55
55
|
"ruff>=0.1.0",
|
|
56
56
|
"mypy>=1.0.0",
|
|
57
57
|
"types-requests>=2.28.0",
|
|
@@ -121,4 +121,3 @@ ignore_missing_imports = true
|
|
|
121
121
|
|
|
122
122
|
[tool.pytest.ini_options]
|
|
123
123
|
testpaths = ["tests"]
|
|
124
|
-
asyncio_mode = "auto"
|
|
@@ -82,7 +82,7 @@ class MTNCloud:
|
|
|
82
82
|
password: str | None = None,
|
|
83
83
|
url: str | None = None,
|
|
84
84
|
timeout: float | None = None,
|
|
85
|
-
verify_ssl: bool =
|
|
85
|
+
verify_ssl: bool | None = None,
|
|
86
86
|
config: MTNCloudConfig | None = None,
|
|
87
87
|
) -> None:
|
|
88
88
|
"""
|
|
@@ -94,7 +94,8 @@ class MTNCloud:
|
|
|
94
94
|
password: Password for authentication
|
|
95
95
|
url: API base URL (default: https://console.cloud.mtn.ng)
|
|
96
96
|
timeout: Request timeout in seconds
|
|
97
|
-
verify_ssl: Verify SSL certificates
|
|
97
|
+
verify_ssl: Verify SSL certificates. When omitted, configuration and
|
|
98
|
+
environment settings are respected.
|
|
98
99
|
config: Full configuration object (overrides other args)
|
|
99
100
|
"""
|
|
100
101
|
if config:
|
|
@@ -102,17 +103,17 @@ class MTNCloud:
|
|
|
102
103
|
else:
|
|
103
104
|
config_kwargs: dict[str, Any] = {}
|
|
104
105
|
|
|
105
|
-
if token:
|
|
106
|
+
if token is not None:
|
|
106
107
|
config_kwargs["token"] = token
|
|
107
|
-
if username:
|
|
108
|
+
if username is not None:
|
|
108
109
|
config_kwargs["username"] = username
|
|
109
|
-
if password:
|
|
110
|
+
if password is not None:
|
|
110
111
|
config_kwargs["password"] = password
|
|
111
|
-
if url:
|
|
112
|
+
if url is not None:
|
|
112
113
|
config_kwargs["url"] = url
|
|
113
|
-
if timeout:
|
|
114
|
+
if timeout is not None:
|
|
114
115
|
config_kwargs["timeout"] = timeout
|
|
115
|
-
if not
|
|
116
|
+
if verify_ssl is not None:
|
|
116
117
|
config_kwargs["verify_ssl"] = verify_ssl
|
|
117
118
|
|
|
118
119
|
self._config = MTNCloudConfig(**config_kwargs)
|