FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

運用スクリプトのお作法:argparseとバリデーションで安全なスクリプトを作る

こんにちは、ピクトリンク開発で運用保守を担当している藤本です。

はじめに

  • 社内運用保守業務で、S3から特定の大量オブジェクトを調査用に取得する必要がありました。
  • 最初は「動けばいい」で書き始めましたが、レビューを経て「プロ仕様」に進化していった過程を紹介します。

1. 1,000件の壁を越える:boto3 Paginatorの活用

S3バケット内に大量のファイル(数千〜数万件)がある場合、通常の list_objects_v2 リクエスト1回では最大1,000件までしか取得できません。

Before:

# 1,000件を超えると古いファイルしか取れない
response = s3.list_objects_v2(Bucket=BUCKET_NAME, Prefix=prefix)
for obj in response.get('Contents', []):
    # 処理...

After (Paginatorによる全件走査):

実務では調査対象が数千件に及ぶこともあるため、paginatorを使用して全件を確実に漏れなくチェックする実装にしました。

paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket=BUCKET_NAME, Prefix=prefix)

for page in pages:
    if 'Contents' not in page: continue
    for obj in page['Contents']:
        # 1,001件目以降も漏れなくチェック可能

2. 「無駄なAPIコール」を未然に防ぐ:課金意識とバリデーション

ここが今回の最重要ポイントです。PRレビューで「S3のバケット操作(ListObjectsV2など)はリクエストごとに課金が発生するため、APIを叩く前にバリデーションを済ませるべき」という指摘を受けました。

そこで、スクリプト実行時のコマンドライン引数を受け取るargparseの機能を最大限に活用し、入力時点で不正な値を弾く形(フェイルファスト)に進化させました。

① カスタム関数による入力チェック

例えば、「特定の日のログを取得したい」といった場合に指定する日付引数。 形式(YYYY-MM-DD)の間違いや、あり得ない時間(25時など)が入力された場合、S3にリクエストを投げる前にスクリプトを停止させます。

def date_type(value):
    # 引数として渡された文字列が、実在する日付か検証する
    try:
        datetime.strptime(value, '%Y-%m-%d')
        return value
    except ValueError:
        raise argparse.ArgumentTypeError(f"Invalid date format or value: {value}")

# argparseのtype引数に関数を渡すことで、パース時点で弾く
parser.add_argument('--date', required=True, type=date_type, help='対象日(YYYY-MM-DD)')
type=intによる型安全の確保

バージョン番号やID類など、数値であるべき引数にはtype=intを指定します。これにより、文字列が混入した際の予期せぬ挙動を防ぎます。

parser.add_argument('--ver', required=True, type=int)
parser.add_argument('--id', type=int)

3. 「入り口は厳格に、内部は柔軟に」:型のハンドリング

ここで面白い工夫をしたのが、「入力はintで受け取るが、S3のパス検索ではstrとして扱う」という設計です。

なぜintで受け取り、strで渡すのか?

  1. 入力時のガード: type=intを指定することで、「誤ってファイル名を混ぜる」「全角数字が入る」といったヒューマンエラーをパース時点で確実にブロックできます。
  2. 比較の整合性: S3のオブジェクトキーはすべて文字列(String)です。内部処理でstrにキャストして渡すことで、S3のキーに対するstartswith()などの文字列操作を安全かつ確実に行えるようになります。
# 呼び出し側で型を合わせる
fetch_s3_objects(
    version=args.ver,  # 数値として整合性を担保
    target_id=str(args.id) if args.id else None
)

4. まとめ:なぜここまでやるのか

単なる自分専用のスクリプトならここまでの対応は不要かもしれません。しかし、チームで運用するツールにおいては以下の3点が重要だと再認識しました。

  1. コスト意識: 不正な入力でAPIを叩き、無駄な課金(S3 Listリクエスト)を発生させない。
  2. 自己文書化: --helpを見ただけで「何を含み、どの型で入れるべきか」が明確であること。
  3. フェイルファスト: 実行直後にエラーを出すことで、数分待った挙句に取得できないという時間の無駄を防ぐ。

今回も記事を読んでくださり、ありがとうございました!!