AWS S3バケットの指定フォルダ直下のフォルダとファイルをLambdaで取得する


Amazon Web ServiceのS3バケットの指定フォルダ直下のフォルダとファイル一覧を取得するLambda関数を作ってみました。

サンプルコード

まずはコードから。Pythonです。

ListFoldersAndFiles.py
import json
import boto3

def lambda_handler(event, context):
    S3Bucket = "{your-bucket-name}"
    S3KeyPrefix = ""
    dirDepth = 1

    folders = []
    files = []
    result = ""

    # Get requesting path from query string and normalize it.
    try:
        path = event['queryStringParameters']['path']
        if path is not None:
            S3KeyPrefix = path
            if (len(S3KeyPrefix) > 0) and (S3KeyPrefix[0] == "/"):
                S3KeyPrefix = S3KeyPrefix[1:]
            if (len(S3KeyPrefix) > 0) and (S3KeyPrefix[-1] == "/"):
                S3KeyPrefix = S3KeyPrefix[:-1]
            if (len(S3KeyPrefix) > 0):
                dirDepth = len(S3KeyPrefix.split('/')) + 1
    except Exception as e:
        print(e)

    # Create instances.
    s3 = boto3.resource('s3')
    bucket = s3.Bucket(S3Bucket)
    objs = bucket.meta.client.list_objects(Bucket = bucket.name, Prefix = S3KeyPrefix, Delimiter = "/")

    try:
        # Enumrate subfolders.
        for o in objs.get('CommonPrefixes'):
            folder = o.get('Prefix')
            folders.append(folder)
            subFolders = bucket.meta.client.list_objects(Bucket = bucket.name, Prefix = folder, Delimiter = "/")
            if (subFolders is not None) and (subFolders.get('CommonPrefixes') is not None):
                for f in subFolders.get('CommonPrefixes'):
                    subFolder = f.get('Prefix')
                    if len(subFolder.split('/')) == dirDepth + 1:
                        folders.append(subFolder)

        # Enumrate files.
        oFiles = bucket.meta.client.list_objects_v2(Bucket = bucket.name, Prefix = S3KeyPrefix)

        for f in oFiles.get('Contents'):
            file = f.get('Key')
            if (file[-1] != '/') and (len(file.split('/')) <= dirDepth):
                files.append(file)
    except Exception as e:
        result += "No such folder : " + S3KeyPrefix

    # Get results.
    folders = sorted(folders)
    result += "\n".join(folders) + "\n"
    files = sorted(files)
    result += "\n".join(files) + "\n"

    return {
        'statusCode': 200,
        'body': result
    }

気をつけた点は、取得しようとするパスの前後に「/」が入っていたり入っていなかったりしても大丈夫にした点と、「/」の数で指定ディレクトリ直下のフォルダ・ファイルのみ取得するようにした点でしょうか。
不恰好ですが、バケットのルートとそれ以外で取得できるフォルダが違ったりしていたので力技で書いてます。

この関数につけるロール

Lambda関数を作成する際にデフォルトのロールを選んで、その後以下のインラインポリシーを追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ListObjectsInBucket",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket-name"
            ]
        },
        {
            "Sid": "AllObjectActions",
            "Effect": "Allow",
            "Action": "s3:*Object",
            "Resource": [
                "arn:aws:s3:::your-bucket-name/*"
            ]
        }
    ]
}

テストイベント

テストイベントに以下の行を追加し、検索パスを指定します。

{
  "queryStringParameters": {
    "path": "path-to-folders"
  }
}