cloudwire 0.2.12__tar.gz → 0.2.13__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 (55) hide show
  1. {cloudwire-0.2.12 → cloudwire-0.2.13}/PKG-INFO +10 -11
  2. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/__init__.py +1 -1
  3. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/errors.py +3 -3
  4. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/graph_store.py +8 -2
  5. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/main.py +2 -0
  6. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scan_jobs.py +3 -1
  7. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanner.py +6 -5
  8. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/cli.py +1 -19
  9. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/PKG-INFO +10 -11
  10. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/requires.txt +0 -2
  11. {cloudwire-0.2.12 → cloudwire-0.2.13}/pyproject.toml +4 -4
  12. {cloudwire-0.2.12 → cloudwire-0.2.13}/LICENSE +0 -0
  13. {cloudwire-0.2.12 → cloudwire-0.2.13}/README.md +0 -0
  14. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/__init__.py +0 -0
  15. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/aws_clients.py +0 -0
  16. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/hcl_parser.py +0 -0
  17. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/models.py +0 -0
  18. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/routes/__init__.py +0 -0
  19. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/routes/scan.py +0 -0
  20. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/routes/tags.py +0 -0
  21. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/routes/terraform.py +0 -0
  22. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/__init__.py +0 -0
  23. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/_utils.py +0 -0
  24. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/apigateway.py +0 -0
  25. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/appsync.py +0 -0
  26. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/cloudfront.py +0 -0
  27. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/cognito.py +0 -0
  28. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/dynamodb.py +0 -0
  29. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/ec2.py +0 -0
  30. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/ecs.py +0 -0
  31. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/elasticache.py +0 -0
  32. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/eventbridge.py +0 -0
  33. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/glue.py +0 -0
  34. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/iam.py +0 -0
  35. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/kinesis.py +0 -0
  36. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/lambda_.py +0 -0
  37. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/rds.py +0 -0
  38. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/redshift.py +0 -0
  39. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/route53.py +0 -0
  40. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/s3.py +0 -0
  41. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/sns.py +0 -0
  42. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/sqs.py +0 -0
  43. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/stepfunctions.py +0 -0
  44. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/scanners/vpc.py +0 -0
  45. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/services.py +0 -0
  46. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/app/terraform_parser.py +0 -0
  47. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/static/assets/index-BlarXbuP.css +0 -0
  48. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/static/assets/index-CJlbfCBD.js +0 -0
  49. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/static/favicon.svg +0 -0
  50. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire/static/index.html +0 -0
  51. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/SOURCES.txt +0 -0
  52. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/dependency_links.txt +0 -0
  53. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/entry_points.txt +0 -0
  54. {cloudwire-0.2.12 → cloudwire-0.2.13}/cloudwire.egg-info/top_level.txt +0 -0
  55. {cloudwire-0.2.12 → cloudwire-0.2.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudwire
3
- Version: 0.2.12
3
+ Version: 0.2.13
4
4
  Summary: Scan and visualize your AWS infrastructure as an interactive graph
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/Himanshu-370/cloudwire
@@ -24,21 +24,20 @@ Classifier: Topic :: System :: Systems Administration
24
24
  Requires-Python: >=3.9
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.100
28
+ Requires-Dist: uvicorn>=0.20
29
+ Requires-Dist: boto3>=1.26
30
+ Requires-Dist: botocore>=1.29
31
+ Requires-Dist: networkx>=2.6
32
+ Requires-Dist: pydantic>=2.0
33
+ Requires-Dist: click>=8.0
34
+ Requires-Dist: python-multipart>=0.0.5
35
+ Requires-Dist: python-hcl2>=4.3
27
36
  Provides-Extra: dev
28
37
  Requires-Dist: twine; extra == "dev"
29
38
  Requires-Dist: build; extra == "dev"
30
39
  Requires-Dist: ruff>=0.4; extra == "dev"
31
40
  Requires-Dist: mypy>=1.0; extra == "dev"
32
- Provides-Extra: dependencies
33
- Requires-Dist: fastapi>=0.100; extra == "dependencies"
34
- Requires-Dist: uvicorn>=0.20; extra == "dependencies"
35
- Requires-Dist: boto3>=1.26; extra == "dependencies"
36
- Requires-Dist: botocore>=1.29; extra == "dependencies"
37
- Requires-Dist: networkx>=2.6; extra == "dependencies"
38
- Requires-Dist: pydantic>=2.0; extra == "dependencies"
39
- Requires-Dist: click>=8.0; extra == "dependencies"
40
- Requires-Dist: python-multipart>=0.0.5; extra == "dependencies"
41
- Requires-Dist: python-hcl2>=4.3; extra == "dependencies"
42
41
 
43
42
  # CloudWire
44
43
 
@@ -5,4 +5,4 @@ from importlib.metadata import version, PackageNotFoundError
5
5
  try:
6
6
  __version__ = version("cloudwire")
7
7
  except PackageNotFoundError:
8
- __version__ = "0.2.12" # fallback when running from source without pip install
8
+ __version__ = "0.2.13" # fallback when running from source without pip install
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- from typing import Any, Dict, Optional
6
+ from typing import Any, Dict, NoReturn, Optional
7
7
 
8
8
  from botocore.exceptions import (
9
9
  BotoCoreError,
@@ -63,8 +63,8 @@ def friendly_exception_message(exc: Exception) -> str:
63
63
  return "Unexpected server error."
64
64
 
65
65
 
66
- def handle_tagging_error(exc: Exception, region: str, operation: str):
67
- """Convert AWS errors from tagging API to APIError."""
66
+ def handle_tagging_error(exc: Exception, region: str, operation: str) -> NoReturn:
67
+ """Convert AWS errors from tagging API to APIError. Always raises."""
68
68
  from fastapi import status
69
69
 
70
70
  logger.warning("Tag API error in %s (region=%s): %s: %s", operation, region, type(exc).__name__, exc)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  import logging
4
5
  from datetime import datetime, timezone
5
6
  from threading import Lock
@@ -73,9 +74,14 @@ class GraphStore:
73
74
  return payload
74
75
 
75
76
  def get_graph_payload(self) -> Dict[str, Any]:
77
+ """Return a deep copy of the cached graph payload.
78
+
79
+ The internal cache is never exposed directly — callers receive an
80
+ independent copy they can safely mutate without corrupting the cache.
81
+ """
76
82
  with self._lock:
77
83
  if self._cached_payload is not None:
78
- return self._cached_payload
84
+ return copy.deepcopy(self._cached_payload)
79
85
  nodes = [self._serialize_node(node_id, attrs) for node_id, attrs in self.graph.nodes(data=True)]
80
86
  edges = [
81
87
  self._serialize_edge(source, target, attrs)
@@ -86,7 +92,7 @@ class GraphStore:
86
92
  metadata["edge_count"] = len(edges)
87
93
  payload = {"nodes": nodes, "edges": edges, "metadata": metadata}
88
94
  self._cached_payload = payload
89
- return payload
95
+ return copy.deepcopy(payload)
90
96
 
91
97
  def iter_nodes_by_service(self, service: str) -> List[Tuple[str, Dict[str, Any]]]:
92
98
  """Return a snapshot of (node_id, attrs_copy) pairs for a given service."""
@@ -83,6 +83,8 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
83
83
  response = await call_next(request)
84
84
  response.headers["X-Content-Type-Options"] = "nosniff"
85
85
  response.headers["X-Frame-Options"] = "DENY"
86
+ # 'unsafe-inline' for styles is required by the React/Tailwind frontend.
87
+ # Acceptable for a localhost-only tool with no user-generated content.
86
88
  response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
87
89
  response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
88
90
  return response
@@ -355,7 +355,9 @@ class ScanJobStore:
355
355
  "finished_at": job.finished_at,
356
356
  "error": job.error,
357
357
  }
358
- graph_store = job.graph_store
358
+ graph_store = job.graph_store # stable reference, set at construction and never reassigned
359
+ # Called outside the job store lock to avoid holding it during serialization.
360
+ # Thread-safe because GraphStore has its own internal lock.
359
361
  graph_payload = graph_store.get_graph_payload()
360
362
  metadata = graph_payload.get("metadata", {})
361
363
  return {
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import logging
5
- import re
6
5
  from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
7
6
  from dataclasses import dataclass
8
7
  from threading import Lock
@@ -51,9 +50,6 @@ def _sanitize_exc(exc: Exception) -> str:
51
50
  from .scanners._utils import _safe_list
52
51
 
53
52
 
54
- _ARN_PATTERN = re.compile(r"^arn:aws:[a-z0-9-]+:")
55
-
56
-
57
53
  @dataclass
58
54
  class ScanExecutionOptions:
59
55
  mode: ScanMode = "quick"
@@ -679,7 +675,9 @@ class AWSGraphScanner(
679
675
 
680
676
  # Infer parent→child edges from ARN hierarchy.
681
677
  # E.g., arn:...:cluster/j-ABC is parent of arn:...:cluster/j-ABC/step/s-DEF
682
- if len(discovered_arns) > 1:
678
+ # Capped at 1000 ARNs to avoid O() blowup with large resource sets.
679
+ _MAX_ARNS_FOR_HIERARCHY = 1000
680
+ if 1 < len(discovered_arns) <= _MAX_ARNS_FOR_HIERARCHY:
683
681
  sorted_arns = sorted(discovered_arns)
684
682
  for i, arn in enumerate(sorted_arns):
685
683
  for j in range(i + 1, len(sorted_arns)):
@@ -693,6 +691,9 @@ class AWSGraphScanner(
693
691
  elif not candidate.startswith(arn[:arn.rfind("/") + 1] if "/" in arn else arn[:arn.rfind(":") + 1]):
694
692
  # No more potential children — different prefix
695
693
  break
694
+ elif len(discovered_arns) > _MAX_ARNS_FOR_HIERARCHY:
695
+ logger.info("%s: skipping ARN hierarchy inference (%d ARNs exceeds cap of %d)",
696
+ service_name, len(discovered_arns), _MAX_ARNS_FOR_HIERARCHY)
696
697
 
697
698
  discovered = len(discovered_arns)
698
699
  if discovered == 0:
@@ -3,12 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- import socket
7
6
  import sys
8
7
  import threading
9
8
  import webbrowser
10
9
 
11
- _REQUIRED_PACKAGES = ["click", "uvicorn", "fastapi", "boto3", "pydantic", "networkx"]
10
+ _REQUIRED_PACKAGES = ["click", "uvicorn", "fastapi", "boto3", "pydantic", "networkx", "multipart", "hcl2"]
12
11
 
13
12
 
14
13
  def _check_dependencies() -> None:
@@ -40,17 +39,6 @@ import uvicorn # noqa: E402
40
39
  from . import __version__ # noqa: E402
41
40
 
42
41
 
43
- def _port_is_available(host: str, port: int) -> bool:
44
- """Return True if the port is free to bind on the given host."""
45
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
46
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
47
- try:
48
- sock.bind((host, port))
49
- return True
50
- except OSError:
51
- return False
52
-
53
-
54
42
  def _check_for_update(current: str, result: list) -> None:
55
43
  """Fetch the latest version from PyPI and store it in result[0]. Runs in a background thread."""
56
44
  try:
@@ -109,12 +97,6 @@ def main(port: int, host: str, profile: str | None, region: str, no_browser: boo
109
97
  # Only set region if not already in environment
110
98
  os.environ.setdefault("AWS_DEFAULT_REGION", region)
111
99
 
112
- # Port conflict check — fail fast with a clear message
113
- if not _port_is_available(host, port):
114
- raise click.ClickException(
115
- f"Port {port} is already in use. Try a different port with --port <number>."
116
- )
117
-
118
100
  # Start background version check — finishes before uvicorn prints its own output
119
101
  _update_result: list = []
120
102
  _update_thread = threading.Thread(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: cloudwire
3
- Version: 0.2.12
3
+ Version: 0.2.13
4
4
  Summary: Scan and visualize your AWS infrastructure as an interactive graph
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/Himanshu-370/cloudwire
@@ -24,21 +24,20 @@ Classifier: Topic :: System :: Systems Administration
24
24
  Requires-Python: >=3.9
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.100
28
+ Requires-Dist: uvicorn>=0.20
29
+ Requires-Dist: boto3>=1.26
30
+ Requires-Dist: botocore>=1.29
31
+ Requires-Dist: networkx>=2.6
32
+ Requires-Dist: pydantic>=2.0
33
+ Requires-Dist: click>=8.0
34
+ Requires-Dist: python-multipart>=0.0.5
35
+ Requires-Dist: python-hcl2>=4.3
27
36
  Provides-Extra: dev
28
37
  Requires-Dist: twine; extra == "dev"
29
38
  Requires-Dist: build; extra == "dev"
30
39
  Requires-Dist: ruff>=0.4; extra == "dev"
31
40
  Requires-Dist: mypy>=1.0; extra == "dev"
32
- Provides-Extra: dependencies
33
- Requires-Dist: fastapi>=0.100; extra == "dependencies"
34
- Requires-Dist: uvicorn>=0.20; extra == "dependencies"
35
- Requires-Dist: boto3>=1.26; extra == "dependencies"
36
- Requires-Dist: botocore>=1.29; extra == "dependencies"
37
- Requires-Dist: networkx>=2.6; extra == "dependencies"
38
- Requires-Dist: pydantic>=2.0; extra == "dependencies"
39
- Requires-Dist: click>=8.0; extra == "dependencies"
40
- Requires-Dist: python-multipart>=0.0.5; extra == "dependencies"
41
- Requires-Dist: python-hcl2>=4.3; extra == "dependencies"
42
41
 
43
42
  # CloudWire
44
43
 
@@ -1,5 +1,3 @@
1
-
2
- [dependencies]
3
1
  fastapi>=0.100
4
2
  uvicorn>=0.20
5
3
  boto3>=1.26
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cloudwire"
7
- version = "0.2.12"
7
+ version = "0.2.13"
8
8
  description = "Scan and visualize your AWS infrastructure as an interactive graph"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -25,9 +25,6 @@ classifiers = [
25
25
  "Topic :: Internet :: WWW/HTTP",
26
26
  "Topic :: System :: Systems Administration",
27
27
  ]
28
- [project.optional-dependencies]
29
- dev = ["twine", "build", "ruff>=0.4", "mypy>=1.0"]
30
-
31
28
  dependencies = [
32
29
  "fastapi>=0.100",
33
30
  "uvicorn>=0.20",
@@ -40,6 +37,9 @@ dependencies = [
40
37
  "python-hcl2>=4.3",
41
38
  ]
42
39
 
40
+ [project.optional-dependencies]
41
+ dev = ["twine", "build", "ruff>=0.4", "mypy>=1.0"]
42
+
43
43
  [project.scripts]
44
44
  cloudwire = "cloudwire.cli:main"
45
45
 
File without changes
File without changes
File without changes