3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【30日でAWSをマスターするハンズオン問題集】Day22:毎朝、自動で S3 内のファイルを拡張子ごとに振り分ける仕組みを作ろう

3
Last updated at Posted at 2025-12-17

📝 概要

こちらの投稿は2025 Japan AWS Jr.Championsの有志メンバーで作成した『30日間で主要AWSサービスを構築できるようになる』をテーマにした初学者向けのハンズオン問題集のDAY22になります!
問題集の趣旨や作成に至るまでの経緯は以下の記事をご覧いただければと思います。
https://qiita.com/satosato_kozakana/items/446971c2deca7e27d0aa

項目 内容
所要時間 約 45 分
メインサービス Amazon EventBridge, AWS Step Functions, Amazon S3
学べること Step Functions の分岐ロジック設計, EventBridge の定時トリガー設定
想定費用 約 0 円~10 円(※無料枠内で完結可能)

⚠️ 今回のハンズオンはほぼ無料枠内で完結しますが、リソースを残したままにすると課金が発生する可能性があります。

🎯 課題内容

overview.png

毎朝決まった時間に、S3 バケットにあるファイルを「拡張子」に応じてフォルダ振り分けする処理を構築します。

📊 アーキテクチャ図

architecture.png

🔧 実装機能

  • 毎朝 9:00 に EventBridge が Step Functions を起動します。
  • S3 バケットに格納されたファイルの拡張子によって、別バケットに以下の構造でファイルをコピーします。
    • handson30-02-sorted-bucket/
      • txt/(txt ファイルを格納)
      • others/(txt 以外のファイルを格納)
  • コピー済みの振り分け元 S3 バケット内のファイルは削除します。

💡 実装のヒント

S3バケット内のファイル数だけ、ループを繰り返す方法  以下のコードをMapステートの「項目を提供 - オプション」に入力すると、ファイルの数だけ ループを繰り返します。
{% $states.input.Contents %}
S3バケット内のファイル名取得方法  以下のコードをPassステートの変数で指定することで、ファイル名を変数に入れることができます。
{
  "TargetFile": "{% $states.input.Key %}"
}
拡張子の判定方法  以下のコードをPassステートの変数で指定することで、変数に入れたファイル名に".txt"が含まれていることかどうかをtrue / falseで変数に入れることができます。
{
  "IsTxt": "{% $contains($TargetFile, '.txt') %}"
}

✅ 完成後のチェックポイント

  • 毎朝 9:00 に Step Functions が自動起動する
    ※朝 9:00 より前に StepFunctions が起動するか確認したい場合は、EventBridge に設定する時刻を変更してください。
  • 振り分け先 S3 バケットの、txt フォルダと others フォルダに、ファイルが振り分けられている
  • コピー完了後に、振り分け元 S3 バケット内のファイルは削除される

🔗 リファレンスリンク

🛠️ 解答・構築手順(クリックで開く)

解答と構築手順を見る

✅ ステップ 1:振り分け前 S3 バケットと、振り分け後 S3 バケットを作成

  1. 「S3」→「バケットを作成」
  2. バケットタイプ:汎用
    バケット名:任意
    オブジェクト所有者:ACL 無効(推奨)
    このバケットのブロックパブリックアクセス設定:パブリックアクセスを全てブロック
    バケットのバージョニング:無効にする
    デフォルトの暗号化:SSE-S3
    バケットキー:有効にする
  3. 上記手順 1.2 を振り分け後バケット用に繰り返す

