CloudWatchのログがS3にエクスポートされない

LambdaやBatchでCloudWatchに出力されたログのエクスポートを試みたところ一部のログがエクスポートされない状況にハマりました。

原因はログがエクスポートできる状態になるまでに最大12時間かかる事が原因でした。

参考

次の日にエクスポートしたら正常にエクスポートされました。

結構ハマった。

続きを読む

Serverless FrameworkでEC2をスケジュール起動/停止するテンプレート(Lambda[java])

Serverless Framework

はじめに

  • コンテナ付いてる昨今は、久しくServerless Framework触って無かったのですが、EC2を8時に起動して、20時半に停止する要件が浮上したので、サクッとslsで作りました。
  • ソースはGithubで公開してます。
  • 至極簡単な内容なので、速攻実現出来ると思ってます。

環境のセットアップ

Serverless FrameworkでDeploy

ソースの取得

  • 以下のGithubからソースを取得します。
$ git clone https://github.com/ukitiyan/operation-ec2-instance.git

STS(Eclipse)にインポート

  • STSを起動して、Project Explorer -> 右クリック -> Maven -> Existing Maven Projectsで先程Githubから取得した「operation-ec2-instance」フォルダを選択します。

serverless.yml の修正 + Build

  • serverless.ymlのL37 周辺の設定を適宜修正します。

    • rate: AWS Lambda – Scheduled EventのCron書式
      を参考に UTC で記載
    • instanceId: 対象インスタンスのinstanceIdを記載
    • scheduleは、縦に増やせるので複数インスタンスに対応できます。(それを踏まえて環境変数でinstanceIdを指定してません)
serverless.yml
- schedule:
    rate: cron(30 11 * * ? *)
    input:
      goal: stop
      instanceId: i-XXXXXXXXXXXXXXXXX
- schedule:
    rate: cron(0 23 * * ? *)
    input:
      goal: start
      instanceId: i-XXXXXXXXXXXXXXXXX
  • プロジェクトを右クリック -> Run As -> Maven Install でビルドします。
  • target配下にoperation-ec2-instance.1.0.0.jarが出来上がります。

Deploy

  • 例のごとく、以下のコマンド一発です。
$ cd operation-ec2-instance
$ serverless deploy -v
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
・
・
Serverless: Stack update finished...
Service Information
service: operation-ec2-instance
stage: prod
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  aws-java-maven-prod-hello: arn:XXXXXXXX
  • 以下のJsonでコンソールから test するか、設定時間になるまで待って、問題ないか気にしておきましょう。
{
  "goal": "stop",
  "instanceId": "i-XXXXXXXXXXXXXXXXX"
}

まとめ

  • まぁ、やっつけですが。速攻実現出来ました。
  • STSで完結するのが、アプリ屋にとっては本当うれしいです。

続きを読む

Lambdaの実行環境にフォントを追加する

はじめに

AWS LambdaでPhantomJS日本語フォント対応では fontconfig をビルドしてデプロイパッケージに含めているが、 Lambdaの実行環境を確認したところ、fontconfig は導入されている。
したがって、フォントキャッシュさえ生成すれば、実行環境のfontconfigが利用できる。

なおLambdaの実行環境のfontconfigはXDGには対応していない。
検証時、fc-cache のバージョンは 2.8.0 であった。

Lambda の実行時 LAMBDA_TASK_ROOT=/var/task/share/fonts となっている。

fontconfig は

~/.fonts.conf
~/.fonts
~/.fontconfig

は見るので、HOME=$LAMBDA_TASK_ROOT に設定して、これらが

$HOME/
        .fontconfig
        .fonts
        .fonts.conf

というツリー構造で見えるようにデプロイパッケージに含めてやれば良い。

フォントキャッシュの生成

NotoSansCJK のパッケージから NotoSansCJK-Regular.ttc を抽出して、.fonts に配置する。

Lambda の実行時に $LAMBDA_TASK_ROOT/.fontconfig は書き込みできないため、そのままでは fc-cache の実行でキャッシュファイルの作成に失敗する。

いったん /tmp/cache/fontconfig にキャッシュを生成して、デプロイパッケージでは .fontconfig に配置する。

このため、次の内容で .fonts.conf を作成する。

fonts.conf
<fontconfig>
    <cachedir>/tmp/cache/fontconfig</cachedir>
</fontconfig>

フォントキャッシュを生成する Lambda 関数は次の通り。

fontcache.py
from __future__ import print_function
import subprocess

import sys
import os
import base64
import boto3
from os.path import join

logger = logging.getLogger()  
logger.setLevel(logging.INFO)  

BUCKET_NAME = 'バケット名'

