cloudconsolelink 2.0.0__tar.gz → 3.0.0__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 (27) hide show
  1. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/PKG-INFO +1 -1
  2. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/__init__.py +1 -1
  3. cloudconsolelink-3.0.0/cloudconsolelink/clouds/aws/__init__.py +180 -0
  4. cloudconsolelink-3.0.0/cloudconsolelink/clouds/aws/errors.py +18 -0
  5. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/clouds/aws/links.py +156 -0
  6. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/clouds/gcp/__init__.py +41 -0
  7. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/clouds/gcp/links.py +319 -0
  8. cloudconsolelink-3.0.0/cloudconsolelink/clouds/oci/__init__.py +25 -0
  9. cloudconsolelink-3.0.0/cloudconsolelink/clouds/oci/links.py +467 -0
  10. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink.egg-info/PKG-INFO +1 -1
  11. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink.egg-info/SOURCES.txt +4 -0
  12. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/tests/test_aws.py +62 -1
  13. cloudconsolelink-3.0.0/tests/test_gcp.py +119 -0
  14. cloudconsolelink-3.0.0/tests/test_oci.py +213 -0
  15. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/tests/test_provider_coverage.py +80 -3
  16. cloudconsolelink-2.0.0/cloudconsolelink/clouds/aws/__init__.py +0 -137
  17. cloudconsolelink-2.0.0/tests/test_gcp.py +0 -30
  18. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/LICENSE +0 -0
  19. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/README.md +0 -0
  20. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/clouds/__init__.py +0 -0
  21. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink/clouds/azure/__init__.py +0 -0
  22. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink.egg-info/dependency_links.txt +0 -0
  23. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink.egg-info/requires.txt +0 -0
  24. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/cloudconsolelink.egg-info/top_level.txt +0 -0
  25. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/setup.cfg +0 -0
  26. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/setup.py +0 -0
  27. {cloudconsolelink-2.0.0 → cloudconsolelink-3.0.0}/tests/test_azure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudconsolelink
