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.
Files changed (84) hide show
  1. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/PKG-INFO +32 -27
  2. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/README.md +30 -23
  3. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/advanced-cookbook.md +15 -5
  4. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-overview.md +5 -4
  5. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/instances.md +25 -0
  6. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/networks.md +5 -2
  7. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/index.md +8 -7
  8. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/instances.md +35 -6
  9. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/networking.md +7 -3
  10. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/quickstart.md +45 -16
  11. mtn_cloud-0.3.2/docs/recipes.md +110 -0
  12. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/pyproject.toml +3 -4
  13. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/_version.py +1 -1
  14. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/http.py +2 -1
  15. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/base.py +7 -1
  16. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/network.py +3 -0
  17. mtn_cloud-0.3.2/src/mtn_cloud/py.typed +0 -0
  18. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/instances.py +185 -1
  19. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/networks.py +7 -1
  20. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_instances.py +110 -0
  21. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_networks.py +8 -0
  22. mtn_cloud-0.2.19/docs/docstring-style.md +0 -50
  23. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/.gitignore +0 -0
  24. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/LICENSE +0 -0
  25. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/archive-buckets.md +0 -0
  26. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/backups.md +0 -0
  27. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/client.md +0 -0
  28. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/clouds.md +0 -0
  29. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/groups.md +0 -0
  30. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/index.md +0 -0
  31. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/instance-types.md +0 -0
  32. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/plans.md +0 -0
  33. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/security-groups.md +0 -0
  34. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/storage-buckets.md +0 -0
  35. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/api-reference/virtual-images.md +0 -0
  36. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/backups.md +0 -0
  37. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/security-groups.md +0 -0
  38. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/storage.md +0 -0
  39. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/stylesheets/extra.css +0 -0
  40. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/docs/virtual-images.md +0 -0
  41. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/__init__.py +0 -0
  42. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/client.py +0 -0
  43. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/config.py +0 -0
  44. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/exceptions.py +0 -0
  45. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/__init__.py +0 -0
  46. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/archive.py +0 -0
  47. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/backup.py +0 -0
  48. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/cloud.py +0 -0
  49. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/group.py +0 -0
  50. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/instance.py +0 -0
  51. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/instance_type.py +0 -0
  52. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/plan.py +0 -0
  53. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/resource_pool.py +0 -0
  54. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/security_group.py +0 -0
  55. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/snapshot.py +0 -0
  56. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/storage_bucket.py +0 -0
  57. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/user.py +0 -0
  58. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/virtual_image.py +0 -0
  59. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/models/volume.py +0 -0
  60. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/__init__.py +0 -0
  61. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/archive_buckets.py +0 -0
  62. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/backups.py +0 -0
  63. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/base.py +0 -0
  64. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/clouds.py +0 -0
  65. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/groups.py +0 -0
  66. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/instance_types.py +0 -0
  67. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/plans.py +0 -0
  68. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/security_groups.py +0 -0
  69. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/storage_buckets.py +0 -0
  70. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/src/mtn_cloud/resources/virtual_images.py +0 -0
  71. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/__init__.py +0 -0
  72. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/conftest.py +0 -0
  73. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_archive_buckets.py +0 -0
  74. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_backups.py +0 -0
  75. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_client.py +0 -0
  76. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_clouds.py +0 -0
  77. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_config.py +0 -0
  78. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_exceptions.py +0 -0
  79. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_groups.py +0 -0
  80. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_instance_types.py +0 -0
  81. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_plans.py +0 -0
  82. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_security_groups.py +0 -0
  83. {mtn_cloud-0.2.19 → mtn_cloud-0.3.2}/tests/test_storage_buckets.py +0 -0
  84. {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.19
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. Discover it by group name (the cloud/zone is resolved for you), or fetch one directly by name:
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
- pool = cloud.instances.get_resource_pool(
158
- "my-project-name",
159
- group="MTNNG_CLOUD_AZ_1",
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(pool.code) # e.g. "pool-214" — pass this as resource_pool_id
165
+ print(instance.id, instance.status, instance.primary_ip)
162
166
  ```
163
167
 
164
- ### 2. Create an Instance
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="my-server",
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=pool.code, # "pool-214" — or pass the numeric ID 214
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=1)
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-focused network
209
- network_types = cloud.networks.list_types(openstack_only=True)
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=1,
213
- group_id=621,
214
- type_id=network_types[0].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
- # Update and list subnets
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. Discover it by group name (the cloud/zone is resolved for you), or fetch one directly by name:
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
- pool = cloud.instances.get_resource_pool(
114
- "my-project-name",
115
- group="MTNNG_CLOUD_AZ_1",
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(pool.code) # e.g. "pool-214" — pass this as resource_pool_id
123
+ print(instance.id, instance.status, instance.primary_ip)
118
124
  ```
119
125
 
120
- ### 2. Create an Instance
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="my-server",
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=pool.code, # "pool-214" — or pass the numeric ID 214
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=1)
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-focused network
165
- network_types = cloud.networks.list_types(openstack_only=True)
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=1,
169
- group_id=621,
170
- type_id=network_types[0].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
- # Update and list subnets
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=1,
106
+ cloud_id=4,
107
107
  group_id=621,
108
- type_id=8,
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
- target_plan = cloud.plans.get_by_name("G2S4")
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.id,
162
- resource_pool_id="pool-214",
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
- # 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)
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
- - `type_id`, `display_name`, `labels`, `description`, `cidr`, `gateway`
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. [Advanced Cookbook](./advanced-cookbook.md)
19
- 9. [API Overview](./api-overview.md)
20
- 10. [API Reference](./api-reference/index.md)
21
- 11. [Docstring Style Standard](./docstring-style.md)
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. API Overview
46
- 9. API Reference
47
- 10. Advanced Cookbook
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
- ## Create an Instance
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
- # 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",
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", # security group name from the console
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=network_types[0].id, # from list_types(openstack_only=True)
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
- 1. Go to **Infrastructure → Network → Security Groups** and create a group.
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
- The group name (e.g., `"default"`) is passed as the `security_group` parameter when creating an instance.
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
- groups = cloud.groups.list()
84
- clouds = cloud.clouds.list_openstack()
85
- instance_types = cloud.instance_types.list_os()
86
- plans = cloud.plans.list()
87
-
88
- print("Groups:", [(g.id, g.name) for g in groups[:5]])
89
- print("Clouds:", [(c.id, c.name) for c in clouds[:5]])
90
- print("Instance types:", [(t.code, t.default_layout_id) for t in instance_types[:5]])
91
- print("Plans:", [(p.id, p.name, p.cores, p.memory_gb) for p in plans[:5]])
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="pool-214", # from your project order
137
+ resource_pool_id=pools[0].code, # from list_resource_pools() above
109
138
  availability_zone="Lagos-AZ-1-fd1",
110
- security_group="default", # security group name from the console
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.