cloudconsolelink 2.1.1__tar.gz → 3.0.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.
Files changed (29) hide show
  1. cloudconsolelink-3.0.1/MANIFEST.in +3 -0
  2. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/PKG-INFO +1 -1
  3. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/__init__.py +1 -1
  4. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/aws/__init__.py +1 -1
  5. cloudconsolelink-3.0.1/cloudconsolelink/clouds/oci/__init__.py +25 -0
  6. cloudconsolelink-3.0.1/cloudconsolelink/clouds/oci/links.py +467 -0
  7. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink.egg-info/PKG-INFO +1 -1
  8. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink.egg-info/SOURCES.txt +7 -0
  9. cloudconsolelink-3.0.1/requirements-dev.txt +7 -0
  10. cloudconsolelink-3.0.1/requirements.txt +1 -0
  11. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/setup.py +8 -3
  12. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/tests/test_aws.py +10 -0
  13. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/tests/test_gcp.py +14 -0
  14. cloudconsolelink-3.0.1/tests/test_oci.py +213 -0
  15. cloudconsolelink-3.0.1/tests/test_packaging.py +25 -0
  16. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/tests/test_provider_coverage.py +69 -0
  17. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/LICENSE +0 -0
  18. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/README.md +0 -0
  19. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/__init__.py +0 -0
  20. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/aws/errors.py +0 -0
  21. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/aws/links.py +0 -0
  22. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/azure/__init__.py +0 -0
  23. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/gcp/__init__.py +0 -0
  24. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink/clouds/gcp/links.py +0 -0
  25. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink.egg-info/dependency_links.txt +0 -0
  26. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink.egg-info/requires.txt +0 -0
  27. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/cloudconsolelink.egg-info/top_level.txt +0 -0
  28. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/setup.cfg +0 -0
  29. {cloudconsolelink-2.1.1 → cloudconsolelink-3.0.1}/tests/test_azure.py +0 -0
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include requirements.txt
3
+ include requirements-dev.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudconsolelink
3
- Version: 2.1.1
3
+ Version: 3.0.1
4
4
  Summary: Generate console links for cloud resources
5
5
  Home-page: https://github.com/Cloudanix/cloud-console-links
6
6
  Maintainer: Cloudanix
@@ -1,2 +1,2 @@
1
1
  # version of the module
2
- __version__ = "2.1.1"
2
+ __version__ = "3.0.1"
@@ -137,7 +137,7 @@ class AWSLinker:
137
137
  data["resource"] = resource_tokens[0]
138
138
  data["hasPath"] = False
139
139
 
140
- else:
140
+ else: # pragma: no cover – defensive; unreachable via str.split
141
141
  logger.error(f"Invalid AWS ARN {arn}")
142
142
  raise InvalidARNError(f"Invalid AWS ARN {arn}")
143
143
 