def lambda_handler(event, context):
    os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
    try:
        os.makedirs('/tmp/cache/fontconfig')
    except OSError as e:
        (errno, strerror) = e
        print('OSError {}'.format(strerror))

    args = [ 'fc-cache', '-v', join(os.environ['HOME'], '.fonts') ]
    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    returncode = p.returncode
    stdout_data, stderr_data = p.communicate()

    print('stdout_data:n' + stdout_data)
    print('stderr_data:n' + stderr_data)
    s3bucket = boto3.resource('s3').Bucket(BUCKET_NAME)
    tmp_fontconfig = '/tmp/cache/fontconfig'
    for cache in os.listdir(tmp_fontconfig):
        response = s3bucket.upload_file(join(tmp_fontconfig, cache), cache)
        with open(join(tmp_fontconfig, cache), mode='rb') as f:
            print("{}:n{}n".format(cache, base64.b64encode(f.read())))

デプロイパッケージの内容

% unzip -l fontcache.zip 
Archive:  fontcache.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
     1116  10-16-17 15:34   fontcache.py
      105  10-12-17 20:54   .fonts.conf
        0  10-12-17 22:01   .fonts/
 18748872  10-12-17 17:04   .fonts/NotoSansCJK-Regular.ttc
 --------                   -------
 18750093                   4 files

この Lambda 関数を実行すると、S3 にキャッシュファイルがアップロードされる。

テスト

先ほど生成したキャッシュファイルを .fontconfig に配置する。

phantomjs のロードモジュールと rasterize.jsbin ディレクトリに配置する。

phantomjs でのスクリーンキャプチャーは、Screen Capture | PhantomJSを参考に、 rasterize.js 使用してテストプログラムを作成した。

phantomjs.py
from __future__ import print_function
import subprocess
import tempfile
import os
import boto3

def phantomjs(url, bucket, name):
    from os.path import join
    run_dir = os.environ['LAMBDA_TASK_ROOT']
    bin_dir = join(run_dir, 'bin')
    args = [ join(bin_dir, 'phantomjs'), join(bin_dir, 'rasterize.js'), url ]

    with tempfile.NamedTemporaryFile(suffix='.png') as f:
        args.append(f.name)
        print('{}n'.format(' '.join(args)))
        p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        returncode = p.returncode
        stdout_data, stderr_data = p.communicate()

        s3 = boto3.resource('s3')
        bucket = s3.Bucket(bucket)
        response = bucket.upload_file(f.name, name)

    print('returncode {}n'.format(returncode))
    print('stdout:: n{}n'.format(stdout_data.decode('utf-8')))
    print('stderr:: n{}n'.format(stderr_data.decode('utf-8')))
    print('stderr:: n{}n'.format(stderr_data.decode('utf-8')))
    print('response {}n'.format(response))

def lambda_handler(event, context):
    os.environ['HOME'] = os.environ['LAMBDA_TASK_ROOT']
    os.chdir(os.environ['LAMBDA_TASK_ROOT'])
    phantomjs('https://www.amazon.co.jp', 'バケット名', 'screenshot.png')

デプロイパッケージの内容

% unzip -l phantomjs.zip 
Archive:  phantomjs.zip
  Length     Date   Time    Name
 --------    ----   ----    ----
     1304  10-13-17 17:09   phantomjs.py
      105  10-12-17 20:54   .fonts.conf
        0  10-12-17 22:01   .fonts/
 18748872  10-12-17 17:04   .fonts/NotoSansCJK-Regular.ttc
        0  10-12-17 23:05   .fontconfig/
    12792  10-12-17 22:27   .fontconfig/996789b4ba9d471a5fd80c008c2b2acf-le64.cache-3
 67932064  01-25-16 10:01   bin/phantomjs
     2241  10-12-17 23:19   bin/rasterize.js
 --------                   -------
 86697378                   8 files

この Lambda 関数を実行して、うまく画面がキャプチャーされていればよい。

phantomjs の実行のために、Lambda が使用するメモリを192MB以上にしておかないと、メモリ不足で処理が途中で打ち切られる。

続きを読む

DynamoDBの予約語一覧を無理やり動的に取得してみた

DynamoDBの予約語を対処するために一覧が欲しくなったのですが、いちいちプログラム内で定義したくなかったので、AWSの予約語一覧ページから一覧を取得するサービスを作りました。

前提

  1. AWSが公開しているDynamoDB予約語一覧のWebページ( http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html )をスクレイピングして予約語一覧を抽出します。
  2. 実装はAWS Lambda (Python3)、Webサービスとして動作させるためにAPI Gatewayを利用します。

結果

