mtn-cloud 0.2.19__tar.gz → 0.3.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.
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/PKG-INFO +32 -27
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/README.md +30 -23
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/advanced-cookbook.md +15 -5
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-overview.md +5 -4
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/instances.md +25 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/networks.md +5 -2
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/index.md +8 -7
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/instances.md +35 -6
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/networking.md +7 -3
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/quickstart.md +45 -16
- mtn_cloud-0.3.2/docs/recipes.md +110 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/pyproject.toml +3 -4
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/_version.py +1 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/http.py +2 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/base.py +7 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/network.py +3 -0
- mtn_cloud-0.3.2/src/mtn_cloud/py.typed +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/instances.py +185 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/networks.py +7 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_instances.py +110 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_networks.py +8 -0
- mtn_cloud-0.2.19/docs/docstring-style.md +0 -50
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/.gitignore +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/LICENSE +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/archive-buckets.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/backups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/client.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/clouds.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/index.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/instance-types.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/plans.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/security-groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/storage-buckets.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/virtual-images.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/backups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/security-groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/storage.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/stylesheets/extra.css +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/virtual-images.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/client.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/config.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/exceptions.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/archive.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/backup.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/cloud.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/group.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/instance.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/instance_type.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/plan.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/resource_pool.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/security_group.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/snapshot.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/storage_bucket.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/user.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/virtual_image.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/volume.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/archive_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/backups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/base.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/clouds.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/instance_types.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/plans.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/security_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/storage_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/virtual_images.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/conftest.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_archive_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_backups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_client.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_clouds.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_config.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_exceptions.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_instance_types.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_plans.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_security_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_storage_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_virtual_images.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mtn-cloud
|
|
3
|
-
Version: 0.2
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -84,7 +82,6 @@ pip install mtn-cloud
|
|
|
84
82
|
- [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
|
|
85
83
|
- [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
|
|
86
84
|
- [API Reference](https://mahveotm.github.io/mtn-cloud-python/api-reference/)
|
|
87
|
-
- [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
|
|
88
85
|
|
|
89
86
|
## Quick Start
|
|
90
87
|
|
|
@@ -151,33 +148,36 @@ for plan in cloud.instances.list_service_plans(
|
|
|
151
148
|
print(plan["id"], plan["name"])
|
|
152
149
|
```
|
|
153
150
|
|
|
154
|
-
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:
|
|
155
156
|
|
|
156
157
|
```python
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
|
|
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
|
|
160
164
|
)
|
|
161
|
-
print(
|
|
165
|
+
print(instance.id, instance.status, instance.primary_ip)
|
|
162
166
|
```
|
|
163
167
|
|
|
164
|
-
|
|
168
|
+
For full control over every ID, use `create()` directly:
|
|
165
169
|
|
|
166
170
|
```python
|
|
167
|
-
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
168
|
-
|
|
169
171
|
instance = cloud.instances.create(
|
|
170
|
-
name="
|
|
172
|
+
name="web-01",
|
|
171
173
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
172
174
|
type="MTN-CS10",
|
|
173
175
|
group="MTNNG_CLOUD_AZ_1",
|
|
174
176
|
layout=327,
|
|
175
177
|
plan=6776,
|
|
176
|
-
resource_pool_id=
|
|
178
|
+
resource_pool_id="pool-214", # or the numeric ID 214
|
|
177
179
|
labels=["production", "web"],
|
|
178
180
|
)
|
|
179
|
-
|
|
180
|
-
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
### 3. Manage an Instance
|
|
@@ -200,27 +200,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
200
200
|
### 4. Work with Networks
|
|
201
201
|
|
|
202
202
|
```python
|
|
203
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
204
|
+
|
|
203
205
|
# List networks
|
|
204
|
-
networks = cloud.networks.list(cloud_id=
|
|
206
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
205
207
|
for n in networks[:5]:
|
|
206
208
|
print(n.id, n.name, n.cidr)
|
|
207
209
|
|
|
208
|
-
# Create an OpenStack
|
|
209
|
-
|
|
210
|
+
# Create an OpenStack network (needs an OpenStack type + a resource pool)
|
|
211
|
+
net_type = next(
|
|
212
|
+
t for t in cloud.networks.list_types(openstack_only=True)
|
|
213
|
+
if t.code == "openstackPrivate"
|
|
214
|
+
)
|
|
215
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
216
|
+
|
|
210
217
|
new_network = cloud.networks.create(
|
|
211
218
|
name="mtn-prod-net",
|
|
212
|
-
cloud_id=
|
|
213
|
-
group_id=
|
|
214
|
-
type_id=
|
|
219
|
+
cloud_id=group.cloud_ids[0],
|
|
220
|
+
group_id=group.id,
|
|
221
|
+
type_id=net_type.id,
|
|
222
|
+
resource_pool_id=pool.id,
|
|
215
223
|
cidr="10.42.10.0/24",
|
|
216
224
|
gateway="10.42.10.1",
|
|
217
225
|
dns_primary="8.8.8.8",
|
|
218
|
-
visibility="private",
|
|
219
|
-
dhcp_server=True,
|
|
220
226
|
)
|
|
221
227
|
|
|
222
|
-
#
|
|
223
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
228
|
+
# Inspect subnets
|
|
224
229
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
225
230
|
print(f"Subnets: {len(subnets)}")
|
|
226
231
|
```
|
|
@@ -40,7 +40,6 @@ pip install mtn-cloud
|
|
|
40
40
|
- [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
|
|
41
41
|
- [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
|
|
42
42
|
- [API Reference](https://mahveotm.github.io/mtn-cloud-python/api-reference/)
|
|
43
|
-
- [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
|
|
44
43
|
|
|
45
44
|
## Quick Start
|
|
46
45
|
|
|
@@ -107,33 +106,36 @@ for plan in cloud.instances.list_service_plans(
|
|
|
107
106
|
print(plan["id"], plan["name"])
|
|
108
107
|
```
|
|
109
108
|
|
|
110
|
-
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:
|
|
111
114
|
|
|
112
115
|
```python
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
|
|
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
|
|
116
122
|
)
|
|
117
|
-
print(
|
|
123
|
+
print(instance.id, instance.status, instance.primary_ip)
|
|
118
124
|
```
|
|
119
125
|
|
|
120
|
-
|
|
126
|
+
For full control over every ID, use `create()` directly:
|
|
121
127
|
|
|
122
128
|
```python
|
|
123
|
-
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
124
|
-
|
|
125
129
|
instance = cloud.instances.create(
|
|
126
|
-
name="
|
|
130
|
+
name="web-01",
|
|
127
131
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
128
132
|
type="MTN-CS10",
|
|
129
133
|
group="MTNNG_CLOUD_AZ_1",
|
|
130
134
|
layout=327,
|
|
131
135
|
plan=6776,
|
|
132
|
-
resource_pool_id=
|
|
136
|
+
resource_pool_id="pool-214", # or the numeric ID 214
|
|
133
137
|
labels=["production", "web"],
|
|
134
138
|
)
|
|
135
|
-
|
|
136
|
-
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
137
139
|
```
|
|
138
140
|
|
|
139
141
|
### 3. Manage an Instance
|
|
@@ -156,27 +158,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
156
158
|
### 4. Work with Networks
|
|
157
159
|
|
|
158
160
|
```python
|
|
161
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
162
|
+
|
|
159
163
|
# List networks
|
|
160
|
-
networks = cloud.networks.list(cloud_id=
|
|
164
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
161
165
|
for n in networks[:5]:
|
|
162
166
|
print(n.id, n.name, n.cidr)
|
|
163
167
|
|
|
164
|
-
# Create an OpenStack
|
|
165
|
-
|
|
168
|
+
# Create an OpenStack network (needs an OpenStack type + a resource pool)
|
|
169
|
+
net_type = next(
|
|
170
|
+
t for t in cloud.networks.list_types(openstack_only=True)
|
|
171
|
+
if t.code == "openstackPrivate"
|
|
172
|
+
)
|
|
173
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
174
|
+
|
|
166
175
|
new_network = cloud.networks.create(
|
|
167
176
|
name="mtn-prod-net",
|
|
168
|
-
cloud_id=
|
|
169
|
-
group_id=
|
|
170
|
-
type_id=
|
|
177
|
+
cloud_id=group.cloud_ids[0],
|
|
178
|
+
group_id=group.id,
|
|
179
|
+
type_id=net_type.id,
|
|
180
|
+
resource_pool_id=pool.id,
|
|
171
181
|
cidr="10.42.10.0/24",
|
|
172
182
|
gateway="10.42.10.1",
|
|
173
183
|
dns_primary="8.8.8.8",
|
|
174
|
-
visibility="private",
|
|
175
|
-
dhcp_server=True,
|
|
176
184
|
)
|
|
177
185
|
|
|
178
|
-
#
|
|
179
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
186
|
+
# Inspect subnets
|
|
180
187
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
181
188
|
print(f"Subnets: {len(subnets)}")
|
|
182
189
|
```
|
|
@@ -103,9 +103,10 @@ from mtn_cloud import (
|
|
|
103
103
|
try:
|
|
104
104
|
cloud.networks.create(
|
|
105
105
|
name="prod-net",
|
|
106
|
-
cloud_id=
|
|
106
|
+
cloud_id=4,
|
|
107
107
|
group_id=621,
|
|
108
|
-
type_id=
|
|
108
|
+
type_id=12, # an OpenStack type from list_types(openstack_only=True)
|
|
109
|
+
resource_pool_id=230, # numeric pool ID from get_resource_pool(...).id
|
|
109
110
|
cidr="10.42.10.0/24",
|
|
110
111
|
)
|
|
111
112
|
except AuthenticationError:
|
|
@@ -149,8 +150,17 @@ with MTNCloud(token="your-api-token") as cloud:
|
|
|
149
150
|
Pick explicit templates and plans instead of "first result" in production.
|
|
150
151
|
|
|
151
152
|
```python
|
|
153
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
152
154
|
target_type = cloud.instance_types.get_by_code("MTN-U24.04LTS")
|
|
153
|
-
|
|
155
|
+
|
|
156
|
+
plans = cloud.instances.list_service_plans(
|
|
157
|
+
zone_id=group.cloud_ids[0],
|
|
158
|
+
layout_id=target_type.default_layout_id,
|
|
159
|
+
group_id=group.id,
|
|
160
|
+
)
|
|
161
|
+
target_plan = next(p for p in plans if p["name"] == "G2S4")
|
|
162
|
+
|
|
163
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
154
164
|
|
|
155
165
|
instance = cloud.instances.create(
|
|
156
166
|
name="api-worker-01",
|
|
@@ -158,8 +168,8 @@ instance = cloud.instances.create(
|
|
|
158
168
|
type=target_type.code,
|
|
159
169
|
group="MTNNG_CLOUD_AZ_1",
|
|
160
170
|
layout=target_type.default_layout_id,
|
|
161
|
-
plan=target_plan
|
|
162
|
-
resource_pool_id=
|
|
171
|
+
plan=target_plan["id"],
|
|
172
|
+
resource_pool_id=pool.code,
|
|
163
173
|
)
|
|
164
174
|
```
|
|
165
175
|
|
|
@@ -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
|
|
|
@@ -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}`
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- Returns: `list[Network]`
|
|
33
33
|
- Raises: common API exceptions
|
|
34
34
|
|
|
35
|
-
### `create(name: str, *, cloud_id: int, group_id: int, type_id=None, display_name=None, labels=None, description=None, cidr=None, gateway=None, dns_primary=None, dns_secondary=None, vlan_id=None, switch_id=None, pool_id=None, allow_static_override=None, assign_public_ip=None, active=None, dhcp_server=None, network_domain_id=None, search_domains=None, network_proxy_id=None, appliance_url_proxy_bypass=None, no_proxy=None, visibility=None, tenant_ids=None, resource_permission_all=None, resource_permission_site_ids=None) -> Network`
|
|
35
|
+
### `create(name: str, *, cloud_id: int, group_id: int, type_id=None, resource_pool_id=None, display_name=None, labels=None, description=None, cidr=None, gateway=None, dns_primary=None, dns_secondary=None, vlan_id=None, switch_id=None, pool_id=None, allow_static_override=None, assign_public_ip=None, active=None, dhcp_server=None, network_domain_id=None, search_domains=None, network_proxy_id=None, appliance_url_proxy_bypass=None, no_proxy=None, visibility=None, tenant_ids=None, resource_permission_all=None, resource_permission_site_ids=None) -> Network`
|
|
36
36
|
|
|
37
37
|
- Endpoint: `POST /api/networks`
|
|
38
38
|
- Parameters:
|
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
- `name`: network name
|
|
41
41
|
- `cloud_id`: cloud/zone ID
|
|
42
42
|
- `group_id`: group/site ID
|
|
43
|
+
- Required for OpenStack networks (e.g. `openstackPrivate`):
|
|
44
|
+
- `type_id`: an OpenStack network type ID from `list_types(openstack_only=True)`
|
|
45
|
+
- `resource_pool_id`: numeric resource pool ID (sent as `zonePool`); get it from `instances.get_resource_pool(...).id`
|
|
43
46
|
- Common optional network config:
|
|
44
|
-
- `
|
|
47
|
+
- `display_name`, `labels`, `description`, `cidr`, `gateway`
|
|
45
48
|
- `dns_primary`, `dns_secondary`, `vlan_id`, `switch_id`, `pool_id`
|
|
46
49
|
- `allow_static_override`, `assign_public_ip`, `active`, `dhcp_server`
|
|
47
50
|
- `network_domain_id`, `search_domains`, `network_proxy_id`
|
|
@@ -15,10 +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
|
|
21
|
-
11. [
|
|
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)
|
|
22
22
|
|
|
23
23
|
## Who This Is For
|
|
24
24
|
|
|
@@ -42,6 +42,7 @@ Use this guide in two ways:
|
|
|
42
42
|
5. Security Groups
|
|
43
43
|
6. Backups
|
|
44
44
|
7. Virtual Images
|
|
45
|
-
8.
|
|
46
|
-
9. API
|
|
47
|
-
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)
|
|
@@ -33,21 +33,25 @@ print([(t.id, t.name) for t in network_types[:5]])
|
|
|
33
33
|
|
|
34
34
|
## Create a Network
|
|
35
35
|
|
|
36
|
+
An OpenStack network requires an OpenStack `type_id` and a `resource_pool_id` (the numeric pool ID — the network is created inside that resource pool). Pick a private network type and a pool:
|
|
37
|
+
|
|
36
38
|
```python
|
|
37
39
|
networks = cloud.networks.list(cloud_id=cloud_id)
|
|
38
40
|
for network in networks[:5]:
|
|
39
41
|
print(network.id, network.name, network.cidr)
|
|
40
42
|
|
|
43
|
+
net_type = next(t for t in network_types if t.code == "openstackPrivate")
|
|
44
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
45
|
+
|
|
41
46
|
new_network = cloud.networks.create(
|
|
42
47
|
name="mtn-prod-net",
|
|
43
48
|
cloud_id=cloud_id, # from group.cloud_ids[0]
|
|
44
49
|
group_id=group.id, # from groups.get_by_name(...)
|
|
45
|
-
type_id=
|
|
50
|
+
type_id=net_type.id, # an OpenStack type from list_types(openstack_only=True)
|
|
51
|
+
resource_pool_id=pool.id, # numeric pool ID from get_resource_pool(...)
|
|
46
52
|
cidr="10.42.10.0/24",
|
|
47
53
|
gateway="10.42.10.1",
|
|
48
54
|
dns_primary="8.8.8.8",
|
|
49
|
-
visibility="private",
|
|
50
|
-
dhcp_server=True, # enables automatic IP assignment
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
print(new_network.id, new_network.name)
|
|
@@ -57,11 +57,28 @@ One tenancy can have multiple resource pools — useful for separating dev, stag
|
|
|
57
57
|
|
|
58
58
|
Security groups are the firewall layer for your VMs. Without one, you will not be able to SSH or RDP in after provisioning.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
2. Add an inbound rule: SSH port 22 (TCP) for Linux VMs, or RDP port 3389 (TCP) for Windows VMs.
|
|
62
|
-
3. Restrict the source to your IP range, e.g., `200.200.113.15/32` for a single machine.
|
|
60
|
+
You can create one either in the console (**Infrastructure → Network → Security Groups**) or directly with the SDK:
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
```python
|
|
63
|
+
# Create the group
|
|
64
|
+
sg = cloud.security_groups.create(
|
|
65
|
+
name="web-servers",
|
|
66
|
+
description="SSH + HTTPS access",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Allow SSH from a single machine (use 3389/tcp instead for Windows RDP)
|
|
70
|
+
cloud.security_groups.create_rule(
|
|
71
|
+
sg.id,
|
|
72
|
+
name="allow-ssh",
|
|
73
|
+
direction="ingress",
|
|
74
|
+
protocol="tcp",
|
|
75
|
+
port_range="22",
|
|
76
|
+
source_type="cidr",
|
|
77
|
+
source="200.200.113.15/32", # restrict to your IP range
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The group name (e.g., `"web-servers"` or `"default"`) is passed as the `security_group` parameter when creating an instance. See the [Security Groups guide](./security-groups.md) for the full workflow.
|
|
65
82
|
|
|
66
83
|
Optionally, add an SSH key pair under **User Settings → SSH Keys** for key-based access.
|
|
67
84
|
|
|
@@ -77,18 +94,30 @@ Keep the credentials from that email — you will pass them directly into the SD
|
|
|
77
94
|
|
|
78
95
|
## 5. Discover Core Reference Data
|
|
79
96
|
|
|
80
|
-
Before creating resources, look up the valid IDs and codes for your account. These are tenant-specific and cannot be copied from documentation or other accounts.
|
|
97
|
+
Before creating resources, look up the valid IDs and codes for your account. These are tenant-specific and cannot be copied from documentation or other accounts. Everything below uses permission-safe endpoints — the admin-level `clouds.list()` and `plans.list()` are restricted on most tenant accounts and aren't needed.
|
|
81
98
|
|
|
82
99
|
```python
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
# Group — also carries the cloud/zone IDs you need
|
|
101
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
102
|
+
cloud_id = group.cloud_ids[0]
|
|
103
|
+
|
|
104
|
+
# Instance type — carries its own default_layout_id
|
|
105
|
+
itype = cloud.instance_types.get_by_code("MTN-CS10")
|
|
106
|
+
|
|
107
|
+
# Resource pool — the resource_pool_id, where the instance is hosted
|
|
108
|
+
pools = cloud.instances.list_resource_pools(group=group.name)
|
|
109
|
+
|
|
110
|
+
# Service plans — CPU/memory/storage tiers for this zone + layout
|
|
111
|
+
plans = cloud.instances.list_service_plans(
|
|
112
|
+
zone_id=cloud_id,
|
|
113
|
+
layout_id=itype.default_layout_id,
|
|
114
|
+
group_id=group.id,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
print("group:", group.id, "cloud_id:", cloud_id)
|
|
118
|
+
print("layout:", itype.default_layout_id)
|
|
119
|
+
print("pools:", [(p.code, p.name) for p in pools[:5]])
|
|
120
|
+
print("plans:", [(pl["id"], pl["name"]) for pl in plans[:5]])
|
|
92
121
|
```
|
|
93
122
|
|
|
94
123
|
MTN Cloud currently has two live zones: **Lagos AZ1** (`MTNNG_CLOUD_AZ_1`) and **Lagos AZ2** (`MTNNG_CLOUD_AZ_2`).
|
|
@@ -105,9 +134,9 @@ instance = cloud.instances.create(
|
|
|
105
134
|
group="MTNNG_CLOUD_AZ_1",
|
|
106
135
|
layout=327,
|
|
107
136
|
plan=6923,
|
|
108
|
-
resource_pool_id=
|
|
137
|
+
resource_pool_id=pools[0].code, # from list_resource_pools() above
|
|
109
138
|
availability_zone="Lagos-AZ-1-fd1",
|
|
110
|
-
security_group="
|
|
139
|
+
security_group="web-servers", # the security group you created above
|
|
111
140
|
)
|
|
112
141
|
|
|
113
142
|
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.
|