@@ -0,0 +1,25 @@
1
+ import logging
2
+
3
+ from .links import Resource
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class OCILinker:
9
+ def get_console_link(
10
+ self,
11
+ resource_name: str = "",
12
+ **kwargs,
13
+ ) -> str:
14
+ if not resource_name:
15
+ logger.error("resource_name is required")
16
+ raise ValueError("Invalid parameters provided")
17
+
18
+ resource = Resource()
19
+ method = getattr(resource, resource_name, None)
20
+
21
+ if method is None or resource_name.startswith("_") or not callable(method):
22
+ logger.error(f"Invalid resource_name - {resource_name}")
23
+ raise ValueError("Invalid parameters provided")
24
+
25
+ return method(**kwargs).replace(" ", "")
@@ -0,0 +1,467 @@
1
+ import logging
2
+
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ BASE = "https://cloud.oracle.com"
7
+
8
+
9
+ class Resource:
10
+ # --- Compute ---
11
+ def compute_instance(self, region: str, ocid: str, **kwargs) -> str:
12
+ if region and ocid:
13
+ return f"{BASE}/compute/instances/{ocid}?region={region}"
14
+ logger.error("region and ocid required")
15
+ raise ValueError("Invalid parameters provided")
16
+
17
+ def compute_image(self, region: str, ocid: str, **kwargs) -> str:
18
+ if region and ocid:
19
+ return f"{BASE}/compute/images/{ocid}?region={region}"
20
+ logger.error("region and ocid required")
21
+ raise ValueError("Invalid parameters provided")
22
+
23
+ def instance_pool(self, region: str, ocid: str, **kwargs) -> str:
24
+ if region and ocid:
25
+ return f"{BASE}/compute/instance-pools/{ocid}?region={region}"
26
+ logger.error("region and ocid required")
27
+ raise ValueError("Invalid parameters provided")
28
+
29
+ def boot_volume(self, region: str, ocid: str, **kwargs) -> str:
30
+ if region and ocid:
31
+ return f"{BASE}/block-storage/boot-volumes/{ocid}?region={region}"
32
+ logger.error("region and ocid required")
33
+ raise ValueError("Invalid parameters provided")
34
+
35
+ def block_volume(self, region: str, ocid: str, **kwargs) -> str:
36
+ if region and ocid:
37
+ return f"{BASE}/block-storage/volumes/{ocid}?region={region}"
38
+ logger.error("region and ocid required")
39
+ raise ValueError("Invalid parameters provided")
40
+
41
+ def dedicated_vm_host(self, region: str, ocid: str, **kwargs) -> str:
42
+ if region and ocid:
43
+ return f"{BASE}/compute/dedicated-vm-hosts/{ocid}?region={region}"
44
+ logger.error("region and ocid required")
45
+ raise ValueError("Invalid parameters provided")
46
+
47
+ # --- Networking ---
48
+ def vcn(self, region: str, ocid: str, **kwargs) -> str:
49
+ if region and ocid:
50
+ return f"{BASE}/networking/vcns/{ocid}?region={region}"
51
+ logger.error("region and ocid required")
52
+ raise ValueError("Invalid parameters provided")
53
+
54
+ def subnet(self, region: str, ocid: str, **kwargs) -> str:
55
+ if region and ocid:
56
+ return f"{BASE}/networking/subnets/{ocid}?region={region}"
57
+ logger.error("region and ocid required")
58
+ raise ValueError("Invalid parameters provided")
59
+
60
+ def security_list(self, region: str, ocid: str, **kwargs) -> str:
61
+ if region and ocid:
62
+ return f"{BASE}/networking/security-lists/{ocid}?region={region}"
63
+ logger.error("region and ocid required")
64
+ raise ValueError("Invalid parameters provided")
65
+
66
+ def network_security_group(self, region: str, ocid: str, **kwargs) -> str:
67
+ if region and ocid:
68
+ return f"{BASE}/networking/network-security-groups/{ocid}?region={region}"
69
+ logger.error("region and ocid required")
70
+ raise ValueError("Invalid parameters provided")
71
+
72
+ def route_table(self, region: str, ocid: str, **kwargs) -> str:
73
+ if region and ocid:
74
+ return f"{BASE}/networking/route-tables/{ocid}?region={region}"
75
+ logger.error("region and ocid required")
76
+ raise ValueError("Invalid parameters provided")
77
+
78
+ def internet_gateway(self, region: str, ocid: str, **kwargs) -> str:
79
+ if region and ocid:
80
+ return f"{BASE}/networking/internet-gateways/{ocid}?region={region}"
81
+ logger.error("region and ocid required")
82
+ raise ValueError("Invalid parameters provided")
83
+
84
+ def nat_gateway(self, region: str, ocid: str, **kwargs) -> str:
85
+ if region and ocid:
86
+ return f"{BASE}/networking/nat-gateways/{ocid}?region={region}"
87
+ logger.error("region and ocid required")
88
+ raise ValueError("Invalid parameters provided")
89
+
90
+ def service_gateway(self, region: str, ocid: str, **kwargs) -> str:
91
+ if region and ocid:
92
+ return f"{BASE}/networking/service-gateways/{ocid}?region={region}"
93
+ logger.error("region and ocid required")
94
+ raise ValueError("Invalid parameters provided")
95
+
96
+ def drg(self, region: str, ocid: str, **kwargs) -> str:
97
+ if region and ocid:
98
+ return f"{BASE}/networking/drgs/{ocid}?region={region}"
99
+ logger.error("region and ocid required")
100
+ raise ValueError("Invalid parameters provided")
101
+
102
+ def load_balancer(self, region: str, ocid: str, **kwargs) -> str:
103
+ if region and ocid:
104
+ return f"{BASE}/networking/load-balancers/{ocid}?region={region}"
105
+ logger.error("region and ocid required")
106
+ raise ValueError("Invalid parameters provided")
107
+
108
+ def network_load_balancer(self, region: str, ocid: str, **kwargs) -> str:
109
+ if region and ocid:
110
+ return f"{BASE}/networking/network-load-balancers/{ocid}?region={region}"
111
+ logger.error("region and ocid required")
112
+ raise ValueError("Invalid parameters provided")
113
+
114
+ def public_ip(self, region: str, ocid: str, **kwargs) -> str:
115
+ if region and ocid:
116
+ return f"{BASE}/networking/public-ips/{ocid}?region={region}"
117
+ logger.error("region and ocid required")
118
+ raise ValueError("Invalid parameters provided")
119
+
120
+ def dhcp_options(self, region: str, ocid: str, **kwargs) -> str:
121
+ if region and ocid:
122
+ return f"{BASE}/networking/dhcp-options/{ocid}?region={region}"
123
+ logger.error("region and ocid required")
124
+ raise ValueError("Invalid parameters provided")
125
+
126
+ def local_peering_gateway(self, region: str, ocid: str, **kwargs) -> str:
127
+ if region and ocid:
128
+ return f"{BASE}/networking/local-peering-gateways/{ocid}?region={region}"
129
+ logger.error("region and ocid required")
130
+ raise ValueError("Invalid parameters provided")
131
+
132
+ def network_firewall(self, region: str, ocid: str, **kwargs) -> str:
133
+ if region and ocid:
134
+ return f"{BASE}/network-firewall/firewalls/{ocid}?region={region}"
135
+ logger.error("region and ocid required")
136
+ raise ValueError("Invalid parameters provided")
137
+
138
+ def network_firewall_policy(self, region: str, ocid: str, **kwargs) -> str:
139
+ if region and ocid:
140
+ return f"{BASE}/network-firewall/firewall-policies/{ocid}?region={region}"
141
+ logger.error("region and ocid required")
142
+ raise ValueError("Invalid parameters provided")
143
+
144
+ # --- Object Storage ---
145
+ def bucket(self, region: str, namespace: str, bucket_name: str, **kwargs) -> str:
146
+ if region and namespace and bucket_name:
147
+ return f"{BASE}/object-storage/buckets/{namespace}/{bucket_name}?region={region}"
148
+ logger.error("region, namespace, and bucket_name required")
149
+ raise ValueError("Invalid parameters provided")
150
+
151
+ # --- Database ---
152
+ def db_system(self, region: str, ocid: str, **kwargs) -> str:
153
+ if region and ocid:
154
+ return f"{BASE}/db-systems/{ocid}?region={region}"
155
+ logger.error("region and ocid required")
156
+ raise ValueError("Invalid parameters provided")
157
+
158
+ def autonomous_database(self, region: str, ocid: str, **kwargs) -> str:
159
+ if region and ocid:
160
+ return f"{BASE}/autonomous-database/{ocid}?region={region}"
161
+ logger.error("region and ocid required")
162
+ raise ValueError("Invalid parameters provided")
163
+
164
+ def mysql_db_system(self, region: str, ocid: str, **kwargs) -> str:
165
+ if region and ocid:
166
+ return f"{BASE}/mysql/db-systems/{ocid}?region={region}"
167
+ logger.error("region and ocid required")
168
+ raise ValueError("Invalid parameters provided")
169
+
170
+ def nosql_table(self, region: str, ocid: str, **kwargs) -> str:
171
+ if region and ocid:
172
+ return f"{BASE}/nosql/tables/{ocid}?region={region}"
173
+ logger.error("region and ocid required")
174
+ raise ValueError("Invalid parameters provided")
175
+
176
+ def postgresql_db_system(self, region: str, ocid: str, **kwargs) -> str:
177
+ if region and ocid:
178
+ return f"{BASE}/postgresql/db-systems/{ocid}?region={region}"
179
+ logger.error("region and ocid required")
180
+ raise ValueError("Invalid parameters provided")
181
+
182
+ # --- Identity & Access Management ---
183
+ def compartment(self, ocid: str, **kwargs) -> str:
184
+ if ocid:
185
+ return f"{BASE}/identity/compartments/{ocid}"
186
+ logger.error("ocid required")
187
+ raise ValueError("Invalid parameters provided")
188
+
189
+ def user(self, ocid: str, **kwargs) -> str:
190
+ if ocid:
191
+ return f"{BASE}/identity/users/{ocid}"
192
+ logger.error("ocid required")
193
+ raise ValueError("Invalid parameters provided")
194
+
195
+ def group(self, ocid: str, **kwargs) -> str:
196
+ if ocid:
197
+ return f"{BASE}/identity/groups/{ocid}"
198
+ logger.error("ocid required")
199
+ raise ValueError("Invalid parameters provided")
200
+
201
+ def policy(self, region: str, ocid: str, **kwargs) -> str:
202
+ if region and ocid:
203
+ return f"{BASE}/identity/policies/{ocid}?region={region}"
204
+ logger.error("region and ocid required")
205
+ raise ValueError("Invalid parameters provided")
206
+
207
+ def dynamic_group(self, ocid: str, **kwargs) -> str:
208
+ if ocid:
209
+ return f"{BASE}/identity/dynamic-groups/{ocid}"
210
+ logger.error("ocid required")
211
+ raise ValueError("Invalid parameters provided")
212
+
213
+ def identity_domain(self, region: str, ocid: str, **kwargs) -> str:
214
+ if region and ocid:
215
+ return f"{BASE}/identity/domains/{ocid}?region={region}"
216
+ logger.error("region and ocid required")
217
+ raise ValueError("Invalid parameters provided")
218
+
219
+ # --- Container & Kubernetes ---
220
+ def oke_cluster(self, region: str, ocid: str, **kwargs) -> str:
221
+ if region and ocid:
222
+ return f"{BASE}/containers/clusters/{ocid}?region={region}"
223
+ logger.error("region and ocid required")
224
+ raise ValueError("Invalid parameters provided")
225
+
226
+ def container_instance(self, region: str, ocid: str, **kwargs) -> str:
227
+ if region and ocid:
228
+ return f"{BASE}/container-instances/{ocid}?region={region}"
229
+ logger.error("region and ocid required")
230
+ raise ValueError("Invalid parameters provided")
231
+
232
+ def container_repository(self, region: str, ocid: str, **kwargs) -> str:
233
+ if region and ocid:
234
+ return f"{BASE}/container-registry/repositories/{ocid}?region={region}"
235
+ logger.error("region and ocid required")
236
+ raise ValueError("Invalid parameters provided")
237
+
238
+ def artifact_repository(self, region: str, ocid: str, **kwargs) -> str:
239
+ if region and ocid:
240
+ return f"{BASE}/artifacts/repositories/{ocid}?region={region}"
241
+ logger.error("region and ocid required")
242
+ raise ValueError("Invalid parameters provided")
243
+
244
+ # --- Functions / Serverless ---
245
+ def functions_application(self, region: str, ocid: str, **kwargs) -> str:
246
+ if region and ocid:
247
+ return f"{BASE}/functions/applications/{ocid}?region={region}"
248
+ logger.error("region and ocid required")
249
+ raise ValueError("Invalid parameters provided")
250
+
251
+ # --- Security ---
252
+ def vault(self, region: str, ocid: str, **kwargs) -> str:
253
+ if region and ocid:
254
+ return f"{BASE}/security/vaults/{ocid}?region={region}"
255
+ logger.error("region and ocid required")
256
+ raise ValueError("Invalid parameters provided")
257
+
258
+ def key(self, region: str, ocid: str, **kwargs) -> str:
259
+ if region and ocid:
260
+ return f"{BASE}/security/keys/{ocid}?region={region}"
261
+ logger.error("region and ocid required")
262
+ raise ValueError("Invalid parameters provided")
263
+
264
+ def secret(self, region: str, ocid: str, **kwargs) -> str:
265
+ if region and ocid:
266
+ return f"{BASE}/security/vaults/secrets/{ocid}?region={region}"
267
+ logger.error("region and ocid required")
268
+ raise ValueError("Invalid parameters provided")
269
+
270
+ def bastion(self, region: str, ocid: str, **kwargs) -> str:
271
+ if region and ocid:
272
+ return f"{BASE}/bastion/bastions/{ocid}?region={region}"
273
+ logger.error("region and ocid required")
274
+ raise ValueError("Invalid parameters provided")
275
+
276
+ def cloud_guard_target(self, region: str, ocid: str, **kwargs) -> str:
277
+ if region and ocid:
278
+ return f"{BASE}/cloud-guard/targets/{ocid}?region={region}"
279
+ logger.error("region and ocid required")
280
+ raise ValueError("Invalid parameters provided")
281
+
282
+ def waf_policy(self, region: str, ocid: str, **kwargs) -> str:
283
+ if region and ocid:
284
+ return f"{BASE}/waf/policies/{ocid}?region={region}"
285
+ logger.error("region and ocid required")
286
+ raise ValueError("Invalid parameters provided")
287
+
288
+ def vulnerability_scanning_target(self, region: str, ocid: str, **kwargs) -> str:
289
+ if region and ocid:
290
+ return f"{BASE}/vulnerability-scanning/targets/{ocid}?region={region}"
291
+ logger.error("region and ocid required")
292
+ raise ValueError("Invalid parameters provided")
293
+
294
+ # --- Observability & Management ---
295
+ def alarm(self, region: str, ocid: str, **kwargs) -> str:
296
+ if region and ocid:
297
+ return f"{BASE}/monitoring/alarms/{ocid}?region={region}"
298
+ logger.error("region and ocid required")
299
+ raise ValueError("Invalid parameters provided")
300
+
301
+ def notification_topic(self, region: str, ocid: str, **kwargs) -> str:
302
+ if region and ocid:
303
+ return f"{BASE}/notification/topics/{ocid}?region={region}"
304
+ logger.error("region and ocid required")
305
+ raise ValueError("Invalid parameters provided")
306
+
307
+ def log_group(self, region: str, ocid: str, **kwargs) -> str:
308
+ if region and ocid:
309
+ return f"{BASE}/logging/log-groups/{ocid}?region={region}"
310
+ logger.error("region and ocid required")
311
+ raise ValueError("Invalid parameters provided")
312
+
313
+ def apm_domain(self, region: str, ocid: str, **kwargs) -> str:
314
+ if region and ocid:
315
+ return f"{BASE}/apm/domains/{ocid}?region={region}"
316
+ logger.error("region and ocid required")
317
+ raise ValueError("Invalid parameters provided")
318
+
319
+ def streaming(self, region: str, ocid: str, **kwargs) -> str:
320
+ if region and ocid:
321
+ return f"{BASE}/streaming/streams/{ocid}?region={region}"
322
+ logger.error("region and ocid required")
323
+ raise ValueError("Invalid parameters provided")
324
+
325
+ def connector(self, region: str, ocid: str, **kwargs) -> str:
326
+ if region and ocid:
327
+ return f"{BASE}/connector-hub/connectors/{ocid}?region={region}"
328
+ logger.error("region and ocid required")
329
+ raise ValueError("Invalid parameters provided")
330
+
331
+ def events_rule(self, region: str, ocid: str, **kwargs) -> str:
332
+ if region and ocid:
333
+ return f"{BASE}/events/rules/{ocid}?region={region}"
334
+ logger.error("region and ocid required")
335
+ raise ValueError("Invalid parameters provided")
336
+
337
+ # --- DevOps ---
338
+ def devops_project(self, region: str, ocid: str, **kwargs) -> str:
339
+ if region and ocid:
340
+ return f"{BASE}/devops/projects/{ocid}?region={region}"
341
+ logger.error("region and ocid required")
342
+ raise ValueError("Invalid parameters provided")
343
+
344
+ def devops_build_pipeline(self, region: str, ocid: str, **kwargs) -> str:
345
+ if region and ocid:
346
+ return f"{BASE}/devops/build-pipelines/{ocid}?region={region}"
347
+ logger.error("region and ocid required")
348
+ raise ValueError("Invalid parameters provided")
349
+
350
+ def devops_deploy_pipeline(self, region: str, ocid: str, **kwargs) -> str:
351
+ if region and ocid:
352
+ return f"{BASE}/devops/deploy-pipelines/{ocid}?region={region}"
353
+ logger.error("region and ocid required")
354
+ raise ValueError("Invalid parameters provided")
355
+
356
+ # --- Resource Manager ---
357
+ def stack(self, region: str, ocid: str, **kwargs) -> str:
358
+ if region and ocid:
359
+ return f"{BASE}/resource-manager/stacks/{ocid}?region={region}"
360
+ logger.error("region and ocid required")
361
+ raise ValueError("Invalid parameters provided")
362
+
363
+ # --- Data & AI ---
364
+ def data_science_project(self, region: str, ocid: str, **kwargs) -> str:
365
+ if region and ocid:
366
+ return f"{BASE}/data-science/projects/{ocid}?region={region}"
367
+ logger.error("region and ocid required")
368
+ raise ValueError("Invalid parameters provided")
369
+
370
+ def data_catalog(self, region: str, ocid: str, **kwargs) -> str:
371
+ if region and ocid:
372
+ return f"{BASE}/data-catalog/catalogs/{ocid}?region={region}"
373
+ logger.error("region and ocid required")
374
+ raise ValueError("Invalid parameters provided")
375
+
376
+ def data_flow_application(self, region: str, ocid: str, **kwargs) -> str:
377
+ if region and ocid:
378
+ return f"{BASE}/data-flow/applications/{ocid}?region={region}"
379
+ logger.error("region and ocid required")
380
+ raise ValueError("Invalid parameters provided")
381
+
382
+ def data_integration_workspace(self, region: str, ocid: str, **kwargs) -> str:
383
+ if region and ocid:
384
+ return f"{BASE}/data-integration/workspaces/{ocid}?region={region}"
385
+ logger.error("region and ocid required")
386
+ raise ValueError("Invalid parameters provided")
387
+
388
+ # --- API Gateway ---
389
+ def api_gateway(self, region: str, ocid: str, **kwargs) -> str:
390
+ if region and ocid:
391
+ return f"{BASE}/api-gateway/gateways/{ocid}?region={region}"
392
+ logger.error("region and ocid required")
393
+ raise ValueError("Invalid parameters provided")
394
+
395
+ def api_deployment(self, region: str, ocid: str, **kwargs) -> str:
396
+ if region and ocid:
397
+ return f"{BASE}/api-gateway/deployments/{ocid}?region={region}"
398
+ logger.error("region and ocid required")
399
+ raise ValueError("Invalid parameters provided")
400
+
401
+ # --- DNS ---
402
+ def dns_zone(self, region: str, ocid: str, **kwargs) -> str:
403
+ if region and ocid:
404
+ return f"{BASE}/dns/zones/{ocid}?region={region}"
405
+ logger.error("region and ocid required")
406
+ raise ValueError("Invalid parameters provided")
407
+
408
+ # --- Email ---
409
+ def email_sender(self, region: str, ocid: str, **kwargs) -> str:
410
+ if region and ocid:
411
+ return f"{BASE}/email-delivery/senders/{ocid}?region={region}"
412
+ logger.error("region and ocid required")
413
+ raise ValueError("Invalid parameters provided")
414
+
415
+ # --- File Storage ---
416
+ def file_system(self, region: str, ocid: str, **kwargs) -> str:
417
+ if region and ocid:
418
+ return f"{BASE}/file-storage/file-systems/{ocid}?region={region}"
419
+ logger.error("region and ocid required")
420
+ raise ValueError("Invalid parameters provided")
421
+
422
+ def mount_target(self, region: str, ocid: str, **kwargs) -> str:
423
+ if region and ocid:
424
+ return f"{BASE}/file-storage/mount-targets/{ocid}?region={region}"
425
+ logger.error("region and ocid required")
426
+ raise ValueError("Invalid parameters provided")
427
+
428
+ # --- Queue ---
429
+ def queue(self, region: str, ocid: str, **kwargs) -> str:
430
+ if region and ocid:
431
+ return f"{BASE}/queue/queues/{ocid}?region={region}"
432
+ logger.error("region and ocid required")
433
+ raise ValueError("Invalid parameters provided")
434
+
435
+ # --- Service home pages ---
436
+ def compute_home(self, region: str, **kwargs) -> str:
437
+ if region:
438
+ return f"{BASE}/compute/instances?region={region}"
439
+ logger.error("region required")
440
+ raise ValueError("Invalid parameters provided")
441
+
442
+ def networking_home(self, region: str, **kwargs) -> str:
443
+ if region:
444
+ return f"{BASE}/networking/vcns?region={region}"
445
+ logger.error("region required")
446
+ raise ValueError("Invalid parameters provided")
447
+
448
+ def object_storage_home(self, region: str, **kwargs) -> str:
449
+ if region:
450
+ return f"{BASE}/object-storage/buckets?region={region}"
451
+ logger.error("region required")
452
+ raise ValueError("Invalid parameters provided")
453
+
454
+ def database_home(self, region: str, **kwargs) -> str:
455
+ if region:
456
+ return f"{BASE}/db-systems?region={region}"
457
+ logger.error("region required")
458
+ raise ValueError("Invalid parameters provided")
459
+
460
+ def identity_home(self, **kwargs) -> str:
461
+ return f"{BASE}/identity/compartments"
462
+
463
+ def oke_home(self, region: str, **kwargs) -> str:
464
+ if region:
465
+ return f"{BASE}/containers/clusters?region={region}"
466
+ logger.error("region required")
467
+ raise ValueError("Invalid parameters provided")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudconsolelink
3
- Version: 2.1.1
3
+ Version: 3.0.1
4
4
  Summary: Generate console links for cloud resources