✅ ステップ 2:Step Functions のステートマシン作成(自動生成された IAM の修正含む)

  1. 「Step Functions」→「ステートマシンの作成」

  2. 定義タイプ:標準

  3. ステートマシンの中身を手動で作成する or JSON 定義を貼り付け

    【手動で作成する場合】 3-1. 「アクション」→「S3:ListObjects」を追加し、ステップ1で作成した振り分け前バケットのバケット名「{"Bucket": "作成したバケット名"}」を、引数に記載する。
    📷

    01-listobject.png

    3-2. 「フロー」→「Map」を追加し、ループ処理を行う。

    ・ 処理名:インライン

    ・ 項目を提供:有効にして「{% $states.input,Contents %}」を入力

    📷

    02-map.png

    3-3. 「Map」内に、「フロー」→「Pass」を追加し、ファイル名を変数に入れる。
    📷

    03-pass-filename.png

    3-4. 「Map」内の 3-3 の Pass の後に、「フロー」→「Pass」を追加し、「変数」に以下コードを入力することで、ファイル名に「.txt」が含まれているかどうかを、true / false で変数内に入れる。

    {
      "IsTxt": "{% $contains($TargetFile, '.txt') %}"
    }
    

    📷

    04-pass-extension.png

    3-5. 「Map」内の 3-4 の Pass の後に、「フロー」→「Choice」を追加し、Rule1 の Condition に以下コードを入力する。

    {% ($IsTxt) = (true) %}
    

    📷

    05-choice.png

    3-6. 「Map」内の 3-5 の choice の後に、「アクション」→「CopyObject」を追加し、「引数と出力」に以下コードを入力することで、txt ファイルを振り分け先バケットの txt フォルダにコピーする。

    {
      "Bucket": "handson30-02-sorted-bucket",
      "CopySource": "{% $join(['handson30-02-unsorted-bucket', $TargetFile], '/') %}",
      "Key": "{% $join(['txt', $TargetFile], '/') %}"
    }
    

    📷

    06-copyobject-txt.png

    3-7. 「Map」内の 3-5 の choice に、「Default State」として「新しい状態を追加」を押下し、「アクション」→「CopyObject」を追加し、「引数と出力」に以下コードを入力することで、txt ファイル以外を振り分け先バケットの others フォルダにコピーする。

    {
      "Bucket": "handson30-02-sorted-bucket",
      "CopySource": "{% $join(['handson30-02-unsorted-bucket', $TargetFile], '/') %}",
      "Key": "{% $join(['others', $TargetFile], '/') %}"
    }
    

    📷

    07-choice-others.png

    08-copyobject-others.png

    3-8. 「Map」内の 3-6 と 3-7 の CopyObject の後に、「アクション」→「DeleteObject」を追加し、「引数と出力」に以下コードを入力することで、振り分け元 S3 バケットのコピー済みファイルを削除する。

    {
      "Bucket": "handson30-02-unsorted-bucket",
      "Key": "{% $TargetFile %}"
    }
    

    📷

    09-deleteobject-txt.png

    10-deleteobject-others.png

    【JSON定義を貼り付ける場合】
    {
      "Comment": "A description of my state machine",
      "StartAt": "ListObjects",
      "States": {
        "ListObjects": {
          "Type": "Task",
          "Arguments": {
            "Bucket": "handson30-02-unsorted-bucket"
          },
          "Resource": "arn:aws:states:::aws-sdk:s3:listObjects",
          "Next": "Map"
        },
        "Map": {
          "Type": "Map",
          "ItemProcessor": {
            "ProcessorConfig": {
              "Mode": "INLINE"
            },
            "StartAt": "Pass",
            "States": {
              "Pass": {
                "Type": "Pass",
                "Assign": {
                  "TargetFile": "{% $states.input.Key %}"
                },
                "Next": "Pass (1)"
              },
              "Pass (1)": {
                "Type": "Pass",
                "Assign": {
                  "IsTxt": "{% $contains($TargetFile, '.txt') %}"
                },
                "Next": "Choice"
              },
              "Choice": {
                "Type": "Choice",
                "Choices": [
                  {
                    "Condition": "{% ($IsTxt) = (true) %}",
                    "Next": "CopyObject"
                  }
                ],
                "Default": "CopyObject (1)"
              },
              "CopyObject (1)": {
                "Type": "Task",
                "Arguments": {
                  "Bucket": "handson30-02-sorted-bucket",
                  "CopySource": "{% $join(['handson30-02-unsorted-bucket', $TargetFile], '/') %}",
                  "Key": "{% $join(['others', $TargetFile], '/') %}"
                },
                "Resource": "arn:aws:states:::aws-sdk:s3:copyObject",
                "Next": "DeleteObject"
              },
              "DeleteObject": {
                "Type": "Task",
                "Arguments": {
                  "Bucket": "handson30-02-unsorted-bucket",
                  "Key": "{% $TargetFile %}"
                },
                "Resource": "arn:aws:states:::aws-sdk:s3:deleteObject",
                "End": true
              },
              "CopyObject": {
                "Type": "Task",
                "Arguments": {
                  "Bucket": "handson30-02-sorted-bucket",
                  "CopySource": "{% $join(['handson30-02-unsorted-bucket', $TargetFile], '/') %}",
                  "Key": "{% $join(['txt', $TargetFile], '/') %}"
                },
                "Resource": "arn:aws:states:::aws-sdk:s3:copyObject",
                "Next": "DeleteObject (1)"
              },
              "DeleteObject (1)": {
                "Type": "Task",
                "Resource": "arn:aws:states:::aws-sdk:s3:deleteObject",
                "End": true,
                "Arguments": {
                  "Bucket": "handson30-02-unsorted-bucket",
                  "Key": "{% $TargetFile %}"
                }
              }
            }
          },
          "End": true,
          "Items": "{% $states.input.Contents %}"
        }
      },
      "QueryLanguage": "JSONata"
    }
    
    
  4. 自動作成された IAM ロールにカスタムポリシーを作成して追加
    4-1. 「IAM」→「ポリシー」→「ポリシーの作成」
    4-2. 以下 json を貼り付けて、作成

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::handson30-02-unsorted-bucket"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::handson30-02-unsorted-bucket/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::handson30-02-sorted-bucket/*"
        }
    ]
}