3
- Version: 2.0.0
3
+ Version: 3.0.0
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.0.0"
2
+ __version__ = "3.0.0"
@@ -0,0 +1,180 @@
1
+ import logging
2
+ import urllib.parse
3
+ from typing import Dict
4
+
5
+ from .errors import ARNTooShortError, InvalidARNError, InvalidPartitionError, InvalidServiceError
6
+ from .links import get_links
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ SERVICE_HOME_OVERRIDES = {
11
+ "elasticloadbalancing": "https://{region}.{console}/ec2/home?region={region}#LoadBalancers:",
12
+ "logs": "https://{region}.{console}/cloudwatch/home?region={region}#logsV2:log-groups",
13
+ "sns": "https://{region}.{console}/sns/v3/home?region={region}",
14
+ "sqs": "https://{region}.{console}/sqs/v2/home?region={region}",
15
+ }
16
+
17
+
18
+ def get_console(partition: str) -> str:
19
+ consoles = {
20
+ "aws": "console.aws.amazon.com",
21
+ "aws-us-gov": "console.amazonaws-us-gov.com",
22
+ "aws-cn": "console.amazonaws.cn",
23
+ }
24
+ return consoles.get(partition, "")
25
+
26
+
27
+ def get_arn_string(data: Dict) -> str:
28
+ if not data.get("resourceType", ""):
29
+ return (
30
+ f"{data.get('prefix', '')}:{data.get('partition', '')}:{data.get('service', '')}:"
31
+ f"{data.get('region', '')}:{data.get('account', '')}:{data.get('resource', '')}"
32
+ )
33
+
34
+ elif data.get("hasPath", ""):
35
+ return (
36
+ f"{data.get('prefix', '')}:{data.get('partition', '')}:{data.get('service', '')}:"
37
+ f"{data.get('region', '')}:{data.get('account', '')}:{data.get('resourceType', '')}"
38
+ f"/{data.get('resource', '')}"
39
+ )
40
+
41
+ else:
42
+ return (
43
+ f"{data.get('prefix', '')}:{data.get('partition', '')}:{data.get('service', '')}:"
44
+ f"{data.get('region', '')}:{data.get('account', '')}:{data.get('resourceType', '')}:"
45
+ f"{data.get('resource', '')}"
46
+ )
47
+
48
+
49
+ def get_qualifiers(resource: str) -> list[str]:
50
+ return resource.split(":")
51
+
52
+
53
+ def get_resource_path(resource: str) -> str:
54
+ return resource.split("/")[-1]
55
+
56
+
57
+ def get_service_home_link(data: Dict) -> str:
58
+ service = str(data.get("service", ""))
59
+ console = str(data.get("console", ""))
60
+ region = str(data.get("region", ""))
61
+
62
+ if not service or not console:
63
+ return ""
64
+
65
+ if region and service in SERVICE_HOME_OVERRIDES:
66
+ return SERVICE_HOME_OVERRIDES[service].format(
67
+ console=console,
68
+ region=urllib.parse.quote(region),
69
+ )
70
+
71
+ host = f"{region}.{console}" if region else console
72
+ path = f"/{service}/home" if region else f"/{service}"
73
+ link = f"https://{host}{path}"
74
+ if region:
75
+ link = f"{link}?region={urllib.parse.quote(region)}"
76
+ return link
77
+
78
+
79
+ class AWSLinker:
80
+ def get_console_link(self, arn: str) -> str:
81
+ logger.debug(f"Start Process for AWS ARN: {arn}")
82
+ arn = arn.strip()
83
+ tokens = arn.split(":")
84
+
85
+ if len(tokens) < 5:
86
+ logger.error(f"AWS ARN {arn} is too short")
87
+ raise ARNTooShortError(f"AWS ARN {arn} is too short")
88
+
89
+ data: dict[str, str | bool] = {
90
+ "prefix": tokens[0],
91
+ "partition": tokens[1],
92
+ "service": tokens[2],
93
+ "region": tokens[3],
94
+ "account": tokens[4],
95
+ "resourceType": "",
96
+ "resource": "",
97
+ "hasPath": False,
98
+ }
99
+
100
+ resource_tokens = tokens[5:]
101
+
102
+ console = get_console(str(data.get("partition", "")))
103
+ if not console:
104
+ logger.error(
105
+ f"AWS ARN {arn} has invalid partition (Valid Partitions: aws, aws-us-gov, aws-cn)",
106
+ )
107
+ raise InvalidPartitionError(
108
+ f"AWS ARN {arn} has invalid partition (Valid Partitions: aws, aws-us-gov, aws-cn)",
109
+ )
110
+ data["console"] = console
111
+
112
+ links = get_links()
113
+
114
+ if data["prefix"] != "arn":
115
+ logger.error(f"Invalid AWS ARN {arn}")
116
+ raise InvalidARNError(f"Invalid AWS ARN {arn}")
117
+
118
+ if data["service"] not in links:
119
+ logger.error(f"AWS service {data['service']} unknown")
120
+ raise InvalidServiceError(f"AWS service {data['service']} unknown")
121
+
122
+ if not resource_tokens or resource_tokens == [""]:
123
+ return get_service_home_link(data)
124
+
125
+ if len(resource_tokens) > 1:
126
+ data["resourceType"] = resource_tokens[0]
127
+ data["resource"] = ":".join(resource_tokens[1:])
128
+ data["hasPath"] = False
129
+
130
+ elif resource_tokens[0].count("/") > 0:
131
+ data["resourceType"] = resource_tokens[0][: resource_tokens[0].index("/")]
132
+ data["resource"] = resource_tokens[0][resource_tokens[0].index("/") + 1 :]
133
+ data["hasPath"] = True
134
+
135
+ elif resource_tokens[0]:
136
+ data["resourceType"] = ""
137
+ data["resource"] = resource_tokens[0]
138
+ data["hasPath"] = False
139
+
140
+ else: # pragma: no cover – defensive; unreachable via str.split
141
+ logger.error(f"Invalid AWS ARN {arn}")
142
+ raise InvalidARNError(f"Invalid AWS ARN {arn}")
143
+
144
+ if not links.get(data["service"], {}).get(data["resourceType"], None):
145
+ logger.error(
146
+ f"AWS service {data['service']} resource type {data['resourceType']} not supported",
147
+ )
148
+ return get_service_home_link(data)
149
+
150
+ template_context = {
151
+ "arn": arn,
152
+ "data": data,
153
+ "get_arn_string": get_arn_string,
154
+ "get_qualifiers": get_qualifiers,
155
+ "get_resource_path": get_resource_path,
156
+ }
157
+ data["resource"] = urllib.parse.quote(str(data["resource"]))
158
+ return eval(
159
+ f"f'{links[data['service']][data['resourceType']]}'",
160
+ {"__builtins__": {}},
161
+ template_context,
162
+ ).replace(
163
+ " ",
164
+ "",
165
+ )
166
+
167
+
168
+ __all__ = [
169
+ "ARNTooShortError",
170
+ "AWSLinker",
171
+ "InvalidARNError",
172
+ "InvalidPartitionError",
173
+ "InvalidServiceError",
174
+ "SERVICE_HOME_OVERRIDES",
175
+ "get_arn_string",
176
+ "get_console",
177
+ "get_qualifiers",
178
+ "get_resource_path",
179
+ "get_service_home_link",
180
+ ]
@@ -0,0 +1,18 @@
1
+ class AWSLinkerError(ValueError):
2
+ """Base class for AWS linker errors."""
3
+
4
+
5
+ class ARNTooShortError(AWSLinkerError):
6
+ """Raised when an ARN is missing required segments."""
7
+
8
+
9
+ class InvalidARNError(AWSLinkerError):
10
+ """Raised when an ARN is malformed."""
11
+
12
+
13
+ class InvalidPartitionError(AWSLinkerError):
14
+ """Raised when an ARN contains an unsupported partition."""
15
+
16
+
17
+ class InvalidServiceError(AWSLinkerError):
18
+ """Raised when an ARN contains an unknown AWS service."""
@@ -38,6 +38,7 @@ def get_links() -> Dict:
38
38
  #/{data.get("resource", "")}/resources/',
39
39
  "stage": 'https://{data.get("region", "")}.{data.get("console", "")}/apigateway/\
40
40
  home?region={data.get("region", "")}#/{data.get("resource", "")}',
41
+ "clientcertificates": 'https://{data.get("region", "")}.{data.get("console", "")}/apigateway/home?region={data.get("region", "")}#/client-certificates',
41
42
  "certificate": 'https://{data.get("region", "")}.{data.get("console", "")}/apigateway/home?region={data.get("region", "")}\
42
43
  #/{data.get("resource", "")}/resources/',