https://github.com/kojiisd/dynamodb-reserved-words

DynamoDB予約語一覧ページの確認

今回は以下のページをParseしたいと思います。

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html

HTMLを見てみると、Parseしたい箇所はcodeタグで囲まれているようで、しかもこのページ内でcodeタグが出現するのは1度だけのようです。

スクリーンショット 2017-10-15 18.03.18.png

これであればすぐにパースできそうです。

HTMLの取得とParse

BeautifulSoupを利用すれば、簡単に実装ができます。BeautifulSoupのインストールは他のページでいくらでも紹介されているので、ここでは省略します。

import sys
import os


sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib'))
from bs4 import BeautifulSoup
import requests

URL = os.environ['TARGET_URL']

def run(event, context):

    response = requests.get(URL)
    soup = BeautifulSoup(response.content, "html.parser")

    result_tmp = soup.find("code")
    if result_tmp == None:
        return "No parsing target"

    result = result_tmp.string

    return result.split('n');

とりあえず申し訳程度にパースできない場合(AWSのページがcodeタグを使わなくなった時)を想定してハンドリングしています。

API Gatewayを定義してサービス化

コードが書けてしまえばAPI Gatewayを定義してサービス化するだけです。

設定は非常に単純なので割愛で。

スクリーンショット 2017-10-15 18.12.11_deco.png

アクセスしてみる

実際にアクセスしてみます。

