mtn-cloud 0.2.17__tar.gz → 0.3.1__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.17 → mtn_cloud-0.3.1}/PKG-INFO +49 -23
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/README.md +48 -22
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/advanced-cookbook.md +15 -5
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-overview.md +5 -1
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/instances.md +35 -2
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/networks.md +21 -22
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/index.md +0 -1
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/instances.md +55 -11
- mtn_cloud-0.3.1/docs/networking.md +100 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/quickstart.md +45 -16
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/_version.py +1 -1
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/client.py +0 -3
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/config.py +1 -7
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/http.py +0 -13
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/__init__.py +9 -2
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/base.py +10 -7
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/cloud.py +0 -10
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/group.py +0 -8
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance.py +3 -55
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance_type.py +0 -8
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/network.py +46 -19
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/plan.py +0 -8
- mtn_cloud-0.3.1/src/mtn_cloud/models/resource_pool.py +50 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/user.py +0 -6
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/volume.py +1 -10
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instances.py +202 -15
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/networks.py +70 -46
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/security_groups.py +1 -1
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/tests/conftest.py +20 -3
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/tests/test_archive_buckets.py +62 -76
- mtn_cloud-0.3.1/tests/test_backups.py +163 -0
- mtn_cloud-0.3.1/tests/test_client.py +230 -0
- mtn_cloud-0.3.1/tests/test_clouds.py +102 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/tests/test_config.py +65 -58
- mtn_cloud-0.3.1/tests/test_groups.py +69 -0
- mtn_cloud-0.3.1/tests/test_instance_types.py +370 -0
- mtn_cloud-0.3.1/tests/test_instances.py +549 -0
- mtn_cloud-0.3.1/tests/test_networks.py +453 -0
- mtn_cloud-0.3.1/tests/test_plans.py +107 -0
- mtn_cloud-0.3.1/tests/test_security_groups.py +239 -0
- mtn_cloud-0.3.1/tests/test_storage_buckets.py +256 -0
- mtn_cloud-0.3.1/tests/test_virtual_images.py +117 -0
- mtn_cloud-0.2.17/docs/docstring-style.md +0 -50
- mtn_cloud-0.2.17/docs/networking.md +0 -100
- mtn_cloud-0.2.17/tests/test_client.py +0 -183
- mtn_cloud-0.2.17/tests/test_clouds.py +0 -50
- mtn_cloud-0.2.17/tests/test_instance_types.py +0 -325
- mtn_cloud-0.2.17/tests/test_instances.py +0 -367
- mtn_cloud-0.2.17/tests/test_networks.py +0 -256
- mtn_cloud-0.2.17/tests/test_storage_buckets.py +0 -152
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/.gitignore +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/LICENSE +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/archive-buckets.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/backups.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/client.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/clouds.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/groups.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/index.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/instance-types.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/plans.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/security-groups.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/storage-buckets.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/api-reference/virtual-images.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/backups.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/security-groups.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/storage.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/stylesheets/extra.css +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/docs/virtual-images.md +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/pyproject.toml +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/__init__.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/exceptions.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/archive.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/backup.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/security_group.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/snapshot.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/storage_bucket.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/models/virtual_image.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/__init__.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/archive_buckets.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/backups.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/base.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/clouds.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/groups.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instance_types.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/plans.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/storage_buckets.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/virtual_images.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/tests/__init__.py +0 -0
- {mtn_cloud-0.2.17 → mtn_cloud-0.3.1}/tests/test_exceptions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mtn-cloud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|
|
@@ -84,7 +84,6 @@ pip install mtn-cloud
|
|
|
84
84
|
- [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
|
|
85
85
|
- [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
|
|
86
86
|
- [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
87
|
|
|
89
88
|
## Quick Start
|
|
90
89
|
|
|
@@ -127,23 +126,45 @@ User Icon (top-right) -> User Settings -> API Access.
|
|
|
127
126
|
|
|
128
127
|
### 1. Discover Reference Data
|
|
129
128
|
|
|
130
|
-
Use these lookups before provisioning so your scripts stay deterministic.
|
|
129
|
+
Use these lookups before provisioning so your scripts stay deterministic. They all use permission-safe endpoints (the admin-level `clouds`/`plans` endpoints are restricted on most tenant accounts — you don't need them).
|
|
131
130
|
|
|
132
131
|
```python
|
|
133
|
-
# Groups (sites)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
132
|
+
# Groups (sites) — also carry the cloud/zone IDs you need
|
|
133
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
134
|
+
print(group.id, group.name, group.cloud_ids)
|
|
135
|
+
|
|
136
|
+
# Instance types — each carries its own default_layout_id
|
|
137
|
+
itype = cloud.instance_types.get_by_code("MTN-CS10")
|
|
138
|
+
print(itype.code, itype.default_layout_id)
|
|
139
|
+
|
|
140
|
+
# Resource pools — where the instance is hosted (the resource_pool_id)
|
|
141
|
+
for pool in cloud.instances.list_resource_pools(group="MTNNG_CLOUD_AZ_1"):
|
|
142
|
+
print(pool.code, pool.name)
|
|
143
|
+
|
|
144
|
+
# Service plans — CPU/memory/storage tiers for this zone + layout
|
|
145
|
+
for plan in cloud.instances.list_service_plans(
|
|
146
|
+
zone_id=group.cloud_ids[0],
|
|
147
|
+
layout_id=itype.default_layout_id,
|
|
148
|
+
group_id=group.id,
|
|
149
|
+
):
|
|
150
|
+
print(plan["id"], plan["name"])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A **resource pool is required** to create an instance — it's where the VM is hosted. Discover it by group name (the cloud/zone is resolved for you), or fetch one directly by name:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
pool = cloud.instances.get_resource_pool(
|
|
157
|
+
"my-project-name",
|
|
158
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
159
|
+
)
|
|
160
|
+
print(pool.code) # e.g. "pool-214" — pass this as resource_pool_id
|
|
142
161
|
```
|
|
143
162
|
|
|
144
163
|
### 2. Create an Instance
|
|
145
164
|
|
|
146
165
|
```python
|
|
166
|
+
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
167
|
+
|
|
147
168
|
instance = cloud.instances.create(
|
|
148
169
|
name="my-server",
|
|
149
170
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
@@ -151,7 +172,7 @@ instance = cloud.instances.create(
|
|
|
151
172
|
group="MTNNG_CLOUD_AZ_1",
|
|
152
173
|
layout=327,
|
|
153
174
|
plan=6776,
|
|
154
|
-
resource_pool_id="pool-214"
|
|
175
|
+
resource_pool_id=pool.code, # "pool-214" — or pass the numeric ID 214
|
|
155
176
|
labels=["production", "web"],
|
|
156
177
|
)
|
|
157
178
|
|
|
@@ -178,27 +199,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
178
199
|
### 4. Work with Networks
|
|
179
200
|
|
|
180
201
|
```python
|
|
202
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
203
|
+
|
|
181
204
|
# List networks
|
|
182
|
-
networks = cloud.networks.list(cloud_id=
|
|
205
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
183
206
|
for n in networks[:5]:
|
|
184
207
|
print(n.id, n.name, n.cidr)
|
|
185
208
|
|
|
186
|
-
# Create an OpenStack
|
|
187
|
-
|
|
209
|
+
# Create an OpenStack network (needs an OpenStack type + a resource pool)
|
|
210
|
+
net_type = next(
|
|
211
|
+
t for t in cloud.networks.list_types(openstack_only=True)
|
|
212
|
+
if t.code == "openstackPrivate"
|
|
213
|
+
)
|
|
214
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
215
|
+
|
|
188
216
|
new_network = cloud.networks.create(
|
|
189
217
|
name="mtn-prod-net",
|
|
190
|
-
cloud_id=
|
|
191
|
-
group_id=
|
|
192
|
-
type_id=
|
|
218
|
+
cloud_id=group.cloud_ids[0],
|
|
219
|
+
group_id=group.id,
|
|
220
|
+
type_id=net_type.id,
|
|
221
|
+
resource_pool_id=pool.id,
|
|
193
222
|
cidr="10.42.10.0/24",
|
|
194
223
|
gateway="10.42.10.1",
|
|
195
224
|
dns_primary="8.8.8.8",
|
|
196
|
-
visibility="private",
|
|
197
|
-
dhcp_server=True,
|
|
198
225
|
)
|
|
199
226
|
|
|
200
|
-
#
|
|
201
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
227
|
+
# Inspect subnets
|
|
202
228
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
203
229
|
print(f"Subnets: {len(subnets)}")
|
|
204
230
|
```
|
|
@@ -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
|
|
|
@@ -83,23 +82,45 @@ User Icon (top-right) -> User Settings -> API Access.
|
|
|
83
82
|
|
|
84
83
|
### 1. Discover Reference Data
|
|
85
84
|
|
|
86
|
-
Use these lookups before provisioning so your scripts stay deterministic.
|
|
85
|
+
Use these lookups before provisioning so your scripts stay deterministic. They all use permission-safe endpoints (the admin-level `clouds`/`plans` endpoints are restricted on most tenant accounts — you don't need them).
|
|
87
86
|
|
|
88
87
|
```python
|
|
89
|
-
# Groups (sites)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
88
|
+
# Groups (sites) — also carry the cloud/zone IDs you need
|
|
89
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
90
|
+
print(group.id, group.name, group.cloud_ids)
|
|
91
|
+
|
|
92
|
+
# Instance types — each carries its own default_layout_id
|
|
93
|
+
itype = cloud.instance_types.get_by_code("MTN-CS10")
|
|
94
|
+
print(itype.code, itype.default_layout_id)
|
|
95
|
+
|
|
96
|
+
# Resource pools — where the instance is hosted (the resource_pool_id)
|
|
97
|
+
for pool in cloud.instances.list_resource_pools(group="MTNNG_CLOUD_AZ_1"):
|
|
98
|
+
print(pool.code, pool.name)
|
|
99
|
+
|
|
100
|
+
# Service plans — CPU/memory/storage tiers for this zone + layout
|
|
101
|
+
for plan in cloud.instances.list_service_plans(
|
|
102
|
+
zone_id=group.cloud_ids[0],
|
|
103
|
+
layout_id=itype.default_layout_id,
|
|
104
|
+
group_id=group.id,
|
|
105
|
+
):
|
|
106
|
+
print(plan["id"], plan["name"])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A **resource pool is required** to create an instance — it's where the VM is hosted. Discover it by group name (the cloud/zone is resolved for you), or fetch one directly by name:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
pool = cloud.instances.get_resource_pool(
|
|
113
|
+
"my-project-name",
|
|
114
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
115
|
+
)
|
|
116
|
+
print(pool.code) # e.g. "pool-214" — pass this as resource_pool_id
|
|
98
117
|
```
|
|
99
118
|
|
|
100
119
|
### 2. Create an Instance
|
|
101
120
|
|
|
102
121
|
```python
|
|
122
|
+
pool = cloud.instances.get_resource_pool("my-project-name", group="MTNNG_CLOUD_AZ_1")
|
|
123
|
+
|
|
103
124
|
instance = cloud.instances.create(
|
|
104
125
|
name="my-server",
|
|
105
126
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
@@ -107,7 +128,7 @@ instance = cloud.instances.create(
|
|
|
107
128
|
group="MTNNG_CLOUD_AZ_1",
|
|
108
129
|
layout=327,
|
|
109
130
|
plan=6776,
|
|
110
|
-
resource_pool_id="pool-214"
|
|
131
|
+
resource_pool_id=pool.code, # "pool-214" — or pass the numeric ID 214
|
|
111
132
|
labels=["production", "web"],
|
|
112
133
|
)
|
|
113
134
|
|
|
@@ -134,27 +155,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
134
155
|
### 4. Work with Networks
|
|
135
156
|
|
|
136
157
|
```python
|
|
158
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
159
|
+
|
|
137
160
|
# List networks
|
|
138
|
-
networks = cloud.networks.list(cloud_id=
|
|
161
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
139
162
|
for n in networks[:5]:
|
|
140
163
|
print(n.id, n.name, n.cidr)
|
|
141
164
|
|
|
142
|
-
# Create an OpenStack
|
|
143
|
-
|
|
165
|
+
# Create an OpenStack network (needs an OpenStack type + a resource pool)
|
|
166
|
+
net_type = next(
|
|
167
|
+
t for t in cloud.networks.list_types(openstack_only=True)
|
|
168
|
+
if t.code == "openstackPrivate"
|
|
169
|
+
)
|
|
170
|
+
pool = cloud.instances.get_resource_pool("my-project", group=group.name)
|
|
171
|
+
|
|
144
172
|
new_network = cloud.networks.create(
|
|
145
173
|
name="mtn-prod-net",
|
|
146
|
-
cloud_id=
|
|
147
|
-
group_id=
|
|
148
|
-
type_id=
|
|
174
|
+
cloud_id=group.cloud_ids[0],
|
|
175
|
+
group_id=group.id,
|
|
176
|
+
type_id=net_type.id,
|
|
177
|
+
resource_pool_id=pool.id,
|
|
149
178
|
cidr="10.42.10.0/24",
|
|
150
179
|
gateway="10.42.10.1",
|
|
151
180
|
dns_primary="8.8.8.8",
|
|
152
|
-
visibility="private",
|
|
153
|
-
dhcp_server=True,
|
|
154
181
|
)
|
|
155
182
|
|
|
156
|
-
#
|
|
157
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
183
|
+
# Inspect subnets
|
|
158
184
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
159
185
|
print(f"Subnets: {len(subnets)}")
|
|
160
186
|
```
|
|
@@ -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,7 +42,10 @@ cloud.instance_types.get_by_code("MTN-CS10")
|
|
|
42
42
|
## Common Write Operations
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
|
-
|
|
45
|
+
# Discover the resource pool an instance will be hosted in
|
|
46
|
+
pool = cloud.instances.get_resource_pool("my-project", group="MTNNG_CLOUD_AZ_1")
|
|
47
|
+
|
|
48
|
+
cloud.instances.create(..., resource_pool_id=pool.code)
|
|
46
49
|
cloud.instances.update(instance_id=123, name="new-name")
|
|
47
50
|
cloud.instances.delete(123)
|
|
48
51
|
|
|
@@ -93,6 +96,7 @@ Resource methods return typed Pydantic models, for example:
|
|
|
93
96
|
- `ArchiveFile`
|
|
94
97
|
- `SecurityGroup`, `SecurityGroupRule`
|
|
95
98
|
- `Snapshot`
|
|
99
|
+
- `ResourcePool`
|
|
96
100
|
- `Backup`, `BackupJob`, `BackupResult`
|
|
97
101
|
- `VirtualImage`
|
|
98
102
|
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
- common API exceptions
|
|
33
33
|
- `NotFoundError` when no instance matches
|
|
34
34
|
|
|
35
|
-
### `create(name: str, *, cloud: str, type: str, group: str, layout: int, plan: int, resource_pool_id: str, description=None, environment=None, labels=None, tags=None, copies=1, layout_size=1, availability_zone=None, security_group="default", os_external_network_id=None, create_user=True, workflow_id=None, shutdown_days=None, expire_days=None, create_backup=None, security_groups=None, ports=None, volumes=None, network_interfaces=None, options=None) -> Instance`
|
|
35
|
+
### `create(name: str, *, cloud: str, type: str, group: str, layout: int, plan: int, resource_pool_id: str | int, description=None, environment=None, labels=None, tags=None, copies=1, layout_size=1, availability_zone=None, security_group="default", os_external_network_id=None, create_user=True, workflow_id=None, shutdown_days=None, expire_days=None, create_backup=None, security_groups=None, ports=None, volumes=None, network_interfaces=None, options=None) -> Instance`
|
|
36
36
|
|
|
37
37
|
- Endpoint sequence:
|
|
38
38
|
- `GET /api/groups?name=<group>&max=1` (resolve group name to `group_id`)
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
- `group`: group/site name (resolved to ID)
|
|
46
46
|
- `layout`: layout ID
|
|
47
47
|
- `plan`: service plan ID
|
|
48
|
-
- `resource_pool_id`:
|
|
48
|
+
- `resource_pool_id`: resource pool code (`"pool-214"`) or numeric ID (`214`); both normalize to the code. Discover with `list_resource_pools()` / `get_resource_pool()`
|
|
49
49
|
- Optional metadata:
|
|
50
50
|
- `description`, `environment`, `labels`, `tags`
|
|
51
51
|
- Optional sizing/provisioning:
|
|
@@ -197,3 +197,36 @@ Client-side polling helper.
|
|
|
197
197
|
- Endpoint: `DELETE /api/instances/{instance_id}/snapshots/{snapshot_id}`
|
|
198
198
|
- Returns: `True` on success
|
|
199
199
|
- Raises: common API exceptions
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Provisioning discovery
|
|
204
|
+
|
|
205
|
+
These helpers resolve the IDs and codes required by `create()` using permission-safe endpoints (the admin-level `clouds`/`plans` endpoints are restricted on most accounts).
|
|
206
|
+
|
|
207
|
+
### `list_resource_pools(group=None, *, cloud_id=None, group_id=None, provision_type_code="openstack") -> list[ResourcePool]`
|
|
208
|
+
|
|
209
|
+
- Endpoint: `GET /api/options/zonePools?cloudId={cloud_id}&groupId={group_id}`
|
|
210
|
+
- A resource pool is where an instance is hosted; its `code` is the `resource_pool_id` for `create()`
|
|
211
|
+
- Parameters:
|
|
212
|
+
- `group`: group name or ID — when given, `cloud_id` and `group_id` are resolved from it automatically (the group embeds its cloud/zone IDs)
|
|
213
|
+
- `cloud_id` / `group_id`: explicit IDs, required if `group` is not given
|
|
214
|
+
- `provision_type_code`: provisioning type, defaults to `"openstack"`
|
|
215
|
+
- Returns: `list[ResourcePool]` (group-header rows are filtered out)
|
|
216
|
+
- Raises: `ValueError` if neither `group` nor both `cloud_id`/`group_id` are provided, or the group has no associated cloud
|
|
217
|
+
|
|
218
|
+
### `get_resource_pool(name: str, *, group=None, cloud_id=None, group_id=None, provision_type_code="openstack") -> ResourcePool`
|
|
219
|
+
|
|
220
|
+
- Fetches a single pool by display name **or** code (e.g. `"pool-214"`)
|
|
221
|
+
- Same resolution rules as `list_resource_pools`
|
|
222
|
+
- Returns: `ResourcePool`
|
|
223
|
+
- Raises: `NotFoundError` if no pool matches `name`
|
|
224
|
+
|
|
225
|
+
### `list_service_plans(zone_id=None, layout_id=None, group_id=None) -> list[dict[str, Any]]`
|
|
226
|
+
|
|
227
|
+
- Endpoint: `GET /api/instances/service-plans?zoneId={zone_id}&layoutId={layout_id}&siteId={group_id}`
|
|
228
|
+
- Returns available service plans (CPU/memory/storage tiers) scoped to the provisioning context
|
|
229
|
+
- `layout_id` is required by the API; use `instance_type.default_layout_id`
|
|
230
|
+
- Returns: list of plan dicts from response key `plans`
|
|
231
|
+
|
|
232
|
+
**`ResourcePool` model fields:** `id` (numeric), `code` (alias of API `value`, e.g. `"pool-214"`), `name`, `external_id` (OpenStack pool ID), `is_default`.
|
|
@@ -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`
|
|
@@ -88,36 +91,32 @@
|
|
|
88
91
|
- Returns: `NetworkTypeInfo`
|
|
89
92
|
- Raises: common API exceptions
|
|
90
93
|
|
|
91
|
-
### `
|
|
94
|
+
### `list_pools(max_results=None, offset=0, phrase=None) -> list[NetworkPool]`
|
|
92
95
|
|
|
93
|
-
- Endpoint: `GET /api/networks/
|
|
96
|
+
- Endpoint: `GET /api/networks/pools`
|
|
94
97
|
- Parameters:
|
|
98
|
+
- `max_results`: maps to query `max`
|
|
99
|
+
- `offset`: pagination offset
|
|
95
100
|
- `phrase`: search phrase
|
|
96
|
-
|
|
97
|
-
- `ip_status`: provider status value
|
|
98
|
-
- `cloud_id`: mapped to `zoneId`
|
|
99
|
-
- `server_id`: mapped to `serverId`
|
|
100
|
-
- Returns: `list[NetworkFloatingIP]`
|
|
101
|
+
- Returns: `list[NetworkPool]` (each includes its `ip_ranges`, `ip_count`, `free_count`)
|
|
101
102
|
- Raises: common API exceptions
|
|
102
103
|
|
|
103
|
-
### `
|
|
104
|
+
### `get_pool(pool_id: int) -> NetworkPool`
|
|
104
105
|
|
|
105
|
-
- Endpoint: `GET /api/networks/
|
|
106
|
-
- Returns: `
|
|
106
|
+
- Endpoint: `GET /api/networks/pools/{pool_id}`
|
|
107
|
+
- Returns: `NetworkPool`
|
|
107
108
|
- Raises: common API exceptions
|
|
108
109
|
|
|
109
|
-
### `
|
|
110
|
+
### `list_pool_ips(pool_id: int, *, max_results=None, phrase=None, ip_address=None, hostname=None) -> list[NetworkPoolIp]`
|
|
110
111
|
|
|
111
|
-
- Endpoint: `
|
|
112
|
+
- Endpoint: `GET /api/networks/pools/{pool_id}/ips`
|
|
112
113
|
- Parameters:
|
|
113
|
-
- `
|
|
114
|
-
- `
|
|
115
|
-
-
|
|
114
|
+
- `max_results`: maps to query `max`
|
|
115
|
+
- `phrase`: partial match on `ipAddress` or `hostname`
|
|
116
|
+
- `ip_address`: exact IP match
|
|
117
|
+
- `hostname`: exact hostname match
|
|
118
|
+
- Returns: `list[NetworkPoolIp]`
|
|
116
119
|
- Raises: common API exceptions
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- Endpoint: `PUT /api/networks/floating-ips/{floating_ip_id}/release`
|
|
121
|
-
- Returns: `True`
|
|
122
|
-
- Raises: common API exceptions
|
|
121
|
+
> **Note:** Standalone floating-IP endpoints (`/api/networks/floating-ips`) are restricted (HTTP 403) on MTN Cloud tenant accounts and are not exposed by the SDK. Assign external/public connectivity at provisioning time via the instance's `os_external_network_id` instead.
|
|
123
122
|
|
|
@@ -50,35 +50,79 @@ Lagos AZ3 (Lagos Mainland) is in progress. Each zone has independent power, cool
|
|
|
50
50
|
|
|
51
51
|
Run this before creating an instance. All IDs are account-specific.
|
|
52
52
|
|
|
53
|
+
Provisioning inputs are discovered from **groups, instance types, resource pools, and service plans** — all permission-safe endpoints. (The admin-level `clouds.list()` and `plans.list()` endpoints are restricted on most tenant accounts and will raise `ForbiddenError`; you do not need them.)
|
|
54
|
+
|
|
53
55
|
```python
|
|
54
56
|
from mtn_cloud import MTNCloud
|
|
55
57
|
|
|
56
58
|
cloud = MTNCloud(token="your-api-token")
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
print(
|
|
66
|
-
|
|
60
|
+
# 1. Group — gives you the group name AND its cloud/zone IDs
|
|
61
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
62
|
+
cloud_id = group.cloud_ids[0]
|
|
63
|
+
print("group:", group.id, group.name, "cloud_ids:", group.cloud_ids)
|
|
64
|
+
|
|
65
|
+
# 2. Instance type — each carries its own default_layout_id
|
|
66
|
+
itype = cloud.instance_types.get_by_code("MTN-CS10")
|
|
67
|
+
print("type:", itype.code, "layout:", itype.default_layout_id)
|
|
68
|
+
|
|
69
|
+
# 3. Resource pool — where the instance is hosted (the resource_pool_id)
|
|
70
|
+
for pool in cloud.instances.list_resource_pools(group="MTNNG_CLOUD_AZ_1"):
|
|
71
|
+
print("pool:", pool.code, pool.name)
|
|
72
|
+
|
|
73
|
+
# 4. Service plans — available CPU/memory/storage tiers for this zone + layout
|
|
74
|
+
for plan in cloud.instances.list_service_plans(
|
|
75
|
+
zone_id=cloud_id,
|
|
76
|
+
layout_id=itype.default_layout_id,
|
|
77
|
+
group_id=group.id,
|
|
78
|
+
):
|
|
79
|
+
print("plan:", plan["id"], plan["name"])
|
|
67
80
|
```
|
|
68
81
|
|
|
69
82
|
The `default_layout_id` on each instance type is the correct `layout` value to use for that type. Always read it from your account rather than hardcoding a value from an example.
|
|
70
83
|
|
|
84
|
+
### Resource Pools (the `resource_pool_id`)
|
|
85
|
+
|
|
86
|
+
A resource pool is **where your instance is hosted** — you cannot create an instance without one. List them by group name (the cloud/zone is resolved for you):
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# List every pool available to a group
|
|
90
|
+
for pool in cloud.instances.list_resource_pools(group="MTNNG_CLOUD_AZ_1"):
|
|
91
|
+
print(pool.code, pool.name, "(default)" if pool.is_default else "")
|
|
92
|
+
|
|
93
|
+
# Or fetch a single pool by name (or by its code)
|
|
94
|
+
pool = cloud.instances.get_resource_pool(
|
|
95
|
+
"my-project-Marv-Osuolale",
|
|
96
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
97
|
+
)
|
|
98
|
+
print(pool.code) # e.g. "pool-214" — pass this as resource_pool_id
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`pool.code` (e.g. `"pool-214"`) is the exact value `create()` expects. When passing it to `create()`, you can use the code (`"pool-214"`), the numeric ID (`214`), or its string form (`"214"`) — they are all normalized to the code internally.
|
|
102
|
+
|
|
103
|
+
If you already know the cloud and group IDs, pass them explicitly to skip the group lookup:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
pools = cloud.instances.list_resource_pools(cloud_id=4, group_id=621)
|
|
107
|
+
```
|
|
108
|
+
|
|
71
109
|
## Create an Instance
|
|
72
110
|
|
|
73
111
|
```python
|
|
112
|
+
# Resolve the resource pool once, then use its code directly
|
|
113
|
+
pool = cloud.instances.get_resource_pool(
|
|
114
|
+
"my-project-Marv-Osuolale",
|
|
115
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
116
|
+
)
|
|
117
|
+
|
|
74
118
|
instance = cloud.instances.create(
|
|
75
119
|
name="app-server-01",
|
|
76
120
|
cloud="MTNNG_CLOUD_AZ_1",
|
|
77
121
|
type="MTN-CS10",
|
|
78
122
|
group="MTNNG_CLOUD_AZ_1",
|
|
79
123
|
layout=327, # use default_layout_id from your type lookup
|
|
80
|
-
plan=6776, # use plan ID from
|
|
81
|
-
resource_pool_id="pool-214"
|
|
124
|
+
plan=6776, # use a plan ID from list_service_plans()
|
|
125
|
+
resource_pool_id=pool.code, # e.g. "pool-214" (or pass 214 directly)
|
|
82
126
|
availability_zone="Lagos-AZ-1-fd1",
|
|
83
127
|
security_group="default", # security group name from the console
|
|
84
128
|
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Networking
|
|
2
|
+
|
|
3
|
+
Use this guide for creating and managing MTN Cloud networks via the SDK.
|
|
4
|
+
|
|
5
|
+
## Before You Start
|
|
6
|
+
|
|
7
|
+
- You need an existing Resource Pool (Project). Create one from **Provisioning → Catalog → Create Project** if you haven't already.
|
|
8
|
+
- MTN Cloud networking runs on OpenStack Neutron. Networks you create are private by default — VMs get private IPs automatically. Public internet access is configured at provisioning time via the instance's external network (`os_external_network_id`), not through a standalone floating-IP API.
|
|
9
|
+
- Use `list_types(openstack_only=True)` to discover the valid network type IDs for your account.
|
|
10
|
+
|
|
11
|
+
## Typical Networking Flow
|
|
12
|
+
|
|
13
|
+
1. Create a network (with CIDR, gateway, DHCP).
|
|
14
|
+
2. Provision instances on your network.
|
|
15
|
+
3. For public access, set `os_external_network_id` when creating the instance (see the [Instances guide](./instances.md)).
|
|
16
|
+
|
|
17
|
+
## Discover Reference Data
|
|
18
|
+
|
|
19
|
+
The `cloud_id` (zone) comes from the group — the admin-level `clouds.list()` endpoint is restricted on most tenant accounts and isn't needed.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from mtn_cloud import MTNCloud
|
|
23
|
+
|
|
24
|
+
cloud = MTNCloud(token="your-api-token")
|
|
25
|
+
|
|
26
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
27
|
+
cloud_id = group.cloud_ids[0]
|
|
28
|
+
network_types = cloud.networks.list_types(openstack_only=True)
|
|
29
|
+
|
|
30
|
+
print("group:", group.id, "cloud_id:", cloud_id)
|
|
31
|
+
print([(t.id, t.name) for t in network_types[:5]])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Create a Network
|
|
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
|
+
|
|
38
|
+
```python
|
|
39
|
+
networks = cloud.networks.list(cloud_id=cloud_id)
|
|
40
|
+
for network in networks[:5]:
|
|
41
|
+
print(network.id, network.name, network.cidr)
|
|
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
|
+
|
|
46
|
+
new_network = cloud.networks.create(
|
|
47
|
+
name="mtn-prod-net",
|
|
48
|
+
cloud_id=cloud_id, # from group.cloud_ids[0]
|
|
49
|
+
group_id=group.id, # from groups.get_by_name(...)
|
|
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(...)
|
|
52
|
+
cidr="10.42.10.0/24",
|
|
53
|
+
gateway="10.42.10.1",
|
|
54
|
+
dns_primary="8.8.8.8",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
print(new_network.id, new_network.name)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Update and Inspect
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
updated = cloud.networks.update(
|
|
64
|
+
new_network.id,
|
|
65
|
+
description="Production network",
|
|
66
|
+
allow_static_override=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
subnets = cloud.networks.list_subnets(updated.id)
|
|
70
|
+
print(f"Subnets: {len(subnets)}")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Delete a Network
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
cloud.networks.delete(new_network.id)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Network Pools
|
|
80
|
+
|
|
81
|
+
Network pools are managed ranges of IP addresses used for static assignment. Inspect them and the individual addresses they track:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# List pools with usage counts
|
|
85
|
+
for pool in cloud.networks.list_pools():
|
|
86
|
+
print(pool.id, pool.name, f"{pool.free_count}/{pool.ip_count} free")
|
|
87
|
+
for r in pool.ip_ranges:
|
|
88
|
+
print(" range:", r.start_address, "-", r.end_address)
|
|
89
|
+
|
|
90
|
+
# Inspect a single pool and the addresses in use
|
|
91
|
+
pool = cloud.networks.get_pool(9)
|
|
92
|
+
for ip in cloud.networks.list_pool_ips(pool.id):
|
|
93
|
+
print(ip.ip_address, ip.ip_type, ip.hostname)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Notes
|
|
97
|
+
|
|
98
|
+
- Many networking fields are OpenStack-specific. The SDK exposes the fields most relevant to MTN Cloud's environment.
|
|
99
|
+
- Always discover and validate IDs (`cloud_id`, `group_id`, `type_id`) for your own account — they are tenant-specific and differ between environments.
|
|
100
|
+
- Security groups (firewall rules) are separate from network configuration. Manage them via `cloud.security_groups` — see the [Security Groups guide](./security-groups.md).
|