43
44
  "resource": 'https://{data.get("region", "")}.{data.get("console", "")}/apigateway/home?region={data.get("region", "")}\
@@ -57,6 +58,12 @@ def get_links() -> Dict:
57
58
  "appmesh-preview": { # AWS App Mesh Preview
58
59
  "mesh": 'https://{data.get("region", "")}.{data.get("console", "")}/appmesh/home?region={data.get("region", "")}#/meshes/{data.get("resource", "")}',
59
60
  },
61
+ "apprunner": { # AWS App Runner
62
+ "service": 'https://{data.get("region", "")}.{data.get("console", "")}/apprunner/home?region={data.get("region", "")}#/services/{data.get("resource", "")}',
63
+ "vpcconnector": 'https://{data.get("region", "")}.{data.get("console", "")}/apprunner/home?region={data.get("region", "")}#/vpc-connectors/{data.get("resource", "")}',
64
+ "autoscalingconfiguration": 'https://{data.get("region", "")}.{data.get("console", "")}/apprunner/home?region={data.get("region", "")}#/autoscaling',
65
+ "observabilityconfiguration": 'https://{data.get("region", "")}.{data.get("console", "")}/apprunner/home?region={data.get("region", "")}#/observability',
66
+ },
60
67
  "appstream": { # Amazon AppStream 2.0
61
68
  "fleet": 'https://{data.get("region", "")}.{data.get("console", "")}/appstream2/home?region={data.get("region", "")}#/fleets/{data.get("resource", "")}',
62
69
  "image": 'https://{data.get("region", "")}.{data.get("console", "")}/appstream2/home?region={data.get("region", "")}#/images',
@@ -117,6 +124,11 @@ def get_links() -> Dict:
117
124
  "chime": { # Amazon Chime
118
125
  "meeting": None, # meetings are transient, no permanent console deep-link
119
126
  },
127
+ "cleanrooms": { # AWS Clean Rooms
128
+ "membership": 'https://{data.get("region", "")}.{data.get("console", "")}/cleanrooms/home?region={data.get("region", "")}#/memberships/{data.get("resource", "")}',
129
+ "collaboration": 'https://{data.get("region", "")}.{data.get("console", "")}/cleanrooms/home?region={data.get("region", "")}#/collaborations/{data.get("resource", "")}',
130
+ "configuredtable": 'https://{data.get("region", "")}.{data.get("console", "")}/cleanrooms/home?region={data.get("region", "")}#/configured-tables/{data.get("resource", "")}',
131
+ },
120
132
  "cloud9": { # AWS Cloud9
121
133
  "environment": 'https://{data.get("region", "")}.{data.get("console", "")}/cloud9/home/environments/{data.get("resource", "")}?region={data.get("region", "")}',
122
134
  },
@@ -222,9 +234,21 @@ def get_links() -> Dict:
222
234
  "connect": { # Amazon Connect
223
235
  "instance": 'https://{data.get("region", "")}.{data.get("console", "")}/connect/home?region={data.get("region", "")}',
224
236
  },
237
+ "controltower": { # AWS Control Tower
238
+ "enabledcontrol": 'https://{data.get("region", "")}.{data.get("console", "")}/controltower/home?region={data.get("region", "")}#/controls',
239
+ "landingzone": 'https://{data.get("region", "")}.{data.get("console", "")}/controltower/home?region={data.get("region", "")}#/landing-zone',
240
+ },
225
241
  "cur": { # AWS Cost and Usage Report
226
242
  "definition": 'https://{data.get("console", "")}/billing/home?#/reports',
227
243
  },
244
+ "databrew": { # AWS Glue DataBrew
245
+ "project": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#project-details?project={data.get("resource", "")}',
246
+ "dataset": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#dataset-details?dataset={data.get("resource", "")}',
247
+ "recipe": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#recipe-details?recipe={data.get("resource", "")}',
248
+ "job": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#job-details?job={data.get("resource", "")}',
249
+ "schedule": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#schedule-details?schedule={data.get("resource", "")}',
250
+ "ruleset": 'https://{data.get("region", "")}.{data.get("console", "")}/databrew/home?region={data.get("region", "")}#ruleset-details?ruleset={data.get("resource", "")}',
251
+ },
228
252
  "dataexchange": { # AWS Data Exchange
229
253
  "data-sets": 'https://{data.get("region", "")}.{data.get("console", "")}/dataexchange/home?region={data.get("region", "")}#/datasets/{data.get("resource", "")}',
230
254
  "jobs": 'https://{data.get("region", "")}.{data.get("console", "")}/dataexchange/home?region={data.get("region", "")}#/jobs',
@@ -234,6 +258,9 @@ def get_links() -> Dict:
234
258
  "location": 'https://{data.get("region", "")}.{data.get("console", "")}/datasync/home?region={data.get("region", "")}#/locations/{data.get("resource", "")}',
235
259
  "task": 'https://{data.get("region", "")}.{data.get("console", "")}/datasync/home?region={data.get("region", "")}#/tasks/{data.get("resource", "")}',
236
260
  },
261
+ "datazone": { # Amazon DataZone
262
+ "domain": 'https://{data.get("region", "")}.{data.get("console", "")}/datazone/home?region={data.get("region", "")}#/domains/{data.get("resource", "")}',
263
+ },
237
264
  "dax": { # Amazon DynamoDB Accelerator (DAX)
238
265
  "cache": 'https://{data.get("region", "")}.{data.get("console", "")}/dynamodbv2/home?region={data.get("region", "")}#dax:cluster-details:{data.get("resource", "")}',
239
266
  },
@@ -277,6 +304,10 @@ def get_links() -> Dict:
277
304
  "upload": None, # sub-resource of project, no direct deep-link
278
305
  "vpceconfiguration": None, # internal config, no direct deep-link
279
306
  },
