mtn-cloud 0.2.19__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.19 → mtn_cloud-0.3.1}/PKG-INFO +16 -12
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/README.md +15 -11
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/advanced-cookbook.md +15 -5
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/networks.md +5 -2
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/index.md +0 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/networking.md +7 -3
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/quickstart.md +45 -16
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/_version.py +1 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/base.py +7 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/network.py +3 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/networks.py +7 -1
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/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.1}/.gitignore +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/LICENSE +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-overview.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/archive-buckets.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/backups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/client.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/clouds.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/index.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/instance-types.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/instances.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/plans.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/security-groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/storage-buckets.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/api-reference/virtual-images.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/backups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/instances.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/security-groups.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/storage.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/stylesheets/extra.css +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/docs/virtual-images.md +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/pyproject.toml +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/client.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/config.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/exceptions.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/http.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/archive.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/backup.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/cloud.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/group.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/instance_type.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/plan.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/resource_pool.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/security_group.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/snapshot.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/storage_bucket.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/user.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/virtual_image.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/models/volume.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/archive_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/backups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/base.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/clouds.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instance_types.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/instances.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/plans.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/security_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/storage_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/src/mtn_cloud/resources/virtual_images.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/__init__.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/conftest.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_archive_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_backups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_client.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_clouds.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_config.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_exceptions.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_instance_types.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_instances.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_plans.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_security_groups.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/tests/test_storage_buckets.py +0 -0
- {mtn_cloud-0.2.19 → mtn_cloud-0.3.1}/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.
|
|
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
|
|
|
@@ -200,27 +199,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
200
199
|
### 4. Work with Networks
|
|
201
200
|
|
|
202
201
|
```python
|
|
202
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
203
|
+
|
|
203
204
|
# List networks
|
|
204
|
-
networks = cloud.networks.list(cloud_id=
|
|
205
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
205
206
|
for n in networks[:5]:
|
|
206
207
|
print(n.id, n.name, n.cidr)
|
|
207
208
|
|
|
208
|
-
# Create an OpenStack
|
|
209
|
-
|
|
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
|
+
|
|
210
216
|
new_network = cloud.networks.create(
|
|
211
217
|
name="mtn-prod-net",
|
|
212
|
-
cloud_id=
|
|
213
|
-
group_id=
|
|
214
|
-
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,
|
|
215
222
|
cidr="10.42.10.0/24",
|
|
216
223
|
gateway="10.42.10.1",
|
|
217
224
|
dns_primary="8.8.8.8",
|
|
218
|
-
visibility="private",
|
|
219
|
-
dhcp_server=True,
|
|
220
225
|
)
|
|
221
226
|
|
|
222
|
-
#
|
|
223
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
227
|
+
# Inspect subnets
|
|
224
228
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
225
229
|
print(f"Subnets: {len(subnets)}")
|
|
226
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
|
|
|
@@ -156,27 +155,32 @@ cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
|
156
155
|
### 4. Work with Networks
|
|
157
156
|
|
|
158
157
|
```python
|
|
158
|
+
group = cloud.groups.get_by_name("MTNNG_CLOUD_AZ_1")
|
|
159
|
+
|
|
159
160
|
# List networks
|
|
160
|
-
networks = cloud.networks.list(cloud_id=
|
|
161
|
+
networks = cloud.networks.list(cloud_id=group.cloud_ids[0])
|
|
161
162
|
for n in networks[:5]:
|
|
162
163
|
print(n.id, n.name, n.cidr)
|
|
163
164
|
|
|
164
|
-
# Create an OpenStack
|
|
165
|
-
|
|
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
|
+
|
|
166
172
|
new_network = cloud.networks.create(
|
|
167
173
|
name="mtn-prod-net",
|
|
168
|
-
cloud_id=
|
|
169
|
-
group_id=
|
|
170
|
-
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,
|
|
171
178
|
cidr="10.42.10.0/24",
|
|
172
179
|
gateway="10.42.10.1",
|
|
173
180
|
dns_primary="8.8.8.8",
|
|
174
|
-
visibility="private",
|
|
175
|
-
dhcp_server=True,
|
|
176
181
|
)
|
|
177
182
|
|
|
178
|
-
#
|
|
179
|
-
cloud.networks.update(new_network.id, description="Production network")
|
|
183
|
+
# Inspect subnets
|
|
180
184
|
subnets = cloud.networks.list_subnets(new_network.id)
|
|
181
185
|
print(f"Subnets: {len(subnets)}")
|
|
182
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
|
|
|
@@ -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`
|
|
@@ -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)
|
|
@@ -7,7 +7,7 @@ from datetime import datetime
|
|
|
7
7
|
from typing import Any, Generic, TypeVar
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel as PydanticBaseModel
|
|
10
|
-
from pydantic import ConfigDict, Field
|
|
10
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class BaseModel(PydanticBaseModel):
|
|
@@ -28,6 +28,12 @@ class BaseModel(PydanticBaseModel):
|
|
|
28
28
|
from_attributes=True,
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
@field_validator("labels", mode="before", check_fields=False)
|
|
32
|
+
@classmethod
|
|
33
|
+
def _coerce_null_labels(cls, value: Any) -> Any:
|
|
34
|
+
"""Treat a null ``labels`` field from the API as an empty list."""
|
|
35
|
+
return [] if value is None else value
|
|
36
|
+
|
|
31
37
|
|
|
32
38
|
class Resource(BaseModel):
|
|
33
39
|
"""
|
|
@@ -238,6 +238,7 @@ class NetworkCreate(BaseModel):
|
|
|
238
238
|
cloud_id: int
|
|
239
239
|
group_id: int
|
|
240
240
|
type_id: int | None = None
|
|
241
|
+
resource_pool_id: int | None = None
|
|
241
242
|
display_name: str | None = Field(default=None, alias="displayName")
|
|
242
243
|
labels: list[str] | None = None
|
|
243
244
|
description: str | None = None
|
|
@@ -272,6 +273,8 @@ class NetworkCreate(BaseModel):
|
|
|
272
273
|
|
|
273
274
|
if self.type_id is not None:
|
|
274
275
|
network["type"] = {"id": self.type_id}
|
|
276
|
+
if self.resource_pool_id is not None:
|
|
277
|
+
network["zonePool"] = {"id": self.resource_pool_id}
|
|
275
278
|
if self.display_name is not None:
|
|
276
279
|
network["displayName"] = self.display_name
|
|
277
280
|
if self.labels is not None:
|
|
@@ -135,6 +135,7 @@ class NetworksResource(BaseResource[Network]):
|
|
|
135
135
|
cloud_id: int,
|
|
136
136
|
group_id: int,
|
|
137
137
|
type_id: int | None = None,
|
|
138
|
+
resource_pool_id: int | None = None,
|
|
138
139
|
display_name: str | None = None,
|
|
139
140
|
labels: List[str] | None = None,
|
|
140
141
|
description: str | None = None,
|
|
@@ -162,6 +163,10 @@ class NetworksResource(BaseResource[Network]):
|
|
|
162
163
|
"""
|
|
163
164
|
Create a network using MTN OpenStack-compatible fields.
|
|
164
165
|
|
|
166
|
+
OpenStack network types (e.g. ``openstackPrivate``) require both an
|
|
167
|
+
OpenStack ``type_id`` from ``list_types(openstack_only=True)`` and a
|
|
168
|
+
``resource_pool_id`` (the numeric pool ID from ``get_resource_pool``).
|
|
169
|
+
|
|
165
170
|
Notes:
|
|
166
171
|
- Non-OpenStack provider-specific `config` variants are intentionally excluded.
|
|
167
172
|
- Use integer IDs for cloud/group/type to keep payload typing uniform.
|
|
@@ -171,6 +176,7 @@ class NetworksResource(BaseResource[Network]):
|
|
|
171
176
|
cloud_id=cloud_id,
|
|
172
177
|
group_id=group_id,
|
|
173
178
|
type_id=type_id,
|
|
179
|
+
resource_pool_id=resource_pool_id,
|
|
174
180
|
display_name=display_name,
|
|
175
181
|
labels=labels,
|
|
176
182
|
description=description,
|
|
@@ -295,7 +301,7 @@ class NetworksResource(BaseResource[Network]):
|
|
|
295
301
|
phrase: Optional phrase filter
|
|
296
302
|
openstack_only: Return only network types tagged for OpenStack
|
|
297
303
|
"""
|
|
298
|
-
params: dict[str, Any] = {}
|
|
304
|
+
params: dict[str, Any] = {"max": 500}
|
|
299
305
|
if name:
|
|
300
306
|
params["name"] = name
|
|
301
307
|
if code:
|
|
@@ -115,6 +115,7 @@ class TestNetworkPayloadModels:
|
|
|
115
115
|
("network.cidr", "192.168.10.0/24"),
|
|
116
116
|
("network.dnsPrimary", "8.8.8.8"),
|
|
117
117
|
("network.visibility", "private"),
|
|
118
|
+
("network.zonePool.id", 230),
|
|
118
119
|
("network.tenants", [{"id": 1}, {"id": 2}]),
|
|
119
120
|
("network.resourcePermission.all", True),
|
|
120
121
|
],
|
|
@@ -126,6 +127,7 @@ class TestNetworkPayloadModels:
|
|
|
126
127
|
cloud_id=1,
|
|
127
128
|
group_id=10,
|
|
128
129
|
type_id=7,
|
|
130
|
+
resource_pool_id=230,
|
|
129
131
|
cidr="192.168.10.0/24",
|
|
130
132
|
gateway="192.168.10.1",
|
|
131
133
|
dns_primary="8.8.8.8",
|
|
@@ -136,6 +138,12 @@ class TestNetworkPayloadModels:
|
|
|
136
138
|
|
|
137
139
|
assert nested_value(payload, path) == expected
|
|
138
140
|
|
|
141
|
+
def test_network_accepts_null_labels(self) -> None:
|
|
142
|
+
"""Coerce a null labels field from the API into an empty list."""
|
|
143
|
+
network = Network.model_validate({"id": 1, "name": "n", "labels": None})
|
|
144
|
+
|
|
145
|
+
assert network.labels == []
|
|
146
|
+
|
|
139
147
|
@pytest.mark.parametrize(
|
|
140
148
|
("path", "expected"),
|
|
141
149
|
[
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Docstring Style Standard
|
|
2
|
-
|
|
3
|
-
This SDK uses a uniform docstring style for public classes and methods.
|
|
4
|
-
|
|
5
|
-
## Goals
|
|
6
|
-
|
|
7
|
-
- Fast scanning for beginners.
|
|
8
|
-
- Predictable structure for advanced users.
|
|
9
|
-
- Consistent IDE help and generated documentation output.
|
|
10
|
-
|
|
11
|
-
## Method Template
|
|
12
|
-
|
|
13
|
-
```python
|
|
14
|
-
def example(arg1: str, arg2: int | None = None) -> bool:
|
|
15
|
-
"""One-line summary.
|
|
16
|
-
|
|
17
|
-
Optional short context paragraph.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
arg1: Required value description.
|
|
21
|
-
arg2: Optional value description.
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
Description of return value.
|
|
25
|
-
|
|
26
|
-
Raises:
|
|
27
|
-
ValueError: If input validation fails.
|
|
28
|
-
MTNCloudError: If the API request fails.
|
|
29
|
-
"""
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Class Template
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
class Example:
|
|
36
|
-
"""One-line summary.
|
|
37
|
-
|
|
38
|
-
Explain what the class represents and when it should be used.
|
|
39
|
-
"""
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Rules
|
|
43
|
-
|
|
44
|
-
1. Start with an imperative one-line summary.
|
|
45
|
-
2. Use `Args`, `Returns`, and `Raises` on public methods.
|
|
46
|
-
3. Include examples only when they add clarity.
|
|
47
|
-
4. Avoid Markdown code fences inside docstrings; prefer plain text blocks.
|
|
48
|
-
5. Keep docstrings accurate to the current method signature.
|
|
49
|
-
6. Prefer exact resource names used by the SDK (`group`, `cloud`, `archive bucket`).
|
|
50
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|