Practical infra coding

Report instances missing required tags

mediumCloud SDK automation

Problem statement

Your tagging policy says every instance must carry owner, env and cost-center, with non-blank values. Finance cannot allocate the bill for anything else. Given a saved "describe instances" response, report every instance that breaks the policy.

The file is a simplified version of an AWS EC2 DescribeInstances response: instances are nested inside Reservations[].Instances[], the state is State.Name, and tags are a list of {"Key", "Value"} pairs that may be missing entirely.

  • Ignore terminated and shutting-down instances; they are on their way out. Stopped instances still count (their volumes still cost money).
  • Tag keys are case-sensitive. If a required key is missing but exists with different casing (Owner), say so: it is the most common cause and the fix is obvious.
  • A value that is empty or only whitespace counts as missing.
  • Print one line per non-compliant instance, sorted by instance ID, then compliant/checked. Exit 1 if anything is non-compliant.

instances.json

JSON
{
"Reservations": [
{"Instances": [
{"InstanceId": "i-0aa1", "State": {"Name": "running"},
"Tags": [{"Key": "Name", "Value": "web-1"}, {"Key": "owner", "Value": "web-team"},
{"Key": "env", "Value": "prod"}, {"Key": "cost-center", "Value": "cc-104"}]},
{"InstanceId": "i-0bb2", "State": {"Name": "running"},
"Tags": [{"Key": "Name", "Value": "web-2"}, {"Key": "Owner", "Value": "web-team"},
{"Key": "env", "Value": "prod"}]}
]},
{"Instances": [
{"InstanceId": "i-0cc3", "State": {"Name": "stopped"}},
{"InstanceId": "i-0dd4", "State": {"Name": "terminated"}},
{"InstanceId": "i-0ee5", "State": {"Name": "running"},
"Tags": [{"Key": "Name", "Value": "batch-7"}, {"Key": "owner", "Value": " "},
{"Key": "env", "Value": "dev"}, {"Key": "cost-center", "Value": "cc-221"}]}
]}
]
}

Examples

Example 1

Input: python solution.py instances.json; echo "exit: $?"

Output: i-0bb2 (web-2, running): owner missing (found 'Owner', wrong case); cost-center missing i-0cc3 (-, stopped): owner missing; env missing; cost-center missing i-0ee5 (batch-7, running): owner is empty 1/4 instances compliant exit: 1

Explanation: i-0dd4 is terminated and not counted. i-0cc3 has no Tags key at all. i-0ee5 has an owner tag whose value is only spaces.

Hints

Approach

Optimal

  1. Flatten. instances() walks Reservations and yields each instance. The response groups instances by the launch request that created them, which is irrelevant for a tag audit, so hide that nesting early.
  2. Filter by state. State.Name in terminated or shutting-down is skipped. Every other state is checked; skipping stopped instances would hide exactly the forgotten machines this report is meant to find.
  3. Normalize tags. inst.get("Tags") or [] covers both a missing key and an explicit null. The list becomes a Key -> Value dict, plus a lowercase index for the case check.
  4. Check each required key. Present with a blank value (after strip()) is "empty"; absent but present in another case is "wrong case"; otherwise "missing". Each instance gets all of its problems in one line, so the owner can fix everything in one go.
  5. Report. Sort by instance ID for stable output, print the compliance ratio, and exit 1 if anything failed so the script can run as a scheduled policy check.

i is the number of instances and t the number of tags per instance.

ComplexityTime O(i * t)Space O(t)
Python
import json
import sys
REQUIRED = ["owner", "env", "cost-center"]
IGNORE_STATES = {"terminated", "shutting-down"}
def instances(response):
for reservation in response.get("Reservations", []):
yield from reservation.get("Instances", [])
def check(instance):
# Returns a list of human-readable problems for one instance.
tags = {t["Key"]: t.get("Value", "") for t in instance.get("Tags") or []}
by_lower = {k.lower(): k for k in tags}
problems = []
for key in REQUIRED:
if key in tags:
if not tags[key].strip():
problems.append(f"{key} is empty")
elif key in by_lower:
problems.append(f"{key} missing (found '{by_lower[key]}', wrong case)")
else:
problems.append(f"{key} missing")
return tags.get("Name", "-"), problems
def main(path):
with open(path, encoding="utf-8") as f:
response = json.load(f)
checked = compliant = 0
report = []
for inst in instances(response):
state = inst.get("State", {}).get("Name", "unknown")
if state in IGNORE_STATES:
continue
checked += 1
name, problems = check(inst)
if problems:
report.append((inst["InstanceId"], name, state, problems))
else:
compliant += 1
for iid, name, state, problems in sorted(report):
print(f"{iid} ({name}, {state}): {'; '.join(problems)}")
print(f"{compliant}/{checked} instances compliant")
return 1 if report else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "instances.json"))

Follow-up questions

  • Also validate values: env must be one of dev, staging, prod.
  • Group the report by the owner tag so each team gets its own list.
  • Generate the tag-fix commands (or API calls) for the wrong-case cases, but only print them unless --apply is given.

Frequently asked questions

Tag hygiene drives cost allocation, ownership and automated cleanup. Cloud and platform teams routinely write audits like this, and interviewers use it to check that you can navigate nested API responses and handle the messy cases: missing lists, wrong case, blank values.

Because the tools consuming the tags usually will not. Cost allocation and many policy engines treat tag keys as case-sensitive, so Owner and owner are different keys. The report should flag it so the tag gets fixed at the source, rather than hide it.

Report-after-the-fact is the second line of defence. The first is preventing it: enforce required tags in your infrastructure-as-code modules, or with the provider's policy features that deny creation of untagged resources.