307
+ "devops-guru": { # Amazon DevOps Guru
308
+ "channel": 'https://{data.get("region", "")}.{data.get("console", "")}/devops-guru/home?region={data.get("region", "")}#/settings',
309
+ "insight": 'https://{data.get("region", "")}.{data.get("console", "")}/devops-guru/home?region={data.get("region", "")}#/insights/{data.get("resource", "")}',
310
+ },
280
311
  "directconnect": { # AWS Direct Connect
281
312
  "dx-gateway": 'https://{data.get("region", "")}.{data.get("console", "")}/directconnect/v2/home?region={data.get("region", "")}#/direct-connect-gateways/{data.get("resource", "")}',
282
313
  "dxcon": 'https://{data.get("region", "")}.{data.get("console", "")}/directconnect/v2/home?region={data.get("region", "")}#/connections/{data.get("resource", "")}',
@@ -388,6 +419,18 @@ def get_links() -> Dict:
388
419
  "elastic-inference": { # Amazon Elastic Inference (retired April 2024)
389
420
  "elastic-inference-accelerator": None, # service retired
390
421
  },
422
+ "elasticache": { # Amazon ElastiCache
423
+ "cluster": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/memcached/{data.get("resource", "")}',
424
+ "replicationgroup": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/redis/{data.get("resource", "")}',
425
+ "reserved-instance": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/reserved-cache-nodes',
426
+ "snapshot": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/snapshots/{data.get("resource", "")}',
427
+ "subnetgroup": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/subnet-group/{data.get("resource", "")}',
428
+ "parametergroup": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/parameter-groups/{data.get("resource", "")}',
429
+ "globalreplicationgroup": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/global-datastores/{data.get("resource", "")}',
430
+ "user": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/users/{data.get("resource", "")}',
431
+ "usergroup": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/user-groups/{data.get("resource", "")}',
432
+ "serverlesscache": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticache/home?region={data.get("region", "")}#/serverless/{data.get("resource", "")}',
433
+ },
391
434
  "elasticbeanstalk": { # AWS Elastic Beanstalk
392
435
  "application": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticbeanstalk/home?region={data.get("region", "")}#/application/overview?applicationName={data.get("resource", "")}',
393
436
  "applicationversion": 'https://{data.get("region", "")}.{data.get("console", "")}/elasticbeanstalk/home?region={data.get("region", "")}#/application/versions?applicationName={data.get("resource", "")}',
@@ -416,6 +459,14 @@ def get_links() -> Dict:
416
459
  "pipeline": 'https://{data.get("region", "")}.{data.get("console", "")}/elastictranscoder/home?region={data.get("region", "")}#pipelines:',
417
460
  "preset": 'https://{data.get("region", "")}.{data.get("console", "")}/elastictranscoder/home?region={data.get("region", "")}#presets:',
418
461
  },
462
+ "emr-containers": { # Amazon EMR on EKS
463
+ "virtualcluster": 'https://{data.get("region", "")}.{data.get("console", "")}/emr/home?region={data.get("region", "")}#/eks/virtual-clusters/{data.get("resource", "")}',
464
+ "jobrun": 'https://{data.get("region", "")}.{data.get("console", "")}/emr/home?region={data.get("region", "")}#/eks/virtual-clusters/job-runs/{data.get("resource", "")}',
465
+ },
466
+ "emr-serverless": { # Amazon EMR Serverless
467
+ "application": 'https://{data.get("region", "")}.{data.get("console", "")}/emr/home?region={data.get("region", "")}#/serverless/applications/{data.get("resource", "")}',
468
+ "jobrun": 'https://{data.get("region", "")}.{data.get("console", "")}/emr/home?region={data.get("region", "")}#/serverless/job-runs/{data.get("resource", "")}',
469
+ },
419
470
  "es": { # Amazon Elasticsearch Service
420
471
  "domain": 'https://{data.get("region", "")}.{data.get("console", "")}/esv3/\
421
472
  home?region={data.get("region", "")}#opensearch/domains/{data.get("resource", "")}',
@@ -427,6 +478,9 @@ def get_links() -> Dict:
427
478
  "event-source": 'https://{data.get("region", "")}.{data.get("console", "")}/events/home?region={data.get("region", "")}#/partner-event-sources',
428
479
  "rule": 'https://{data.get("region", "")}.{data.get("console", "")}/events/home?region={data.get("region", "")}#/rules/{data.get("resource", "")}',
429
480
  },
481
+ "evidently": { # Amazon CloudWatch Evidently
482
+ "project": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#evidently/projects/{data.get("resource", "")}',
483
+ },
430
484
  "execute-api": { # Amazon API Gateway
431
485
  },
432
486
  "firehose": { # Amazon Kinesis Firehose
@@ -481,6 +535,9 @@ def get_links() -> Dict:
481
535
  "userDefinedFunction": None, # accessible only through database view
482
536
  "workflow": 'https://{data.get("region", "")}.{data.get("console", "")}/glue/home?region={data.get("region", "")}#/etl/workflows/{data.get("resource", "")}',
483
537
  },
538
+ "grafana": { # Amazon Managed Grafana
539
+ "workspace": 'https://{data.get("region", "")}.{data.get("console", "")}/grafana/home?region={data.get("region", "")}#/workspaces/{data.get("resource", "")}',
540
+ },
484
541
  "greengrass": { # AWS IoT Greengrass
485
542
  "": 'https://{data.get("region", "")}.{data.get("console", "")}/greengrass/v2/home?region={data.get("region", "")}',
486
543
  },
@@ -528,6 +585,14 @@ def get_links() -> Dict:
528
585
  "image-recipe": 'https://{data.get("region", "")}.{data.get("console", "")}/imagebuilder/home?region={data.get("region", "")}#/imageRecipes/{data.get("resource", "")}',
529
586
  "infrastructure-configuration": 'https://{data.get("region", "")}.{data.get("console", "")}/imagebuilder/home?region={data.get("region", "")}#/infrastructureConfiguration/{data.get("resource", "")}',
530
587
  },
