Space separated values; how to provide a value containing a space

4k Views Asked by At

I'm creating a bash script to provision multiple Azure resources via the Azure CLI. So far so good, however I'm having a problem tagging resources.

My goal is to store multiple tags in a variable and provide that variable to the --tags option of several az commands in the script. The problem however is that a space in the value will be interpreted as a new key.

If we take for example the command az group update (which will update a resource group) the docs state the following about the --tags option:

--tags Space-separated tags in 'key[=value]' format. Use "" to clear existing tags.

When a value (or key) contains spaces it must be enclosed in quotes. So when we provide the key-value pairs directly to the command including a value with spaces, like in the following example, the result will be as expected:

az group update --tags owner="FirstName LastName" application=coolapp --name resource-group-name

The result will be that two tags have been added to the resource group:

{
  "id": "/subscriptions/1e42c44c-bc55-4b8a-b35e-de1dfbcfe481/resourceGroups/resource-group-name",
  "location": "westeurope",
  "managedBy": null,
  "name": "resource-group-name",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "application": "coolapp",
    "owner": "FirstName LastName"
  }
}

However, when we store the same value we used in the previous step in a variable the problem occurs.

tag='owner="FirstName LastName" application=coolapp'

I use echo $tag to validate that the variable contains exactly the same value as we provided in the previous example to the --tags option:

owner="FirstName LastName" application=coolapp

But when we provide this tag variable to the tags option of the command as shown in the next line:

az group update --tags $tag --name resource-group-name

The result will be three tags instead of the expected two:

{
  "id": "/subscriptions/1e42c44c-bc55-4b8a-b35e-de1dfbcfe481/resourceGroups/resource-group-name",
  "location": "westeurope",
  "managedBy": null,
  "name": "resource-group-name",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "LastName\"": "",
    "application": "coolapp",
    "owner": "\"FirstName"
  }
}

I've already tried defining the variable in the following ways, but no luck so far:

tag="owner=FirstName LastName application=coolapp"
tag=owner="Firstname Lastname" application=cool-name
tag='`owner="Firstname Lastname" application=cool-name`'

I even tried defining the variable as an array and providing it to the command as shown on the next line, but also that didn't provide the correct result:

tag=(owner="Firstname Lastname" application=cool-name)

az group update --tags ${tag[*]}--name resource-group-name

I also tried putting quotes around the variable in the command, as was suggested by @socowi, but this leads to the following incorrect result of one tag instead of two:

az group update --tags "$tag" --name resource-group-name

{
  "id": "/subscriptions/1e42c44c-bc55-4b8a-b35e-de1dfbcfe481/resourceGroups/resource-group-name",
  "location": "westeurope",
  "managedBy": null,
  "name": "resource-group-name",
  "properties": {
    "provisioningState": "Succeeded"
  },
  "tags": {
    "owner": "Firstname Lastname application=cool-name"
  }
}

Does anybody know how to solve this?

4

There are 4 best solutions below

0
On BEST ANSWER

Define your tags as

tags=("owner=Firstname Lastname" "application=cool-name")

then use

--tags "${tags[@]}"
1
On

I used the following bash script to tag all resources in a subscription. It has worked for us.

# set subscription before running script
tags=('App=Datawarehouse', '[email protected]', 'Cost Center=1234', 'Division=IT', 'Support Tier=0')
resourceIds=$(az resource list --query "[*].[id]" -o tsv)

for resourceId in $resourceIds
do
    
    az tag update --resource-id $resourceId --operation replace --tags "${tags[@]}"

done
0
On

First, build your string like so and double quote all keys/values just in case of spaces in either: (Sorry this is PoSH just example)

[string] $tags = [string]::Empty;
97..99 |% {
  $tags += "&`"$([char]$_)`"=`"$($_)`"";
}

The results of this is a string "&"a"="97"&"b"="98"&"c"="99".

Now pass it as a string array using the split function of the base string class which results in a 4 element array, the first element is blank. The CLI command ignores the first empty element. Here I set the tags for a storage account.

$tag='application=coolapp&owner="FirstName LastName"&"business Unit"="Human Resources"'
az resource tag -g rg -n someResource --resource-type Microsoft.Storage/storageaccounts -tags $tag.split("&")

I also employed this approach when I wanted to override the parameters provided in a parameter file for a resource group deployment.

az group deployment create --resource-group $rgName --template-file $templatefile --parameters $parametersFile --parameters $($overrideParams.split("&"));
0
On

I've found the following works. It requires a resource group already be created.

I used the following template:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "resourceName": {
      "type": "string",
      "metadata": {
        "description": "Specifies the name of the resource"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for the resources."
      }
    },
    "resourceTags": {
      "type": "object",
      "defaultValue": {
        "Cost Center": "Admin"
      }
    }
  },
  "resources": [
    {
      "apiVersion": "2019-06-01",
      "kind": "StorageV2",
      "location": "[parameters('location')]",
      "name": "[parameters('resourceName')]",
      "properties": {
        "supportsHttpsTrafficOnly": true
      },
      "sku": {
        "name": "Standard_LRS"
      },
      "type": "Microsoft.Storage/storageAccounts",
      "tags": "[parameters('resourceTags')]"
    }
  ]
}

In the Azure CLI using Bash, you can pass in the tag as a JSON object. In the following example, a template file with a location requires two parameters, resourceName and the tags which is an ARM object named resourceTags:

az deployment group create --name addstorage  --resource-group myResourceGroup \
--template-file $templateFile \
--parameters resourceName=abcdef45216 resourceTags='{"owner":"bruce","Cost Cen":"2345-324"}'

If you want to pass it as an environment variable, use:

tags='{"owner":"bruce","Cost Center":"2345-324"}'
az deployment group create --name addstorage  --resource-group myResourceGroup \
--template-file $templateFile \
--parameters resourceName=abcdef4556 resourceTags="$tags"

The $tags must be in double quotes. (You are passing in a JSON object string)

The JSON string also works when you are passing in the tags into Azure DevOps pipeline. See https://github.com/MicrosoftDocs/azure-devops-docs/issues/9051