5
5
  Home-page: https://github.com/Cloudanix/cloud-console-links
6
6
  Maintainer: Cloudanix
@@ -1,5 +1,8 @@
1
1
  LICENSE
2
+ MANIFEST.in
2
3
  README.md
4
+ requirements-dev.txt
5
+ requirements.txt
3
6
  setup.cfg
4
7
  setup.py
5
8
  cloudconsolelink/__init__.py
@@ -15,7 +18,11 @@ cloudconsolelink/clouds/aws/links.py
15
18
  cloudconsolelink/clouds/azure/__init__.py
16
19
  cloudconsolelink/clouds/gcp/__init__.py
17
20
  cloudconsolelink/clouds/gcp/links.py
21
+ cloudconsolelink/clouds/oci/__init__.py
22
+ cloudconsolelink/clouds/oci/links.py
18
23
  tests/test_aws.py
19
24
  tests/test_azure.py
20
25
  tests/test_gcp.py
26
+ tests/test_oci.py
27
+ tests/test_packaging.py
21
28
  tests/test_provider_coverage.py
@@ -0,0 +1,7 @@
1
+ pre-commit~=4.2
2
+ pytest~=8.3
3
+ pytest-cov==2.11
4
+ pytest-mock~=3.14
5
+
6
+ types-PyYAML~=6.0
7
+ types-requests~=2.32
@@ -0,0 +1 @@
1
+ # Runtime dependencies are currently empty.
@@ -6,13 +6,18 @@ import setuptools
6
6
  ROOT = Path(__file__).parent