588
+ "inspector2": { # Amazon Inspector
589
+ "owner": 'https://{data.get("region", "")}.{data.get("console", "")}/inspector/v2/home?region={data.get("region", "")}',
590
+ "finding": 'https://{data.get("region", "")}.{data.get("console", "")}/inspector/v2/home?region={data.get("region", "")}#/findings/{data.get("resource", "")}',
591
+ "filter": 'https://{data.get("region", "")}.{data.get("console", "")}/inspector/v2/home?region={data.get("region", "")}#/filters/{data.get("resource", "")}',
592
+ },
593
+ "internetmonitor": { # Amazon CloudWatch Internet Monitor
594
+ "monitor": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#internet-monitor/monitors/{data.get("resource", "")}',
595
+ },
531
596
  "iot": { # AWS IoT
532
597
  "authorizer": 'https://{data.get("region", "")}.{data.get("console", "")}/iot/home?region={data.get("region", "")}#/security/authorizers/{data.get("resource", "")}',
533
598
  "billinggroup": 'https://{data.get("region", "")}.{data.get("console", "")}/iot/home?region={data.get("region", "")}#/billinggrouphub/{data.get("resource", "")}',
@@ -581,6 +646,17 @@ def get_links() -> Dict:
581
646
  "System": None, # service retired
582
647
  "Workflow": None, # service retired
583
648
  },
649
+ "iottwinmaker": { # AWS IoT TwinMaker
650
+ "workspace": 'https://{data.get("region", "")}.{data.get("console", "")}/iottwinmaker/home?region={data.get("region", "")}#/workspaces/{data.get("resource", "")}',
651
+ "entity": 'https://{data.get("region", "")}.{data.get("console", "")}/iottwinmaker/home?region={data.get("region", "")}#/workspaces/{data.get("resource", "")}/entities',
652
+ "scene": 'https://{data.get("region", "")}.{data.get("console", "")}/iottwinmaker/home?region={data.get("region", "")}#/workspaces/{data.get("resource", "")}/scenes',
653
+ },
654
+ "ivs": { # Amazon Interactive Video Service
655
+ "channel": 'https://{data.get("region", "")}.{data.get("console", "")}/ivs/home?region={data.get("region", "")}#/channels/{data.get("resource", "")}',
656
+ "playback-key-pair": 'https://{data.get("region", "")}.{data.get("console", "")}/ivs/home?region={data.get("region", "")}#/playback-keys',
657
+ "recording-configuration": 'https://{data.get("region", "")}.{data.get("console", "")}/ivs/home?region={data.get("region", "")}#/recording-configurations/{data.get("resource", "")}',
658
+ "stage": 'https://{data.get("region", "")}.{data.get("console", "")}/ivs/home?region={data.get("region", "")}#/stages/{data.get("resource", "")}',
659
+ },
584
660
  "kafka": { # Amazon Managed Streaming for Kafka
585
661
  "cluster": 'https://{data.get("region", "")}.{data.get("console", "")}/msk/home?region={data.get("region", "")}#/cluster/{data.get("resource", "")}/view',
586
662
  },
@@ -602,6 +678,9 @@ def get_links() -> Dict:
602
678
  "key": 'https://{data.get("region", "")}.{data.get("console", "")}/kms/\
603
679
  home?region={data.get("region", "")}#/kms/keys/{data.get("resource", "")}',
604
680
  },
