S3からGoogle Driveに同期する(Lambda Ruby + CDK)
やりたかったこと
- S3にアップロードされたアイテムをGoogleDriveの共有フォルダに同期する
- S3のオブジェクト作成イベントでLambdaを実行して処理
- 対象のバケットやLambda関数などはCDKで作成する
この記事に書いたこと
- Lambda Ruby で gem を使う方法
- AWS SDK for Ruby で SecretsManager の秘密情報を取得する方法
- AWS SDK for Ruby で S3 からダウンロードする方法
- Google API Client for Ruby で GoogleDrive にアップロードする方法
- S3 + EventNotification + Lambda でアップロードされたオブジェクトの情報を取得する方法
- CDK で S3 + EventNotification + Lambda を構成する方法
GoogleDriveにアクセスするための情報を用意する
- サービスアカウントキー(JSON)
- 共有フォルダのID
こちらの記事が参考になります。
SecretsManager
秘密情報はAmazon SecretsManagerに入れておき、Lambda関数内で取り出して使うことにします。
サービスアカウントキー(JSON)
$ aws secretsmanager create-secret --name "GoogleDriveServiceAccountKey" --secret-string file://serviceaccount-1234567890234-123456789012.json
共有フォルダのID
$ aws secretsmanager create-secret --name "GoogleDriveDirectoryID" --secret-string 1AMGFIquotsMPeGz1glPoLS39sB6GBy5j
Lambda関数
コード
次のフォルダ構成で作成します。
- functions
- sync_to_google_drive
- index.rb
- Gemfile
- sync_to_google_drive
Gemfile
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem 'aws-sdk' gem 'google-api-client'
index.rb
require "json" require "aws-sdk-secretsmanager" require "aws-sdk-s3" require 'googleauth' require 'google/apis/drive_v3' def handler(event:, context:) puts "event: #{event.inspect}" puts "context: #{context.inspect}" secretsmanager = Aws::SecretsManager::Client.new service_account_key_json = secretsmanager.get_secret_value(secret_id: "GoogleDriveServiceAccountKey").secret_string google_drive_directory_id = secretsmanager.get_secret_value(secret_id: "GoogleDriveDirectoryID").secret_string bucket_name = event['Records'][0]['s3']['bucket']['name'] key = event['Records'][0]['s3']['object']['key'] local_file = File.join('/tmp', File.basename(key)) s3 = Aws::S3::Client.new s3_obj = s3.get_object( response_target: local_file, bucket:bucket_name, key: key ) puts "s3_obj: #{s3_obj.inspect}" authorizer = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: StringIO.new(service_account_key_json), scope: 'https://www.googleapis.com/auth/drive' ) authorizer.fetch_access_token! drive = Google::Apis::DriveV3::DriveService.new drive.authorization = authorizer file_object = { name: key, parents: [google_drive_directory_id], modifiedTime: s3_obj.last_modified } drive.create_file( file_object, upload_source: local_file ) { statusCode: 200, body: JSON.dump({ok: true}) } rescue => e message = "#{e.class.name} (#{e.message})" puts message { statusCode: 501, body: JSON.dump({ok: false, error: message}) } end
bundle install
gemを vendor/bundle
以下にインストールして、本体と一緒にアップロードできるようにしておきます。
$ cd functions/sync_to_google_drive $ bundle install --path vendor/bundle
CDK
抜粋
import * as iam from "@aws-cdk/aws-iam"; import * as lambda from "@aws-cdk/aws-lambda"; import * as s3 from "@aws-cdk/aws-s3"; import * as s3n from "@aws-cdk/aws-s3-notifications"; const myBucket = new s3.Bucket(this, "myBucket", { bucketName: 'my-bucket', blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, versioned: true, removalPolicy: cdk.RemovalPolicy.RETAIN, }); const syncToGoogleDriveFunctionRole = new iam.Role( this, "syncToGoogleDriveFunctionRole", { assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName("AWSLambdaExecute"), ], } ); syncToGoogleDriveFunctionRole.attachInlinePolicy( new iam.Policy(this, "syncToGoogleDriveFunctionRolePolicy", { statements: [ new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["secretsmanager:GetSecretValue"], resources: ["*"], }), new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ["s3:GetObject"], resources: [ myBucket.arnForObjects("*") ], }), ], }) ); const syncToGoogleDriveFunction = new lambda.Function(this, "syncToGoogleDriveFunction", { runtime: lambda.Runtime.RUBY_2_5, handler: "index.handler", code: new lambda.AssetCode("./functions/sync_to_google_drive"), role: syncToGoogleDriveFunctionRole, environment: {}, timeout: cdk.Duration.seconds(900), }); myBucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(syncToGoogleDriveFunction));