mapping array of keys to array of values for substitution - jq

Given this data, which is two distinct objects, one having an array of keys and the other a dictionary of key value pairs:
{
"servers": [
{
"location": "server4",
"services": [
"srv07",
"srv06",
"srv01",
"srv04"
]
},
{
"location": "server2",
"services": [
"srv07",
"srv02",
"srv05",
"srv03"
]
}
],
"release": {
"id": "release1",
"services": [
{
"service": "srv01",
"URL": "/srv01_service/v1.20.0"
},
{
"service": "srv02",
"URL": "/srv02_service/v1.14.0"
},
{
"service": "srv03",
"URL": "/srv03_service/v1.15.0"
},
{
"service": "srv04",
"URL": "/srv04_service/v1.18.0"
},
{
"service": "srv05",
"URL": "/srv05_service/v1.14.0"
},
{
"service": "srv06",
"URL": "/srv06_serv/v1.13.0"
},
{
"service": "srv07",
"URL": "/srv07_service/v1.19.0"
}
]
}
}
I am trying to produce this, the first object with the keys replaced with the values from the dictionary. NOTE: I would be fine with renaming services[] to URLs[] if it makes things easier.
{
"servers": [
{
"location": "server4",
"services": [
"/srv07_service/v1.19.0",
"/srv06_serv/v1.13.0",
"/srv01_service/v1.20.0",
"/srv04_service/v1.18.0"
]
},
{
"location": "server2",
"services": [
"/srv07_service/v1.19.0",
"/srv02_service/v1.14.0",
"/srv05_service/v1.14.0",
"/srv03_service/v1.15.0"
]
}
]
}
My latest attempt is close but returns something akin to a Cartesian.
. | .servers[].services[] = (.servers[] as $s | .release.services[] | select(.service as $v | $s.services[] | index($v)).URL) | {servers}

Create an INDEX to lookup in, then map each element you want to change according to the index.
jq '
INDEX(.release.services[]; .service) as $index
| {servers} | .servers[].services[] |= $index[.].URL
'
{
"servers": [
{
"location": "server4",
"services": [
"/srv07_service/v1.19.0",
"/srv06_serv/v1.13.0",
"/srv01_service/v1.20.0",
"/srv04_service/v1.18.0"
]
},
{
"location": "server2",
"services": [
"/srv07_service/v1.19.0",
"/srv02_service/v1.14.0",
"/srv05_service/v1.14.0",
"/srv03_service/v1.15.0"
]
}
]
}
Demo

Related

jq error cannot iterate over null but need to make a different choice