681
+ "lakeformation": { # AWS Lake Formation
682
+ "resource": 'https://{data.get("region", "")}.{data.get("console", "")}/lakeformation/home?region={data.get("region", "")}#/data-lake/resources',
683
+ },
605
684
  "lambda": { # AWS Lambda
606
685
  "event-source-mapping": 'https://{data.get("region", "")}.{data.get("console", "")}/lambda/home?region=\
607
686
  {data.get("region", "")}#/functions/{data.get("resource", "")}',
@@ -640,6 +719,18 @@ def get_links() -> Dict:
640
719
  "logs": { # Amazon CloudWatch Logs
641
720
  "log-group": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#logsV2:log-groups/log-group/{data.get("resource", "")}',
642
721
  },
722
+ "lookoutequipment": { # Amazon Lookout for Equipment
723
+ "dataset": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutequipment/home?region={data.get("region", "")}#/datasets/{data.get("resource", "")}',
724
+ "model": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutequipment/home?region={data.get("region", "")}#/models/{data.get("resource", "")}',
725
+ "inference-scheduler": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutequipment/home?region={data.get("region", "")}#/inference-schedulers/{data.get("resource", "")}',
726
+ },
727
+ "lookoutmetrics": { # Amazon Lookout for Metrics
728
+ "AnomalyDetector": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutmetrics/home?region={data.get("region", "")}#/detectors/{data.get("resource", "")}',
729
+ "Alert": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutmetrics/home?region={data.get("region", "")}#/detectors/{data.get("resource", "")}/alerts',
730
+ },
731
+ "lookoutvision": { # Amazon Lookout for Vision
732
+ "model": 'https://{data.get("region", "")}.{data.get("console", "")}/lookoutvision/home?region={data.get("region", "")}#/projects/{data.get("resource", "")}',
733
+ },
643
734
  "machinelearning": { # Amazon Machine Learning (retired)
644
735
  "batchprediction": None, # service retired
645
736
  "datasource": None, # service retired
@@ -696,9 +787,22 @@ def get_links() -> Dict:
696
787
  "mediatailor": { # AWS Elemental MediaTailor
697
788
  "playbackConfiguration": 'https://{data.get("region", "")}.{data.get("console", "")}/mediatailor/home?region={data.get("region", "")}#/configurations/{data.get("resource", "")}',
698
789
  },
790
+ "memorydb": { # Amazon MemoryDB
791
+ "cluster": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/clusters/{data.get("resource", "")}',
792
+ "snapshot": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/snapshots/{data.get("resource", "")}',
793
+ "subnetgroup": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/subnet-groups/{data.get("resource", "")}',
794
+ "parametergroup": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/parameter-groups/{data.get("resource", "")}',
795
+ "user": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/users/{data.get("resource", "")}',
796
+ "acl": 'https://{data.get("region", "")}.{data.get("console", "")}/memorydb/home?region={data.get("region", "")}#/acls/{data.get("resource", "")}',
797
+ },
699
798
  "mgh": { # AWS Migration Hub
700
799
  "progressUpdateStream": 'https://{data.get("console", "")}/migrationhub/home?region={data.get("region", "")}#/discover',
701
800
  },
801
+ "mgn": { # AWS Application Migration Service
802
+ "source-server": 'https://{data.get("region", "")}.{data.get("console", "")}/mgn/home?region={data.get("region", "")}#/source-servers/{data.get("resource", "")}',
803
+ "job": 'https://{data.get("region", "")}.{data.get("console", "")}/mgn/home?region={data.get("region", "")}#/jobs/{data.get("resource", "")}',
804
+ "launch-configuration-template": 'https://{data.get("region", "")}.{data.get("console", "")}/mgn/home?region={data.get("region", "")}#/launch-templates',
805
+ },
702
806
  "mobilehub": { # AWS Mobile Hub (retired)
703
807
  "project": None, # AWS Mobile Hub retired March 2023, no console
704
808
  },
@@ -719,6 +823,9 @@ def get_links() -> Dict:
719
823
  "link": 'https://{data.get("console", "")}/networkmanager/home?region={data.get("region", "")}#/networks/{data.get("resource", "")}/links',
720
824
  "site": 'https://{data.get("console", "")}/networkmanager/home?region={data.get("region", "")}#/networks/{data.get("resource", "")}/sites',
721
825
  },
826
+ "opensearchservice": { # Amazon OpenSearch Service
827
+ "domain": 'https://{data.get("region", "")}.{data.get("console", "")}/aos/home?region={data.get("region", "")}#opensearch/domains/{data.get("resource", "")}',
828
+ },
722
829
  "opsworks": { # AWS OpsWorks
723
830
  "stack": 'https://{data.get("region", "")}.{data.get("console", "")}/opsworks/home?region={data.get("region", "")}#/stack/{data.get("resource", "")}',
724
831
  },
@@ -735,6 +842,11 @@ def get_links() -> Dict:
735
842
  "outpost": 'https://{data.get("region", "")}.{data.get("console", "")}/outposts/home?region={data.get("region", "")}#/outposts/{data.get("resource", "")}',
736
843
  "site": 'https://{data.get("region", "")}.{data.get("console", "")}/outposts/home?region={data.get("region", "")}#/sites/{data.get("resource", "")}',
737
844
  },
