docker-compose
などで動かしたいとき、ホストへライブラリのインストールをなるべくしたくない。(依存パッケージの関係でそもそも pg
が入らない、とか)
bundle install
と rails webpacker:install
を避ける
rails new myapp --skip-bundle --database=postgresql --skip-webpack-install
docker-compose
などで動かしたいとき、ホストへライブラリのインストールをなるべくしたくない。(依存パッケージの関係でそもそも pg
が入らない、とか)
bundle install
と rails webpacker:install
を避ける
rails new myapp --skip-bundle --database=postgresql --skip-webpack-install
こちらの記事が参考になります。
秘密情報はAmazon SecretsManagerに入れておき、Lambda関数内で取り出して使うことにします。
$ aws secretsmanager create-secret --name "GoogleDriveServiceAccountKey" --secret-string file://serviceaccount-1234567890234-123456789012.json
$ aws secretsmanager create-secret --name "GoogleDriveDirectoryID" --secret-string 1AMGFIquotsMPeGz1glPoLS39sB6GBy5j
次のフォルダ構成で作成します。
# 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'
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
gemを vendor/bundle
以下にインストールして、本体と一緒にアップロードできるようにしておきます。
$ cd functions/sync_to_google_drive $ bundle install --path vendor/bundle
抜粋
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));
cert_content = <<EOF -----BEGIN CERTIFICATE----- dummy -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- dummy -----END CERTIFICATE----- EOF pkey_content = <<EOF -----BEGIN EC PARAMETERS----- dummy -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- dummy -----END EC PRIVATE KEY----- EOF cert = OpenSSL::X509::Certificate.new(cert_content) pkey = OpenSSL::PKey::EC.new(pkey_content) # 秘密鍵が正しいか? cert.check_private_key(pkey) # => true # コモンネームが期待したものか? cert.subject.to_a.find { |name, _| name == 'CN' }.fetch(1) #=> "*.takeyuweb.co.jp"
AWSの機械学習レコメンデーションサービスである Amazon Personalize を案件で利用したのでSDKの使い方メモです。
Amazon Personalize は、機械学習の知識がなくても、簡単にレコメンデーションをアプリに組み込むことができるサービスです。
これまで、AWSで機械学習レコメンデーションを行うには、ElasticMapReduce + Mahoutで自前で計算するなど、インフラ構築と保守の手間があったり、モデル設計や評価についての知識が必要だったりとなかなかに大変だったのですが、一般的なレコメンデーションに限っては Amazon Personalize を使うことで、サーバーレスで簡単に実現できるようになりました。
この記事では Amazon Personalize の使い方については解説しません。公式ドキュメントその他を参照してください。
DATASET_GROUP_NAME = "my-first-datasetgroup" personalize = Aws::Personalize::Client.new dataset_group_arn = personalize.create_dataset_group(name: DATASET_GROUP_NAME).dataset_group_arn
ユーザー、アイテム、インタラクションのどのデータセットタイプなのかににより、適切な形式のスキーマを作成する。
SCHEMA_NAME = "Users" DATASET_TYPE = "users" # or "items" or "interactions " personalize = Aws::Personalize::Client.new avro_schema = case DATASET_TYPE when "users" { "type": "record", "name": "Users", "namespace": "com.amazonaws.personalize.schema", "fields": [ { "name": "USER_ID", "type": "string" }, { "name": "AGE", "type": "int" }, { "name": "GENDER", "type": "string" } ], "version": "1.0" } when "items" { "type": "record", "name": "Items", "namespace": "com.amazonaws.personalize.schema", "fields": [ { "name": "ITEM_ID", "type": "string" }, { "name": "LABEL", "type": "string", "categorical": true }, { "name": "CAST_ID", "type": "string", "categorical": true }, { "name": "DURATION", "type": "long" }, { "name": "PUBLISH_AT", "type": "long" } ], "version": "1.0" } when "interactions" { "type": "record", "name": "Interactions", "namespace": "com.amazonaws.personalize.schema", "fields": [ { "name": "USER_ID", "type": "string" }, { "name": "ITEM_ID", "type": "string" }, { "name": "EVENT_TYPE", "type": "string" }, { "name": "EVENT_VALUE", "type": "float" }, { "name": "TIMESTAMP", "type": "long" } ], "version": "1.0" } end schema_arn = personalize.create_schema( name: SCHEMA_NAME, schema: avro_schema.to_json ).schema_arn
DATASET_NAME = "users" DATASET_TYPE = "users" # or "items" or "interactions " SCHEMA_ARN = "arn:aws:personalize:{{REGION}}:{{ACCOUNT_ID}}:schema/{{SCHEMA_NAME}}" DATASET_GROUP_ARN = "arn:aws:personalize:{{REGION}}:{{ACCOUNT_ID}}:dataset/{{DATASET_GROUP_NAME}}" personalize = Aws::Personalize::Client.new personalize.create_dataset( name: DATASET_NAME, schema_arn: SCHEMA_ARN, dataset_group_arn: DATASET_GROUP_ARN , dataset_type: DATASET_TYPE )
Amazon Personalize からアクセスできるようにバケットポリシーを設定する必要があります。
BUCKET_NAME = "myapp-personalize" s3 = Aws::S3::Client.new s3.create_bucket(bucket: S3_BUCKET_NAME) s3.put_bucket_policy( bucket: S3_BUCKET_NAME, policy: { "Version": "2008-10-17", "Id": "PolicyForPersonalizePrivateContent", "Statement": [ { "Sid": "PersonalizeS3BucketAccessPolicy", "Effect": "Allow", "Principal": { "Service": "personalize.amazonaws.com" }, "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::#{S3_BUCKET_NAME}", "arn:aws:s3:::#{S3_BUCKET_NAME}/*" ] } ] }.to_json )
AmazonPersonalizeFullAccess
のサービスロールを使うのに加え、学習用データを置くS3バケットへのアクセスを許可します。
BUCKET_NAME = "myapp-personalize" ROLE_NAME = "personalizeExecutionRole" ROLE_PATH = "/myapp/" iam = Aws::IAM::Client.new role = iam.create_role( assume_role_policy_document: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "personalize.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }.to_json, path: ROLE_PATH, role_name: ROLE_NAME, ).role iam.attach_role_policy( policy_arn: "arn:aws:iam::aws:policy/service-role/AmazonPersonalizeFullAccess", role_name: role.role_name, ) iam.put_role_policy( policy_document: { "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:ListBucket" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::#{S3_BUCKET_NAME}" ] }, { "Action": [ "s3:GetObject", "s3:PutObject" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::#{S3_BUCKET_NAME}/*" ] } ] }.to_json, policy_name: "AllowAccessToS3Bucket", role_name: role.role_name, )
イベントトラッキングを行うには、イベントトラッカーを作る必要があります。 イベントトラッカーを作ると、イベントトラッカーが受け取ったデータを投入するデータセットなども一緒に作られます。
DATASET_GROUP_ARN = "arn:aws:personalize:{{REGION}}:{{ACCOUNT_ID}}:dataset/{{DATASET_GROUP_NAME}}" personalize = Aws::Personalize::Client.new event_tracker_arn = personalize.create_event_tracker( name: EVENT_TRACKER_NAME, dataset_group_arn: DATASET_GROUP_ARN ).event_tracker_arn
ユーザー、アイテム、インタラクションそれぞれ学習させたいデータセットタイプにあわせてCSVを作成します。 アップロードしたCSVを指定したデータセットインポートジョブを作成します。
データセットインポートジョブのパラメータとして、インポート先のデータセット、データセットタイプに対応するスキーマで作成したCSVのS3オブジェクトキー、実行IAMロールを指定します。
# CSV BUCKET_NAME = "myapp-personalize" OBJECT_KEY = "/users.csv" FILE_PATH = "/tmp/users.csv" # Amazon Personalize リソース DATASET_ARN = "arn:aws:personalize:{{REGION}}:{{ACCOUNT_ID}}:dataset/{{DATASET_GROUP_NAME}}/{{DATASET_NAME}}" # 作成したデータセット ROLE_ARN = "arn:aws:iam::{{ACCOUNT_ID}}:role/myapp/{{ROLE_NAME}}" # 作成した実行ロール s3 = Aws::S3::Client.new File.open(FILE_PATH, 'r') do |file| s3.put_object( bucket: BUCKET_NAME, key: OBJECT_KEY, body: file, content_type: 'text/csv' ) end personalize = Aws::Personalize::Client.new personalize.create_dataset_import_job( job_name: "users-import-job-#{Time.current.to_i}", # 重複はできない dataset_arn: DATASET_ARN, data_source: { data_location: File.join('s3://', BUCKET_NAME , OBJECT_KEY) }, role_arn: ROLE_ARN, )
EVENT_TRACKER_TRACKING_ID = "0000000-0000-0000-0000-000000000000" USER_ID = current_user.id SESSION_ID = session.id EVENT_TYPE = "rating" # 今回は★1-5のレビュー評価をトラッキングする想定。自由に決められる。 ITEM_ID = review.item_id EVENT_VALUE = review.rating SENT_AT = review.created_at personalize_events = Aws::PersonalizeEvents::Client.new payload = { tracking_id: EVENT_TRACKER_TRACKING_ID, user_id: USER_ID.to_s, session_id: SESSION_ID , event_list: [ { event_type: EVENT_TYPE, properties: { itemId: ITEM_ID.to_s, eventValue: EVENT_VALUE, }.to_json, sent_at: SENT_AT, } ] } personalize_events.put_events(payload)
アイテムベース (SIMS)も、ユーザーベースも、レコメンデーション結果を取得するAPIは同じです。 パラメータが違います。
item_id
を渡します。
CAMPAIGN_ARN = "arn:aws:personalize:{{REGION}}:{{AWS_ACCOUNT_ID}}:campaign/{{CAMPAIGN_NAME}}" ITEM_ID = 1234 NUM_RESULTS = 20 personalize_runtime = Aws::PersonalizeRuntime::Client.new # item_list[0].item_id #=> String # item_list[0].score #=> 常に 0 item_list = personalize_runtime.get_recommendations( campaign_arn: CAMPAIGN_ARN , item_id: ITEM_ID.to_s, num_results: NUM_RESULTS, ).item_list
user_id
を渡します。
結果セットにスコアが含まれるので、おすすめ順に並べるのに使えますね。
CAMPAIGN_ARN = "arn:aws:personalize:{{REGION}}:{{AWS_ACCOUNT_ID}}:campaign/{{CAMPAIGN_NAME}}" USER_ID = 1234 NUM_RESULTS = 20 personalize_runtime = Aws::PersonalizeRuntime::Client.new # item_list[0].item_id #=> String # item_list[0].score #=> Float item_list = personalize_runtime.get_recommendations( campaign_arn: CAMPAIGN_ARN , user_id: USER_ID.to_s, num_results: NUM_RESULTS, ).item_list
GraphQLにおける graphql-codegen のような、 OpenAPI のSpecification から TypeScript の型定義を作ってくれる swagger-to-ts を触ってみました。
Shipments.v1.yaml
という OpenAPI 3.0 Specification から、Shipments.ts
という TypeScriptを生成します。
OpenAPI 3.0 Specification は YAML でも JSON でも大丈夫です。
"scripts": { "generate:difinitions:shipments": "npx @manifoldco/swagger-to-ts Shipments.v1.yaml -o Shipments.ts" }
swagger: "2.0" info: title: Shipments version: "1.0" description: "" host: "localhost:3000" schemes: - http produces: - application/json consumes: - application/json paths: /addresses: get: summary: List all addresses tags: - Shipments - Addresses responses: "200": description: OK schema: type: object properties: next_token: type: string format: uuid addresses: type: - string - array items: $ref: "#/definitions/Address" total: type: integer examples: example-1: {} operationId: get-addresses description: List all addresses parameters: - type: integer in: query name: limit - type: string in: query name: token format: uuid allowEmptyValue: false post: summary: Create a new address tags: - Shipments - Addresses operationId: post-addresses responses: "200": description: OK schema: $ref: "#/definitions/Address" definitions: Address: title: Address type: object x-tags: - Addresses - Shipments x-examples: example-1: {} description: Delivery address properties: id: type: string country: type: string enum: - US - JP example: JP description: Country code state: type: string city: type: string street: type: string name: type: string company: type: string email: type: string format: email phone: type: string pattern: "[0-9]{10,11}" required: - country - state - city - street - name
リクエストのパラメータなどはなく、definitions
のみのようですね。
/** * This file was auto-generated by swagger-to-ts. * Do not make direct changes to the file. */ export interface definitions { /** * Delivery address */ Address: { id?: string; /** * Country code */ country: "US" | "JP"; state: string; city: string; street: string; name: string; company?: string; email?: string; phone?: string; }; }
import * as Shipments from "../lib/Shipments"; const address: Shipments.definitions["Address"] = { country: "JP", state: "Saitama-ken", city: "Saitama Shi Omiya Ku", street: "Ginza Bld.7F, 1-5, Miyacho", name: "Yuichi Takeuchi", };
OpenAPI の定義ファイルの作成と管理のためのツールを探していて、Stoplight Studio が良いと聞いたので試してみることにしました。
Design APIs 10x faster with our free OpenAPI editor. Prototype and share your API within minutes.
無料の OpenAPI エディタを使用して、API を 10 倍速く設計します。数分以内にAPIをプロトタイプ化して共有します。
Start designing your API in minutes. Use the OpenAPI Specification (formerly known as Swagger) with Stoplight Studio to design consistent and standardized HTTP APIs for your organization. Without writing any code, model complex APIs faster than ever while simultaneously getting feedback on your prototypes with our instant mock servers.
数分でAPIの設計を開始します。Stoplight StudioでOpenAPI仕様(旧称Swagger)を使用して、一貫性のある標準化されたHTTP APIを設計します。コードを書くことなく、複雑なAPIをこれまで以上に迅速にモデル化すると同時に、インスタントモックサーバーを使用してプロトタイプのフィードバックを得ることができます。
Design an API with speed and efficiency from scratch. Stoplight Studio decreases the learning curve so you don’t need to be an OpenAPI expert to create first-class API designs. Describe endpoints, headers, bodies, multiple responses, query string parameters, shared models, and examples, and much more for complex APIs.
ゼロから迅速かつ効率的に API を設計します。Stoplight Studioは学習曲線を減らしてくれるので、OpenAPIの専門家でなくてもファーストクラスのAPIデザインを作成できます。エンドポイント、ヘッダ、ボディ、複数応答、クエリ文字列パラメータ、共有モデル、例など、複雑な API のための多くの機能を説明します。
Use the Git integration with your organization’s Git provider (GitHub, GitLab, Bitbucket, etc.). Invite teammates, partners, and API consumers to view and collaborate on your API designs privately or publicly with the tooling you already know and use.
With Offline File Support, open an existing OpenAPI document on your local machine or start from scratch with a new API, zero coding required. You can also share your OpenAPI document where ever you want.
組織のGitプロバイダー(GitHub、GitLab、Bitbucketなど)とのGit統合を使用します。チームメイト、パートナー、API消費者を招待して、APIデザインを非公開で、または既に知っていて使用しているツールを使って公開して、APIデザインを表示して共同作業を行うことができます。
オフラインファイルサポートでは、ローカルマシン上で既存のOpenAPIドキュメントを開くか、新しいAPIでゼロから始めることができ、コーディングは必要ありません。また、お好きな場所で OpenAPI ドキュメントを共有することもできます。
Instantly prototype and collaborate on your API design with our integrated, instant Mock Servers, powered by Prism. A mock API simulates the behavior of a real API allowing collaboration and feedback on your API design. Automatically update to match your API design. Mock a single API or multiple APIs at once.
Increase efficiency by allowing your frontend teams to start implementation while the backend team develops the API.
Prismを搭載した統合されたインスタントモックサーバーを使用して、API設計のプロトタイプを即座に作成し、共同作業を行うことができます。モックAPIは、実際のAPIの動作をシミュレートし、API設計のコラボレーションとフィードバックを可能にします。APIデザインに合わせて自動的に更新されます。1つのAPIまたは複数のAPIを一度にモックします。
バックエンドチームがAPIを開発している間にフロントエンドチームが実装を開始できるようにすることで、効率を向上させます。
Prism, an Open-Source HTTP Mock & Proxy Server
Accelerate API development with realistic mock servers, powered by OpenAPI documents.
オープンソースのHTTPモック&プロキシサーバPrism
OpenAPIドキュメントを利用したリアルなモックサーバーでAPI開発を加速します。
Create API documentation in minutes. Combine OpenAPI documents with Markdown to create robust, searchable, internal and external API documentation for your organization.
数分でAPIドキュメントを作成します。OpenAPIドキュメントとMarkdownを組み合わせて、組織のための堅牢で検索可能な内部および外部APIドキュメントを作成します。
Ask 100 API designers what makes a good API design and you’ll get 101 answers, but all most developers really want is consistency. Using an API style guide, can reduce decision making and improve consistency for all your teams.
With built-in linting and API style guides, powered by Spectral, Stoplight Studio can improve the quality of your API. You can use the default style guide, extend it, or write one to match your organization’s style guide (coming soon in Studio).
100人のAPIデザイナーに何が良いAPIデザインになるのかを聞いてみると、101の答えが返ってきます。APIスタイルガイドを使用することで、意思決定を減らし、すべてのチームの一貫性を向上させることができます。
Spectralが提供する組み込みのリンティングとAPIスタイルガイドにより、Stoplight StudioはAPIの品質を向上させることができます。デフォルトのスタイルガイドを使用することも、拡張することも、組織のスタイルガイドに合わせて記述することもできます(Studioでは近日公開予定)。
Spectral, an Open Source JSON/YAML Linter
Improve the quality of your API descriptions, Kubernetes config, GitHub Actions, or any other JSON/YAML data.
Spectral, オープンソースの JSON/YAML リンター
API の説明、Kubernetes の設定、GitHub のアクション、その他の JSON/YAML データの品質を向上させます。
Web版、Macアプリ、Windowsアプリ、Linuxアプリ が提供されています。
私は Windows 使いなので、Windowsアプリ版をダウンロード・インストールします。
ドキュメントのGetting Startedに従って進めてみます。
Open Git Project に提供されているサンプルプロジェクトのGitHubリポジトリURL https://github.com/stoplightio/studio-demo を指定して、プロジェクトを作成します。
API作成モーダルが開くので、
を入力して Create をクリック。
API記述ドキュメントを作成できました。
エンドポイントは、個別のパスと操作です。
エンドポイント作成モーダルが開くので、
を入力して Create をクリック。
エンドポイントを作成できました。
モデルではAPIで使用されるデータ構造を定義できます。
モデル作成モーダルが開くので
を入力して Create をクリック。
モデルを作成できました。
フォーマットやEnumなど追加のプロパティも設定できます。
GET /addresses
の 200 応答として Address
モデルの配列を返す定義を作ってみます。
応答の Schema で
$ref
#/definitions/Address
を選びます。
応答は定義内容から自動生成してくれてますね。
右下にLinting結果が表示されています。
クリックすると警告内容が表示されました。
クリックすると該当箇所にジャンプします。 今回はレスポンスを1つも定義していない操作があることについての警告なので、該当する操作が表示されました。
この画面でレスポンスを定義すると警告が消えました。
VScodeの拡張でもOpenAPI記述ドキュメント用のフォームを表示できるものはあるが、やはり専用の統合開発環境だけあって、使い勝手がよいように感じました。
GUIで指示に従って操作するだけで形になるので、仕様に詳しい人がいないチームで「なんもわからん」という状態でも始めやすいかなと思いました。
しばらく使ってみて、各機能の使い方などまたまとめていきたいと思います。
Amazon SSM を利用することで、Lambda を使って、EC2インスタンス内で任意のコマンドを実行することができます。
これを CloudWatch Events と組み合わせると、従来CRONによって行っていたような定期実行タスクを、特定のEC2インスタンスをSPOFにすることなく実装することができ、オートスケーリングを有効にしたEC2クラスタ環境などで便利です。
対象のEC2インスタンスには事前にSSMエージェントをインストールしておく必要があります。 手動や、cloud-initなどでインストールしておきます。
Parameters: Command: Type: String # 実行したい Lambda Function # 起動中のEC2インスタンスから1件を取り出してコマンドを実行する Function: Type: AWS::Lambda::Function Properties: Code: ZipFile: |+ const AWS = require('aws-sdk'); const ssm = new AWS.SSM({apiVersion: '2014-11-06'}); const ec2 = new AWS.EC2({apiVersion: '2016-11-15'}); const { Command } = process.env; const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)); const debug = (key, object) => { console.log(`DEBUG: ${key}\n`, JSON.stringify(object)); } class InstanceNotFoundError extends Error { constructor(message) { super(message); this.name = 'InstanceNotFoundError'; } } exports.handler = async (event, context) => { console.log("INFO: request Recieved.\nEvent:\n", JSON.stringify(event)); const describeInstancesParams = { Filters: [ { Name: "tag:Service", Values: [ "app" ] }, { Name: "instance-state-name", Values: [ "running" ] } ] }; debug("describeInstancesParams", describeInstancesParams); const describeInstancesResult = await ec2.describeInstances(describeInstancesParams).promise(); debug("describeInstancesResult", describeInstancesResult); const reservation = describeInstancesResult.Reservations[0]; if (!reservation) { throw new InstanceNotFoundError("App is Not Found"); } const instanceStatus = reservation.Instances[0]; const sendCommandParams = { DocumentName: 'AWS-RunShellScript', InstanceIds: [instanceStatus.InstanceId], Parameters: { commands: [Command], executionTimeout: ['3600'] }, MaxConcurrency: '1', MaxErrors: '0', TimeoutSeconds: 3600, }; debug("sendCommandParams", sendCommandParams); const sendCommandResult = await ssm.sendCommand(sendCommandParams).promise(); debug("sendCommandResult", sendCommandResult); const results = { ec2InstanceId: instanceStatus.InstanceId, sendCommandParams: sendCommandParams, sendCommandResult: sendCommandResult }; debug("results", results); return results; }; Environment: Variables: Command: !Ref Command Handler: index.handler Role: !GetAtt LambdaExecutionRole.Arn Runtime: "nodejs10.x" MemorySize: 128 Timeout: 60 # Lambda の実行ロール # 標準の AWSLambdaBasicExecutionRole サービスロールポリシーに加えて、 # SSMコマンド実行用の ssm:SendCommand # EC2インスタンス一覧取得用の ec2:describeInstances # をインラインポリシーに追加 LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - "sts:AssumeRole" Path: / ManagedPolicyArns: - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: run-command PolicyDocument: Statement: - Effect: Allow Action: - ssm:SendCommand - ec2:describeInstances Resource: "*"