Saturday, May 16, 2015

PowerShell + Azure : Validate ResourceGroup Tags

Recently been working on some DevOps stuff in Azure using Python & PowerShell, so would be doing few posts revolving around that.

Why I have added the below pic ?
Python is what I have been picking from the Dev world (currently) and PowerShell is what I have picked from the Ops world.

In Azure Resource Manager, one can add tags to the Resource Groups (check out the new Azure Portal to explore ResourceGroups ). Last week had to script a way to check that there is a Tag on the resource group with a valid set of values. Python Azure SDK doesn't yet support Azure Resource Manager operations so had to turn to the Ops side (PowerShell way).

Don't worry if you have no idea what a tag is, the validation code is pretty neat.

For Example - the Resource Group should have a tag named "Environment" on it with the valid values of "Dev","QA" & "Prod" .

There can be other tags on it but we are looking only for the existence of this tag & values.

Let's get started.
  1. Since Azure Resource Manager cmdlets doesn't support Certificate based authentication. We have to use Azure AD here. First step is to use below cmdlet to add your account using which you login to the Azure Portal.
  2. Add-AzureAccount

  3. Once authenticated , Switch to using Azure resource manager cmdlets using the below cmdlet. So that we get the AzureResourceManager Module loaded.
  4. Switch-AzureMode -Name AzureResourceManager

  5. Now you can see in the PowerShell host that the AzureResourceManager Module is loaded:
  6. PS>Get-Module
    ModuleType Version    Name                                ExportedCommands
    ---------- -------    ----                                ----------------
    Manifest   0.9.1      AzureResourceManager                {Add-AlertRule, Add-AutoscaleSetting, Add-AzureAccount, Ad...
  7.  Use the cmdlet Get-AzureResourceGroup to get Resource Groups and store them in a variable for later processing.
  8. PS>$ResourceGroups = Get-AzureResourceGroup
  9. Now we can filter Resource Groups which have tags property like below :
    PS>$ResourceGroups | where tags
    ResourceGroupName : DexterPOSHCloudService
    Location          : southeastasia
    ProvisioningState : Succeeded
    Tags              :
                        Name         Value
                        ===========  =========
                        Environment  QA
                        TestKey      TestValue
    ResourceId        : /subscriptions/4359ee69-61ce-430c-b885-4083b2656de7/resourceGroups/DexterPOSHCloudService
    ResourceGroupName : dexterseg
    Location          : southeastasia
    ProvisioningState : Succeeded
    Tags              :
                        Name         Value
                        ===========  =======
                        Environment  Testing
    ResourceId        : /subscriptions/4359ee69-61ce-430c-b885-4083b2656de7/resourceGroups/dexterseg

    Maybe those who don't have the Tags property we can throw a warning but I leave the Scripting logic for you to build upon.
  10. Now out of the 2 Resource groups above one has a valid Environment Tag of value "QA" (in green) but the other one has an invalid tag value of "Testing"  (in yellow).
    Before we start validating the Values we need to check if the Tag contains the desired Environment tag, for this we can use the contains() method.

    But if you look closely you will find something strange with the tags property :
    Name                           Value
    ----                           -----
    Value                          LAB
    Name                           Environment
    Value                          TestValue
    Name                           TestKey

    Tags property is a hashtable but the keys are "Name" & "Value" literal. Not very intuitive as I thought I would be getting the Environment as one of the key for the hashtable returned, submitted a issue at the GitHub repo for this.
    Until it is fixed we can check if at all our Environment tag is present by using the ContainsValue method on the Hashtable, like below :

    Now if the tag has the Environment as the value for 'Name' key then evidently the value for 'Value' key will be the one we are seeking.

    Wow ! so much work am already dozing off :P
  11. It appears validating the value is one of our permitted values ('Dev','QA','Prod') is relatively easy using the validateset() parameter attribute, see the below:
    PS>[Validateset('DEV','QA','PROD')]$testme = $ResourceGroups[0].tags[0]['Value']
    PS>[Validateset('DEV','QA','PROD')]$testme = $ResourceGroups[1].tags[0]['Value']
    The attribute cannot be added because variable testme with value LAB would no longer be valid.
    At line:1 char:1
    + [Validateset('DEV','QA','PROD')]$testme = $ResourceGroups[0].tags[0]['Value']
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : MetadataError: (:) [], ValidationMetadataException
        + FullyQualifiedErrorId : ValidateSetFailure

    When we decorate the $testme variable with the ValidateSet() attribute and perform the assignment it will throw an exception if the value is not in the set (note that the second resource group in step 5 doesn't have a valid Environment tag), which we can catch later and display a message saying that the Environment tag doesn't have a valid value.

[Update] - Forgot to mention a detail, which I got reminded when I saw a tweet by Stefan Stranger mentioning that the ContainsValue() method on a hashtable is case-sensitive. Workaround to that by MVP Dave Wyatt is in below tweet :

Below is the sample code for one to build upon :
Switch-AzureMode -Name AzureResourceManager

$ResourceGroups = Get-AzureResourceGroup | where Tags

foreach ($ResourceGroup in $ResourceGroups) {  
  if ($ResourceGroup.tags.values.Contains('Environment')) {
    Write-Verbose -Message "$($ResourceGroup.ResourceGroupName) Environment tag not found" -Verbose
        foreach ($Tag in $ResourceGroup.Tags) {
            if ($Tag.ContainsValue('Environment')) {
                TRY {
                    [validateset('DEV','QA','PROD')]$testme = $Tag['Value']
                CATCH [System.Management.Automation.ValidationMetadataException] {
                    Write-Error -Message "Environment tag doesn't contain a valid value -->('DEV','QA','PROD')"
    else {
        Write-Warning -Message "$($ResourceGroup.ResourceGroupName) Environment tag not found"

Thanks for reading and that is it for today's post.