845
+ "panorama": { # AWS Panorama
846
+ "device": 'https://{data.get("region", "")}.{data.get("console", "")}/panorama/home?region={data.get("region", "")}#/devices/{data.get("resource", "")}',
847
+ "package": 'https://{data.get("region", "")}.{data.get("console", "")}/panorama/home?region={data.get("region", "")}#/packages/{data.get("resource", "")}',
848
+ "applicationInstance": 'https://{data.get("region", "")}.{data.get("console", "")}/panorama/home?region={data.get("region", "")}#/deployed-apps/{data.get("resource", "")}',
849
+ },
738
850
  "personalize": { # Amazon Personalize
739
851
  "algorithm": None, # built-in algorithm, not directly navigable
740
852
  "campaign": 'https://{data.get("region", "")}.{data.get("console", "")}/personalize/home?region={data.get("region", "")}#/datasetGroups/{data.get("resource", "")}/campaigns',
@@ -750,9 +862,18 @@ def get_links() -> Dict:
750
862
  "pi": { # AWS Performance Insights
751
863
  "metrics": None, # Performance Insights is accessed via RDS/Aurora console, not a standalone resource
752
864
  },
865
+ "pipes": { # Amazon EventBridge Pipes
866
+ "pipe": 'https://{data.get("region", "")}.{data.get("console", "")}/events/home?region={data.get("region", "")}#/pipes/{data.get("resource", "")}',
867
+ },
753
868
  "polly": { # Amazon Polly
754
869
  "lexicon": 'https://{data.get("region", "")}.{data.get("console", "")}/polly/home?region={data.get("region", "")}#/lexicons',
755
870
  },
871
+ "proton": { # AWS Proton
872
+ "environment-template": 'https://{data.get("region", "")}.{data.get("console", "")}/proton/home?region={data.get("region", "")}#/templates/environments/{data.get("resource", "")}',
873
+ "service-template": 'https://{data.get("region", "")}.{data.get("console", "")}/proton/home?region={data.get("region", "")}#/templates/services/{data.get("resource", "")}',
874
+ "environment": 'https://{data.get("region", "")}.{data.get("console", "")}/proton/home?region={data.get("region", "")}#/environments/{data.get("resource", "")}',
875
+ "service": 'https://{data.get("region", "")}.{data.get("console", "")}/proton/home?region={data.get("region", "")}#/services/{data.get("resource", "")}',
876
+ },
756
877
  "qldb": { # Amazon QLDB
757
878
  "ledger": 'https://{data.get("region", "")}.{data.get("console", "")}/qldb/home?region={data.get("region", "")}#/ledger/{data.get("resource", "")}',
758
879
  "stream": 'https://{data.get("region", "")}.{data.get("console", "")}/qldb/home?region={data.get("region", "")}#/ledger/{data.get("resource", "")}/streams',
@@ -817,6 +938,10 @@ def get_links() -> Dict:
817
938
  "project": 'https://{data.get("region", "")}.{data.get("console", "")}/rekognition/home?region={data.get("region", "")}#/projects/{data.get("resource", "")}',
818
939
  "streamprocessor": 'https://{data.get("region", "")}.{data.get("console", "")}/rekognition/home?region={data.get("region", "")}#/stream-processors',
819
940
  },
941
+ "resiliencehub": { # AWS Resilience Hub
942
+ "app": 'https://{data.get("region", "")}.{data.get("console", "")}/resiliencehub/home?region={data.get("region", "")}#/application/{data.get("resource", "")}',
943
+ "resiliency-policy": 'https://{data.get("region", "")}.{data.get("console", "")}/resiliencehub/home?region={data.get("region", "")}#/policies/{data.get("resource", "")}',
944
+ },
820
945
  "resource-groups": { # AWS Resource Groups
821
946
  "group": 'https://{data.get("region", "")}.{data.get("console", "")}/resource-groups/home?region={data.get("region", "")}#/groups/{data.get("resource", "")}',
822
947
  },
@@ -846,6 +971,9 @@ def get_links() -> Dict:
846
971
  "resolver-endpoint": 'https://{data.get("region", "")}.{data.get("console", "")}/route53resolver/home?region={data.get("region", "")}#/endpoints/{data.get("resource", "")}',
847
972
  "resolver-rule": 'https://{data.get("region", "")}.{data.get("console", "")}/route53resolver/home?region={data.get("region", "")}#/rules/{data.get("resource", "")}',
848
973
  },
974
+ "rum": { # Amazon CloudWatch RUM
975
+ "appmonitor": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#rum/app-monitors/{data.get("resource", "")}',
976
+ },
849
977
  "s3": { # Amazon S3
850
978
  "": 'https://s3.{data.get("console", "")}/s3/buckets/{data.get("resource", "")}',
851
979
  "accesspoint": 'https://s3.{data.get("console", "")}/s3/accesspoints/{data.get("resource", "")}',
@@ -883,6 +1011,10 @@ def get_links() -> Dict:
883
1011
  "savingsplans": { # AWS Savings Plans
884
1012
  "savingsplan": 'https://{data.get("console", "")}/savingsplans/home?region={data.get("region", "")}#/savingsplans/inventory',
885
1013
  },
1014
+ "scheduler": { # Amazon EventBridge Scheduler
1015
+ "schedule-group": 'https://{data.get("region", "")}.{data.get("console", "")}/scheduler/home?region={data.get("region", "")}#/schedule-groups/{data.get("resource", "")}',
1016
+ "schedule": 'https://{data.get("region", "")}.{data.get("console", "")}/scheduler/home?region={data.get("region", "")}#/schedules/{data.get("resource", "")}',
1017
+ },
886
1018
  "schemas": { # Amazon EventBridge Schemas
887
1019
  "discoverer": 'https://{data.get("region", "")}.{data.get("console", "")}/events/home?region={data.get("region", "")}#/registries/discovered-schemas',
888
1020
  "registry": 'https://{data.get("region", "")}.{data.get("console", "")}/events/home?region={data.get("region", "")}#/registries/{data.get("resource", "")}',
@@ -898,6 +1030,9 @@ def get_links() -> Dict:
898
1030
  "hub": 'https://{data.get("region", "")}.{data.get("console", "")}/securityhub/home?region={data.get("region", "")}#/summary',
899
1031
  "product": 'https://{data.get("region", "")}.{data.get("console", "")}/securityhub/home?region={data.get("region", "")}#/integrations',
900
1032
  },
1033
+ "securitylake": { # Amazon Security Lake
1034
+ "subscriber": 'https://{data.get("region", "")}.{data.get("console", "")}/securitylake/home?region={data.get("region", "")}#/subscribers/{data.get("resource", "")}',
1035
+ },
901
1036
  "serverlessrepo": { # AWS Serverless Application Repository
902
1037
  "applications": 'https://{data.get("region", "")}.{data.get("console", "")}/serverlessrepo/home?region={data.get("region", "")}#/published-applications/{data.get("resource", "")}',
903
1038
  },
@@ -951,6 +1086,10 @@ def get_links() -> Dict:
951
1086
  "windowtarget": 'https://{data.get("region", "")}.{data.get("console", "")}/systems-manager/maintenance-windows?region={data.get("region", "")}',
952
1087
  "windowtask": 'https://{data.get("region", "")}.{data.get("console", "")}/systems-manager/maintenance-windows?region={data.get("region", "")}',
953
1088
  },