4-3. StepFunctions ステートマシン作成時に自動で生成されたロールに、手順 4-2 のポリシーをアタッチ
※ロール名が不明の場合は、ステートマシンの設定タブにロール名が記載されています。

✅ ステップ 3:EventBridge の定時トリガー作成

  1. 「EventBridge」→「ルールの作成」
  2. スケジュール式でローカルタイムゾーン cron(0 0 0 * * ? *) を設定(JST では毎朝 9:00)
  3. 以下を入力・選択する。
    ・ターゲットタイプ:AWS のサービス
    ・ターゲットを選択:Step Functions ステートマシン
    ・ステートマシン:ステップ 2 で作成したステートマシン
    ・実行ロール:新しいロールを作成

📷
11-eventtbridge.png

12-eventtbridge-cron.png

13-eventtbridge-target.png

✅ ステップ 4:動作確認

  • 振り分け元 S3 バケットに、テスト用のファイルを置いた上で、Step Functions を手動実行して、分岐結果を確認
    ※Step Functions を実行するときは、「保存」ボタンを押下後に、実行すること。
  • EventBridge の定時起動を待って、Step Functions が実行されるか確認

🧹 片付け(リソース削除)

  • EventBridge のルールを削除
  • Step Functions のステートマシンを削除
  • 振り分け元 S3 バケットを削除
  • 振り分け先 S3 バケットを削除

🏁 おつかれさまでした!

この課題では EventBridge をトリガーとして Step Functions が中心となり、S3 のファイルを自動で分類・整理する仕組みを構築しました。

特に注目すべきは、Lambda を一切使わずにここまでの自動化を実現できた点です。
Step Functions には AWS SDK との「サービス統合機能」が用意されており、今回のように S3:ListObjects や S3:CopyObject、S3:DeleteObject などを 直接呼び出すことが可能です。
これにより、コードを書くことなく「業務ロジックを見える化したワークフロー」を構築できます。

次回は応用編として、さらに多くの AWS サービスを組み合わせて、より現場で使える自動化ワークフロー作成に挑戦してみましょう!

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?