Not sure if this functionality exists. I'm trying to transform a list of comma separated IP addresses from the Azure DevOps build parameters into an array of objects. So far it's only splitting a comma separated list into an array of strings, but the template needs an array of objects.
The parameter value is a comma separated list of IP Addresses.
e.g. "192.168.0.1,192.168.0.2/32,127.0.0.1"
The ARM template would look like:
"variables": {
"ipaddresses": "[split(parameters('ipaddresses'), ',')]"
},
"resources": [
...
"ipRestrictions": "[stringArrToObjArr(variables('ipaddresses'))]" <--
...
]
And ideally function with the arrow above would yield a value for ipRestictions would be something like:
[
{
"ipAddress": "192.168.0.1"
},
{
"ipAddress": "192.168.0.2/32"
},
{
"ipAddress": "127.0.0.1"
},
]
you can use copy() function to do that:
"variables": {
"ipaddresses": "[split(parameters('ipaddresses'), ',')]"
"copy": [
{
"name": "myVariable",
"count": "[length(variables('ipaddresses'))]",
"input": {
"ipAddress": "[variables('ipaddresses')[copyIndex('myVariable')]]"
}
}
]
},
this would return the desired object into a variable called myVariable. if you want to rename it >> don't forget to rename it inside copyIndex() as well
Related
Given two variables of type array in an ARM template, using concat gives me:
The template variable 'thirdArray' is not valid: Unable to evaluate language function 'concat': al
l function arguments must be string literals, integer values, boolean values or arrays.
All function arguments are arrays, so I don't see what's wrong.
"variables": {
"firstArray": {
"type": "array",
"value": [
"1-1",
"1-2",
"1-3"
]
},
"secondArray": {
"type": "array",
"value": [
"2-1",
"2-2",
"2-3"
]
},
"thirdArray": {
"type": "array",
"value": "[concat(variables('firstArray'), variables('secondArray'))]"
}
}
Looking at the documentation, your syntax is incorrect:
When defining a variable, you don't specify a data type for the variable. Instead provide a value or template expression. The variable type is inferred from the resolved value.
This works for me:
"variables": {
"firstArray": [
"1-1",
"1-2",
"1-3"
],
"secondArray": [
"2-1",
"2-2",
"2-3"
],
"thirdArray": "[concat(variables('firstArray'), variables('secondArray'))]"
}
I am creating a logic app which will update the tags column in Azure DevOps with some value.
So how do I fetch that column into my logic apps so that I can update the values on that columns.
This is easiest achieved using the code view. Firstly, Tags are not by default output as array, rather they are a string like:
"System_Tags": "MyTag1; MyTag2"
So, you'll also need to construct the array from that string using a split.
I have here a sample of how to initialize a variable using the result of a Get Work Item Details from Azure Devops:
"Initialize_variable": {
"inputs": {
"variables": [
{
"name": "Work item tags",
"type": "array",
"value": "#split(body('Get_work_item_details')?['fields']?['System_Tags'], '; ')"['System_Tags']}"
}
]
},
"runAfter": {
"Get_work_item_details": [
"Succeeded"
]
},
"type": "InitializeVariable"
}
This will initialize your new variable with the tags of the queried work item.
How do I create a JSON collection based on the Compose actions of the for each mentioned below? If I add a comma after the "}" it will be one too many at the end. Secondly, I somehow need to wrap it in a collection [ ]
I have a Foreach which has a Compose (iteration 1):
{
"Name": "A"
"Value": "1"
}
Second Compose contains another object (iteration 2)
{
"Name": "B"
"Value": "2"
}
However, I now do want to merge these composes to:
"CollectionName": [{
"Name": "A"
"Value": "1"
},
{
"Name": "B"
"Value": "2"
},
{
"Name": "C"
"Value": "3"
}
]
You can use union
union('<collection1>', '<collection2>', ...)
union([<collection1>], [<collection2>], ...)
You can refer to Combine two JSON Arrays to one and How can I merge the outputs from a For_Each loop in an Azure Logic App to a single flat array?
I am looking for a way to convert an array (e.g. of strings) into one object, where the properties are generated from the array values.
Use case: I want to generate a tags object with links to resources, based on a list of resource names. I need to do this, to link App Service resources to an Application Insights resource.
The list of resources could be supplied using a parameter:
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
}
}
}
Sample input:
['appName1', 'appName2', 'appName3']
Sample output:
"tags":
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource",
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
I know you can use copy to loop over arrays but that will create an array of objects and not a single object (which is required for tags), for example:
[
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName1'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName2'))]": "Resource"
},
{
"[concat('hidden-link:', resourceId('Microsoft.Web/sites/', 'appName3'))]": "Resource"
}
]
It would be possible to use union to merge those objects again, but that function requires you to hardcode the objects you want to merge, so it does not work when you have an input with variable length.
What I am looking for is a way to do this in a dynamic way.
There is no direct option to convert array to object.
But here's a hack to achieve what you need. This will work for array of any length.
Steps:
append hidden-link text to service names
convert array to string
replace necessary symbols and make it a valid json string.
use json() to convert string to object
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appServices": {
"type": "array",
"metadata": {
"description": "Names of app services to link this application insights resource to via hidden tags"
},
"defaultValue": [ "appName1", "appName2", "appName3" ]
}
},
"functions": [],
"variables": {
"copy": [
{
"name": "as",
"count": "[length(parameters('appServices'))]",
"input": "[concat('hidden-link:', resourceId('Microsoft.Web/sites/', parameters('appServices')[copyIndex('as')]))]"
}
],
"0": "[string(variables('as'))]",
"1": "[replace(variables('0'), '[', '{')]",
"2": "[replace(variables('1'), '\",', '\":\"Resource\",')]",
"3": "[replace(variables('2'), '\"]', '\":\"Resource\"}')]"
},
"resources": [],
"outputs": {
"op1": {
"type": "object",
"value": "[json(variables('3'))]"
}
}
}
Now that lambdas have been added to bicep you can convert arrays to objects using reduce. Note that the array you reduce must consist of object items. If it doesn't you can convert it to an object using map.
// With array of objects
var names = [
{
name: 'foo'
id: 'foo-id'
}
{
name: 'bar'
id: 'bar-id'
}
]
var nameIds = reduce(names, {}, (cur, next) => union(cur, {
'${next.name}': next.id
}))
output test object = nameIds
// With array of strings
var names = [
'foo'
'bar'
]
var nameMaps = map(names, (name) => {name: name})
var nameIds = reduce(nameMaps, {}, (cur, next) => union(cur, {
'${next.name}': next.name
}))
output test object = nameIds
I'm not sure if this is the best approach to this problem.
Tags are supposed to be metadata about a specific object/service. Wouldn't it make more sense to apply a tag (say your system name, environment, etc..) and then run a query against azure on that tag?
This should achieve the same result pulling back all related resources.
I don't know if it is still relevant, but since 2021 it is possible to do with items() function
I have a For_Each loop in an Azure Logic App that calls another, nested, Logic App. The result from each iteration of the nested Logic Apps is a JSON object that contains an array of strings, like this:
{
"Results": ["string a", "string b"]
}
So the output from my For_Each loop in the parent Logic App looks like this:
[
{"Results": ["string a", "string b"]},
{"Results": ["string c", "string d"]}
]
I want to put all these strings into a single flat list that I can pass to another action.
How can I do this? Is it possible using the workflow definition language and built-in functions, or do I need to use an external function (in a service, or an Azure Function)?
There's a simpler solution, working with Array Variables.
At the top level, outside the For Each loop, declare a variable with an InitializeVariable action:
"Initialize_Items_variable": {
"inputs": {
"variables": [
{
"name": "Items",
"type": "Array",
"value": []
}
]
},
"runAfter": {},
"type": "InitializeVariable"
}
Inside the For Each, use a AppendToArrayVariable action. You can append the Response object of the Nested Logic App you just called.
"Append_to_Items_variable": {
"inputs": {
"name": "Items",
"value": "#body('Nested_Logic_App_Response')"
},
"runAfter": {
},
"type": "AppendToArrayVariable"
}
Hope it helps.
Picking up on #DerekLi's useful comment above, it seems this is not possible at the time of writing with Logic Apps schema version 2016-06-01.
One of the great strengths of Logic Apps is the ability to leverage the power of Azure Functions to solve problems like this that can't (yet) be solved in the schema language.
Re-writing the array is trivial in c# within a function:
using System.Net;
public class Result
{
public List<string> Results {get; set;}
}
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var inputs = await req.Content.ReadAsAsync<List<Result>>();
var outputs = new List<string>();
foreach(var item in inputs)
{
log.Info(item.Results.ToString());
outputs.AddRange(item.Results.Where(x => !string.IsNullOrEmpty(x)));
}
return req.CreateResponse(HttpStatusCode.OK, outputs);
}
And this function can then be passed the result of the For_Each loop:
"MyFunction": {
"inputs": {
"body": "#body('Parse_JSON')",
"function": {
"id": "/subscriptions/{subscription-id}/resourceGroups/{resource-group-name}/providers/Microsoft.Web/sites/{function-app-name}/functions/{function-name}"
},
"method": "POST"
},
"runAfter": {
"For_each": [
"Succeeded"
]
},
"type": "Function"
}
There is also a way to do it using the workflow definition language. (https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-workflow-definition-language).
Using the fonctions string and replace you can work on your json as a string rather than on objects.
Here is a Flat_List action that follows a Parse_JSON action with your data:
Your data:
[
{"Results": ["string a", "string b"]},
{"Results": ["string c", "string d"]}
]
Flat_List component:
"Flat_List": {
"inputs": "#replace(replace(replace(string(body('Parse_JSON')),']},{\"Results\":[',','),'}]','}'),'[{','{')",
"runAfter": {
"Parse_JSON": [
"Succeeded"
]
},
"type": "Compose"
},
What happens here? First we use string that takes your json data and gives:
[{"Results":["string a", "string b"]},{"Results":["string c", "string d"]}]
We replace all the ]},{"Results":[ by ,.
We replace all the }] by }.
We replace all the [{ by {.
We get the string {"Results":["string a","string b","string c","string d"]}
Then you are free to parse it back to json with:
"Parse_JSON_2": {
"inputs": {
"content": "#outputs('Flat_List')",
"schema": {
"properties": {
"Results": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object"
}
},
"runAfter": {
"Flat_List": [
"Succeeded"
]
},
"type": "ParseJson"
}
You can see it as a proof of concept as the Azure Function may be easier to re-read later but there may be many reason not to want to instantiate a new Azure Function while you can do the job in Logic App.
Feel free to ask for more details if needed :)
This technique works pretty well, and only uses run-of-the-mill Logic App actions:
1. start with declaring an empty array variable (action Variable: Initialise variable)
2. iterate through your items (action Control: For each), e.g. the resultset from a previous action
in each iteration, first compose the JSON fragment you need (action Data Operations: Compose)
then append the output of your Compose action to the array (action: Variable: Append to array variable)
3. then, outside the loop, join the elements of the array (action Data Operations: Join)
4. do what you need with the output of the Join action, e.g. send as response payload (action Request: Response)
This is what it looks like in the end:
You can use #body(nestedLogicApp) outside of the for-each loop to access all the nested Logic Apps' response in an array.