cloudmap 1.3.0__tar.gz → 1.3.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.
- {cloudmap-1.3.0 → cloudmap-1.3.1}/PKG-INFO +1 -1
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/__init__.py +1 -1
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ask/__init__.py +4 -4
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/graph.py +14 -3
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_ask.py +32 -5
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_cli_exports.py +3 -1
- {cloudmap-1.3.0 → cloudmap-1.3.1}/.github/workflows/ci.yml +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/.github/workflows/publish.yml +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/.gitignore +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/ARCHITECTURE.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/CONTRIBUTING.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/FORMAT.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/LICENSE +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/PLAN.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/README.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/SECURITY.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/__main__.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/adapters/__init__.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ask/intent.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ask/narration.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ask/queries.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/cli.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/extract/__init__.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/extract/extractors.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/extract/llm.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ingest/__init__.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ingest/azure.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/ingest/fixture.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/interactive.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/local_model.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/model.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/__init__.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/azure_icons.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/csv_export.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/drawio.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/html.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/json_out.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/render/mermaid.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/cloudmap/scrub.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/docs/social-preview.html +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/docs/social-preview.png +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/estate-viewer.png +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/fixtures/acme_orders.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/fixtures/contoso.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/fixtures/estate.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/pyproject.toml +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/01_input_complex_random.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/04_scrubbed_output.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/06_trace_output.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/07_trace_output.html +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/08_trace_output.csv +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/09_trace_output.drawio +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/10_input_enterprise_architecture.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/12_enterprise_trace.html +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/13_enterprise_trace.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/14_enterprise_scrubbed.json +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/README.md +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/data/azure_resource_types.txt +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_adapters.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_arg_rows.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_azure.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_common_types.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_containerapps.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_containment_vs_association.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_drawio_xml.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_enrich.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_estate.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_fixtures_safe.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_golden_orders.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_graph.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_html.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_ingest_paging.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_interactive_wizard.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_llm.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_local_model.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_render_text.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_scrub.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_trust.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/tests/test_type_agnostic.py +0 -0
- {cloudmap-1.3.0 → cloudmap-1.3.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cloudmap
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Trace the blast radius of an Azure resource: one name in, a verified dependency graph out.
|
|
5
5
|
Project-URL: Homepage, https://github.com/KatsaounisThanasis/cloudmap
|
|
6
6
|
Project-URL: Repository, https://github.com/KatsaounisThanasis/cloudmap
|
|
@@ -67,10 +67,10 @@ def _no_subject(graph, default):
|
|
|
67
67
|
return default
|
|
68
68
|
groups = sorted({n.name for n in graph.nodes.values()
|
|
69
69
|
if str(n.id).startswith(HIGH_LEVEL_PREFIXES)})
|
|
70
|
-
return ("This map is the high-level view:
|
|
71
|
-
"
|
|
72
|
-
f"({', '.join(groups[:6])}{', ...' if len(groups) > 6 else ''}),
|
|
73
|
-
"the trace with --level detail to keep instance
|
|
70
|
+
return ("This map is the high-level view: types with several instances are "
|
|
71
|
+
"grouped into one box, so that name is not on it. Ask about a group "
|
|
72
|
+
f"instead ({', '.join(groups[:6])}{', ...' if len(groups) > 6 else ''}), "
|
|
73
|
+
"or re-run the trace with --level detail to keep every instance name.")
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def _error(message, query=None):
|
|
@@ -101,14 +101,25 @@ def collapse_high_level(graph, seed_id):
|
|
|
101
101
|
if gkey == seed_id:
|
|
102
102
|
new_nodes[gkey] = rep
|
|
103
103
|
else:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
role = rep.type.split("/", 1)[-1] if rep.external else friendly_type(rep.type)
|
|
105
|
+
# Grouping exists for readability, not anonymity: when a "group" has
|
|
106
|
+
# exactly one member, hiding its name buys nothing and costs the one
|
|
107
|
+
# thing the reader needs - WHICH vault, WHICH workspace this is.
|
|
108
|
+
if len(members) == 1:
|
|
109
|
+
label = rep.name or role
|
|
110
|
+
else:
|
|
111
|
+
label = f"{role} ×{len(members)}"
|
|
107
112
|
# an external group keeps its members' reasons: "why is this unverified"
|
|
108
113
|
# must not be lost to grouping either
|
|
109
114
|
note = ""
|
|
110
115
|
for m in members:
|
|
111
116
|
note = _merge_semis(note, graph.nodes[m].note)
|
|
117
|
+
if len(members) > 1:
|
|
118
|
+
# the instance names survive collapsing - on hover in the HTML
|
|
119
|
+
# viewer, in the drawio tooltip and in `ask` answers
|
|
120
|
+
shown = sorted(graph.nodes[m].name for m in members)[:8]
|
|
121
|
+
more = "" if len(members) <= 8 else f", +{len(members) - 8} more"
|
|
122
|
+
note = _merge_semis(note, "instances: " + ", ".join(shown) + more)
|
|
112
123
|
new_nodes[gkey] = Node(id=gkey, name=label, type=rep.type,
|
|
113
124
|
external=rep.external, note=note)
|
|
114
125
|
distances[gkey] = dist
|
|
@@ -343,18 +343,45 @@ def test_warnings_stay_quiet_on_a_clean_map():
|
|
|
343
343
|
assert warnings(graph) == []
|
|
344
344
|
|
|
345
345
|
|
|
346
|
-
def
|
|
347
|
-
#
|
|
348
|
-
#
|
|
349
|
-
# "name a resource from this map" sends them hunting for a typo.
|
|
346
|
+
def test_a_singleton_group_keeps_its_name_so_asking_about_it_just_works():
|
|
347
|
+
# Grouping is for readability, not anonymity: with one vault in the map,
|
|
348
|
+
# the box carries its real name and the question answers directly.
|
|
350
349
|
graph, seed = _real()
|
|
351
350
|
collapsed = collapse_high_level(blast_radius(graph, seed), seed)
|
|
352
351
|
res = answer(collapsed, "what breaks if I touch kv-orders-dev")
|
|
353
352
|
|
|
353
|
+
assert not res.get("error")
|
|
354
|
+
assert res["subject_name"] == "kv-orders-dev"
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def test_high_level_map_explains_itself_instead_of_stonewalling():
|
|
358
|
+
# With SEVERAL vaults the box really is anonymous ("Key Vault ×2"), so this
|
|
359
|
+
# is the error people actually hit: the vault IS on their diagram, inside
|
|
360
|
+
# the group box, and "name a resource" sends them hunting for a typo.
|
|
361
|
+
S = "/subscriptions/s/resourcegroups/rg/providers"
|
|
362
|
+
rows = [
|
|
363
|
+
{"id": f"{S}/microsoft.web/sites/web", "name": "web", "type": "microsoft.web/sites",
|
|
364
|
+
"properties": {"siteConfig": {"appSettings": [
|
|
365
|
+
{"name": "A", "value": "https://kv-orders-dev.vault.azure.net/"},
|
|
366
|
+
{"name": "B", "value": "https://kv-payments-dev.vault.azure.net/"}]}}},
|
|
367
|
+
{"id": f"{S}/microsoft.keyvault/vaults/kv-orders-dev", "name": "kv-orders-dev",
|
|
368
|
+
"type": "microsoft.keyvault/vaults",
|
|
369
|
+
"properties": {"vaultUri": "https://kv-orders-dev.vault.azure.net/"}},
|
|
370
|
+
{"id": f"{S}/microsoft.keyvault/vaults/kv-payments-dev", "name": "kv-payments-dev",
|
|
371
|
+
"type": "microsoft.keyvault/vaults",
|
|
372
|
+
"properties": {"vaultUri": "https://kv-payments-dev.vault.azure.net/"}},
|
|
373
|
+
]
|
|
374
|
+
graph = build_graph(rows)
|
|
375
|
+
seed = find_seeds(graph, "web")[0]
|
|
376
|
+
collapsed = collapse_high_level(blast_radius(graph, seed), seed)
|
|
377
|
+
res = answer(collapsed, "what breaks if I touch kv-orders-dev")
|
|
378
|
+
|
|
354
379
|
assert res["error"]
|
|
355
380
|
assert "high-level view" in res["error"]
|
|
356
381
|
assert "--level detail" in res["error"]
|
|
357
|
-
assert "Key Vault" in res["error"]
|
|
382
|
+
assert "Key Vault ×2" in res["error"] # names a group they can actually ask about
|
|
383
|
+
grouped = next(n for n in collapsed.nodes.values() if "×2" in n.name)
|
|
384
|
+
assert "kv-orders-dev" in grouped.note # the names survive in the note
|
|
358
385
|
|
|
359
386
|
|
|
360
387
|
def test_high_level_map_warns_that_counts_are_per_type():
|
|
@@ -142,7 +142,9 @@ def test_a_live_trace_seeded_by_arm_id_renders_valid_xml(tmp_path, monkeypatch):
|
|
|
142
142
|
labels = {o.get("label") for o in ET.parse(tmp_path / "map.drawio").getroot().iter("object")}
|
|
143
143
|
|
|
144
144
|
assert "web (sites)" in labels
|
|
145
|
-
|
|
145
|
+
# the default view groups by type, but a group of ONE keeps its name -
|
|
146
|
+
# a box saying just "Key Vault" answers the wrong question (which one?)
|
|
147
|
+
assert "kv-1 (vaults)" in labels
|
|
146
148
|
|
|
147
149
|
|
|
148
150
|
def _capture_stubs(monkeypatch, rows, truncated=False):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudmap-1.3.0 → cloudmap-1.3.1}/tests/complex_mock_demo/10_input_enterprise_architecture.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|