7
7
 
8
8
 
9
- def read_text(path: str) -> str:
10
- return (ROOT / path).read_text(encoding="utf-8")
9
+ def read_text(path: str, *, required: bool = True) -> str:
10
+ file_path = ROOT / path
11
+ if not file_path.exists():
12
+ if required:
13
+ raise FileNotFoundError(file_path)
14
+ return ""
15
+ return file_path.read_text(encoding="utf-8")
11
16
 
12
17
 
13
18
  def read_requirements(path: str) -> list[str]:
14
19
  lines = []
15
- for line in read_text(path).splitlines():
20
+ for line in read_text(path, required=False).splitlines():
16
21
  requirement = line.strip()
17
22
  if requirement and not requirement.startswith("#"):
18
23
  lines.append(requirement)
@@ -5,6 +5,7 @@ from cloudconsolelink.clouds.aws import ARNTooShortError
5
5
  from cloudconsolelink.clouds.aws import InvalidARNError
6
6
  from cloudconsolelink.clouds.aws import InvalidPartitionError
7
7
  from cloudconsolelink.clouds.aws import InvalidServiceError
8
+ from cloudconsolelink.clouds.aws import get_service_home_link
8
9
  from cloudconsolelink.clouds.aws.links import get_links
9
10
 
10
11
  aws = AWSLinker()
