Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Microsoft Azure profile imageAaron Powell
Aaron Powell forMicrosoft Azure

Posted on • Originally published ataaron-powell.com on

     

Finding Resource Groups With No Resources

I have a lot of resources and a lot of Azure subscriptions, and as a result, often find that I’m forgetting what everything is used for. Sure, I try to name the resource groups something useful, add tags, and things of that nature, but even still, things can get out of control quickly. For example, I have 47 resource groups in my primary subscription at the moment (let along me second and tertiary ones).

I figured a good start would be to delete all the resource groups that don’t have any resources in them. No resource? well, it’s probably not one that I need anymore (I likely deleted some expensive resource but didn’t do the full cleanup).

But how do we find those, short of clicking through the portal?

Well, let’s start withshell.azure.com and start scripting.

To do this task, there’s two bits of information we’ll need, the names of all resource groups and the count of items in those resource groups.

Getting the names of all resource groups is simple:

az group list | jq'map(.name)'
Enter fullscreen modeExit fullscreen mode

This will output:

["aaron-cloud-cli","dddsydney","httpstatus","personal-website","restream-streamdeck","NetworkWatcherRG","stardust-codespace"]
Enter fullscreen modeExit fullscreen mode

Unfortunately, this won't tell you how many resources are in a group (yes, we areonly getting thename property, but the whole JSON doesn't contain it). In fact, you can't get that withaz group at all, evenaz group show --name <name> won't give you it, we'll have to tackle this differently, instead we'll get all resources and group them by their resource group, which we can do withaz resource list:

az resource list | jq'map(.resourceGroup) | group_by(.) | map({ name: .[0], length: length }) | sort_by(.length) | reverse'
Enter fullscreen modeExit fullscreen mode

Thisjq command is a bit complex, but if we break it down, the first thing we're doing is selecting the resource group name from each resource withmap(.resourceGroup), to give us an array of resource group names. Next, we usegroup_by(.) to group them together and pipe that to anothermap function that makes an object with the name of the resource group (obtained from the first item of the index) and the length (how many resources are in the resource group). Lastly, it just sorts and orders it withsort_by andreverse, giving us this output:

[{"name":"httpstatus","length":11},{"name":"personal-website","length":3},{"name":"stardust-codespace","length":1},{"name":"restream-streamdeck","length":1},{"name":"dddsydney","length":1},{"name":"aaron-cloud-cli","length":1}]
Enter fullscreen modeExit fullscreen mode

Great! Except... it only contains resource groups that have resources, meaning we know what resource groups have items, when we want the inverse, we want the ones that don't have items.

So, we will need that original query to get all the resource group names and we'll find the negative intersection between the two arrays, with the leftovers being the resource groups we can discard.

Start by pushing all resource groups with items into a bash variable:

RG_NAMES=$(az resource list | jq-r'map(.resourceGroup) | group_by(.) | map(.[0])')
Enter fullscreen modeExit fullscreen mode

Next, we'll use$RG_NAMES as a substitution into a query againstaz group list:

az group list | jq-r"map(.name) | map(select(. as\$NAME |$RG_NAMES | any(. ==\$NAME) | not)) | sort"
Enter fullscreen modeExit fullscreen mode

Again, let's break this more complex jq statement down. We start with getting the names of the resource groups (since it's all we need) withmap(.name). That is then piped to amap call so we can operate on each item of the array. In the secondmap we use assign the item to a variable$NAME (which we've escaped since we're doing substitution with the environment variable$RG_NAMES), pipe to the$RG_NAMES variable, so we can pipethat toany and see if any item in$RG_NAMES matches$NAME. The result of theany is inverted by piping throughnot and the result is provided toselect to filter down the resource group names to only that didn't have resources!

["NetworkWatcherRG"]
Enter fullscreen modeExit fullscreen mode

And there we have it, we've successfully executed two lines of code and got back the resource groups that are empty and can be deleted.

Summary

Here's those two lines again:

RG_NAMES=$(az resource list | jq-r'map(.resourceGroup) | group_by(.) | map(.[0])')az group list | jq-r"map(.name) | map(select(. as\$NAME |$RG_NAMES | any(. ==\$NAME) | not)) | sort"
Enter fullscreen modeExit fullscreen mode

Yes, thejq can look a bit daunting, especially considering how many pipes they are executing, but all in all, it does what's advertised, returns a list of resource groups that contain no items.

And yes, I may have spent more time trying to figure this out than it would have been clicking through them all, but hey, at least I have it ready for next time! 🤣

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Invent with purpose

Any language. Any platform.

More fromMicrosoft Azure

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp