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.
Files changed (25) hide show
  1. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/PKG-INFO +27 -4
  2. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/README.md +16 -0
  3. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/__init__.py +1 -1
  4. cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/__init__.py +180 -0
  5. cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/errors.py +18 -0
  6. cloudconsolelink-2.1.1/cloudconsolelink/clouds/aws/links.py +1191 -0
  7. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/gcp/__init__.py +41 -0
  8. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/gcp/links.py +319 -0
  9. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/PKG-INFO +27 -4
  10. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/SOURCES.txt +4 -1
  11. cloudconsolelink-2.1.1/cloudconsolelink.egg-info/requires.txt +8 -0
  12. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/setup.cfg +1 -1
  13. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/setup.py +30 -9
  14. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/tests/test_aws.py +125 -1
  15. cloudconsolelink-2.1.1/tests/test_gcp.py +105 -0
  16. cloudconsolelink-2.1.1/tests/test_provider_coverage.py +296 -0
  17. cloudconsolelink-1.0.25/cloudconsolelink/clouds/aws/__init__.py +0 -120
  18. cloudconsolelink-1.0.25/cloudconsolelink/clouds/aws/links.py +0 -1022
  19. cloudconsolelink-1.0.25/tests/test_gcp.py +0 -30
  20. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/LICENSE +0 -0
  21. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/__init__.py +0 -0
  22. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink/clouds/azure/__init__.py +0 -0
  23. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/dependency_links.txt +0 -0
  24. {cloudconsolelink-1.0.25 → cloudconsolelink-2.1.1}/cloudconsolelink.egg-info/top_level.txt +0 -0
  25. {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.0.25
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: ganesh@cloudanix.com
8
- License: apache2
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.0.25"
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."""