@@ -250,3 +251,12 @@ def test_aws_links_resource_keys_are_trimmed():
250
251
  assert resource_type == resource_type.strip(), (
251
252
  f"{service!r} contains malformed resource key {resource_type!r}"
252
253
  )
254
+
255
+
256
+ def test_aws_get_service_home_link_returns_empty_when_service_missing():
257
+ assert get_service_home_link({"service": "", "console": "console.aws.amazon.com"}) == ""
258
+
259
+
260
+ def test_aws_get_service_home_link_returns_empty_when_console_missing():
261
+ assert get_service_home_link({"service": "ec2", "console": ""}) == ""
262
+
@@ -103,3 +103,17 @@ def test_cloud_spanner_database():
103
103
  )
104
104
 
105
105
  assert out_link == expected_link
106
+
107
+
108
+ def test_cloud_spanner_database_without_database_name():
109
+ out_link = gcp.get_console_link(
110
+ project_id="12345",
111
+ instance_name="spanner-1",
112
+ resource_name="cloud_spanner_database",
113
+ )
114
+ assert out_link == "https://console.cloud.google.com/spanner/instances/spanner-1/databases?project=12345"
115
+
116
+
117
+ def test_cloud_spanner_database_requires_params():
118
+ with pytest.raises(ValueError):
119
+ gcp.get_console_link(resource_name="cloud_spanner_database", project_id="12345")
@@ -0,0 +1,213 @@
1
+ import pytest
2
+
3
+ from cloudconsolelink.clouds.oci import OCILinker
4
+
5
+ oci = OCILinker()
6
+
7
+ REGION = "us-ashburn-1"
8
+ OCID = "ocid1.instance.oc1.iad.abc123"
9
+
10
+
11
+ # --- Compute ---
12
+ def test_compute_instance():
13
+ link = oci.get_console_link(
14
+ resource_name="compute_instance", region=REGION, ocid=OCID,
15
+ )
16
+ assert link == f"https://cloud.oracle.com/compute/instances/{OCID}?region={REGION}"
17
+
18
+
19
+ def test_compute_image():
20
+ ocid = "ocid1.image.oc1.iad.abc123"
21
+ link = oci.get_console_link(resource_name="compute_image", region=REGION, ocid=ocid)
22
+ assert link == f"https://cloud.oracle.com/compute/images/{ocid}?region={REGION}"
23
+
24
+
25
+ def test_boot_volume():
26
+ ocid = "ocid1.bootvolume.oc1.iad.abc123"
27
+ link = oci.get_console_link(resource_name="boot_volume", region=REGION, ocid=ocid)
28
+ assert link == f"https://cloud.oracle.com/block-storage/boot-volumes/{ocid}?region={REGION}"
29
+
30
+
31
+ def test_block_volume():
32
+ ocid = "ocid1.volume.oc1.iad.abc123"
33
+ link = oci.get_console_link(resource_name="block_volume", region=REGION, ocid=ocid)
34
+ assert link == f"https://cloud.oracle.com/block-storage/volumes/{ocid}?region={REGION}"
35
+
36
+
37
+ # --- Networking ---
38
+ def test_vcn():
39
+ ocid = "ocid1.vcn.oc1.iad.abc123"
40
+ link = oci.get_console_link(resource_name="vcn", region=REGION, ocid=ocid)
41
+ assert link == f"https://cloud.oracle.com/networking/vcns/{ocid}?region={REGION}"
42
+
43
+
44
+ def test_subnet():
45
+ ocid = "ocid1.subnet.oc1.iad.abc123"
46
+ link = oci.get_console_link(resource_name="subnet", region=REGION, ocid=ocid)
47
+ assert link == f"https://cloud.oracle.com/networking/subnets/{ocid}?region={REGION}"
48
+
49
+
50
+ def test_security_list():
51
+ ocid = "ocid1.securitylist.oc1.iad.abc123"
52
+ link = oci.get_console_link(resource_name="security_list", region=REGION, ocid=ocid)
53
+ assert link == f"https://cloud.oracle.com/networking/security-lists/{ocid}?region={REGION}"
54
+
55
+
56
+ def test_network_security_group():
57
+ ocid = "ocid1.networksecuritygroup.oc1.iad.abc123"
58
+ link = oci.get_console_link(resource_name="network_security_group", region=REGION, ocid=ocid)
59
+ assert link == f"https://cloud.oracle.com/networking/network-security-groups/{ocid}?region={REGION}"
60
+
61
+
62
+ def test_load_balancer():
63
+ ocid = "ocid1.loadbalancer.oc1.iad.abc123"
64
+ link = oci.get_console_link(resource_name="load_balancer", region=REGION, ocid=ocid)
65
+ assert link == f"https://cloud.oracle.com/networking/load-balancers/{ocid}?region={REGION}"
66
+
67
+
68
+ def test_drg():
69
+ ocid = "ocid1.drg.oc1.iad.abc123"
70
+ link = oci.get_console_link(resource_name="drg", region=REGION, ocid=ocid)
71
+ assert link == f"https://cloud.oracle.com/networking/drgs/{ocid}?region={REGION}"
72
+
73
+
74
+ # --- Object Storage ---
75
+ def test_bucket():
76
+ link = oci.get_console_link(
77
+ resource_name="bucket", region=REGION, namespace="mytenancy", bucket_name="my-bucket",
78
+ )
79
+ assert link == f"https://cloud.oracle.com/object-storage/buckets/mytenancy/my-bucket?region={REGION}"
80
+
81
+
82
+ # --- Database ---
83
+ def test_autonomous_database():
84
+ ocid = "ocid1.autonomousdatabase.oc1.iad.abc123"
85
+ link = oci.get_console_link(resource_name="autonomous_database", region=REGION, ocid=ocid)
86
+ assert link == f"https://cloud.oracle.com/autonomous-database/{ocid}?region={REGION}"
87
+
88
+
89
+ def test_db_system():
90
+ ocid = "ocid1.dbsystem.oc1.iad.abc123"
91
+ link = oci.get_console_link(resource_name="db_system", region=REGION, ocid=ocid)
92
+ assert link == f"https://cloud.oracle.com/db-systems/{ocid}?region={REGION}"
93
+
94
+
95
+ def test_mysql_db_system():
96
+ ocid = "ocid1.mysqldbsystem.oc1.iad.abc123"
97
+ link = oci.get_console_link(resource_name="mysql_db_system", region=REGION, ocid=ocid)
98
+ assert link == f"https://cloud.oracle.com/mysql/db-systems/{ocid}?region={REGION}"
99
+
100
+
101
+ # --- IAM ---
102
+ def test_compartment():
103
+ ocid = "ocid1.compartment.oc1..abc123"
104
+ link = oci.get_console_link(resource_name="compartment", ocid=ocid)
105
+ assert link == f"https://cloud.oracle.com/identity/compartments/{ocid}"
106
+
107
+
108
+ def test_user():
109
+ ocid = "ocid1.user.oc1..abc123"
110
+ link = oci.get_console_link(resource_name="user", ocid=ocid)
111
+ assert link == f"https://cloud.oracle.com/identity/users/{ocid}"
112
+
113
+
114
+ def test_group():
115
+ ocid = "ocid1.group.oc1..abc123"
116
+ link = oci.get_console_link(resource_name="group", ocid=ocid)
117
+ assert link == f"https://cloud.oracle.com/identity/groups/{ocid}"
118
+
119
+
120
+ def test_policy():
121
+ ocid = "ocid1.policy.oc1.iad.abc123"
122
+ link = oci.get_console_link(resource_name="policy", region=REGION, ocid=ocid)
123
+ assert link == f"https://cloud.oracle.com/identity/policies/{ocid}?region={REGION}"
124
+
125
+
126
+ # --- Containers ---
127
+ def test_oke_cluster():
128
+ ocid = "ocid1.cluster.oc1.iad.abc123"
129
+ link = oci.get_console_link(resource_name="oke_cluster", region=REGION, ocid=ocid)
130
+ assert link == f"https://cloud.oracle.com/containers/clusters/{ocid}?region={REGION}"
131
+
132
+
133
+ # --- Security ---
134
+ def test_vault():
135
+ ocid = "ocid1.vault.oc1.iad.abc123"
136
+ link = oci.get_console_link(resource_name="vault", region=REGION, ocid=ocid)
137
+ assert link == f"https://cloud.oracle.com/security/vaults/{ocid}?region={REGION}"
138
+
139
+
140
+ def test_secret():
141
+ ocid = "ocid1.vaultsecret.oc1.iad.abc123"
142
+ link = oci.get_console_link(resource_name="secret", region=REGION, ocid=ocid)
143
+ assert link == f"https://cloud.oracle.com/security/vaults/secrets/{ocid}?region={REGION}"
144
+
145
+
146
+ def test_bastion():
147
+ ocid = "ocid1.bastion.oc1.iad.abc123"
148
+ link = oci.get_console_link(resource_name="bastion", region=REGION, ocid=ocid)
149
+ assert link == f"https://cloud.oracle.com/bastion/bastions/{ocid}?region={REGION}"
150
+
151
+
152
+ # --- Observability ---
153
+ def test_alarm():
154
+ ocid = "ocid1.alarm.oc1.iad.abc123"
155
+ link = oci.get_console_link(resource_name="alarm", region=REGION, ocid=ocid)
156
+ assert link == f"https://cloud.oracle.com/monitoring/alarms/{ocid}?region={REGION}"
157
+
158
+
159
+ def test_notification_topic():
160
+ ocid = "ocid1.onstopic.oc1.iad.abc123"
161
+ link = oci.get_console_link(resource_name="notification_topic", region=REGION, ocid=ocid)
162
+ assert link == f"https://cloud.oracle.com/notification/topics/{ocid}?region={REGION}"
163
+
164
+
165
+ # --- DevOps ---
166
+ def test_devops_project():
167
+ ocid = "ocid1.devopsproject.oc1.iad.abc123"
168
+ link = oci.get_console_link(resource_name="devops_project", region=REGION, ocid=ocid)
169
+ assert link == f"https://cloud.oracle.com/devops/projects/{ocid}?region={REGION}"
170
+
171
+
172
+ # --- API Gateway ---
173
+ def test_api_gateway():
174
+ ocid = "ocid1.apigateway.oc1.iad.abc123"
175
+ link = oci.get_console_link(resource_name="api_gateway", region=REGION, ocid=ocid)
176
+ assert link == f"https://cloud.oracle.com/api-gateway/gateways/{ocid}?region={REGION}"
177
+
178
+
179
+ # --- Service home pages ---
180
+ def test_compute_home():
181
+ link = oci.get_console_link(resource_name="compute_home", region=REGION)
182
+ assert link == f"https://cloud.oracle.com/compute/instances?region={REGION}"
183
+
184
+
185
+ def test_networking_home():
186
+ link = oci.get_console_link(resource_name="networking_home", region=REGION)
187
+ assert link == f"https://cloud.oracle.com/networking/vcns?region={REGION}"
188
+
189
+
190
+ def test_identity_home():
191
+ link = oci.get_console_link(resource_name="identity_home")
192
+ assert link == "https://cloud.oracle.com/identity/compartments"
193
+
194
+
195
+ # --- Error cases ---
196
+ def test_invalid_resource_name():
197
+ with pytest.raises(ValueError):
198
+ oci.get_console_link(resource_name="nonexistent_resource", region=REGION, ocid=OCID)
199
+
200
+
201
+ def test_missing_resource_name():
202
+ with pytest.raises(ValueError):
203
+ oci.get_console_link(region=REGION, ocid=OCID)
204
+
205
+
206
+ def test_missing_required_params():
207
+ with pytest.raises(ValueError):
208
+ oci.get_console_link(resource_name="compute_instance", region="", ocid="")
209
+
210
+
211
+ def test_bucket_missing_namespace():
212
+ with pytest.raises(ValueError):
213
+ oci.get_console_link(resource_name="bucket", region=REGION, namespace="", bucket_name="my-bucket")
@@ -0,0 +1,25 @@
1
+ import subprocess
2
+ import sys
3
+ import tarfile
4
+ from pathlib import Path
5
+
6
+
7
+ REPO_ROOT = Path(__file__).resolve().parents[1]
8
+
9
+
10
+ def test_sdist_includes_requirement_files(tmp_path):
11
+ subprocess.run(
12
+ [sys.executable, "setup.py", "sdist", "--dist-dir", str(tmp_path)],
13
+ cwd=REPO_ROOT,
14
+ check=True,
15
+ capture_output=True,
16
+ text=True,
17
+ )
18
+
19
+ archive_path = next(tmp_path.glob("cloudconsolelink-*.tar.gz"))
20
+ with tarfile.open(archive_path, "r:gz") as archive:
21
+ archive_names = set(archive.getnames())
22
+
23
+ archive_root = archive_path.name.removesuffix(".tar.gz")
24
+ assert f"{archive_root}/requirements.txt" in archive_names
25
+ assert f"{archive_root}/requirements-dev.txt" in archive_names
@@ -12,6 +12,8 @@ from cloudconsolelink.clouds.aws import (
12
12
  from cloudconsolelink.clouds.azure import AzureLinker
13
13
  from cloudconsolelink.clouds.gcp import GCPLinker
14
14
  from cloudconsolelink.clouds.gcp.links import Resource
15
+ from cloudconsolelink.clouds.oci import OCILinker
16
+ from cloudconsolelink.clouds.oci.links import Resource as OCIResource
15
17
 
16
18
 
17
19
  GCP_SAMPLE_ARGS = {
@@ -294,3 +296,70 @@ def test_aws_linker_builds_links_for_raw_arn_templates():
294
296
  )
295
297
  == "https://us-east-1.console.aws.amazon.com/ec2/home?region=us-east-1#TargetGroups:targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/demo-group/1234567890abcdef"
296
298
  )
