本記事は
AWSアワード記念!夏のアドベントカレンダー
13日目の記事です。
🎆🏆
12日目
▶▶ 本記事 ▶▶
14日目
🏆🎆
こんにちは、西内です。
この度、2024 AWS All Certifications Engineersに選出していただきました!
今年も資格が新たに3つ追加されましたが、引き続き維持できるよう頑張ります!!
そして、来年のswagは更に豪華になっていることを期待しています...!
(Amazonのギフト券とかもらえないかなあ~)
- AWSのAPIを実行したときに見るアレ
- NextTokenとは何者か
- NextTokenとの付き合い方 その1 whileを使う
- NextTokenとの付き合い方 その2 Paginatorsという方法
- 最後に
AWSのAPIを実行したときに見るアレ
今回はAWSでList系のAPI等を叩いたときによく見るアイツ、NextTokenについて話そうと思います。
私は普段PythonでAWSのAPIを叩くことが多いので、boto3を例にご説明します。
ちなみにBoto3をご存知でない方向けに軽く説明すると、これはAWS SDK for PythonのことでPythonのコードからAPIを叩く際に使用します。
AWS LambdaをPythonで作成してAWS環境を操作される方は、よくお世話になっているかと思います。
では、さっそくBoto3を実行してみましょう。
今回はAWS CloudFormationのAPI「ListStackSets」を実行してみます。
import boto3 client = boto3.client('cloudformation') response = client.list_stack_sets() print(response) # 以下実行結果 {'Summaries': [ {'StackSetName': 'StackSet1', 'StackSetId': 'StackSet1: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, {'StackSetName': 'StackSet2', 'StackSetId': 'StackSet2: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, {'StackSetName': 'StackSet3', 'StackSetId': 'StackSet3: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' } ... {'StackSetName': 'StackSetX', 'StackSetId': 'StackSetX: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' } ], 'NextToken': 'Ab36b5f08ca0d4db79f59c4e4b27c6c1ceyJIYXNoS2V5IjoiOTY4NDEyOTk4Mzg1L0FXU0NvbnRyb2xUb3dlckJQLUJBU0VMSU5FLUNMT1V EV0FUQ0g6MTkzMTAzZjctYjgzZC00OWUzLWFlMDktZDU0NDAzMzdhYTNiIiwiUmFuZ2VLZXkiOm51bGwsIlNlY29uZGFyeUluZGV4SGFzaEtleSI6Ijk2ODQxM jk5ODM4NSIsIlNlY29uZGFyeUluZGV4UmFuZ2VLZXkiOiJBQ1RJVkV8QVdTQ29udHJvbFRvd2VyQlAtQkFTRUxJTkUtQ0xPVURXQVRDSCIsIkluZGV4TmFtZSI6Ik 5hbWVzcGFjZUxpZmVjeWNsZU5hbWUifQ==', 'ResponseMetadata': {'RequestId': '853568b3-e346-4e52-bf4f-d9551662c37b', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '853568b3-e346-4e52-bf4f-d9551662c37b', 'date': 'Mon, 22 Jul 2024 12: 13: 15 GMT', 'content-type': 'text/xml', 'content-length': '1487', 'connection': 'keep-alive' }, 'RetryAttempts': 0 } }
結果が表示された後、末尾に「NextToken」というランダムな文字列があります。
この「NextToken」とは何なのでしょうか。
公式では以下のように言及されています。
If present, this value indicates that more output is available than is included in the current response. Use this value in the NextToken request parameter in a subsequent call to the operation to get the next part of the output. You should repeat this until the NextToken response element comes back as null. This indicates that this is the last page of results.
簡潔に訳しますと、「この値が存在するときは今回の結果以外にも出力が存在しており、NextTokenがNULLになるまでこれを繰り返す必要がある。」と書かれています。
これはどういうことでしょうか?
NextTokenとは何者か
boto3等でAPI叩いて結果を取得した場合、一度に結果を全て取得することが出来ない場合があります。
例えば、Amazon S3のlist_objectsは一度に1000個の結果を取得できますが、出力結果としては全て表示できません。
(仮に表示できたとしてもめちゃ見づらいですよね)
そのため一定件数表示した後、残りの表示しきれない分はNextTokenを指定して再度メソッドを叩く必要があります。
では、NextTokenを扱って全結果を取得するにはどうすれば良いでしょうか。
NextTokenとの付き合い方 その1 whileを使う
「次の結果が存在する場合は再帰処理を行い、存在しない場合はそこで処理を止める」という操作が必要になります。
これをPythonで実現すると考えたとき、思いつくのはwhileの使用でしょう。
whileを使うことで下記のような結果を得ることが出来ます。
import boto3 client = boto3.client('cloudformation') response = client.list_stack_sets(MaxResults=2) # NextTokenを変更して実行した結果を都度格納する変数を設定 total_response = response['Summaries'] # NextTokenの有無を判定し、ある場合はNextTokenを取り出して引数として渡して再実行し、total_resonseに結果を格納する while 'NextToken' in response: next_token = response['NextToken'] response = client.list_stack_sets(MaxResults=2, NextToken=next_token) total_response.extend(response['Summaries']) print(total_response) # 以下実行結果 {'StackSetName': 'StackSet1', 'StackSetId': 'StackSet1: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, {'StackSetName': 'StackSet2', 'StackSetId': 'StackSet2: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, ... {'StackSetName': 'StackSetZ', 'StackSetId': 'StackSet2: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }
NextTokenが無いのでこれで全件取得することが出来ました。
NextTokenとの付き合い方 その2 Paginatorsという方法
もう少しすっきりした書き方は出来ないでしょうか?
実はboto3ではPaginatorsという機能を利用して、全結果の取得が出来ます。
get_paginators() メソッド内にて全件取得を行いたい操作を指定し、 paginate() メソッドを呼び出してAPI 操作に適用するパラメータを渡します。
Paginators are created via the get_paginator() method of a boto3 client. The get_paginator() method accepts an operation name and returns a reusable Paginator object. You then call the paginate method of the Paginator, passing in any relevant operation parameters to apply to the underlying API operation.
Paginatorsを使って全結果の取得をしてみましょう。
import boto3 client = boto3.client('cloudformation') paginator = client.get_paginator('list_stack_sets') for page in paginator.paginate(): print(page) # 以下実行結果 {'StackSetName': 'StackSet1', 'StackSetId': 'StackSet1: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, {'StackSetName': 'StackSet2', 'StackSetId': 'StackSet2: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' }, ... {'StackSetName': 'StackSetX', 'StackSetId': 'StackSet2: xxxxx-xxxxx', 'Description': '', 'Status': 'ACTIVE', 'DriftStatus': 'NOT_CHECKED' } ...
whileを使ったコードよりもすっきりした内容で、同様の結果を得ることが出来ました!
じゃあ全部Paginators使うやり方でええやん、と思うかもしれないですが全てのメソッドに対応しているわけではありません。
ここで、AWS Resource Access Managerのlist_resource_typesというメソッドの結果をPaginatorsで取得してみましょう。
import boto3 client = boto3.client('ram') paginator = client.get_paginator('list_resource_types') for page in paginator.paginate(): print(page) # 以下実行結果 (省略) botocore.exceptions.OperationNotPageableError: Operation cannot be paginated: list_resource_types
先述のlist_stack_setsと同様にList系のメソッドですが、こちらはPaginatorsに対応していないためエラーになります。
どのメソッドが対応しているかは下記GitHubリポジトリにて、各サービスのフォルダ配下にあるpaginators-1.jsonというファイルを見ると確認ができます。
上記paginators-1.json内に記載のないメソッドはPaginatorsを使うことが出来ません。
Paginatorsを使う際は事前に対象のメソッドが対応しているかを確認しましょう。
最後に
今回はNextTokenについての記事を書かせていただきました。
Boto3でリソースの一覧等を漏れなく取得する際はNextTokenの存在有無は非常に重要です。
今回ご紹介した方法で情報取得の抜け漏れを回避しましょう。