1089
+ "sso": { # AWS IAM Identity Center
1090
+ "instance": 'https://{data.get("region", "")}.{data.get("console", "")}/singlesignon/home?region={data.get("region", "")}',
1091
+ "permissionSet": 'https://{data.get("region", "")}.{data.get("console", "")}/singlesignon/home?region={data.get("region", "")}#/permission-sets/{data.get("resource", "")}',
1092
+ },
954
1093
  "states": { # AWS Step Functions
955
1094
  "activity": 'https://{data.get("region", "")}.{data.get("console", "")}/states/home?region={data.get("region", "")}#/activities/{arn}',
956
1095
  "execution": 'https://{data.get("region", "")}.{data.get("console", "")}/states/home?region={data.get("region", "")}#/executions/details/{arn}',
@@ -971,6 +1110,11 @@ def get_links() -> Dict:
971
1110
  "synthetics": { # Amazon CloudWatch Synthetics
972
1111
  "canary": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#synthetics:canary/detail/{data.get("resource", "")}',
973
1112
  },
1113
+ "timestream": { # Amazon Timestream
1114
+ "database": 'https://{data.get("region", "")}.{data.get("console", "")}/timestream/home?region={data.get("region", "")}#/databases/{data.get("resource", "")}',
1115
+ "table": 'https://{data.get("region", "")}.{data.get("console", "")}/timestream/home?region={data.get("region", "")}#/tables/{data.get("resource", "")}',
1116
+ "scheduled-query": 'https://{data.get("region", "")}.{data.get("console", "")}/timestream/home?region={data.get("region", "")}#/scheduled-queries/{data.get("resource", "")}',
1117
+ },
974
1118
  "transfer": { # AWS Transfer for SFTP
975
1119
  "server": 'https://{data.get("region", "")}.{data.get("console", "")}/transfer/home?region={data.get("region", "")}#/servers/{data.get("resource", "")}',
976
1120
  "user": 'https://{data.get("region", "")}.{data.get("console", "")}/transfer/home?region={data.get("region", "")}#/servers/{data.get("resource", "")}/users',
@@ -978,6 +1122,14 @@ def get_links() -> Dict:
978
1122
  "trustedadvisor": { # AWS Trusted Advisor
979
1123
  "checks": 'https://{data.get("console", "")}/trustedadvisor/home?#/category/all',
980
1124
  },
1125
+ "verifiedpermissions": { # Amazon Verified Permissions
1126
+ "policy-store": 'https://{data.get("region", "")}.{data.get("console", "")}/verifiedpermissions/home?region={data.get("region", "")}#/policy-stores/{data.get("resource", "")}',
1127
+ },
1128
+ "vpc-lattice": { # Amazon VPC Lattice
1129
+ "servicenetwork": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#VpcLatticeServiceNetworks:service-network/{data.get("resource", "")}',
1130
+ "service": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#VpcLatticeServices:service/{data.get("resource", "")}',
1131
+ "targetgroup": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#VpcLatticeTargetGroups:target-group/{data.get("resource", "")}',
1132
+ },
981
1133
  "waf": { # AWS WAF (Classic, global)
982
1134
  "bytematchset": None, # WAF Classic condition, no dedicated console deep-link
983
1135
  "geomatchset": None, # WAF Classic condition, no dedicated console deep-link
@@ -1011,6 +1163,10 @@ def get_links() -> Dict:
1011
1163
  "wellarchitected": { # AWS Well-Architected Tool
1012
1164
  "workload": 'https://{data.get("region", "")}.{data.get("console", "")}/wellarchitected/home?region={data.get("region", "")}#/workload/{data.get("resource", "")}',
1013
1165
  },
1166
+ "wisdom": { # Amazon Connect Wisdom
1167
+ "assistant": 'https://{data.get("region", "")}.{data.get("console", "")}/connect/home?region={data.get("region", "")}#/wisdom/assistants/{data.get("resource", "")}',
1168
+ "knowledge-base": 'https://{data.get("region", "")}.{data.get("console", "")}/connect/home?region={data.get("region", "")}#/wisdom/knowledge-bases/{data.get("resource", "")}',
1169
+ },
1014
1170
  "worklink": { # Amazon WorkLink
1015
1171
  "fleet": 'https://{data.get("region", "")}.{data.get("console", "")}/worklink/home?region={data.get("region", "")}#/fleets/{data.get("resource", "")}',
1016
1172
  },