$ curl -X GET https://xxxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod
["", "ABORT", "ABSOLUTE", "ACTION", "ADD", "AFTER", "AGENT", "AGGREGATE", "ALL", "ALLOCATE", "ALTER", "ANALYZE", "AND", "ANY", "ARCHIVE", "ARE", "ARRAY", "AS", "ASC", "ASCII", "ASENSITIVE", "ASSERTION", "ASYMMETRIC", "AT", "ATOMIC", "ATTACH", "ATTRIBUTE", "AUTH", "AUTHORIZATION", "AUTHORIZE", "AUTO", "AVG", "BACK", "BACKUP", "BASE", "BATCH", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BIT", "BLOB", "BLOCK", "BOOLEAN", "BOTH", "BREADTH", "BUCKET", "BULK", "BY", "BYTE", "CALL", "CALLED", "CALLING", "CAPACITY", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHECK", "CLASS", "CLOB", "CLOSE", "CLUSTER", "CLUSTERED", "CLUSTERING", "CLUSTERS", "COALESCE", "COLLATE", "COLLATION", "COLLECTION", "COLUMN", "COLUMNS", "COMBINE", "COMMENT", "COMMIT", "COMPACT", "COMPILE", "COMPRESS", "CONDITION", "CONFLICT", "CONNECT", "CONNECTION", "CONSISTENCY", "CONSISTENT", "CONSTRAINT", "CONSTRAINTS", "CONSTRUCTOR", "CONSUMED", "CONTINUE", "CONVERT", "COPY", "CORRESPONDING", "COUNT", "COUNTER", "CREATE", "CROSS", "CUBE", "CURRENT", "CURSOR", "CYCLE", "DATA", "DATABASE", "DATE", "DATETIME", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DEFINE", "DEFINED", "DEFINITION", "DELETE", "DELIMITED", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DETACH", "DETERMINISTIC", "DIAGNOSTICS", "DIRECTORIES", "DISABLE", "DISCONNECT", "DISTINCT", "DISTRIBUTE", "DO", "DOMAIN", "DOUBLE", "DROP", "DUMP", "DURATION", "DYNAMIC", "EACH", "ELEMENT", "ELSE", "ELSEIF", "EMPTY", "ENABLE", "END", "EQUAL", "EQUALS", "ERROR", "ESCAPE", "ESCAPED", "EVAL", "EVALUATE", "EXCEEDED", "EXCEPT", "EXCEPTION", "EXCEPTIONS", "EXCLUSIVE", "EXEC", "EXECUTE", "EXISTS", "EXIT", "EXPLAIN", "EXPLODE", "EXPORT", "EXPRESSION", "EXTENDED", "EXTERNAL", "EXTRACT", "FAIL", "FALSE", "FAMILY", "FETCH", "FIELDS", "FILE", "FILTER", "FILTERING", "FINAL", "FINISH", "FIRST", "FIXED", "FLATTERN", "FLOAT", "FOR", "FORCE", "FOREIGN", "FORMAT", "FORWARD", "FOUND", "FREE", "FROM", "FULL", "FUNCTION", "FUNCTIONS", "GENERAL", "GENERATE", "GET", "GLOB", "GLOBAL", "GO", "GOTO", "GRANT", "GREATER", "GROUP", "GROUPING", "HANDLER", "HASH", "HAVE", "HAVING", "HEAP", "HIDDEN", "HOLD", "HOUR", "IDENTIFIED", "IDENTITY", "IF", "IGNORE", "IMMEDIATE", "IMPORT", "IN", "INCLUDING", "INCLUSIVE", "INCREMENT", "INCREMENTAL", "INDEX", "INDEXED", "INDEXES", "INDICATOR", "INFINITE", "INITIALLY", "INLINE", "INNER", "INNTER", "INOUT", "INPUT", "INSENSITIVE", "INSERT", "INSTEAD", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "INVALIDATE", "IS", "ISOLATION", "ITEM", "ITEMS", "ITERATE", "JOIN", "KEY", "KEYS", "LAG", "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEAD", "LEADING", "LEAVE", "LEFT", "LENGTH", "LESS", "LEVEL", "LIKE", "LIMIT", "LIMITED", "LINES", "LIST", "LOAD", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOCATION", "LOCATOR", "LOCK", "LOCKS", "LOG", "LOGED", "LONG", "LOOP", "LOWER", "MAP", "MATCH", "MATERIALIZED", "MAX", "MAXLEN", "MEMBER", "MERGE", "METHOD", "METRICS", "MIN", "MINUS", "MINUTE", "MISSING", "MOD", "MODE", "MODIFIES", "MODIFY", "MODULE", "MONTH", "MULTI", "MULTISET", "NAME", "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NULLIF", "NUMBER", "NUMERIC", "OBJECT", "OF", "OFFLINE", "OFFSET", "OLD", "ON", "ONLINE", "ONLY", "OPAQUE", "OPEN", "OPERATOR", "OPTION", "OR", "ORDER", "ORDINALITY", "OTHER", "OTHERS", "OUT", "OUTER", "OUTPUT", "OVER", "OVERLAPS", "OVERRIDE", "OWNER", "PAD", "PARALLEL", "PARAMETER", "PARAMETERS", "PARTIAL", "PARTITION", "PARTITIONED", "PARTITIONS", "PATH", "PERCENT", "PERCENTILE", "PERMISSION", "PERMISSIONS", "PIPE", "PIPELINED", "PLAN", "POOL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVATE", "PRIVILEGES", "PROCEDURE", "PROCESSED", "PROJECT", "PROJECTION", "PROPERTY", "PROVISIONING", "PUBLIC", "PUT", "QUERY", "QUIT", "QUORUM", "RAISE", "RANDOM", "RANGE", "RANK", "RAW", "READ", "READS", "REAL", "REBUILD", "RECORD", "RECURSIVE", "REDUCE", "REF", "REFERENCE", "REFERENCES", "REFERENCING", "REGEXP", "REGION", "REINDEX", "RELATIVE", "RELEASE", "REMAINDER", "RENAME", "REPEAT", "REPLACE", "REQUEST", "RESET", "RESIGNAL", "RESOURCE", "RESPONSE", "RESTORE", "RESTRICT", "RESULT", "RETURN", "RETURNING", "RETURNS", "REVERSE", "REVOKE", "RIGHT", "ROLE", "ROLES", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", "RULE", "RULES", "SAMPLE", "SATISFIES", "SAVE", "SAVEPOINT", "SCAN", "SCHEMA", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SECTION", "SEGMENT", "SEGMENTS", "SELECT", "SELF", "SEMI", "SENSITIVE", "SEPARATE", "SEQUENCE", "SERIALIZABLE", "SESSION", "SET", "SETS", "SHARD", "SHARE", "SHARED", "SHORT", "SHOW", "SIGNAL", "SIMILAR", "SIZE", "SKEWED", "SMALLINT", "SNAPSHOT", "SOME", "SOURCE", "SPACE", "SPACES", "SPARSE", "SPECIFIC", "SPECIFICTYPE", "SPLIT", "SQL", "SQLCODE", "SQLERROR", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATIC", "STATUS", "STORAGE", "STORE", "STORED", "STREAM", "STRING", "STRUCT", "STYLE", "SUB", "SUBMULTISET", "SUBPARTITION", "SUBSTRING", "SUBTYPE", "SUM", "SUPER", "SYMMETRIC", "SYNONYM", "SYSTEM", "TABLE", "TABLESAMPLE", "TEMP", "TEMPORARY", "TERMINATED", "TEXT", "THAN", "THEN", "THROUGHPUT", "TIME", "TIMESTAMP", "TIMEZONE", "TINYINT", "TO", "TOKEN", "TOTAL", "TOUCH", "TRAILING", "TRANSACTION", "TRANSFORM", "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", "TRUNCATE", "TTL", "TUPLE", "TYPE", "UNDER", "UNDO", "UNION", "UNIQUE", "UNIT", "UNKNOWN", "UNLOGGED", "UNNEST", "UNPROCESSED", "UNSIGNED", "UNTIL", "UPDATE", "UPPER", "URL", "USAGE", "USE", "USER", "USERS", "USING", "UUID", "VACUUM", "VALUE", "VALUED", "VALUES", "VARCHAR", "VARIABLE", "VARIANCE", "VARINT", "VARYING", "VIEW", "VIEWS", "VIRTUAL", "VOID", "WAIT", "WHEN", "WHENEVER", "WHERE", "WHILE", "WINDOW", "WITH", "WITHIN", "WITHOUT", "WORK", "WRAPPED", "WRITE", "YEAR", "ZONE "]

