cloudconsolelink 1.0.25__tar.gz → 2.1.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-1.0.25 → cloudconsolelink-2.1.1}/PKG-INFO +27 -4
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/README.md +16 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/__init__.py +1 -1
- cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/__init__.py +180 -0
- cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/errors.py +18 -0
- cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/links.py +1191 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/gcp/__init__.py +41 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/gcp/links.py +319 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/PKG-INFO +27 -4
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/SOURCES.txt +4 -1
- cloudconsolelink-2.1.1/cloudconsolelink.egg-info/requires.txt +8 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/setup.cfg +1 -1
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/setup.py +30 -9
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/tests/test_aws.py +125 -1
- cloudconsolelink-2.1.1/tests/test_gcp.py +105 -0
- cloudconsolelink-2.1.1/tests/test_provider_coverage.py +296 -0
- cloudconsolelink-1.0.25/cloudconsolelink/clouds/aws/__init__.py +0 -120
- cloudconsolelink-1.0.25/cloudconsolelink/clouds/aws/links.py +0 -1022
- cloudconsolelink-1.0.25/tests/test_gcp.py +0 -30
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/LICENSE +0 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/__init__.py +0 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/azure/__init__.py +0 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/dependency_links.txt +0 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/top_level.txt +0 -0
- {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/tests/test_azure.py +0 -0
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudconsolelink
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.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
|
|
7
|
-
Maintainer-email:
|
|
8
|
-
License:
|
|
7
|
+
Maintainer-email: support@cloudanix.com
|
|
8
|
+
License: Apache-2.0
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
11
|
Classifier: Natural Language :: English
|
|
13
12
|
Classifier: Operating System :: OS Independent
|
|
14
13
|
Description-Content-Type: text/markdown
|
|
15
14
|
License-File: LICENSE
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pre-commit~=4.2; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest~=8.3; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov==2.11; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-mock~=3.14; extra == "dev"
|
|
20
|
+
Requires-Dist: types-PyYAML~=6.0; extra == "dev"
|
|
21
|
+
Requires-Dist: types-requests~=2.32; extra == "dev"
|
|
16
22
|
Dynamic: classifier
|
|
17
23
|
Dynamic: description
|
|
18
24
|
Dynamic: description-content-type
|
|
@@ -21,6 +27,7 @@ Dynamic: license
|
|
|
21
27
|
Dynamic: license-file
|
|
22
28
|
Dynamic: maintainer
|
|
23
29
|
Dynamic: maintainer-email
|
|
30
|
+
Dynamic: provides-extra
|
|
24
31
|
Dynamic: summary
|
|
25
32
|
|
|
26
33
|
# What is cloudconsolelink?
|
|
@@ -29,6 +36,22 @@ This project for generate console links for AWS, GCP and Azure cloud resources
|
|
|
29
36
|
# Install
|
|
30
37
|
Run ```pip install cloudconsolelink``` to install our code
|
|
31
38
|
|
|
39
|
+
For local development, install the package with dev extras:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install -e '.[dev]'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Verification shortcuts:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
make test
|
|
49
|
+
make coverage
|
|
50
|
+
make test-arn
|
|
51
|
+
make test-aws-links
|
|
52
|
+
make check
|
|
53
|
+
```
|
|
54
|
+
|
|
32
55
|
# Usage
|
|
33
56
|
|
|
34
57
|
### Import Package
|
|
@@ -4,6 +4,22 @@ This project for generate console links for AWS, GCP and Azure cloud resources
|
|
|
4
4
|
# Install
|
|
5
5
|
Run ```pip install cloudconsolelink``` to install our code
|
|
6
6
|
|
|
7
|
+
For local development, install the package with dev extras:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install -e '.[dev]'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Verification shortcuts:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
make test
|
|
17
|
+
make coverage
|
|
18
|
+
make test-arn
|
|
19
|
+
make test-aws-links
|
|
20
|
+
make check
|
|
21
|
+
```
|
|
22
|
+
|
|
7
23
|
# Usage
|
|
8
24
|
|
|
9
25
|
### Import Package
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# version of the module
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "2.1.1"
|
|
@@ -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:
|
|
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."""
|