在 JMESPath 中使用过滤器时,如何将一个数组级别的字段与嵌套数组级别的字段联接起来



我正在尝试从 aws ec2 描述安全组输出中提取一些数据,寻找描述以值开头的入口规则(即 Temp-JDoe ...),使用过滤器来限制返回的结果。我得到了我想要的结果,但是在一个带有嵌入式数组的深度嵌套数组中。我想将第一级的所有值与向上拉出的嵌套级别的值合并,并用":"连接,这样我就可以迭代生成的多行字符串,创建一个 aws ec2 revoke-security-group-ingress 命令来删除这些规则。

我以为我已经煞费苦心地计算出了一个复杂的JMESPath查询的每个部分,其中每个部分都独立工作,但是当组合在一起时,我得到了一个我无法弄清楚的错误。

我目前还有两个过滤器,当第二个数组级别的匹配集为 null 时,它们似乎是消除第一级数组值所必需的,这似乎令人费解,并且想知道是否有更优雅的方法来获得相同的结果。

我们的目标是创建一对 bash 脚本,当云管理员必须在未知位置工作时,可以运行这些脚本来快速添加和删除堡垒主机安全组的临时安全组规则。我认为这很可能是其他人会遇到的问题。在创建时,我们希望首先清除所有具有描述"Temp-JDoe *"的规则,然后创建包含当前 IP 的描述为"Temp-JDoe (SSH)"的所需集,因此当工作完成后,可以通过此查询再次找到规则并将其删除。

大多数灵感来自这里:https://opensourceconnections.com/blog/2015/07/27/advanced-aws-cli-jmespath-query/

我在这里发现了一个类似的问题:JMESPath - 在嵌套数组中连接项目,但由于我使用了过滤器,它不是一个很好的匹配。

此语句返回我想要的数据。显示 json 输出格式以显示我要折叠和连接的结构:

prefix=Temp
username=JDoe
aws ec2 describe-security-groups --group-id $sg 
--query 'SecurityGroups[].IpPermissions[?not_null(IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`])].[IpProtocol,FromPort,ToPort,IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`].[CidrIp,Description]]' 
--profile $profile --region $region --output json

使用此原始输出:

[
[
[
"tcp",
22,
22,
[
[
"77.111.222.223/32",
"Temp-JDoe (SSH)"
]
]
],
[
"udp",
1194,
1194,
[
[
"77.111.222.223/32",
"Temp-JDoe (VPN-UDP)"
]
]
]
]
]

我需要进入最终形式:

udp:1194:1194:70.185.154.223/32:Temp-JDoe (VPN-UDP)
tcp:22:22:70.185.154.223/32:Temp-JDoe (SSH)

虽然这个带有 joins 的语句在没有过滤器的情况下工作来限制数组数据(在外部级别获取所有数据,在内部级别获取第一个):

aws ec2 describe-security-groups --group-id $sg 
--query 'SecurityGroups[].IpPermissions[].[join(`:`,[IpProtocol,to_string(FromPort),to_string(ToPort),IpRanges[0].join(`:`,[CidrIp,Description])])]' 
--profile $profile --region $region --output text

示例输出(错误的记录,正确的格式):

[
[
"tcp:22:22:111.55.111.123/32:Home-FName (SSH)"
],
[
"udp:1194:1194:111.55.111.123/32:Home-FName (VPN-UDP)"
],
[
"tcp:943:943:70.185.154.223/32:Home-JDoe (Console)"
],
[
"tcp:443:443:111.55.111.123/32:Home-FName (VPN-TCP)"
],
[
"icmp:-1:-1:192.66.55.0/24:Office-NewYork (ICMP)"
]
]

一旦我组合了两个工作子集来过滤我想要的数据,然后加入它,它就不起作用,第二个连接存在一些问题。这是合并声明:

aws ec2 describe-security-groups --group-id $sg 
--query 'SecurityGroups[].IpPermissions[?not_null(IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`])].[join(`:`,[IpProtocol,to_string(FromPort),to_string(ToPort),IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`].join(`:`,[CidrIp,Description])])]' 
--profile $profile --region $region --output text

和错误消息:

In function join(), invalid type for value: ['70.185.154.223/32:Temp-JDoe (VPN-UDP)'], expected one of: ['array-string'], received: "list"

我自己想通了。在嵌套的 IpRanges 数组中,[0] 将第一个元素作为字符串返回,而 [?starts...] 过滤器表示法返回字符串数组,即使它只是一个字符串。因此,我们可以通过管道获取第一个元素:

aws ec2 describe-security-groups --group-id $sg 
--query 'SecurityGroups[].IpPermissions[?not_null(IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`])].[join(`:`,[IpProtocol,to_string(FromPort),to_string(ToPort),IpRanges[?Description!=`null`]|[?starts_with(Description, `'$prefix-$username'`) == `true`]|[0].join(`:`,[CidrIp,Description])])]' 
--profile $profile --region $region --output text

请注意,这确实有一个缺点 - 它只返回可能有多个具有匹配描述的 CIDR 中的第一个。但是,由于此查询已经足够复杂,并且对于我的用例来说,一个通常是唯一的结果,我只是循环调用它的脚本,以便在删除第一次迭代中找到的内容后检查其他规则。

如果有一种更有效的更简单的方式来运行两个过滤器,删除第一个not_null过滤器,并将带有空 IpRange 的结果传送到第二个查询,以查找修剪的那个,那仍然很好。也许改天...

最新更新