うまくできました。

まとめ

思いつきで作成してみましたが、AWSのページが閉鎖されたりHTML構造が変更されない限り、仕様変更などは気にせずに使えそうです。

あとBeautifulSoup初めて使いましたが、かなり便利。

続きを読む

AWSのIAMロール・ポリシーをソースコード管理するのに役立つツールライブラリ

AWS の IAM ロールとポリシーをどうやって管理していますか?

JSON ファイルでロールとポリシー定義を管理できるのに便利なツール(npmライブラリ)を作りました。
github.com/tilfin/aws-iam-policy-tool

できること

  • ローカルの JSON ファイルと IAM のロールとポリシー定義の相互インポート・エクスポート
  • ローカルの定義ファイルに沿って IAM ロールとポリシーが構成されているかの検証
  • 正規表現マッチするカスタムポリシーの複数削除(AWS Management Console では一個ずつしか消せない)

できないこと

  • ロールのインラインポリシーはサポートしていない

どのように使えるのか

  • IAM ロールとポリシー定義を JSON ファイルでソースコード同様にGit等のリポジトリで管理できる。
  • npm ライブラリなので Lambda から定期的にデプロイ環境のロールとポリシーが正しいかの検証ができる。

定義ファイルの仕組み

  • IAM のブラウザ上で編集する JSON とほぼ同じです。
  • デプロイ環境別にリソース名に prefix/suffix を付けている場合は ENV としておくことで、コマンド実行時に引数で指定します。
  • AWS アカウント ID も ACCOUNT_ID でコマンド実行時に引数指定することで変えることができます。

ロール

{
    "Role": {
        "RoleName": "yourapp-ec2-api-ENV",
        "Path": "/",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    },
    "AttachedPolicies": [
        {
            "PolicyName": "yourapp-s3-storage-ENV",
            "PolicyArn": "arn:aws:iam::ACCOUNT_ID:policy/yourapp-s3-storage-ENV"
        }
    ]
}

ポリシー

ポリシー名は内容に含まれないため JSON ファイル名そのものが適用されます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::yourapp-storage-ENV/*"
        }
    ]
}

導入と使い方

npm からインストールします。

$ npm install -g aws-iam-policy-tool

現状のロールを ./roles とポリシーを ./policies にそれぞれ書き出します。

$ awsiamtool export-role ./roles
$ awsiamtool export-policy ./policies

各ファイルの ARN 内の AWS アカウント ID の部分を ACCOUNT_ID に置換します。動作環境を示すもの (例. dev, staging など) が名前に含まれていれば、ENV に置換します。ポリシーの場合はファイル名もリネームします。

検証してみます。

$ awsiamtool validate-role -i 111122223333 -e dev roles
$ awsiamtool validate-policy -i 111122223333  -e dev policies

本番環境に定義を作成します。

$ AWS_PROFILE=prod awsiamtool import-policy -i 111122224444 -e prd policies
$ AWS_PROFILE=prod awsiamtool import-role -i 111122224444 -e prd roles

※ポリシー修正時 import-policy に上書きオプションを --overwrite を指定します。

ライブラリとして使う(別の変数を適用するには)

現状コマンド引数ではサポートしていませんがライブラリとして使えば可能です。

const awsIamPolicyLib = require('aws-iam-policy-tool');

const opts = {
  json: true,
  overwrite: true
};

const varSets = {
  ACCOUNT_ID: '000011112222',
  ENV: 'stg',
  COMMON_ACCOUNT_ID: '333344445555'
};

awsIamPolicyLib.importPolicy('./policies', varSets, opts);
.then(() => {
  return awsIamPolicyLib.importRole('./roles', varSets, opts);  
})
.then(() => { console.info('Importing done') })
.catch(err => { console.error(err) });

その他、コマンドの実行結果例などは README を見てください。

続きを読む