AWS IoT ルールで配列操作が安全に出来ず、正規表現で解決した話

2020.04.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

現在担当しているシステムで下記の項目を全て満たすAWS IoTルールを作る必要があり、嵌ったため記事にしました。

  • シャドウには配列を持つフィールドがある
  • 前述の配列には値に数字型を持つフィールドを有したオブジェクトが添字の0〜2のいずれか1つに存在
  • 前述の数字がカウントアップしたら、アクション(Lambda)を実行する

詳しくは後述しますが、AWS IoT ルールでは、ロギングを有効化していると、存在しない配列の添字に対してget関数を利用すると、isUndefinedでチェックをしていてもERRORがロギングされます。 上記を回避する手段として正規表現を扱う関数を利用するという方法があります。

存在しない添字に対してgetしてエラーが発生することを確認

下記のようなシャドウとAWS IoTルールを作ります。

シャドウ

{
  "desired": {
    "array": [
      {
        "obj": "obj1"
      },
      {
        "obj": "obj2",
        "count": 1
      },
      {
        "obj": "obj3"
      }
    ]
  }
}

配列の添字の3番目(存在しない添字)にアクセスするAWS IoTルール

SELECT * FROM "$aws/things/+/shadow/update/documents"
WHERE isUndefined(get(current.state.desired.array, 3))

上記状態で、シャドウ更新する。AWS IoTのCloudwatch Logsによるモニタリング設定を有効にしている 場合、AWSIotLogsV2に下記のようなERRORログが出力されます。

{
    "timestamp": "2020-04-10 07:25:31.579",
    "logLevel": "ERROR",
    "traceId": "7c0caf1c-0d4e-db72-1373-b2a9119c14c5",
    "accountId": "XXXXX",
    "status": "Failure",
    "eventType": "RuleExecution",
    "clientId": "N/A",
    "topicName": "$aws/things/test/shadow/update/documents",
    "ruleName": "test_rule",
    "ruleAction": "FunctionEval",
    "resources": {
        "FunctionName": "get"
    },
    "principalId": "XXXXX",
    "details": "Undefined result"
}

正規表現で解決する

配列を文字列型に変換して、正規表現でカウントアップされたことを検知出来るようにしました。 (配列の長さチェックを挟めば、回避できるのでは?と思いましたが、該当する関数がありませんでした)

最終的なSQLの形は下記の通りです。

SELECT * FROM "$aws/things/+/shadow/update/documents" WHERE
WHERE cast(regexp_replace(current.state.desired.array, '^.*count\":([0-9]{1}).*$', "$1") as Int)
> cast(regexp_replace(previous.state.desired.array, '^.*count\":([0-9]{1}).*$', "$1") as Int)

WHERE句で行っていることは、現在のシャドウのcount数 > 前のシャドウのcount数です。countフィールドの中身を取得するところが肝で、少し見辛いので、一部を切り出して説明致します。

regexp_replace(current.state.desired.array, '^.*count\":([0-9]{1}).*$', "$1")

やっていることはシンプルで、regexp_replace関数を利用します regexp_replace関数は、第一引数に置換対象の文字列、第二引数に置換したい文字列、第三引数に置換する文字列を指定します。 それぞれ見ていきます。

第一引数(current.state.desired.array)

arrayをまるっと指定しています。本関数に配列型を代入すると、文字列に変換されます。

第二引数(^.*count\":([0-9]{1}).*$)

よくある正規表現です。.*で最短マッチになるみたいです。\"は、配列が文字列に変換された際に、"\"に変換されたためで、エスケープしている訳ではありません。

第三引数($1)

キャップチャグループが指定でき、第二引数で丸括弧でマッチさせた文字列を取り出すことができます。取り出した文字列で、第二引数のarray文字列全てを置換しています。

さいごに

正規表現を使ったIoT Coreルールの紹介を致しました。 可読性が悪いため、既存の組み込み関数でさくっと実装できない場合の最後の手段として活用するのが良い思います。 またLambdaの同時実行数がシビアでない場合は、Lambda側にロジックを持たせるという解決策もありだと思います。