I have a jq filter that selects the rows I need. But sometimes these lines can be empty, and then everything breaks and the rule does not work. I tried to use the if-then-else construct but to no avail.
A rule that works if you process the following json:
.metadata.namespace as $ns | (.spec.rules[0].match.any[].resources.kinds[] / "/") | [select(.[1])[0] // null, select(.[2])[1] // null, last] as [$version,$group,$kind] | {namespace: $ns, kind: $kind, group: $version, version: $group} | with_entries(select(.value!=null))
suitable json:
{
"apiVersion": "kyverno.io/v1",
"kind": "posdfsdf",
"metadata": {
"name": "e-eion",
"namespace": "kke",
"annotations": {
"policies.kyverno.io/title": "Dation",
"policies.kyverno.io/category": "Pod Security Standards (Restricted)",
"policies.kyverno.io/severity": "medium",
"policies.kyverno.io/subject": "Pod",
"kyverno.io/kyverno-version": "1.6.0",
"kyverno.io/kubernetes-version": "1.22-1.23",
"policies.kyverno.io/description": "se`. "
}
},
"spec": {
"validationFailureAction": "audit",
"background": true,
"rules": [
{
"name": "tion",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"message": "Prisd",
"pattern": {
"spec": {
"=(eners)": [
{
"secxt": {
"altion": "false"
}
}
],
"=(i)": [
{
"sext": {
"alcalation": "false"
}
}
],
"containers": [
{
"setext": {
"an": "false"
}
}
]
}
}
}
}
]
}
}
example on which the rule stops working:
{
"apiVersion": "k/v1",
"kind": "Picy",
"metadata": {
"name": "denylation",
"namespace": "what",
},
"spec": {
"validationFailureAction": "audit",
"background": true,
"rules": [
{
"name": "deny-privilege-escalation",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Priviles[*].securityContext.allowPrind spec.initContalse`.",
"pattern": {
"spec": {
"=(iners)": [
{
"=(seext)": {
"=(aln)": "false"
}
}
],
"containers": [
{
"=(stext)": {
"=(al)": "false"
}
}
]
}
}
}
}
]
}
}
how can this be fixed? I need the rule to work out in any cases and give output
This should work :
.metadata.namespace as $ns |
((.spec.rules[0].match | .. | (objects | .resources.kinds[]?)) / "/") |
[select(.[1])[0] // null, select(.[2])[1] // null, last] as [$version,$group,$kind] |
{namespace: $ns, kind: $kind, group: $version, version: $group} |
with_entries(select(.value!=null))
The failing json gives the following error:
parse error: Expected another key-value pair at line 7, column 3
This is caused by the trailing comma found here:
"metadata": {
"name": "denylation",
"namespace": "what",
},
Removing that comma, the following error is thrown:
jq: error (at :44): Cannot iterate over null (null)
This is caused by the missing any key inside the match object.
We can 'catch' that error using a ? (docs):
(.spec.rules[0].match.any[]?.resources.kinds[] / "/")
^
But due to the missing key, the filter does not find anything and the output is empty.
Updated jqPlay

Can I get the user email within a Velocity template of AWS Amplify?

When I query a resolver in my GraphQL API, in which I have added a $util.error($ctx) to return the context object, I get the following result (removed unnecessary values).
{
"data": {
"listXData": null
},
"errors": [
{
"message": {
"arguments": {},
"args": {},
"info": {
"fieldName": "listXData",
"variables": {},
"parentTypeName": "Query",
"selectionSetList": [
"items",
"items/id",
"items/createdAt",
"items/updatedAt",
"nextToken"
],
"selectionSetGraphQL": "{\n items {\n id\n createdAt\n updatedAt\n }\n nextToken\n}"
},
"request": {...},
"identity": {
"sub": "",
"issuer": "",
"username": "013fe9d2-95f7-4885-83ec-b7e2e0a1423f",
"sourceIp": "",
"claims": {
"origin_jti": "",
"sub": "",
"event_id": "",
"token_use": "",
"scope": "",
"auth_time": ,
"iss": "",
"exp": ,
"iat": ,
"jti": "",
"client_id": "",
"username": "013fe9d2-95f7-4885-83ec-b7e2e0a1423f"
},
"defaultAuthStrategy": "ALLOW"
},
"stash": {},
"source": null,
"result": {
"items": [],
"scannedCount": 0,
"nextToken": null
},
"error": null,
"prev": {
"result": {}
}
},
"errorType": null,
"data": null,
"errorInfo": null,
"path": [
"listXData"
],
"locations": [
{
"line": 2,
"column": 3,
"sourceName": "GraphQL request"
}
]
}
]
}
As you can see, the username is an ID, however I would prefer to (also) have the email. Is it possible to get the user email (within the Velocity template)?
Let me know if I need to add more details or if my question is unclear.
The identity context only returns back the Cognito username for the user pool. You will need to setup pipeline functions to perform additional queries to get your user information. Here is one intro to setting them up.
At this point, it seems that it is not possible to do this purely by vtl.
I have implemented it using a lambda function, as follow:
Lambda function (node):
/* Amplify Params - DO NOT EDIT
ENV
REGION
Amplify Params - DO NOT EDIT */
const aws = require('aws-sdk')
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
region: 'eu-west-1'
})
exports.handler = async (context, event, callback) => {
if (!context.identity?.username) {
callback('Not signed in')
}
const params = {
'AccessToken': context.request.headers.authorization
}
const result = await cognitoidentityserviceprovider.getUser(params).promise()
const email = result.UserAttributes.find(attribute => attribute.Name === 'email')
callback(null, JSON.stringify({ email }))
}
CustomResources.json
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "An auto-generated nested stack.",
"Metadata": {...},
"Parameters": {...},
"Resources": {
"GetEmailLambdaDataSourceRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Join": [
"-",
[
"GetEmail17ec",
{
"Ref": "GetAttGraphQLAPIApiId"
},
{
"Ref": "env"
}
]
]
},
{
"Fn::Join": [
"-",
[
"GetEmail17ec",
{
"Ref": "GetAttGraphQLAPIApiId"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "appsync.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "InvokeLambdaFunction",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:InvokeFunction"
],
"Resource": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail-${env}",
{
"env": {
"Ref": "env"
}
}
]
},
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail",
{}
]
}
]
}
}
]
}
}
]
}
},
"GetEmailLambdaDataSource": {
"Type": "AWS::AppSync::DataSource",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "GetEmailLambdaDataSource",
"Type": "AWS_LAMBDA",
"ServiceRoleArn": {
"Fn::GetAtt": [
"GetEmailLambdaDataSourceRole",
"Arn"
]
},
"LambdaConfig": {
"LambdaFunctionArn": {
"Fn::If": [
"HasEnvironmentParameter",
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail-${env}",
{
"env": {
"Ref": "env"
}
}
]
},
{
"Fn::Sub": [
"arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:GetEmail",
{}
]
}
]
}
}
},
"DependsOn": "GetEmailLambdaDataSourceRole"
},
"InvokeGetEmailLambdaDataSource": {
"Type": "AWS::AppSync::FunctionConfiguration",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "InvokeGetEmailLambdaDataSource",
"DataSourceName": "GetEmailLambdaDataSource",
"FunctionVersion": "2018-05-29",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/${ResolverFileName}",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
},
"ResolverFileName": {
"Fn::Join": [
".",
[
"InvokeGetEmailLambdaDataSource",
"req",
"vtl"
]
]
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/${ResolverFileName}",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
},
"ResolverFileName": {
"Fn::Join": [
".",
[
"InvokeGetEmailLambdaDataSource",
"res",
"vtl"
]
]
}
}
]
}
},
"DependsOn": "GetEmailLambdaDataSource"
},
"IsOrganizationMember": {
"Type": "AWS::AppSync::FunctionConfiguration",
"Properties": {
"FunctionVersion": "2018-05-29",
"ApiId": {
"Ref": "AppSyncApiId"
},
"Name": "IsOrganizationMember",
"DataSourceName": "PermissionsPerOrganizationTable",
"RequestMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.isOrganizationMember.req.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
},
"ResponseMappingTemplateS3Location": {
"Fn::Sub": [
"s3://${S3DeploymentBucket}/${S3DeploymentRootKey}/resolvers/Query.isOrganizationMember.res.vtl",
{
"S3DeploymentBucket": {
"Ref": "S3DeploymentBucket"
},
"S3DeploymentRootKey": {
"Ref": "S3DeploymentRootKey"
}
}
]
}
}
},
"OrganizationAccessPipeline": {
"Type": "AWS::AppSync::Resolver",
"Properties": {
"ApiId": {
"Ref": "AppSyncApiId"
},
"TypeName": "Query",
"Kind": "PIPELINE",
"FieldName": "listXData",
"PipelineConfig": {
"Functions": [
{
"Fn::GetAtt": [
"InvokeGetEmailLambdaDataSource",
"FunctionId"
]
},
{
"Fn::GetAtt": [
"IsOrganizationMember",
"FunctionId"
]
}
]
},
"RequestMappingTemplate": "{}",
"ResponseMappingTemplate": "$util.toJson($ctx.result)"
}
}
},
"Conditions": {...},
"Outputs": {...}
}
The lambda is created with the CLI and IsOrganizationMember is a regular VTL which has the user email in the $context.prev.result.

How to apply function to multiple fields in jq

I try to reduce the size of a jq call. The current command is:
jq '.result | .[].newValueJson.action |= (. | tostring // .) | .[].oldValueJson.action |= (. | tostring // .) | .[].metadata.value |= (. | tostring // .)'
As you can see, the function "tostring" is being applied to "newValueJson.action", "oldValueJson.action" and "metadata.value" in just the same way. I am wondering if there is a more compact syntax so I only need to apply tostring once?
I extracted sample data show what is being done (this is not the full json tree).
Source:
{
"result": [{
"id": 1,
"action": {
"result": true,
"type": "filter_create"
},
"newValueJson": {
"action": "simulate"
},
"oldValueJson": {
"action": "enforce"
},
"metadata": {
"value": false
}
},
{
"id": 2,
"action": {
"result": true,
"type": "filter_create"
},
"newValueJson": {
"action": {
"mode": "simulate",
"timeout": 3600
}
},
"oldValueJson": {
"action": {
"mode": "enforce",
"timeout": 3600
}
},
"metadata": {
"value": "off"
}
}
]
}
Result:
[{
"id": 1,
"action": {
"result": true,
"type": "filter_create"
},
"newValueJson": {
"action": "simulate"
},
"oldValueJson": {
"action": "enforce"
},
"metadata": {
"value": "false"
}
},
{
"id": 2,
"action": {
"result": true,
"type": "filter_create"
},
"newValueJson": {
"action": "{\"mode\":\"simulate\",\"timeout\":3600}"
},
"oldValueJson": {
"action": "{\"mode\":\"enforce\",\"timeout\":3600}"
},
"metadata": {
"value": "off"
}
}
]
Thanks and Best.
You can group the actions together using (..) as below. Also instead of using the array notation .[], you can use map(..) to handle apply the filter expression inside the result array
.result | map((.newValueJson.action, .oldValueJson.action, .metadata.value ) |= (. | tostring // .))

How to use "jagged" objects in Azure Resource Manger templates to iterate over them?

I have object with not equal number of properties (and would like to keep it like this), i.e. second object is missing property "routeTable" and properties repeat i.e. "NSG-AllowAll"
"value": [
{
"name": "GatewaySubnet",
"addressPrefix": "10.2.0.0/24",
"networkSecurityGroup": "NSG-AllowAll",
"routeTable": "UDR-Default"
},
{
"name": "UnTrusted",
"addressPrefix": "10.2.1.0/24",
"networkSecurityGroup": "NSG-AllowAll"
},
{
"name": "routed",
"addressPrefix": "10.2.2.0/24",
"routeTable": "UDR-Default1"
}
]
The solution to create vnet works fine but only if exisiting properties don't repeat i.e. the second NSG above has different name from the first one. In my scenario there will be a lot of repeating property names
In that case you just need to remove nsg\udr from that template I've created. I think thats what I've told you there as well. check the ps in my previous answer.
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"deploymentPrefix": {
"type": "string"
},
"subnets": {
"type": "array",
"defaultValue": [
{
"name": "GatewaySubnet",
"addressPrefix": "10.2.0.0/24",
"networkSecurityGroup": "NSG-AllowAll",
"routeTable": "UDR-Default"
},
{
"name": "UnTrusted",
"addressPrefix": "10.2.1.0/24",
"networkSecurityGroup": "NSG-AllowAll"
},
{
"name": "routed",
"addressPrefix": "10.2.2.0/24",
"routeTable": "UDR-Default"
}
]
}
},
"variables": {
"copy": [
{
"name": "subnetsBase",
"count": "[length(parameters('subnets'))]",
"input": {
"name": "[concat('subnet-', parameters('subnets')[copyIndex('subnetsBase')].name)]",
"properties": {
"addressPrefix": "[parameters('subnets')[copyIndex('subnetsBase')].addressPrefix]"
}
}
},
{
"name": "subnetsUDR",
"count": "[length(parameters('subnets'))]",
"input": {
"routeTable": {
"id": "[if(contains(parameters('subnets')[copyIndex('subnetsUDR')], 'routeTable'), resourceId('Microsoft.Network/routeTables', parameters('subnets')[copyIndex('subnetsUDR')].routeTable), 'skip')]"
}
}
},
{
"name": "subnetsNSG",
"count": "[length(parameters('subnets'))]",
"input": {
"networkSecurityGroup": {
"id": "[if(contains(parameters('subnets')[copyIndex('subnetsNSG')], 'networkSecurityGroup'), resourceId('Microsoft.Network/networkSecurityGroups', parameters('subnets')[copyIndex('subnetsNSG')].networkSecurityGroup), 'skip')]"
}
}
}
]
},
"resources": [
{
"apiVersion": "2017-06-01",
"type": "Microsoft.Network/virtualNetworks",
"name": "[concat(parameters('deploymentPrefix'), '-vNet')]",
"location": "[resourceGroup().location]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.2.0.0/16"
]
},
"copy": [
{
"name": "subnets",
"count": "[length(parameters('subnets'))]",
"input": {
"name": "[concat('subnet-', parameters('subnets')[copyIndex('subnets')].name)]",
"properties": "[union(variables('subnetsBase')[copyIndex('subnets')].properties, if(equals(variables('subnetsUDR')[copyIndex('subnets')].routetable.id, 'skip'), variables('subnetsBase')[copyIndex('subnets')].properties, variables('subnetsUDR')[copyIndex('subnets')]), if(equals(variables('subnetsNSG')[copyIndex('subnets')].networkSecurityGroup.id, 'skip'), variables('subnetsBase')[copyIndex('subnets')].properties, variables('subnetsNSG')[copyIndex('subnets')]))]"
}
}
]
}
}
]
}
I dont see any reason why this wont work if you have nsg\udr in place

How Can I group by Gremlin Server (Titan 1.0) Response on Basis of Vertex Id?

I'm trying following query :
g.V(835776).out('Follow').in('WallPost').order().by('PostedTimeLong', decr).range(0,2)
and I'm getting following response :
{
"requestId": "524462bc-5e46-40bf-aafd-64d00351dc87",
"status": {
"message": "",
"code": 200,
"attributes": { }
},
"result": {
"data": [
{
"id": 1745112,
"label": "Post",
"type": "vertex",
"properties": {
"PostImage": [
{
"id": "sd97-11ejc-2wat",
"value": ""
}
],
"PostedByUser": [
{
"id": "sc2j-11ejc-2txh",
"value": "orbitpage#gmail.com"
}
],
"PostedTime": [
{
"id": "scgr-11ejc-2upx",
"value": "2016-06-19T09:17:27.6791521Z"
}
],
"PostMessage": [
{
"id": "sbob-11ejc-2t51",
"value": "Hello #[tag:Urnotice_Profile|835776|1] , #[tag:Abhinav_Srivastava|872488|1] and #[tag:Rituraj_Rathore|839840|1]"
}
],
"PostedTimeLong": [
{
"id": "scuz-11ejc-2vid",
"value": 636019246476802029
}
]
}
},
{
"id": 1745112,
"label": "Post",
"type": "vertex",
"properties": {
"PostImage": [
{
"id": "sd97-11ejc-2wat",
"value": ""
}
],
"PostedByUser": [
{
"id": "sc2j-11ejc-2txh",
"value": "orbitpage#gmail.com"
}
],
"PostedTime": [
{
"id": "scgr-11ejc-2upx",
"value": "2016-06-19T09:17:27.6791521Z"
}
],
"PostMessage": [
{
"id": "sbob-11ejc-2t51",
"value": "Hello #[tag:Urnotice_Profile|835776|1] , #[tag:Abhinav_Srivastava|872488|1] and #[tag:Rituraj_Rathore|839840|1]"
}
],
"PostedTimeLong": [
{
"id": "scuz-11ejc-2vid",
"value": 636019246476802029
}
]
}
}
],
"meta": { }
}
}
since same post is posted on two different Id's it is coming twice in response. I want to group by response on basis of vertex id ( both have same vertex id. or i just want to get one object out of them as both are same only.
I've tried following queries but nothing worked for me :
g.V(835776).out('Follow').in('WallPost').groupBy{it.id}.order().by('PostedTimeLong', decr).range(0,3)
g.V(835776).out('Follow').in('WallPost').group().by(id).order().by('PostedTimeLong', decr).range(0,3)
How can I group by the result on basis of vertex id.
The query
g.V(835776).out('Follow').in('WallPost').group().by(id).order().by('PostedTimeLong', decr).range(0,3)
should work, although order().by() and range() will have no effect. However, I don'tthink you really want to group(), you more likely want to dedup():
g.V(835776).out('Follow').in('WallPost').dedup().order().by('PostedTimeLong', decr).limit(3)

Resources