SQSでCognitoユーザー一覧をS3に作成するキュー処理を行う関数とAPIをLambdaにSAMでデプロイする


日本語が不自由なタイトルになりましたが順に設定していきます。

AWSのCognitoで管理されているユーザー一覧のCSVファイルをS3上に自動で作成する関数をLambda上に構築します。
そのLambdaを発動させるトリガーにSQSを設定し、SQSにメッセージキューを追加するAPIも別のLambdaに実装しAPI Gateway経由でHTTPリクエストを受け取るようにします。

  • Cognito: ユーザー管理サービス。多要素認証(MFA)やSNS連携などを簡単に実装できる。
  • S3: クラウドストレージ。バージョニングや一時的に利用可能なダウンロードURL作成などもできる。
  • SQS: AWS上でキューを管理する。非同期処理などに利用される。
  • Lambda: サーバーレスの関数実行環境
  • API Gateway: LambdaやEC2にAPIのインターフェースを提供する。

構成図は以下のとおりです。

AWS-CLIのインストールとIAMの設定

$ brew install awscli
$ aws configure
AWS Access Key ID [None]: ************
AWS Secret Access Key [None]: ************************
Default region name [None]: ap-northeast-1
Default output format [None]: json

SAMのインストール

※ Dockerも必要になるためインストールと起動をしてください。

$ brew tap aws/tap
$ brew install aws-sam-cli
$ sam --version
SAM CLI, version 1.0.0

SAMプロジェクトの初期化

今回はPython3.8でHello Worldテンプレートをベースに実装を進めます

% sam init
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.7
    ・・・
Runtime: 2

Project name [sam-app]: sample-app

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    ・・・
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: sample-app
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./sample-app/README.md

Cognitoのユーザープールの作成

今回はひとまずデフォルト設定で作成して、適当なユーザーを一つ手動で追加しました。
作成したユーザープールIDをファイル作成時に利用します。

実装

主要なファイルを以下のように実装しました。

app.py
import json
import requests
import boto3
from datetime import datetime
import pandas as pd


def lambda_handler(event, context):

    try:
        sqs_client = boto3.client("sqs")
        # 一度SQSをデプロイすると自動でSQSにキューが作成されるのでそれを設定します
        queue_url = "https://sqs.ap-northeast-1.amazonaws.com/********/**********"
        print(queue_url)
        now = datetime.now()
        date_str = now.strftime('%Y/%m/%d-%H:%M:%S')
        sqs_response = sqs_client.send_message(
            QueueUrl=queue_url,
            MessageBody=json.dumps(date_str)
        )
    except requests.RequestException as e:
        # Send some context about this error to Lambda Logs
        print(e)

        raise e

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world"
        }),
    }

def csv_create_handler(event, context):
    for record in event['Records']:
        payload=record["body"]

        date_str = str(payload)
        s3 = boto3.resource('s3')
        bucket = s3.Bucket('arkth-user-list-files')

        cognito_client = boto3.client('cognito-idp')

        response = cognito_client.list_users(
            UserPoolId = 'ap-northeast-***********',
            AttributesToGet = ['email','sub']
        )

        data = []
        for user in response["Users"]:
            data.append([user['Username'], user['Enabled']])

        Coulum = ['Username', 'Enabled']
        df = pd.DataFrame(data, columns=Coulum)

        df_csv = df.to_csv(index=None)
        objkey = 'user-list.csv'
        print(objkey)
        putobj = bucket.Object(objkey)
        putobj.put(Body=df_csv)

def support_datetime_default(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(repr(obj) + " is not JSON serializable")
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sample-app

  Sample SAM Template for sample-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3
    Runtime: python3.8

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

  UsersCsv:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 60

  CsvCreateFunction:
    Properties:
      CodeUri: hello_world/
      Handler: app.csv_create_handler
      Events:
        MySQSEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt UsersCsv.Arn
            BatchSize: 10
    Type: AWS::Serverless::Function
requirement.txt
requests
boto3
pandas

SAMのビルド

$ sam build
Building function 'HelloWorldFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Building function 'CsvCreateFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

ローカルでSAMの起動

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-09-05 22:11:22  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

SQSのエンドポイントがダミーのためエラーになりますが、ブラウザから http://127.0.0.1:3000/hello にアクセスしてSyntax Errorなどがないか確かめます。

SAMのデプロイ

初回は --guided でデプロイすることで対話式で、samconfig.tomlが作成されます。

$ sam deploy --guided

Configuring SAM deploy
======================

    Looking for samconfig.toml :  Not found

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]: sample-app
    AWS Region [us-east-1]: ap-northeast-1
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [y/N]: y
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]: Y
    HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
    Save arguments to samconfig.toml [Y/n]: Y
・・・・
・・・・
・・・・

少し時間がかかりますが、IAMやApi Gatewayなども適当に設定してデプロイしてくれます。

環境設定

  • app.py内のSQSのエンドポイントを、作成されたSQSのエンドポイントに書き換えます。
  • ファイル作成のLambda関数に割りてられているIAMのRoleにSQSとS3にアクセスする権限を付与します。

  • 以下のように、APIの方のLambda関数のDestinationに作成されたSQSを設定します。ここはSAMで設定できるのかもしれないのですが、今回は手動で行いました。

その後、再度build & deployします。

$ sam build
$ sam deploy

動作確認

HelloWorldの方のLambda関数に設定されているAPI Gatewayのエンドポイントにブラウザでアクセスします。

その後S3のBucketにアクセスすると、以下のようなCSVファイルが作成されています。