From 666deb7f23f48c4f5c2d88c0f4a4f21fec3b4e62 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:24:50 -0400 Subject: [PATCH 01/12] Create remove_custom_property.py --- .../remove_custom_property.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 scripts/remove_custom_property/remove_custom_property.py diff --git a/scripts/remove_custom_property/remove_custom_property.py b/scripts/remove_custom_property/remove_custom_property.py new file mode 100644 index 0000000..f14dc50 --- /dev/null +++ b/scripts/remove_custom_property/remove_custom_property.py @@ -0,0 +1,59 @@ +import os, requests # os: read the API token from your environment; requests: HTTP calls + +# ---- Configuration: the only lines you normally touch ----------------------- +TOKEN = os.environ["OPSLEVEL_API_TOKEN"] # your OpsLevel API token (set it in the shell, not in the file) +URL = "https://app.opslevel.com/graphql" # OpsLevel's single GraphQL endpoint +PROP = "name" # the property to remove (matched by alias OR display name) +PREFIX = "aws_" # which component types count as "infra": those whose alias starts with this +APPLY = True # safety switch: False = dry run (prints only), True = actually deletes + +def gql(q, v=None): + r = requests.post(URL, headers={"Authorization": f"Bearer {TOKEN}"}, json={"query": q, "variables": v or {}}) + r.raise_for_status(); d = r.json() # raise if HTTP failed (401 bad token, 5xx, etc.) + if d.get("errors"): raise SystemExit(d["errors"]) # GraphQL can return 200 OK but still carry errors + return d["data"] # the useful payload lives under "data" + +def infra_types(): + """Return every component type whose alias starts with PREFIX (your 21 infra types).""" + # componentTypes returns ALL types (Services, Teams, infra...) so we filter ourselves. + # It's paginated (max 100 per page), so we loop, passing the previous page's cursor. + + q = "query($a:String){account{componentTypes(first:100,after:$a){nodes{id name aliases} pageInfo{hasNextPage endCursor}}}}" + + out, after = [], None # accumulated matches, and the paging cursor (None = first page) + while True: + c = gql(q, {"a": after})["account"]["componentTypes"] + + # keep only nodes where any alias begins with PREFIX + out += [n for n in c["nodes"] if any((a or "").startswith(PREFIX) for a in (n["aliases"] or []))] + + if not c["pageInfo"]["hasNextPage"]: return out # no more pages -> done + + after = c["pageInfo"]["endCursor"] # otherwise fetch the next page + +def delete_name(): + """For each infra type: find the PROP definition and delete it (or preview it).""" + # PQ: given one component type, list its property definitions (id + aliases + name). + PQ = "query($i:IdentifierInput!){account{componentType(input:$i){properties(first:100){nodes{id aliases name}}}}}" + + # DEL: delete one property definition by its id; returns any per-item errors. + DEL = "mutation($r:IdentifierInput!){propertyDefinitionDelete(resource:$r){errors{message}}}" + types = infra_types() + print(f"{len(types)} infra type(s) matched prefix '{PREFIX}' (expecting 21)") + for t in types: + alias = (t["aliases"] or [t["name"]])[0] # a readable label for logging + + # Each component type has its OWN copy of the property, so we look it up per type by id. + props = gql(PQ, {"i": {"id": t["id"]}})["account"]["componentType"]["properties"]["nodes"] + + # find the property whose alias matches PROP exactly, or whose name matches case-insensitively + hit = next((p for p in props if PROP in (p["aliases"] or []) or p["name"].lower() == PROP.lower()), None) + + if not hit: print(f"{alias}: no '{PROP}'"); continue # this type doesn't have the property + if not APPLY: print(f"{alias}: would delete {hit['id']}"); continue # dry run: show what *would* happen, change nothing + + # live delete: send the mutation, then read back any errors for THIS type + errs = gql(DEL, {"r": {"id": hit["id"]}})["propertyDefinitionDelete"]["errors"] + print(f"{alias}: {'FAILED ' + str(errs) if errs else 'deleted'}") + +delete_name() # run it From 5487880b0288805bc0b91f9842879a9a37e5f89e Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:35:33 -0400 Subject: [PATCH 02/12] Update remove_custom_property.py --- scripts/remove_custom_property/remove_custom_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remove_custom_property/remove_custom_property.py b/scripts/remove_custom_property/remove_custom_property.py index f14dc50..5106a8e 100644 --- a/scripts/remove_custom_property/remove_custom_property.py +++ b/scripts/remove_custom_property/remove_custom_property.py @@ -5,7 +5,7 @@ URL = "https://app.opslevel.com/graphql" # OpsLevel's single GraphQL endpoint PROP = "name" # the property to remove (matched by alias OR display name) PREFIX = "aws_" # which component types count as "infra": those whose alias starts with this -APPLY = True # safety switch: False = dry run (prints only), True = actually deletes +APPLY = False # safety switch: False = dry run (prints only), True = actually deletes def gql(q, v=None): r = requests.post(URL, headers={"Authorization": f"Bearer {TOKEN}"}, json={"query": q, "variables": v or {}}) From e888daad7bfc933f025651a531616da4fe6a5747 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:37:58 -0400 Subject: [PATCH 03/12] Create README.md --- scripts/remove_custom_property/README.md | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 scripts/remove_custom_property/README.md diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md new file mode 100644 index 0000000..cbd9620 --- /dev/null +++ b/scripts/remove_custom_property/README.md @@ -0,0 +1,32 @@ +# Remove a Custom Property Across OpsLevel Infra Types + +`remove_infra_property.py` deletes a custom property (default: `name`) from all +AWS-backed infrastructure component types via the OpsLevel GraphQL API. Each type +holds its own copy of the property, so the script finds all 21 and deletes each +one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. + +## Setup + +​``` +bash +pip install requests +export OPSLEVEL_API_TOKEN=xxxxxxxx +​``` + +## Run + +``` +python remove_infra_property.py # 1. dry run: confirms 21 matches, deletes nothing +# set PREFIX="aws_dynamodb", APPLY=True # 2. delete one type first to confirm it works +# set PREFIX="aws_", keep APPLY=True # 3. run the rest +``` +## Config (top of file) + +- `PROP` — property to remove (alias or name). Default `name`. +- `PREFIX` — alias prefix that marks a type as "infra". Default `aws_` (the API has no infra filter, so we match by prefix). +- `APPLY` — `False` = dry run, `True` = delete. + +## Notes + +- Deletes are **irreversible** and drop stored values — dry-run, test one, then run all. +- `name` ("The name of the resource") may be a built-in field; a `FAILED` line means the API protects it. To hide instead of delete, use `propertyDefinitionUpdate` with `propertyDisplayStatus: hidden`. From a5e8ac4c529adfeceb13ea9eb9f96a0803e99ab8 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:38:15 -0400 Subject: [PATCH 04/12] Update README.md From 87f8945784271b7d0872ba03fd3d4e46c258e5a4 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:39:13 -0400 Subject: [PATCH 05/12] Update README.md --- scripts/remove_custom_property/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index cbd9620..ad528dc 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -7,8 +7,7 @@ one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by defaul ## Setup -​``` -bash +​```bash pip install requests export OPSLEVEL_API_TOKEN=xxxxxxxx ​``` From 688e29f0bbd1221a9aa8c6e7bd0e3641d19e17ae Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:41:06 -0400 Subject: [PATCH 06/12] Update README.md --- scripts/remove_custom_property/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index ad528dc..c1c2509 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -7,7 +7,7 @@ one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by defaul ## Setup -​```bash +​``` pip install requests export OPSLEVEL_API_TOKEN=xxxxxxxx ​``` From 9cebb0166c6cc66781f99d77f4c9bd918440ca38 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:42:00 -0400 Subject: [PATCH 07/12] Update README.md --- scripts/remove_custom_property/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index c1c2509..21fbf39 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -6,11 +6,10 @@ holds its own copy of the property, so the script finds all 21 and deletes each one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. ## Setup - -​``` +``` pip install requests export OPSLEVEL_API_TOKEN=xxxxxxxx -​``` +``` ## Run From 512ee5cb46544623bed933fb6e603298f7e8280f Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 15:43:52 -0400 Subject: [PATCH 08/12] Update README.md --- scripts/remove_custom_property/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index 21fbf39..73a11ff 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -18,6 +18,7 @@ python remove_infra_property.py # 1. dry run: confirms 21 matches, del # set PREFIX="aws_dynamodb", APPLY=True # 2. delete one type first to confirm it works # set PREFIX="aws_", keep APPLY=True # 3. run the rest ``` +Output per type: would delete (dry run), deleted, FAILED [...], or no 'name' (skipped). No bulk rollback, so the log is your record. ## Config (top of file) - `PROP` — property to remove (alias or name). Default `name`. From cbcee9272386083a94ab9e2c3a8a043dd2591860 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 16:12:18 -0400 Subject: [PATCH 09/12] Update README.md --- scripts/remove_custom_property/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index 73a11ff..68117bd 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -2,7 +2,7 @@ `remove_infra_property.py` deletes a custom property (default: `name`) from all AWS-backed infrastructure component types via the OpsLevel GraphQL API. Each type -holds its own copy of the property, so the script finds all 21 and deletes each +holds its own copy of the property, so the script finds all infra components and deletes each one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. ## Setup @@ -14,9 +14,9 @@ export OPSLEVEL_API_TOKEN=xxxxxxxx ## Run ``` -python remove_infra_property.py # 1. dry run: confirms 21 matches, deletes nothing -# set PREFIX="aws_dynamodb", APPLY=True # 2. delete one type first to confirm it works -# set PREFIX="aws_", keep APPLY=True # 3. run the rest +python3 remove_custom_property.py # 1. dry run: confirms matches, deletes nothing +# edit PREFIX="aws_dynamodb", APPLY=True # 2. delete one type first to confirm it works +# edit PREFIX="aws_", keep APPLY=True # 3. run the rest ``` Output per type: would delete (dry run), deleted, FAILED [...], or no 'name' (skipped). No bulk rollback, so the log is your record. ## Config (top of file) From 212093ab49adb53ebe00f2c5054c50b3c101bf33 Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 16:12:31 -0400 Subject: [PATCH 10/12] Update README.md --- scripts/remove_custom_property/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index 68117bd..bf2f592 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -1,6 +1,6 @@ # Remove a Custom Property Across OpsLevel Infra Types -`remove_infra_property.py` deletes a custom property (default: `name`) from all +`remove_custom_property.py` deletes a custom property (default: `name`) from all AWS-backed infrastructure component types via the OpsLevel GraphQL API. Each type holds its own copy of the property, so the script finds all infra components and deletes each one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. From 4543748de1d7fcbe8f9929ede01f2115f83c832f Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 16:14:52 -0400 Subject: [PATCH 11/12] Update README.md --- scripts/remove_custom_property/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index bf2f592..5614a7f 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -2,8 +2,8 @@ `remove_custom_property.py` deletes a custom property (default: `name`) from all AWS-backed infrastructure component types via the OpsLevel GraphQL API. Each type -holds its own copy of the property, so the script finds all infra components and deletes each -one (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. +holds its own copy of the property, so the script finds all infrastructure component types then deletes the property from each type. +(`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. ## Setup ``` From 6b2fd973ff1d27921f451ef27223dfa524a37cbd Mon Sep 17 00:00:00 2001 From: synergy0225 Date: Mon, 15 Jun 2026 16:15:34 -0400 Subject: [PATCH 12/12] Update README.md --- scripts/remove_custom_property/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remove_custom_property/README.md b/scripts/remove_custom_property/README.md index 5614a7f..6f13f37 100644 --- a/scripts/remove_custom_property/README.md +++ b/scripts/remove_custom_property/README.md @@ -2,7 +2,7 @@ `remove_custom_property.py` deletes a custom property (default: `name`) from all AWS-backed infrastructure component types via the OpsLevel GraphQL API. Each type -holds its own copy of the property, so the script finds all infrastructure component types then deletes the property from each type. +holds its own copy of the property, so the script finds all infrastructure component types then deletes the custom property from each type. (`propertyDefinitionDelete` — there's no bulk mutation). Dry-run by default. ## Setup