I'm trying to select the SerialNumber
of a specific AWS MFADevice
for different profiles.
This command returns the list of MFADevices for a certain profile:
aws iam list-mfa-devices --profile xxx
and this is a sample JSON output:
{
"MFADevices": [
{
"UserName": "[email protected]",
"SerialNumber": "arn:aws:iam::000000000000:mfa/foo",
"EnableDate": "2022-12-06T16:23:41 00:00"
},
{
"UserName": "[email protected]",
"SerialNumber": "arn:aws:iam::111111111111:mfa/bar_cli",
"EnableDate": "2022-12-12T09:13:10 00:00"
}
]
}
I would like to select the SerialNumber
of the device containing the string cli
. But in case there is only one device in the list (regardless of the presence or absence of the string cli
), I'd like to get its SerialNumber
.
I have this expression which already filters for the first condition, namely the desired string:
aws iam list-mfa-devices --profile xxx --query 'MFADevices[].SerialNumber | [?contains(@,`cli`)] | [0]'
However I still haven't been able to figure out how to add the if number_of_devices == 1 then return the serial of that single device
.
I can get the number of MFADevices
with this command:
aws iam list-mfa-devices --profile yyy --query 'length(MFADevices)'
And as a first step towards my final solution I wanted to initially get the SerialNumber
only in the case the list has exactly one element, so, I thought of something like this:
aws iam list-mfa-devices --profile yyy --query 'MFADevices[].SerialNumber | [?length(MFADevices) ==`1`]'
but actually already at this stage I get the error below (left alone the fact that I still need to combine it with the cli
part):
In function length(), invalid type for value: None, expected one of: ['string', 'array', 'object'], received: "null"
Does anybody know how to achieve what I want?
I know that I could just pipe the raw output to jq
and do the filtering there, but I was wondering if there is a way to do it directly in the command using some JMESPath expression.
CodePudding user response:
With stedolan/jq you can filter for the substring and unconditonally add the first, then take the first of them:
.MFADevices | map(.SerialNumber) | first((.[] | select(contains("cli"))), first)
or
[.MFADevices[].SerialNumber] | map(select(contains("cli"))) .[:1] | first
Output:
arn:aws:iam::111111111111:mfa/bar_cli
CodePudding user response:
In order to do those kind of condition in JMESPath you will have to rely on logical or (||
) and logical and (&&
), because the language does not have a conditional keyword, per se.
So, in pseudo-code, instead of doing:
if length(MFADevices) == 1
MFADevices[0]
else
MFADevices[?someFilter]
You have to do, like in bash:
length(MFADevices) == 1 and MFADevices[0] or MFADevices[?someFilter]
So, in JMESPath:
length(MFADevices) == `1`
&& MFADevices[0].SerialNumber
|| (MFADevices[?contains(SerialNumber, `cli`)] | [0]).SerialNumber
Note: this assumes that, if there are more than one element but none contains cli
, we should get null
.
If you want the first element, even when there are multiple devices and the SerialNumber
does not contains cli
, then you can simplify it further and simply do a logical or
, when the contains filter return nothing (as a null
result will evaluates to false
):
(MFADevices[?contains(SerialNumber, `cli`)] | [0]).SerialNumber
|| MFADevices[0].SerialNumber