cloudconsolelink 3.0.2__tar.gz → 4.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.
- {cloudconsolelink-3.0.2/cloudconsolelink.egg-info → cloudconsolelink-4.0.1}/PKG-INFO +1 -1
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/__init__.py +1 -1
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/aws/__init__.py +49 -35
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/aws/links.py +18 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/azure/__init__.py +7 -8
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/gcp/__init__.py +39 -2
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/gcp/links.py +376 -91
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/oci/__init__.py +9 -3
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/oci/links.py +261 -73
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1/cloudconsolelink.egg-info}/PKG-INFO +1 -1
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_aws.py +81 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/LICENSE +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/MANIFEST.in +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/README.md +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/__init__.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink/clouds/aws/errors.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink.egg-info/SOURCES.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink.egg-info/dependency_links.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink.egg-info/requires.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/cloudconsolelink.egg-info/top_level.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/requirements-dev.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/requirements.txt +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/setup.cfg +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/setup.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_azure.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_gcp.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_oci.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_packaging.py +0 -0
- {cloudconsolelink-3.0.2 → cloudconsolelink-4.0.1}/tests/test_provider_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version of the module
|
|
2
|
-
__version__ = "
|
|
2
|
+
__version__ = "4.0.1"
|
|
@@ -1,18 +1,48 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import re
|
|
2
3
|
import urllib.parse
|
|
3
4
|
from typing import Dict
|
|
4
5
|
|
|
5
6
|
from .errors import ARNTooShortError, InvalidARNError, InvalidPartitionError, InvalidServiceError
|
|
6
|
-
from .links import get_links
|
|
7
|
+
from .links import HOME_URLS, get_links
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger(__name__)
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
11
|
+
_EXPR_RE = re.compile(r"\{([^}]+)\}")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _render(template: str, data: Dict, arn: str = "") -> str:
|
|
15
|
+
"""Render a link template without eval.
|
|
16
|
+
|
|
17
|
+
Templates in links.py use patterns like {data.get("region", "")} and {arn}.
|
|
18
|
+
We substitute each {expr} by looking it up in a pre-built context dict after
|
|
19
|
+
stripping all internal whitespace (backslash-continuation lines in the source
|
|
20
|
+
produce spaces inside expressions). Unknown expressions render as empty string.
|
|
21
|
+
"""
|
|
22
|
+
resource = str(data.get("resource", ""))
|
|
23
|
+
ctx: Dict[str, str] = {
|
|
24
|
+
'data.get("region","")': str(data.get("region", "")),
|
|
25
|
+
'data.get("console","")': str(data.get("console", "")),
|
|
26
|
+
'data.get("resource","")': resource,
|
|
27
|
+
'data.get("account","")': str(data.get("account", "")),
|
|
28
|
+
'data.get("service","")': str(data.get("service", "")),
|
|
29
|
+
'get_arn_string(data)': get_arn_string(data),
|
|
30
|
+
'get_resource_path(data.get("resource",""))': get_resource_path(resource),
|
|
31
|
+
"arn": arn,
|
|
32
|
+
"region": str(data.get("region", "")),
|
|
33
|
+
"console": str(data.get("console", "")),
|
|
34
|
+
"resource": resource,
|
|
35
|
+
"account": str(data.get("account", "")),
|
|
36
|
+
"service": str(data.get("service", "")),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def _sub(m: re.Match) -> str:
|
|
40
|
+
expr = re.sub(r"\s+", "", m.group(1)) # collapse whitespace from line continuations
|
|
41
|
+
if expr not in ctx:
|
|
42
|
+
logger.debug("Unknown template expression %r", expr)
|
|
43
|
+
return ctx.get(expr, "")
|
|
44
|
+
|
|
45
|
+
return _EXPR_RE.sub(_sub, template).replace(" ", "")
|
|
16
46
|
|
|
17
47
|
|
|
18
48
|
def get_console(partition: str) -> str:
|
|
@@ -62,11 +92,9 @@ def get_service_home_link(data: Dict) -> str:
|
|
|
62
92
|
if not service or not console:
|
|
63
93
|
return ""
|
|
64
94
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
region=urllib.parse.quote(region),
|
|
69
|
-
)
|
|
95
|
+
home_template = HOME_URLS.get(service)
|
|
96
|
+
if region and home_template:
|
|
97
|
+
return _render(home_template, data)
|
|
70
98
|
|
|
71
99
|
host = f"{region}.{console}" if region else console
|
|
72
100
|
path = f"/{service}/home" if region else f"/{service}"
|
|
@@ -83,7 +111,7 @@ class AWSLinker:
|
|
|
83
111
|
tokens = arn.split(":")
|
|
84
112
|
|
|
85
113
|
if len(tokens) < 5:
|
|
86
|
-
logger.
|
|
114
|
+
logger.debug(f"AWS ARN {arn} is too short")
|
|
87
115
|
raise ARNTooShortError(f"AWS ARN {arn} is too short")
|
|
88
116
|
|
|
89
117
|
data: dict[str, str | bool] = {
|
|
@@ -101,7 +129,7 @@ class AWSLinker:
|
|
|
101
129
|
|
|
102
130
|
console = get_console(str(data.get("partition", "")))
|
|
103
131
|
if not console:
|
|
104
|
-
logger.
|
|
132
|
+
logger.debug(
|
|
105
133
|
f"AWS ARN {arn} has invalid partition (Valid Partitions: aws, aws-us-gov, aws-cn)",
|
|
106
134
|
)
|
|
107
135
|
raise InvalidPartitionError(
|
|
@@ -112,17 +140,17 @@ class AWSLinker:
|
|
|
112
140
|
links = get_links()
|
|
113
141
|
|
|
114
142
|
if data["prefix"] != "arn":
|
|
115
|
-
logger.
|
|
143
|
+
logger.debug(f"Invalid AWS ARN {arn}")
|
|
116
144
|
raise InvalidARNError(f"Invalid AWS ARN {arn}")
|
|
117
145
|
|
|
118
146
|
if data["service"] not in links:
|
|
119
|
-
logger.
|
|
147
|
+
logger.debug(f"AWS service {data['service']} unknown")
|
|
120
148
|
raise InvalidServiceError(f"AWS service {data['service']} unknown")
|
|
121
149
|
|
|
122
150
|
if not resource_tokens or resource_tokens == [""]:
|
|
123
151
|
return get_service_home_link(data)
|
|
124
152
|
|
|
125
|
-
if len(resource_tokens) > 1:
|
|
153
|
+
if len(resource_tokens) > 1 and "/" not in resource_tokens[0]:
|
|
126
154
|
data["resourceType"] = resource_tokens[0]
|
|
127
155
|
data["resource"] = ":".join(resource_tokens[1:])
|
|
128
156
|
data["hasPath"] = False
|
|
@@ -138,40 +166,26 @@ class AWSLinker:
|
|
|
138
166
|
data["hasPath"] = False
|
|
139
167
|
|
|
140
168
|
else: # pragma: no cover – defensive; unreachable via str.split
|
|
141
|
-
logger.
|
|
169
|
+
logger.debug(f"Invalid AWS ARN {arn}")
|
|
142
170
|
raise InvalidARNError(f"Invalid AWS ARN {arn}")
|
|
143
171
|
|
|
144
172
|
if not links.get(data["service"], {}).get(data["resourceType"], None):
|
|
145
|
-
logger.
|
|
173
|
+
logger.debug(
|
|
146
174
|
f"AWS service {data['service']} resource type {data['resourceType']} not supported",
|
|
147
175
|
)
|
|
148
176
|
return get_service_home_link(data)
|
|
149
177
|
|
|
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
178
|
data["resource"] = urllib.parse.quote(str(data["resource"]))
|
|
158
|
-
return
|
|
159
|
-
f"f'{links[data['service']][data['resourceType']]}'",
|
|
160
|
-
{"__builtins__": {}},
|
|
161
|
-
template_context,
|
|
162
|
-
).replace(
|
|
163
|
-
" ",
|
|
164
|
-
"",
|
|
165
|
-
)
|
|
179
|
+
return _render(links[data["service"]][data["resourceType"]], data, arn)
|
|
166
180
|
|
|
167
181
|
|
|
168
182
|
__all__ = [
|
|
169
183
|
"ARNTooShortError",
|
|
170
184
|
"AWSLinker",
|
|
185
|
+
"HOME_URLS",
|
|
171
186
|
"InvalidARNError",
|
|
172
187
|
"InvalidPartitionError",
|
|
173
188
|
"InvalidServiceError",
|
|
174
|
-
"SERVICE_HOME_OVERRIDES",
|
|
175
189
|
"get_arn_string",
|
|
176
190
|
"get_console",
|
|
177
191
|
"get_qualifiers",
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
from typing import Dict
|
|
2
2
|
|
|
3
|
+
# Explicit home page URL for each service where the computed default is wrong.
|
|
4
|
+
# Template syntax matches links below: eval'd as an f-string with {"data": data}.
|
|
5
|
+
# Add an entry here whenever a service's console home does not follow the pattern
|
|
6
|
+
# https://{region}.console.aws.amazon.com/{service}/home?region={region}.
|
|
7
|
+
HOME_URLS: Dict[str, str] = {
|
|
8
|
+
"elasticloadbalancing": 'https://{data.get("region", "")}.{data.get("console", "")}/ec2/home?region={data.get("region", "")}#LoadBalancers:',
|
|
9
|
+
"network-firewall": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#Firewalls:',
|
|
10
|
+
"logs": 'https://{data.get("region", "")}.{data.get("console", "")}/cloudwatch/home?region={data.get("region", "")}#logsV2:log-groups',
|
|
11
|
+
"sns": 'https://{data.get("region", "")}.{data.get("console", "")}/sns/v3/home?region={data.get("region", "")}',
|
|
12
|
+
"sqs": 'https://{data.get("region", "")}.{data.get("console", "")}/sqs/v2/home?region={data.get("region", "")}',
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
|
|
4
16
|
def get_links() -> Dict:
|
|
5
17
|
links = {
|
|
@@ -817,6 +829,12 @@ def get_links() -> Dict:
|
|
|
817
829
|
},
|
|
818
830
|
"neptune-db": { # Amazon Neptune
|
|
819
831
|
},
|
|
832
|
+
"network-firewall": { # AWS Network Firewall
|
|
833
|
+
"firewall": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#FirewallDetails:firewallArn={arn}',
|
|
834
|
+
"firewall-policy": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#FirewallPolicyDetails:firewallPolicyArn={arn}',
|
|
835
|
+
"stateful-rulegroup": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#StatefulRuleGroupDetails:ruleGroupArn={arn}',
|
|
836
|
+
"stateless-rulegroup": 'https://{data.get("region", "")}.{data.get("console", "")}/vpc/home?region={data.get("region", "")}#StatelessRuleGroupDetails:ruleGroupArn={arn}',
|
|
837
|
+
},
|
|
820
838
|
"networkmanager": { # Network Manager
|
|
821
839
|
"device": 'https://{data.get("console", "")}/networkmanager/home?region={data.get("region", "")}#/networks/{data.get("resource", "")}/devices',
|
|
822
840
|
"global-network": 'https://{data.get("console", "")}/networkmanager/home?region={data.get("region", "")}#/networks/{data.get("resource", "")}',
|
|
@@ -13,7 +13,7 @@ class AzureLinker:
|
|
|
13
13
|
iam_entity_type: str = "",
|
|
14
14
|
) -> str:
|
|
15
15
|
if not primary_ad_domain_name and not iam_entity_type:
|
|
16
|
-
logger.
|
|
16
|
+
logger.debug(
|
|
17
17
|
"For IAM entity required `iam_entity_type` and for other entity required `primary_ad_domain_name`",
|
|
18
18
|
)
|
|
19
19
|
raise ValueError("Invalid parameters provided")
|
|
@@ -29,12 +29,11 @@ class AzureLinker:
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if iam_entity_type and id and iam_entities.get(iam_entity_type):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return eval(f"f'{iam_entities[iam_entity_type]}'").replace(" ", "")
|
|
32
|
+
return (
|
|
33
|
+
iam_entities[iam_entity_type]
|
|
34
|
+
.format(id=id, primary_ad_domain_name=primary_ad_domain_name, app_id=app_id)
|
|
35
|
+
.replace(" ", "")
|
|
36
|
+
)
|
|
38
37
|
|
|
39
38
|
elif primary_ad_domain_name and id:
|
|
40
39
|
if id.startswith("https"):
|
|
@@ -48,5 +47,5 @@ class AzureLinker:
|
|
|
48
47
|
return f"https://portal.azure.com/#@{primary_ad_domain_name}/resource{id}/overview"
|
|
49
48
|
|
|
50
49
|
else:
|
|
51
|
-
logger.
|
|
50
|
+
logger.debug("entity id required")
|
|
52
51
|
raise ValueError("Invalid parameters provided")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Dict
|
|
3
3
|
|
|
4
|
-
from .links import Resource
|
|
4
|
+
from .links import Resource, SERVICE_HOME_MAP
|
|
5
5
|
|
|
6
6
|
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
@@ -212,6 +212,38 @@ class GCPLinker:
|
|
|
212
212
|
"vpc_home": resource.vpc_home,
|
|
213
213
|
"apigateway_home": resource.apigateway_home,
|
|
214
214
|
"cloudrun_home": resource.cloudrun_home,
|
|
215
|
+
"compute_home": resource.compute_home,
|
|
216
|
+
"storage_home": resource.storage_home,
|
|
217
|
+
"bigtable_home": resource.bigtable_home,
|
|
218
|
+
"cloudfunction_home": resource.cloudfunction_home,
|
|
219
|
+
"kms_home": resource.kms_home,
|
|
220
|
+
"gke_home": resource.gke_home,
|
|
221
|
+
"sql_home": resource.sql_home,
|
|
222
|
+
"iam_home": resource.iam_home,
|
|
223
|
+
"firestore_home": resource.firestore_home,
|
|
224
|
+
"pubsub_home": resource.pubsub_home,
|
|
225
|
+
"logging_home": resource.logging_home,
|
|
226
|
+
"dataproc_home": resource.dataproc_home,
|
|
227
|
+
"monitoring_home": resource.monitoring_home,
|
|
228
|
+
"cloud_armor_home": resource.cloud_armor_home,
|
|
229
|
+
"cloud_build_home": resource.cloud_build_home,
|
|
230
|
+
"cloud_composer_home": resource.cloud_composer_home,
|
|
231
|
+
"cloud_scheduler_home": resource.cloud_scheduler_home,
|
|
232
|
+
"cloud_tasks_home": resource.cloud_tasks_home,
|
|
233
|
+
"spanner_home": resource.spanner_home,
|
|
234
|
+
"artifact_registry_home": resource.artifact_registry_home,
|
|
235
|
+
"secret_manager_home": resource.secret_manager_home,
|
|
236
|
+
"memorystore_home": resource.memorystore_home,
|
|
237
|
+
"vpn_home": resource.vpn_home,
|
|
238
|
+
"vertex_ai_home": resource.vertex_ai_home,
|
|
239
|
+
"workflows_home": resource.workflows_home,
|
|
240
|
+
"eventarc_home": resource.eventarc_home,
|
|
241
|
+
"alloydb_home": resource.alloydb_home,
|
|
242
|
+
"filestore_home": resource.filestore_home,
|
|
243
|
+
"cloud_deploy_home": resource.cloud_deploy_home,
|
|
244
|
+
"cloud_ids_home": resource.cloud_ids_home,
|
|
245
|
+
"dialogflow_home": resource.dialogflow_home,
|
|
246
|
+
"cloud_endpoints_home": resource.cloud_endpoints_home,
|
|
215
247
|
"cloud_armor_policy": resource.cloud_armor_policy,
|
|
216
248
|
"cloud_build_trigger": resource.cloud_build_trigger,
|
|
217
249
|
"cloud_composer_environment": resource.cloud_composer_environment,
|
|
@@ -299,5 +331,10 @@ class GCPLinker:
|
|
|
299
331
|
if resource_name in resources_list:
|
|
300
332
|
return resources[resource_name](**param).replace(" ", "") # type: ignore
|
|
301
333
|
|
|
302
|
-
|
|
334
|
+
home_name = SERVICE_HOME_MAP.get(resource_name)
|
|
335
|
+
if home_name and home_name in resources:
|
|
336
|
+
logger.debug(f"resource_name {resource_name!r} not found, falling back to {home_name!r}")
|
|
337
|
+
return resources[home_name](**param).replace(" ", "") # type: ignore
|
|
338
|
+
|
|
339
|
+
logger.debug(f"Invalid resource_name - {resource_name}")
|
|
303
340
|
raise ValueError("Invalid parameters provided")
|