299
+
300
+
301
+ # ---------------------------------------------------------------------------
302
+ # OCI provider coverage
303
+ # ---------------------------------------------------------------------------
304
+
305
+ OCI_SAMPLE_ARGS = {
306
+ "region": "us-ashburn-1",
307
+ "ocid": "ocid1.test.oc1.iad.abc123",
308
+ "namespace": "demo-namespace",
309
+ "bucket_name": "demo-bucket",
310
+ }
311
+
312
+
313
+ def _oci_method_kwargs(method_name: str) -> dict[str, str]:
314
+ resource = OCIResource()
315
+ signature = inspect.signature(getattr(resource, method_name))
316
+ kwargs: dict[str, str] = {}
317
+ for name, parameter in signature.parameters.items():
318
+ if parameter.kind is inspect.Parameter.VAR_KEYWORD:
319
+ continue
320
+ kwargs[name] = OCI_SAMPLE_ARGS[name]
321
+ return kwargs
322
+
323
+
324
+ OCI_RESOURCE_METHODS = sorted(
325
+ method_name
326
+ for method_name, member in inspect.getmembers(OCIResource, predicate=inspect.isfunction)
327
+ if not method_name.startswith("_")
328
+ )
329
+
330
+
331
+ @pytest.mark.parametrize("method_name", OCI_RESOURCE_METHODS)
332
+ def test_oci_resource_methods_return_console_urls(method_name: str):
333
+ resource = OCIResource()
334
+
335
+ out_link = getattr(resource, method_name)(**_oci_method_kwargs(method_name))
336
+
337
+ assert out_link.startswith("https://cloud.oracle.com/")
338
+
339
+
340
+ @pytest.mark.parametrize("method_name", OCI_RESOURCE_METHODS)
341
+ def test_oci_resource_methods_validate_required_parameters(method_name: str):
342
+ resource = OCIResource()
343
+ kwargs = _oci_method_kwargs(method_name)
344
+ if not kwargs:
345
+ pytest.skip("no required args to blank out")
346
+ first_arg = next(iter(kwargs))
347
+ kwargs[first_arg] = ""
348
+
349
+ with pytest.raises(ValueError, match="Invalid parameters provided"):
350
+ getattr(resource, method_name)(**kwargs)
351
+
352
+
353
+ @pytest.mark.parametrize("method_name", OCI_RESOURCE_METHODS)
354
+ def test_oci_linker_supports_all_resource_builders(method_name: str):
355
+ kwargs = _oci_method_kwargs(method_name)
356
+ expected_link = getattr(OCIResource(), method_name)(**kwargs).replace(" ", "")
357
+
358
+ out_link = OCILinker().get_console_link(resource_name=method_name, **kwargs)
359
+
360
+ assert out_link == expected_link
361
+
362
+
363
+ def test_oci_linker_rejects_unknown_resource_name():
364
+ with pytest.raises(ValueError, match="Invalid parameters provided"):
365
+ OCILinker().get_console_link(resource_name="missing_resource", region="us-ashburn-1")