mtn-cloud 0.1.13__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mtn_cloud-0.2.2/PKG-INFO +353 -0
- mtn_cloud-0.2.2/README.md +309 -0
- mtn_cloud-0.2.2/docs/advanced-cookbook.md +178 -0
- mtn_cloud-0.2.2/docs/api-overview.md +86 -0
- mtn_cloud-0.2.2/docs/docstring-style.md +50 -0
- mtn_cloud-0.2.2/docs/index.md +39 -0
- mtn_cloud-0.2.2/docs/instances.md +81 -0
- mtn_cloud-0.2.2/docs/networking.md +78 -0
- mtn_cloud-0.2.2/docs/quickstart.md +133 -0
- mtn_cloud-0.2.2/docs/storage.md +100 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/pyproject.toml +6 -2
- mtn_cloud-0.2.2/src/mtn_cloud/__init__.py +34 -0
- mtn_cloud-0.2.2/src/mtn_cloud/_version.py +3 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/client.py +42 -24
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/config.py +6 -10
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/exceptions.py +3 -7
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/http.py +64 -7
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/__init__.py +29 -6
- mtn_cloud-0.2.2/src/mtn_cloud/models/archive.py +190 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/base.py +1 -6
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/cloud.py +2 -10
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/group.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/instance.py +1 -10
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/instance_type.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/network.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/plan.py +1 -8
- mtn_cloud-0.2.2/src/mtn_cloud/models/storage_bucket.py +104 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/user.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/models/volume.py +1 -6
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/__init__.py +5 -6
- mtn_cloud-0.2.2/src/mtn_cloud/resources/archive_buckets.py +588 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/base.py +1 -6
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/clouds.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/groups.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/instance_types.py +1 -10
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/instances.py +1 -10
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/networks.py +1 -8
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/src/mtn_cloud/resources/plans.py +1 -8
- mtn_cloud-0.2.2/src/mtn_cloud/resources/storage_buckets.py +268 -0
- mtn_cloud-0.2.2/tests/test_archive_buckets.py +569 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_client.py +16 -0
- mtn_cloud-0.2.2/tests/test_storage_buckets.py +152 -0
- mtn_cloud-0.1.13/PKG-INFO +0 -521
- mtn_cloud-0.1.13/README.md +0 -480
- mtn_cloud-0.1.13/src/mtn_cloud/__init__.py +0 -72
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/.gitignore +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/LICENSE +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/__init__.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/conftest.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_clouds.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_config.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_exceptions.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_instance_types.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_instances.py +0 -0
- {mtn_cloud-0.1.13 → mtn_cloud-0.2.2}/tests/test_networks.py +0 -0
mtn_cloud-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mtn-cloud
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Community Python SDK for MTN Cloud (Morpheus) - Deploy and manage cloud resources with ease
|
|
5
|
+
Project-URL: Homepage, https://github.com/mahveotm/mtn-cloud-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/mahveotm/mtn-cloud-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/mahveotm/mtn-cloud-python
|
|
8
|
+
Project-URL: Issues, https://github.com/mahveotm/mtn-cloud-python/issues
|
|
9
|
+
Author-email: Marvellous Osuolale <m@rvellous.com>
|
|
10
|
+
Maintainer-email: Marvellous Osuolale <m@rvellous.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: api,cloud,devops,infrastructure,morpheus,mtn,sdk
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: requests>=2.28.0
|
|
30
|
+
Requires-Dist: typing-extensions>=4.5.0
|
|
31
|
+
Requires-Dist: urllib3>=2.0.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: respx>=0.20.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: types-requests>=2.28.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: docs
|
|
41
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
|
|
42
|
+
Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+
# MTN Cloud Python SDK
|
|
46
|
+
|
|
47
|
+
[](https://badge.fury.io/py/mtn-cloud)
|
|
48
|
+
[](https://github.com/mahveotm/mtn-cloud-python/actions/workflows/test.yml)
|
|
49
|
+
[](https://github.com/mahveotm/mtn-cloud-python/actions/workflows/docs.yml)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
51
|
+
[](https://opensource.org/licenses/MIT)
|
|
52
|
+
|
|
53
|
+
A Modern Python SDK for [MTN Cloud](https://console.cloud.mtn.ng) (Morpheus) with typed models, clear resource managers, and practical workflows for compute, networking, storage, and archives.
|
|
54
|
+
|
|
55
|
+
Docs: [mtn-cloud-python](https://mahveotm.github.io/mtn-cloud-python/)
|
|
56
|
+
|
|
57
|
+
> **Disclaimer:** Unofficial community project. Not affiliated with MTN Nigeria.
|
|
58
|
+
|
|
59
|
+
## Why You'll Like It
|
|
60
|
+
|
|
61
|
+
- Simple Pythonic API across core MTN Cloud resources
|
|
62
|
+
- Typed Pydantic models with IDE autocomplete
|
|
63
|
+
- Built-in retry behavior and timeout controls
|
|
64
|
+
- Token or username/password authentication
|
|
65
|
+
- Structured exceptions for better error handling
|
|
66
|
+
- Examples for real-world automation scripts
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install mtn-cloud
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
- [Docs Index](https://mahveotm.github.io/mtn-cloud-python/)
|
|
77
|
+
- [Quickstart](https://mahveotm.github.io/mtn-cloud-python/quickstart/)
|
|
78
|
+
- [Instances](https://mahveotm.github.io/mtn-cloud-python/instances/)
|
|
79
|
+
- [Networking](https://mahveotm.github.io/mtn-cloud-python/networking/)
|
|
80
|
+
- [Storage](https://mahveotm.github.io/mtn-cloud-python/storage/)
|
|
81
|
+
- [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
|
|
82
|
+
- [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
|
|
83
|
+
- [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
|
|
84
|
+
|
|
85
|
+
## Quick Start
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from mtn_cloud import MTNCloud
|
|
89
|
+
|
|
90
|
+
cloud = MTNCloud(token="your-api-token")
|
|
91
|
+
|
|
92
|
+
# Verify auth and connectivity
|
|
93
|
+
user = cloud.whoami()
|
|
94
|
+
print(f"Connected as: {user.username}")
|
|
95
|
+
print("Ping:", cloud.ping())
|
|
96
|
+
|
|
97
|
+
# List a few running instances
|
|
98
|
+
running = cloud.instances.list(status="running", max_results=5)
|
|
99
|
+
for instance in running:
|
|
100
|
+
print(instance.id, instance.name, instance.status, instance.primary_ip)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Authentication
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from mtn_cloud import MTNCloud
|
|
107
|
+
|
|
108
|
+
# Option 1: token (recommended)
|
|
109
|
+
cloud = MTNCloud(token="your-api-token")
|
|
110
|
+
|
|
111
|
+
# Option 2: environment variable
|
|
112
|
+
# export MTN_CLOUD_TOKEN="your-api-token"
|
|
113
|
+
cloud = MTNCloud()
|
|
114
|
+
|
|
115
|
+
# Option 3: username/password
|
|
116
|
+
cloud = MTNCloud(username="user@example.com", password="your-password")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Get your API token from MTN Cloud Console:
|
|
120
|
+
User Icon (top-right) -> User Settings -> API Access.
|
|
121
|
+
|
|
122
|
+
## What You Can Do
|
|
123
|
+
|
|
124
|
+
### 1. Discover Reference Data
|
|
125
|
+
|
|
126
|
+
Use these lookups before provisioning so your scripts stay deterministic.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# Groups (sites)
|
|
130
|
+
groups = cloud.groups.list()
|
|
131
|
+
for group in groups[:5]:
|
|
132
|
+
print(group.id, group.name)
|
|
133
|
+
|
|
134
|
+
# Clouds/zones
|
|
135
|
+
clouds = cloud.clouds.list_openstack()
|
|
136
|
+
for c in clouds[:5]:
|
|
137
|
+
print(c.id, c.name, c.type_code)
|
|
138
|
+
|
|
139
|
+
# Instance types
|
|
140
|
+
types = cloud.instance_types.list_os()
|
|
141
|
+
for t in types[:5]:
|
|
142
|
+
print(t.code, t.name, t.default_layout_id)
|
|
143
|
+
|
|
144
|
+
# Service plans
|
|
145
|
+
plans = cloud.plans.list()
|
|
146
|
+
for p in plans[:5]:
|
|
147
|
+
print(p.id, p.name, p.cores, p.memory_gb)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. Create an Instance
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
instance = cloud.instances.create(
|
|
154
|
+
name="my-server",
|
|
155
|
+
cloud="MTNNG_CLOUD_AZ_1",
|
|
156
|
+
type="MTN-CS10",
|
|
157
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
158
|
+
layout=327,
|
|
159
|
+
plan=6776,
|
|
160
|
+
labels=["production", "web"],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 3. Manage an Instance
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
instance = cloud.instances.get(123)
|
|
170
|
+
print(instance.name, instance.status, instance.primary_ip)
|
|
171
|
+
|
|
172
|
+
# Action methods from model
|
|
173
|
+
instance.stop()
|
|
174
|
+
instance.start()
|
|
175
|
+
instance.restart()
|
|
176
|
+
instance.refresh()
|
|
177
|
+
|
|
178
|
+
# Or from resource manager
|
|
179
|
+
cloud.instances.resize(123, plan_id=6780)
|
|
180
|
+
cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 4. Work with Networks
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
# List networks
|
|
187
|
+
networks = cloud.networks.list(cloud_id=1)
|
|
188
|
+
for n in networks[:5]:
|
|
189
|
+
print(n.id, n.name, n.cidr)
|
|
190
|
+
|
|
191
|
+
# Create an OpenStack-focused network
|
|
192
|
+
network_types = cloud.networks.list_types(openstack_only=True)
|
|
193
|
+
new_network = cloud.networks.create(
|
|
194
|
+
name="mtn-prod-net",
|
|
195
|
+
cloud_id=1,
|
|
196
|
+
group_id=621,
|
|
197
|
+
type_id=network_types[0].id,
|
|
198
|
+
cidr="10.42.10.0/24",
|
|
199
|
+
gateway="10.42.10.1",
|
|
200
|
+
dns_primary="8.8.8.8",
|
|
201
|
+
visibility="private",
|
|
202
|
+
dhcp_server=True,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Update and list subnets
|
|
206
|
+
cloud.networks.update(new_network.id, description="Production network")
|
|
207
|
+
subnets = cloud.networks.list_subnets(new_network.id)
|
|
208
|
+
print(f"Subnets: {len(subnets)}")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 5. Work with Storage and Archives
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
# Create S3-compatible storage provider
|
|
215
|
+
storage = cloud.storage_buckets.create_s3(
|
|
216
|
+
name="my-s3-storage",
|
|
217
|
+
bucket_name="my-app-objects",
|
|
218
|
+
access_key="your-access-key",
|
|
219
|
+
secret_key="your-secret-key",
|
|
220
|
+
endpoint="https://ps1csp-s3.ict.mtn.com.ng:9021",
|
|
221
|
+
create_bucket=True,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Create archive bucket linked to storage provider
|
|
225
|
+
archive = cloud.archive_buckets.create(
|
|
226
|
+
name="my-app-archives",
|
|
227
|
+
storage_provider_id=storage.id,
|
|
228
|
+
visibility="private",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Upload one file
|
|
232
|
+
uploaded = cloud.archive_buckets.upload_file(
|
|
233
|
+
bucket_name=archive.name,
|
|
234
|
+
remote_path="/",
|
|
235
|
+
local_path="./backup.sql",
|
|
236
|
+
)
|
|
237
|
+
print(uploaded.id, uploaded.name)
|
|
238
|
+
|
|
239
|
+
# Upload a directory (preserves local folder structure)
|
|
240
|
+
summary = cloud.archive_buckets.upload_directory(
|
|
241
|
+
bucket_name=archive.name,
|
|
242
|
+
remote_path="/imports/",
|
|
243
|
+
local_directory="./reports",
|
|
244
|
+
recursive=True,
|
|
245
|
+
)
|
|
246
|
+
print(
|
|
247
|
+
f"scanned={summary.scanned_count} "
|
|
248
|
+
f"uploaded={summary.uploaded_count} "
|
|
249
|
+
f"failed={summary.failed_count} "
|
|
250
|
+
f"skipped={summary.skipped_count}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# List and download
|
|
254
|
+
files = cloud.archive_buckets.list_files(bucket_name=archive.name, remote_path="/", full_tree=True)
|
|
255
|
+
if files:
|
|
256
|
+
content = cloud.archive_buckets.download_file(
|
|
257
|
+
bucket_name=archive.name,
|
|
258
|
+
remote_path=files[0].file_path or files[0].name,
|
|
259
|
+
)
|
|
260
|
+
print(f"Downloaded {len(content)} byte(s)")
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Storage vs archive model:
|
|
264
|
+
- `cloud.storage_buckets`: provider configuration (endpoint, credentials, backing bucket)
|
|
265
|
+
- `cloud.archive_buckets`: logical file container attached to a provider
|
|
266
|
+
- file operations happen through archive APIs
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
## Error Handling
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from mtn_cloud import (
|
|
273
|
+
AuthenticationError,
|
|
274
|
+
MTNCloudError,
|
|
275
|
+
NotFoundError,
|
|
276
|
+
RateLimitError,
|
|
277
|
+
ValidationError,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
cloud.instances.get(999999)
|
|
282
|
+
except NotFoundError:
|
|
283
|
+
print("Resource not found")
|
|
284
|
+
except AuthenticationError:
|
|
285
|
+
print("Authentication failed")
|
|
286
|
+
except ValidationError as exc:
|
|
287
|
+
print(f"Validation error: {exc}")
|
|
288
|
+
except RateLimitError as exc:
|
|
289
|
+
print(f"Rate limited, retry_after={exc.retry_after}")
|
|
290
|
+
except MTNCloudError as exc:
|
|
291
|
+
print(f"SDK/API error: {exc}")
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Configuration
|
|
295
|
+
|
|
296
|
+
Environment variables:
|
|
297
|
+
|
|
298
|
+
| Variable | Description | Default |
|
|
299
|
+
|----------|-------------|---------|
|
|
300
|
+
| `MTN_CLOUD_TOKEN` | API access token | - |
|
|
301
|
+
| `MTN_CLOUD_URL` | API base URL | `https://console.cloud.mtn.ng` |
|
|
302
|
+
| `MTN_CLOUD_TIMEOUT` | Request timeout in seconds | `30` |
|
|
303
|
+
| `MTN_CLOUD_MAX_RETRIES` | Maximum retry attempts | `3` |
|
|
304
|
+
| `MTN_CLOUD_RETRY_DELAY` | Retry backoff factor | `1.0` |
|
|
305
|
+
| `MTN_CLOUD_VERIFY_SSL` | Enable SSL verification | `true` |
|
|
306
|
+
|
|
307
|
+
Programmatic configuration:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from mtn_cloud import MTNCloud, MTNCloudConfig
|
|
311
|
+
|
|
312
|
+
config = MTNCloudConfig(
|
|
313
|
+
token="your-token",
|
|
314
|
+
timeout=60,
|
|
315
|
+
max_retries=5,
|
|
316
|
+
retry_delay=1.5,
|
|
317
|
+
verify_ssl=True,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
cloud = MTNCloud(config=config)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Examples
|
|
324
|
+
|
|
325
|
+
| Script | What it demonstrates |
|
|
326
|
+
|--------|-----------------------|
|
|
327
|
+
| `examples/basic_usage.py` | Auth, connectivity, resource listing |
|
|
328
|
+
| `examples/create_instance.py` | End-to-end instance creation scaffold |
|
|
329
|
+
| `examples/storage_archive_s3.py` | Storage provider + archive bucket + file ops |
|
|
330
|
+
| `examples/list_storage_buckets.py` | Storage bucket and archive bucket discovery |
|
|
331
|
+
| `examples/upload_archive_directory.py` | Bulk archive upload from local directory |
|
|
332
|
+
| `examples/copy_archive_file.py` | Archive file copy between buckets |
|
|
333
|
+
|
|
334
|
+
## API Notes
|
|
335
|
+
|
|
336
|
+
- Some endpoints can be tenant-restricted in specific MTN Cloud environments.
|
|
337
|
+
- Build scripts to discover IDs dynamically (`list` + `get_by_name`) before create/update operations.
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
Contributions are welcome. Open an issue or submit a PR.
|
|
342
|
+
|
|
343
|
+
## License
|
|
344
|
+
|
|
345
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
346
|
+
|
|
347
|
+
## Links
|
|
348
|
+
|
|
349
|
+
- [MTN Cloud Console](https://console.cloud.mtn.ng)
|
|
350
|
+
- [MTN Cloud Guide](https://cloud.mtn.ng/documentation)
|
|
351
|
+
- [Morpheus API Documentation](https://apidocs.morpheusdata.com/)
|
|
352
|
+
- [GitHub Repository](https://github.com/mahveotm/mtn-cloud-python)
|
|
353
|
+
- [PyPI Package](https://pypi.org/project/mtn-cloud/)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# MTN Cloud Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/mtn-cloud)
|
|
4
|
+
[](https://github.com/mahveotm/mtn-cloud-python/actions/workflows/test.yml)
|
|
5
|
+
[](https://github.com/mahveotm/mtn-cloud-python/actions/workflows/docs.yml)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
A Modern Python SDK for [MTN Cloud](https://console.cloud.mtn.ng) (Morpheus) with typed models, clear resource managers, and practical workflows for compute, networking, storage, and archives.
|
|
10
|
+
|
|
11
|
+
Docs: [mtn-cloud-python](https://mahveotm.github.io/mtn-cloud-python/)
|
|
12
|
+
|
|
13
|
+
> **Disclaimer:** Unofficial community project. Not affiliated with MTN Nigeria.
|
|
14
|
+
|
|
15
|
+
## Why You'll Like It
|
|
16
|
+
|
|
17
|
+
- Simple Pythonic API across core MTN Cloud resources
|
|
18
|
+
- Typed Pydantic models with IDE autocomplete
|
|
19
|
+
- Built-in retry behavior and timeout controls
|
|
20
|
+
- Token or username/password authentication
|
|
21
|
+
- Structured exceptions for better error handling
|
|
22
|
+
- Examples for real-world automation scripts
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install mtn-cloud
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Documentation
|
|
31
|
+
|
|
32
|
+
- [Docs Index](https://mahveotm.github.io/mtn-cloud-python/)
|
|
33
|
+
- [Quickstart](https://mahveotm.github.io/mtn-cloud-python/quickstart/)
|
|
34
|
+
- [Instances](https://mahveotm.github.io/mtn-cloud-python/instances/)
|
|
35
|
+
- [Networking](https://mahveotm.github.io/mtn-cloud-python/networking/)
|
|
36
|
+
- [Storage](https://mahveotm.github.io/mtn-cloud-python/storage/)
|
|
37
|
+
- [Advanced Cookbook](https://mahveotm.github.io/mtn-cloud-python/advanced-cookbook/)
|
|
38
|
+
- [API Overview](https://mahveotm.github.io/mtn-cloud-python/api-overview/)
|
|
39
|
+
- [Docstring Style Standard](https://mahveotm.github.io/mtn-cloud-python/docstring-style/)
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from mtn_cloud import MTNCloud
|
|
45
|
+
|
|
46
|
+
cloud = MTNCloud(token="your-api-token")
|
|
47
|
+
|
|
48
|
+
# Verify auth and connectivity
|
|
49
|
+
user = cloud.whoami()
|
|
50
|
+
print(f"Connected as: {user.username}")
|
|
51
|
+
print("Ping:", cloud.ping())
|
|
52
|
+
|
|
53
|
+
# List a few running instances
|
|
54
|
+
running = cloud.instances.list(status="running", max_results=5)
|
|
55
|
+
for instance in running:
|
|
56
|
+
print(instance.id, instance.name, instance.status, instance.primary_ip)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from mtn_cloud import MTNCloud
|
|
63
|
+
|
|
64
|
+
# Option 1: token (recommended)
|
|
65
|
+
cloud = MTNCloud(token="your-api-token")
|
|
66
|
+
|
|
67
|
+
# Option 2: environment variable
|
|
68
|
+
# export MTN_CLOUD_TOKEN="your-api-token"
|
|
69
|
+
cloud = MTNCloud()
|
|
70
|
+
|
|
71
|
+
# Option 3: username/password
|
|
72
|
+
cloud = MTNCloud(username="user@example.com", password="your-password")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Get your API token from MTN Cloud Console:
|
|
76
|
+
User Icon (top-right) -> User Settings -> API Access.
|
|
77
|
+
|
|
78
|
+
## What You Can Do
|
|
79
|
+
|
|
80
|
+
### 1. Discover Reference Data
|
|
81
|
+
|
|
82
|
+
Use these lookups before provisioning so your scripts stay deterministic.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Groups (sites)
|
|
86
|
+
groups = cloud.groups.list()
|
|
87
|
+
for group in groups[:5]:
|
|
88
|
+
print(group.id, group.name)
|
|
89
|
+
|
|
90
|
+
# Clouds/zones
|
|
91
|
+
clouds = cloud.clouds.list_openstack()
|
|
92
|
+
for c in clouds[:5]:
|
|
93
|
+
print(c.id, c.name, c.type_code)
|
|
94
|
+
|
|
95
|
+
# Instance types
|
|
96
|
+
types = cloud.instance_types.list_os()
|
|
97
|
+
for t in types[:5]:
|
|
98
|
+
print(t.code, t.name, t.default_layout_id)
|
|
99
|
+
|
|
100
|
+
# Service plans
|
|
101
|
+
plans = cloud.plans.list()
|
|
102
|
+
for p in plans[:5]:
|
|
103
|
+
print(p.id, p.name, p.cores, p.memory_gb)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Create an Instance
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
instance = cloud.instances.create(
|
|
110
|
+
name="my-server",
|
|
111
|
+
cloud="MTNNG_CLOUD_AZ_1",
|
|
112
|
+
type="MTN-CS10",
|
|
113
|
+
group="MTNNG_CLOUD_AZ_1",
|
|
114
|
+
layout=327,
|
|
115
|
+
plan=6776,
|
|
116
|
+
labels=["production", "web"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
print(f"Created: {instance.id} {instance.name} ({instance.status})")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 3. Manage an Instance
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
instance = cloud.instances.get(123)
|
|
126
|
+
print(instance.name, instance.status, instance.primary_ip)
|
|
127
|
+
|
|
128
|
+
# Action methods from model
|
|
129
|
+
instance.stop()
|
|
130
|
+
instance.start()
|
|
131
|
+
instance.restart()
|
|
132
|
+
instance.refresh()
|
|
133
|
+
|
|
134
|
+
# Or from resource manager
|
|
135
|
+
cloud.instances.resize(123, plan_id=6780)
|
|
136
|
+
cloud.instances.delete(123, force=True, preserve_volumes=True)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 4. Work with Networks
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
# List networks
|
|
143
|
+
networks = cloud.networks.list(cloud_id=1)
|
|
144
|
+
for n in networks[:5]:
|
|
145
|
+
print(n.id, n.name, n.cidr)
|
|
146
|
+
|
|
147
|
+
# Create an OpenStack-focused network
|
|
148
|
+
network_types = cloud.networks.list_types(openstack_only=True)
|
|
149
|
+
new_network = cloud.networks.create(
|
|
150
|
+
name="mtn-prod-net",
|
|
151
|
+
cloud_id=1,
|
|
152
|
+
group_id=621,
|
|
153
|
+
type_id=network_types[0].id,
|
|
154
|
+
cidr="10.42.10.0/24",
|
|
155
|
+
gateway="10.42.10.1",
|
|
156
|
+
dns_primary="8.8.8.8",
|
|
157
|
+
visibility="private",
|
|
158
|
+
dhcp_server=True,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Update and list subnets
|
|
162
|
+
cloud.networks.update(new_network.id, description="Production network")
|
|
163
|
+
subnets = cloud.networks.list_subnets(new_network.id)
|
|
164
|
+
print(f"Subnets: {len(subnets)}")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 5. Work with Storage and Archives
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
# Create S3-compatible storage provider
|
|
171
|
+
storage = cloud.storage_buckets.create_s3(
|
|
172
|
+
name="my-s3-storage",
|
|
173
|
+
bucket_name="my-app-objects",
|
|
174
|
+
access_key="your-access-key",
|
|
175
|
+
secret_key="your-secret-key",
|
|
176
|
+
endpoint="https://ps1csp-s3.ict.mtn.com.ng:9021",
|
|
177
|
+
create_bucket=True,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Create archive bucket linked to storage provider
|
|
181
|
+
archive = cloud.archive_buckets.create(
|
|
182
|
+
name="my-app-archives",
|
|
183
|
+
storage_provider_id=storage.id,
|
|
184
|
+
visibility="private",
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Upload one file
|
|
188
|
+
uploaded = cloud.archive_buckets.upload_file(
|
|
189
|
+
bucket_name=archive.name,
|
|
190
|
+
remote_path="/",
|
|
191
|
+
local_path="./backup.sql",
|
|
192
|
+
)
|
|
193
|
+
print(uploaded.id, uploaded.name)
|
|
194
|
+
|
|
195
|
+
# Upload a directory (preserves local folder structure)
|
|
196
|
+
summary = cloud.archive_buckets.upload_directory(
|
|
197
|
+
bucket_name=archive.name,
|
|
198
|
+
remote_path="/imports/",
|
|
199
|
+
local_directory="./reports",
|
|
200
|
+
recursive=True,
|
|
201
|
+
)
|
|
202
|
+
print(
|
|
203
|
+
f"scanned={summary.scanned_count} "
|
|
204
|
+
f"uploaded={summary.uploaded_count} "
|
|
205
|
+
f"failed={summary.failed_count} "
|
|
206
|
+
f"skipped={summary.skipped_count}"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# List and download
|
|
210
|
+
files = cloud.archive_buckets.list_files(bucket_name=archive.name, remote_path="/", full_tree=True)
|
|
211
|
+
if files:
|
|
212
|
+
content = cloud.archive_buckets.download_file(
|
|
213
|
+
bucket_name=archive.name,
|
|
214
|
+
remote_path=files[0].file_path or files[0].name,
|
|
215
|
+
)
|
|
216
|
+
print(f"Downloaded {len(content)} byte(s)")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Storage vs archive model:
|
|
220
|
+
- `cloud.storage_buckets`: provider configuration (endpoint, credentials, backing bucket)
|
|
221
|
+
- `cloud.archive_buckets`: logical file container attached to a provider
|
|
222
|
+
- file operations happen through archive APIs
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
## Error Handling
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from mtn_cloud import (
|
|
229
|
+
AuthenticationError,
|
|
230
|
+
MTNCloudError,
|
|
231
|
+
NotFoundError,
|
|
232
|
+
RateLimitError,
|
|
233
|
+
ValidationError,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
cloud.instances.get(999999)
|
|
238
|
+
except NotFoundError:
|
|
239
|
+
print("Resource not found")
|
|
240
|
+
except AuthenticationError:
|
|
241
|
+
print("Authentication failed")
|
|
242
|
+
except ValidationError as exc:
|
|
243
|
+
print(f"Validation error: {exc}")
|
|
244
|
+
except RateLimitError as exc:
|
|
245
|
+
print(f"Rate limited, retry_after={exc.retry_after}")
|
|
246
|
+
except MTNCloudError as exc:
|
|
247
|
+
print(f"SDK/API error: {exc}")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Configuration
|
|
251
|
+
|
|
252
|
+
Environment variables:
|
|
253
|
+
|
|
254
|
+
| Variable | Description | Default |
|
|
255
|
+
|----------|-------------|---------|
|
|
256
|
+
| `MTN_CLOUD_TOKEN` | API access token | - |
|
|
257
|
+
| `MTN_CLOUD_URL` | API base URL | `https://console.cloud.mtn.ng` |
|
|
258
|
+
| `MTN_CLOUD_TIMEOUT` | Request timeout in seconds | `30` |
|
|
259
|
+
| `MTN_CLOUD_MAX_RETRIES` | Maximum retry attempts | `3` |
|
|
260
|
+
| `MTN_CLOUD_RETRY_DELAY` | Retry backoff factor | `1.0` |
|
|
261
|
+
| `MTN_CLOUD_VERIFY_SSL` | Enable SSL verification | `true` |
|
|
262
|
+
|
|
263
|
+
Programmatic configuration:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from mtn_cloud import MTNCloud, MTNCloudConfig
|
|
267
|
+
|
|
268
|
+
config = MTNCloudConfig(
|
|
269
|
+
token="your-token",
|
|
270
|
+
timeout=60,
|
|
271
|
+
max_retries=5,
|
|
272
|
+
retry_delay=1.5,
|
|
273
|
+
verify_ssl=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
cloud = MTNCloud(config=config)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Examples
|
|
280
|
+
|
|
281
|
+
| Script | What it demonstrates |
|
|
282
|
+
|--------|-----------------------|
|
|
283
|
+
| `examples/basic_usage.py` | Auth, connectivity, resource listing |
|
|
284
|
+
| `examples/create_instance.py` | End-to-end instance creation scaffold |
|
|
285
|
+
| `examples/storage_archive_s3.py` | Storage provider + archive bucket + file ops |
|
|
286
|
+
| `examples/list_storage_buckets.py` | Storage bucket and archive bucket discovery |
|
|
287
|
+
| `examples/upload_archive_directory.py` | Bulk archive upload from local directory |
|
|
288
|
+
| `examples/copy_archive_file.py` | Archive file copy between buckets |
|
|
289
|
+
|
|
290
|
+
## API Notes
|
|
291
|
+
|
|
292
|
+
- Some endpoints can be tenant-restricted in specific MTN Cloud environments.
|
|
293
|
+
- Build scripts to discover IDs dynamically (`list` + `get_by_name`) before create/update operations.
|
|
294
|
+
|
|
295
|
+
## Contributing
|
|
296
|
+
|
|
297
|
+
Contributions are welcome. Open an issue or submit a PR.
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
302
|
+
|
|
303
|
+
## Links
|
|
304
|
+
|
|
305
|
+
- [MTN Cloud Console](https://console.cloud.mtn.ng)
|
|
306
|
+
- [MTN Cloud Guide](https://cloud.mtn.ng/documentation)
|
|
307
|
+
- [Morpheus API Documentation](https://apidocs.morpheusdata.com/)
|
|
308
|
+
- [GitHub Repository](https://github.com/mahveotm/mtn-cloud-python)
|
|
309
|
+
- [PyPI Package](https://pypi.org/project/mtn-cloud/)
|