RddshiftからS3へUNLOADする

S3へ書き出したDynamoDBデータをRedshiftへコピーする [Copyコマンドについて]の逆をする。

UNLOADコマンドを使用する際の個人的な確認用まとめ

構文(抜粋)

UNLOAD ('select-statement')
TO 's3://object-path/name-prefix'
authorization
[ option [ ... ] ]

where option is

{ MANIFEST
| DELIMITER [ AS ] 'delimiter-char' 
| FIXEDWIDTH [ AS ] 'fixedwidth-spec' }  
| ENCRYPTED
| BZIP2  
| GZIP     
| ADDQUOTES 
| NULL [ AS ] 'null-string'
| ESCAPE
| ALLOWOVERWRITE
| PARALLEL [ { ON | TRUE } | { OFF | FALSE } ]
[ MAXFILESIZE [AS] max-size [ MB | GB ] ]

相変わらずわかりにくい構文っすよね。。。

簡単なサンプル

sample
UNLOAD ('SELECT * FROM test;')
TO 's3://mybucket_name/target_dir/'
CREDENTIALS 'aws_access_key_id=<キー>;aws_secret_access_key=<シークレットキー>'
MANIFEST
DELIMITER AS 't'
GZIP
ALLOWOVERWRITE

UNLOAD (‘select-statement’)

SQL文を書く。途中で '' を使用したい場合はちゃんとエスケープすること ''

TO ‘s3://object-path/name-prefix’

出力先のS3を指定する。
object-path 配下に name-prefixXXXXXXXXXXXX で作成される。

CREDENTIALS

COPYにも書いたけどIAMなりクレデンシャルキーなり指定する

オプション指定

一覧
MANIFEST
DELIMITER [ AS ] 'delimiter-char' 
FIXEDWIDTH [ AS ] 'fixedwidth-spec' }  
ENCRYPTED
BZIP2  
GZIP     
ADDQUOTES 
NULL [ AS ] 'null-string'
ESCAPE
ALLOWOVERWRITE
PARALLEL [ { ON | TRUE } | { OFF | FALSE } ]
MAXFILESIZE [AS] max-size [ MB | GB ] ]

幾つかかいつまんで

MANIFEST

UNLOAD プロセスによって作成されたデータファイルを明示的にリストするマニフェストファイルを作成します。

DELIMITER

パイプ文字 (|)、カンマ (,)、タブ (t) など、出力ファイル内のフィールドを分離する単一の ASCII 文字。デフォルトの区切り文字はパイプ文字です。

BZIP2/GZIP

出力時に圧縮して出力する

ADDQUOTES

アンロードされた各データフィールドは引用符で囲まれるため、Amazon Redshift は区切り文字自体を含んでいるデータ値をアンロードすることができます。

出力データに区切り文字が含まれている場合、カラム毎に ”” で囲んで出力してくれる機能
読み込み時の使い方に寄る。

NULL AS ‘null-string’

NULL文字の置き換え

ALLOWOVERWRITE

デフォルトでは、UNLOAD によってファイルの上書きが発生する可能性がある場合、その UNLOAD 操作は失敗します。ALLOWOVERWRITE が指定された場合、UNLOAD によって、マニフェストファイルを含めた既存のファイルが上書きされます。

ファイルが存在すると上書き禁止でエラー終了する。
これを指定すると上書きする。

MAXFILESIZE AS 最大サイズ [ MB | GB ]

Amazon S3 で作成された UNLOAD ファイルの最大サイズ。5 MB ~ 6.2 GB の十進値を指定します。AS キーワードはオプションです。デフォルト単位は MB です。MAXFILESIZE を指定しない場合、デフォルトの最大ファイルサイズは 6.2 GB です。

マニフェストファイルが使用されている場合、このサイズは MAXFILESIZE に影響されません。

LIMIT

SELECT クエリは、外部の SELECT で LIMIT 句を使用することはできません。その代わり、ネスティングされた LIMIT 句を使用してください。

sample
SELECT 
 * 
FROM 
 test 
WHERE id IN (
  SELECT 
   id
  FROM 
   test
  LIMIT 100
);

こんな感じにしろとのこと。

参考

UNLOAD

続きを読む

S3へ書き出したDynamoDBデータをRedshiftへコピーする [Copyコマンドについて]

S3へ書き出したDynamoDBのデータをRedshifへ取り込む際、指定方法がいろいろあったりして自分でも忘れやすいのでメモ

ここで打つコマンドはRedshfitコマンドであり、psqlコマンドとは異なる。

構文(抜粋)

COPY table-name 
[ column-list ]
FROM data_source
authorization
[ [ FORMAT ] [ AS ] data_format ] 
[ parameter [ argument ] [, ... ] ]

table-name

テーブルはすでにデータベースに存在する必要があります。テーブルは一時テーブルまたは永続的テーブルです。COPY コマンドは、新しい入力データをテーブルの既存の行に追加します。

FROM data_source

ターゲットテーブルにロードするソースデータの場所

  • S3

    • FROM ‘s3://mybucket_name/target_dir/’
  • DynamoDB

    • FROM ‘dynamodb://table-name’
  • EMR

    • FROM ‘emr://emr_cluster_id/hdfs_filepath’

CREDENTIALの指定

S3やDynamoDBから引っ張ってくるときはクレデンシャルの指定が必要

  • AWS_CLIのキーを使用: 'aws_access_key_id=<キー>;aws_secret_access_key=<シークレットキー>'
  • IAM_Roleを使用: 'arn:aws:iam::<aws-account-id>:role/<role-name>'

オプション

列のマッピングオプション

  • 列リスト

    • COPY tablenem の後に、列をリストで列挙する方法

ソースデータフィールドを特定のターゲット列にロードするには、列名のカンマ区切りリストを指定します。COPY ステートメントで列は任意の順序に指定できますが、Amazon S3 バケットなどにあるフラットファイルからロードする場合、ソースデータの順序に一致する必要があります。Amazon DynamoDB テーブルからロードする場合、順序は関係ありません

COPY sample (id, name, age)
  • JSONPaths ファイル

    • ソースデータの列情報をJSONファイルで記述し、S3に置いておく方法

データファイルを JSON または Avro 形式でロードする場合、COPY は Avro スキーマのフィールド名をターゲットテーブルまたは列リストの列名と一致させることで、JSON または Avro ソースデータのデータ要素をターゲットテーブルに自動的にマッピングします。

基本的にこっちを使っている。

JSONPath式

jsonpaths.json
{
    "jsonpaths": [ // 個々の記述は固定
        "$['venuename']", // 各列情報を"$['列名']で記載
        "$['venuecity']",
        "$['venuestate']",
        "$['venueseats']"
    ]
}

S3に書き出したDynamoDBの場合、 "$['カラム名']['型']", の形式で記載する。
項目の個数はCOPY先のテーブルに合わせなければならない

余談:
jsonpaths.jsonで指定したカラム数が7でCOPY先のテーブルのカラム数が8といったカラム数に差がある場合、下記のようなエラーがでる。

err
 error:  Number of jsonpaths and the number of columns should match. JSONPath size: 7, Number of columns in table or column list: 8

DynamoDBには確実に存在しないカラムであっても、Redshift側のテーブルにカラムが存在する場合、jsonpaths.jsonにそのカラムを指定し、COPY先のテーブルとカラム設定をあわせておけば
COPYを実行することが出来る。その際、DynamoDBに存在しないカラムのデータについては NULL で取り込む。指定した値で取り込むといった事がオプション指定で可能である。

取り込むテーブルとJSONのカラム数合わせろ、そこだけ確認しろ、自分。

データ形式パラメータ

  • FORMAT AS json

    • jsonpathsファイルを指定する方法

      • FORMAT AS json ‘s3://mybucket_name/target_dir/jsonpaths.json’
  • DELIMITER [AS] [‘delimiter_char’]

    • csv,tsv形式等を任意の区切り文字を指定して取り込む

      • DELIMITER AS ‘\t’
  • FIXEDWIDTH ‘fixedwidth_spec’

    • 固定長の場合に使用(ワタシは使用したこと無い。)

      • FIXEDWIDTH ‘colLabel1:colWidth1,colLabel:colWidth2, …’
  • BZIP2/GZIP/LZOP

    • 取り込むデータがどんな圧縮をされているかを指定する。gzip圧縮されていてもそのまま取り込むことが出来る。

データ変換パラメータ

テーブルをロードする際に、COPY は暗黙的にソースデータの文字列をターゲット列のデータ型に変換しようとします。デフォルトの動作とは異なる変換を指定する必要がある場合、またはデフォルトの変換がエラーになった場合、次のパラメータを指定してデータ変換を管理できます。

ここは使ったことがあるのと無いのがある。

一覧
ACCEPTANYDATE
ACCEPTINVCHARS
BLANKSASNULL
DATEFORMAT
EMPTYASNULL
ENCODING
ESCAPE
EXPLICIT_IDS
FILLRECORD
IGNOREBLANKLINES
IGNOREHEADER
NULL AS
REMOVEQUOTES
ROUNDEC
TIMEFORMAT
TRIMBLANKS
TRUNCATECOLUMNS

幾つか使うやつの抜粋

  • BLANKSASNUL

NULL など、空白文字のみから構成される空のフィールドをロードします。このオプションは CHAR と VARCHAR の列にのみ適用されます。INT など、他のデータ型の空のフィールドは常に NULL でロードされます。

  • EMPTYASNULL

Amazon Redshift で CHAR と VARCHAR の空のフィールドを NULL としてロードすることを指定します。INT など、他のデータ型の空のフィールドは常に NULL でロードされます。

  • IGNOREHEADER [ AS ] number_rows

指定された number_rows をファイルヘッダーとして扱い、ロードされせん。

  • NULL AS ‘null_string’

null_string に一致するフィールドを NULL としてロードします。

マニュフェストファイル

Datapipeline等を使用する場合、マニュフェストファイルが生成されます。
それを使用して取り込む場合に指定する。

  • manifest

manifest を指定する場合は、FORMAT でマニュフェストファイルを指定する。

サンプル

S3からCOPY

sample
COPY table
FROM 's3://mybucket_name/target_dir/' 
CREDENTIALS 'aws_access_key_id=<キー>;aws_secret_access_key=<シークレットキー>'
FORMAT AS json 's3://mybucket_name/target_dir/jsonpaths.json'
GZIP;

DynamoDBからCOPY

sample
COPY table 
FROM 'dynamodb://table_name' 
CREDENTIALS 'aws_access_key_id=<キー>;aws_secret_access_key=<シークレットキー>'
readratio 0.7;

readratio は確保されたキャパシティに対して使用する割合

マニュフェストファイルを使用した取り込み

COPY table
FROM 's3://mybucket_name/target_dir/manifest'
CREDENTIALS 'aws_access_key_id=<キー>;aws_secret_access_key=<シークレットキー>'
FORMAT AS json 's3://mybucket_name/target_dir/jsonpaths.json'
manifest;

資料

COPY

続きを読む

【聴講メモ】Developers Festa Sapporo 2017

2017年11月10日(金)に開催されたDevelopers Festa Sapporo 2017を聴講してきました。

JavaOne 2017 フィードバック~今後のJava開発・運用のために知っておくべき事総まとめ~

伊藤 敬(日本オラクル株式会社)


2017/10/1~2017/10/5までサンフランシスコで開催されたJavaOne 2017の内容についてのお話し。
こういうクラスができてとかいう技術的な話しはほとんどなく、リリースサイクルとかオープン化な話が多かった。
Javaから離れて結構久しいので、何か軽量化されるぐらいしか知らなかったんですが、半年後にはJava10がリリースされるとかいう話しは結構衝撃的。

Java SE 9

▼過去最多の機能追加

20(Java7)→55(Java8)→91(Java9)
リリースが延びたのも要因の1つだそう…

▼リリースモデルの転換

従来
・OpenJDKとOracleJDKに機能差分がある
 私、これ聞くまでOpenJDK自体知りませんでした…

・機能リリースは2年に1度(ただし目標)

・それぞれの機能リリースごとの長期サポート
 Java8まではおおむね8年サポートしてるんだそう

・更新リリースは3か月ごと
 セキュリティパッチに加えて限定的機能更新も含まれていたそう

これから
・OpenJDKとOracleJDKに機能差分をなくしていく
 すぐにではなく徐々に的な雰囲気

・機能リリースは6か月に1度(固定)
 リリース日は決めて、そこに間に合った機能を載せていくイメージ
 次出るバージョンはJava10→11→12のナンバリング
 18.3→18.9→19.3と西暦年+月の組み合わせにしてたのが最近方針転換されるらしい

・長期サポートはOracleJDKのみ
 ここの話が、最近Javaから離れている自分にはよく理解できなかった。
 長期サポート(LTS:Long Time Support)は、6か月ごとの機能リリースではなく、その中の特定のリリース(おおむね3年ごと)が対象となると認識しました。
 それのスタートが2018年9月で、次の長期サポートターゲットが201年9月ということらしい。
 商用利用で頻繁にバージョンアップをしたくない企業向けなんですかね…

・更新リリースは3か月ごと
 セキュリティパッチのみで機能更新は含まない

▼転換期故の弊害

公式アップデート終了はJava10より9の方が早い
Java8:2018.9
Java9:2018.3
Java10:2018.9

今Java8を使っている場合は、次のターゲットは10にした方が良い

▼その他

Java8から後継バージョンへの自動更新は行われない

Java SE9の32bit版バイナリの提供予定はない
官公庁への影響は大きいだろうとのこと(実際説明に行って叱責を受けることが多いそう…)

jlinkを利用したパッケージングとデプロイ
 クライアントに入っているJREに依存しないアプリを作れるんだそう
 (Developers Festaなんだから、こういう話しをもっと聞きたかったですね…)

Java EE 8

▼JavaEEが抱える問題点
・軽量でない
・業界トレンドとのギャップ

▼Eclipse Foundationへの移管

Oracleはもう関わらないというわけではなく、仕様策定の主管をEclipse Foundationへということ。

Eclipseでの開発プロジェクト名はEE4J(Eclipse Enterprise for Java)

Other

▼fn.project.io
Java対応のFaaS(Function as a Service)
いわゆる、Oracleのサーバレスプラットフォームのようです

蛇足

じゃんけんでTシャツいただきましたw
Mサイズで自分には若干小さかったので妻へのプレゼントとなりました。

image.png

The State of Serverless Computing

西谷 圭介(アマゾン ウェブ サービス ジャパン株式会社)


昨年に続いての聴講。
序盤でサーバレスとは?に簡単に触れ、後はアーキテクチャパターンについての実用的なお話しでした。
西谷さんが使っていた、スライドが暗くなって、指し示した一部分だけが明るくなるアイテムが気になって途中から話半分でしたw

サーバレスとは?

コンピューティングの進化の中、未だ残る制限
物理サーバー

仮想サーバー(オンプレ)

仮想サーバー(クラウド)

と進化していく中で、サーバーの管理業務からは解放されていない

サーバーは管理しない方が簡単
ファンクションは短命
なので
・サーバレスはよりセキュア
・コストを抑えられる
 アイドル時の支払いなし
 ただし、実行回数の分は掛かる(コスト効率はいいが、何でもかんでも安くなるわけではない)

サーバレスは管理業務からの解放

アーキテクチャーパターン

・Web Application
・Backends
・Data Processing
 →一番使われるパターン(RealTime、MapReduce、Batch)
・Chatbots、Amazon Alexa
・IT Automation
 →一番ハードルが低い(CIなど)

ベストプラクティス

The Twelve-Factor App (日本語訳)

この中からいくつか出てきましたが、印象に残ったのは
・ステートレス
・開発/本番で一致した状態

この話の流れでLambdaについての技術的なお話し
・FUnctionのコンピューティングリソース設定
 メモリ設定の見極めが肝心

・コールドスタートとウォームスタート
 安定的にリクエストが来ていれば最初しかコールドスタートは発生しない
 コールドスタートのスピードを許容できないのであれば、Lambdaは正解ではない(使うな)

アンチパターン

▼AWS Lambda + RDBMS

・コネクション数の問題
・VPCコールドスタートの問題
・DynamoDBを使うべし

▼IP固定したがり問題

▼Serverless != Monitorless
・監視はちゃんとしよう

IoT 時代を生き抜くエンジニアに必要な技術とは

松下 享平(株式会社ソラコム)


こちらは別記事にまとめました。

以上

続きを読む

aws-mobile-react-native-starterの紹介

ReactNativeでv4signをどのように実装すれば良いのか調べていたところ、aws-mobile-react-native-starterというリポジトリを見つけました。

このリポジトリはReactNativeの実装のサンプルで、AWSを利用したサーバーレスアーキテクチャでペットトラッカーというペットの情報を登録することができるアプリとサーバーサイドのサンプルになっています。
ReactNativeとAWSを利用してアプリ開発を初めてする時の参考になりそうです。

使い方

MobileHubセットアップ

このアプリをビルドして使用するには、AWSのMobileHubのセットアップが必要です。
以下の手順でMobileHubのプロジェクトを作成します。

  1. MobileHubのプロジェクトの作成ページからimportをクリックします。
    rn_starter_1.png

  2. backend/import_mobilehub/reactnative-starter.zipをインポートして、MobileHubのプロジェクトを作成します。
    rn_starter_2.png

  3. 正常にプロジェクトが作成できると次のような画面になるので、Get Startedをクリックして使用開始しましょう。
    rn_starter_3.png

  4. 作成したプロジェクトのページをスクロールして、Hosting and Streamingをクリックします。
    rn_starter_4.png

  5. スクロールして、Download aws-exports.js fileをクリックして、aws-exports.jsをダウンロードします。 このファイルにいま作成したMobile HubのプロジェクトやAWSへの接続情報などが含まれるので、この後アプリに組み込んで使用します。
    rn_starter_5.png

アプリの実行

  1. ダウンロードしたaws-exports.jsをclient/に保存します。
  2. cd client します。
  3. npm install で必要なパッケージのダウンロード
  4. npm start します。
  5. npm startが正常に待ち受け始めたら、別のターミナルからcd clientして、react-native run-iosします。

これでiOSシミュレータが起動し、アプリが起動されます。

アプリの機能

アプリの機能を紹介します。

ユーザー登録

新規にユーザー登録することができます。Cognito User Poolに登録されます。
rn.png

パスワード

8文字以上で記号と大文字小文字が必要です。次のパスワードは利用できました。
Hoge_123

電話番号

ちょっと分かりにくいですが+<国コード><携帯番号番号>を入力します。
電話番号が08012345678だとしたら、入力する値は+8108012345678とします。

2要素認証

入力した電話番号にSMSが届くので、確認コードを入力するとユーザー登録が完了します。

ログイン

ユーザー名、パスワードを入力してSIGN INをクリックすると、SMSに2要素認証のコードが届くので、それを入力することでログインできます。

a a

ペット登録

ペットを登録できます。画像のアップロードやDatePicker、SegmentedControl的なものの使用方法がわかります。画像はS3に、データはAPIGatewayを経由してDynamoDBに保存されます。

a

ペット一覧

ペットが登録されているとリストで確認することができます。
ListView的なものの使用方法がわかります。
+ボタンを見るとマテリアルデザインですね。

その他

パスワードの変更や、サインアウトすることもできます。
特に認証周りを手厚く実装しているサンプルですね。

まとめ

いかがでしたでしょうか。
ReactNativeとAWSを使ったサンプルアプリの紹介でした。このような参照実装があると便利でありがたいですね。
このアプリ使ってみてReactNativeいい感じなので、次にプライベートで作るアプリはReactNativeで作ろうかなと思います。

追伸

v4signは aws4-react-nativeというパッケージを使用しているみたいでした。

続きを読む

【AWS SDK】IAMロール情報での認証に失敗したらcredentialsが邪魔をしている。かも

前提

EC2からDynamoDBへIAMロール情報を使って認証しようと試みる(AWS SDK for PHP)

IAM認証が働かない

ソースコード内でアクセスキー/シークレットキーを明記しなかったので、IAMロール情報を使ってくれる、と思いきやその前にcredentialsファイルを勝手に読みに行く。credentialsファイルに書かれたアクセスキー/シークレットキーを使って認証を試みるので認証に失敗する。

解決策

環境変数AWS_PROFILEでアクセスキー/シークレットキーの記載のないプロファイルを指定する。
(credentialsファイルを読めないようにしてもいいけど)
※最初はdefaultを勝手に読んでる??

まとめ

関係のないアクセスキー/シークレットキーが、IAMロール認証を邪魔している事がある

参考

より詳細な認証順番は公式のドキュメント見てください。credentialsより早く探される認証情報もあります。(ちなみにIAMロール情報は最後に検索される)
https://docs.aws.amazon.com/ja_jp/sdk-for-java/v1/developer-guide/credentials.html

続きを読む

AWS PrivateLinkが登場 EC2やELBでどのように使うか

AWS Private link について

先日 AWS Private linkという機能が発表されました。
https://aws.amazon.com/jp/blogs/aws/new-aws-privatelink-endpoints-kinesis-ec2-systems-manager-and-elb-apis-in-your-vpc/

これまでの方式(DynamoDB,S3)はエンドポイントのゲートウェイ経由で各サービスのAPIに接続する形でしたが
新しい方式ではエンドポイントがENIに紐付き、VPCのプライベートIPを持つかたちになります。
この方式のメリットとしては
・セキュリティグループでエンドポイントへのアクセスを管理できるようになる
・エンドポイントにDirect Connectを介してアクセスすることができる
というのが大きいかなと思います。

Private linkに対応するサービス

今回のタイミングで、 以下のサービスが新たにVPCの中から使えるようになりました。
その他のサービスもComing Soonでサポート予定とのこと。

  • Kinesis
  • Service Catalog
  • Amazon EC2
  • EC2 Systems Manager
  • Elastic Load Balancing

EC2やELBでの使いどころ

Kinesisの対応はわかりやすいです。
プライベートなEC2からKinesisにデータプッシュできるとか最高やないか。
でもEC2やELBのエンドポイントって?と私は思ってしまったのでした。
例えば、EC2なら別VPCのパブリックなEC2にプライベートに接続?とかよく分からないことを考え初めてしまった。

基本に立ち返ってドキュメントを確認しました。
新しい方式(インターフェースVPCエンドポイント)についてはまだ日本語のドキュメントはありませんが
原文のドキュメントは以下のURLから参照できます。
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/vpce-interface.html

ページ下部のAccessing an AWS Service Through an Interface EndpointにELBの
エンドポイントにAWS CLIからアクセスする例がありました。

aws elbv2 describe-load-balancers --endpoint-url https://vpce-0f89a33420c193abc-bluzidnv.elasticloadbalancing.us-east-1.vpce.amazonaws.com/

単純にAWS CLIやSDKからAPI操作する際に使えば良いイメージでしょうか。
言われてみれば冒頭のBlogのタイトルも ~Amazon EC2 APIs, and ELB APIs in your VPC でした。

EC2でやってみる

まずは単純にプライベートなEC2でAWS CLIを操作してみます。
当然ですが、接続エラーになりました。

$aws ec2 describe-availability-zones

HTTPSConnectionPool(host='ec2.ap-northeast-1.amazonaws.com', port=443): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<botocore.awsrequest.AWSHTTPSConnection object at 0x7f002bf53e10>, 'Connection to ec2.ap-northeast-1.amazonaws.com timed out. (connect timeout=60)'))

PrivateLinkの作成

コンソールのVPCのメニューからエンドポイント → Create Endpoint と進みます。
S3とDynamodbのエンドポイントしか選択できない場合は、一度コンソールの言語設定を英語にすると
以下のような画面が表示されました。

image.png

Service Name: com.amazonaws.ap-northeast-1.ec2 を選択

VPC, subnet, SecurityGroup:それぞれ任意のものを指定します。
SecurityGroupでは443のインバウンドを許可する必要があります。

Enable for this endpoint:
PrivateLinkのエンドポイントはVPCのIPアドレスを使用するため、VPCのプライベートDNSを使用して
AWSサービスのDNS名を上書きすることができます。
チェックを入れると「ec2.ap-northeast-1.amazonaws.com」のVPC内のルックアップが、
作成するエンドポイントのIPアドレスに解決されます。

上記を指定したら、画面下部のCreate endpointを押下します。

動作確認

先ほどと同じく、プライベートなEC2からAWS CLIを実行します。

$ aws ec2 describe-availability-zones
{
    "AvailabilityZones": [
        {
            "State": "available",
            "ZoneName": "ap-northeast-1a",
            "Messages": []
            "RegionName": "ap-northeast-1"
        },
        {
            "State": "available",
            "ZoneName": "ap-northeast-1c",
            "Messages": [],
            "RegionName": "ap-northeast-1"
        }
    ]
}

Enable for this endpointにチェックを入れているので全く同じコマンドで実行することができました。
プライベートDNS名を有効にしない(チェックを入れない)場合は –endpoint-url で
エンドポイント固有に割り当てられているDNS名を指定すると接続することができます。

$ aws ec2 describe-availability-zones --endpoint-url https://vpce-xxxxxxxxxx-xxxxxx.ec2.ap-northeast-1.vpce.amazonaws.com
{
    "AvailabilityZones": [
        {
            "State": "available",
            "ZoneName": "ap-northeast-1a",
            "Messages": [],
            "RegionName": "ap-northeast-1"
        },
        {
            "State": "available",
            "ZoneName": "ap-northeast-1c",
            "Messages": [],
            "RegionName": "ap-northeast-1"
        }
    ]
}

基本的な内容かもしれませんが、参考になれば幸いです。
以上です。

続きを読む

AWS+Reactアプリ作成入門(ログイン後のAdmin編)

AWS+Reactアプリ作成入門(Cognito編)
AWS+Reactアプリ作成入門(S3編)
AWS+Reactアプリ作成入門(DynamoDB編)
AWS+Reactアプリ作成入門(IAM Role編)
AWS+Reactアプリ作成入門(ログイン後のAdmin編)

今回作成したアプリ ==>久喜SNS

 AWS+Reactアプリ作成入門は今回で最後です。書き残したことで重要なところを書きたいと思います。

 今回特に難しかったところはCognitoの使い方でした。一通り「AWS+Reactアプリ作成入門(Cognito編)」に書きましたが、重要な点をまだ書いていません。Reactアプリは複数のComponentファイルから成り立っています。「AWS+Reactアプリ作成入門(Cognito編)」で示したApp Componentを参照してください。App.jsファイルがロードされたときに非ログインユーザとして権限を持ち、LoginView Componentでログインした時にログインユーザとしての権限を持つようになり、Admin ComponentでS3やDynamoDBに画像ファイルやドキュメントをputするわけです。明記しているドキュメントが見つからず、ログイン状態はAdmin.jsでもそのまま保持されるのかが疑問でした。いろいろ試行錯誤した結果、答えはYesです。
 LoginView Componentで一度ログインすれば、その他のComponentのプログラムはログインユーザとして動作します。ユーザIDはAWS.config.credentials.identityIdでグローバルに参照できます。

1.Admin Component

 Admin Componentはログイン後にメニューに現れる管理画面で、画像掲示板の投稿・編集・削除を行う場所です。ログインユーザとしての権限を最大限に発揮できる画面です。特にCognito認証関係のコードを書かずに権限を実行できます。

src/views/Admin.jsの一部
import AWS from "aws-sdk";
import React from 'react';

---

export default class Admin extends  React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {

---

  _fetch(identityId) {
    const _self = this;
    var dynamo = new AWS.DynamoDB.DocumentClient();
    var param = {
      TableName : tablename,
      ScanIndexForward: false, //queryには効くが、scanには効かない
      KeyConditionExpression : "identityId = :identityId",
      ExpressionAttributeValues : {":identityId" : identityId}
    };
    dynamo.query(param, function(err, data) {
        if (err) {
            console.log("### Error="+err);
        } else {
            //console.log("### data="+JSON.stringify(data.Items));
            _self.setState({items: data.Items});
        }
    });
  }

  componentWillMount() {
    const _self = this;
    const email = localStorage.getItem('email');
    const username = localStorage.getItem('username');
    var identityId = AWS.config.credentials.identityId;

    if( !email ) {
        handleErrorFunc('エラー:ログインしていません');
        return;
    }
    this.setState({identityId: identityId});
    this.setState({email: email});
    this.setState({username: username});
    this._fetch(identityId);
  }

---

  postAdd() {
    const _self = this;
    const identityId = _self.state.identityId;

//-------------------------------
// Date
//-------------------------------
    let uploadTime = 0;
    let uploadDate = "";
    let partitionYear = 0;
    if( !this.state.updateItem ) { //新規投稿
        const date = new Date() ;
        uploadTime = date.getTime();
        uploadDate = toLocaleString(date);
        partitionYear = date.getFullYear();
    } else {                        //編集投稿
        uploadTime = _self.state.updateItem.uploadTime;
        uploadDate = _self.state.updateItem.uploadDate;
        partitionYear = _self.state.updateItem.partitionYear;
    }


//-------------------------------
// S3 put
//-------------------------------
    let filepath = noimage;
    let thumbnail = noimage;
    let fileType = noimage;
    if( _self.state.imageOverwrite && !!_self.state.file) { 
        if (!_self.state.file.name.match(/^[0-9a-zA-Z._-]*$/)) {
            handleErrorFunc('エラー:ファイル名は小文字の英数字と . - _ しか使えません: '+_self.state.file.name );
            return;
        }
        filepath = 'contents/images/'+identityId+'/'+_self.state.file.name;
        thumbnail = filepath.replace(/images/, 'thumbnail');
        fileType = _self.state.file.type;
        console.log("filepath="+filepath);
        var params = {
            Bucket: bucketname,
            Key: filepath,
            ContentType: _self.state.file.type,
            Body: _self.state.file,
            Metadata: {
              data: JSON.stringify({
                identityId: identityId,
                uploadTime: uploadTime,
                uploadDate: uploadDate
              })
            }
        };
        var s3 = new AWS.S3();
        s3.putObject(params, function(err, data) {
            if(err) {
                console.log("Err: upload failed :" +err);
            } else {
                console.log("Success: upload ok");
                let url = 'http://'+bucketname+'.s3-'+appConfig.region+'.amazonaws.com/'+filepath;
                console.log("######11 imgurl="+url);
                _self.setState({imgurl: url});
            }
        });
    } else if ( _self.state.updateItem && !_self.state.imageOverwrite ) { //編集投稿で上書きアップロード無し
        filepath = _self.state.updateItem.filename;
        thumbnail = _self.state.updateItem.thumbnail;
        fileType = _self.state.updateItem.fileType;;    
    }
//-------------------------------
// DynamoDB putItem
//-------------------------------
    const title = escape_html(_self.state.title);
    let story = escape_html(_self.state.story);
    story = story.replace(/((http:|https:)//[x21-x26x28-x7e]+)/gi, "<a href='$1'>$1</a>");


    var docClient = new AWS.DynamoDB.DocumentClient();
    var params = {
        TableName: tablename,
        Item:{
             identityId: identityId, // ★prime partition key
             email: _self.state.email,
             username: _self.state.username,
             filename: filepath,
             thumbnail: thumbnail,
             type: fileType,
             title: title,
             story: story,
             imageOverwrite: _self.state.imageOverwrite,
             mapUse: _self.state.mapUse,
             position: _self.state.position,
             uploadTime: uploadTime, // ★prime & secondary sort key
             uploadDate: uploadDate,
             partitionYear: partitionYear, //★secondary partition key
             refCounter: 0
        }
    };
    docClient.put(params, function(err, data) {
        if(err) {
            console.log("Err: table put :" +err);
        } else {
            console.log("Success: table put ok");
        }
    });

//-------------------------------
// Clear form
//-------------------------------
    this.handlePostNew();

    this._fetch(identityId);

  }

---


 LoginView Componentでログインしているので、このAdmin ComponentでもログインユーザとしてS3やDynamoDBにputする権限を持っています。一応念のためにcomponentWillMount()でログインしているかの確認をしています。localStorageにemailがセットされているか否かで判断しています。LoginView Componentの以下のコードが効いています。再掲載していきます。

    AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: appConfig.IdentityPoolId,
        Logins : {
            // Change the key below according to the specific region your user pool is in.
            'cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xn9ihTu0b' : result.getIdToken().getJwtToken()
        }
    });

 見直しや、修正を入れること張りますが、以上で「AWS+Reactアプリ作成入門」を終わります。

続きを読む

AWS+Reactアプリ作成入門(IAM Role編)

AWS+Reactアプリ作成入門(Cognito編)
AWS+Reactアプリ作成入門(S3編)
AWS+Reactアプリ作成入門(DynamoDB編)
AWS+Reactアプリ作成入門(IAM Role編)
AWS+Reactアプリ作成入門(ログイン後のAdmin編)

今回作成したアプリ ==>久喜SNS

 AWSのIAMサービスにRoleというものがあります。Roleは、AWSサービスのリソースへのアクセスを制御します。今回のアプリの例で言えば、Conginitoサービスを使っていますので、そのCognitoアプリがどのリソースへアクセスできるかをRoleによって決められています。CognitoアプリのRoleは、非ログインユーザとログインユーザで別のものが用意されていますので、ログイン状態によってユーザはアクセス権限が異なります。ちなみにRoleはPolicyのセットとして定義されます。実際に制御の記述はPolicyに記載します。つまりあるPolicyが複数のRoleに割り当てられることが可能です。但しこの記事の中ではRoleとPolicyを混同して使うことがありますが、文脈から読み取っていただければ幸いです。以下に今回のアプリで使われているRoleを説明していきます。

 ※未確認ですが、以下の複数のRoleでs3:GetObjectをAllowしていますが、バケットポリシーで既にAllowしているので不要かもしれません。

1.Cognito_sandAppIDPoolUnauth_Role

 このRoleは「AWS+Reactアプリ作成入門(Cognito編」でsandAppIDPoolというフェデレーテッドアイデンティティを作成した時に指定したものです。ログイン前のCognitoアプリユーザの権限です。非ログインユーザがどのような権限でS3やDynamoDBのリソースにアクセスできるかを記述しています。

Cognito_sandAppIDPoolUnauth_Role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/contents/cache.json"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:Query",
                "dynamodb:Update",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/掲示板テーブル名",
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/掲示板テーブル名/index/partitionYear-uploadTime-index"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:Query",
                "dynamodb:Put",
                "dynamodb:PutItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/コメントテーブル名"
            ]
        }
    ]
}

2.Cognito_sandAppIDPoolAuth_Role

 このRoleも「AWS+Reactアプリ作成入門(Cognito編」でsandAppIDPoolというフェデレーテッドアイデンティティを作成した時に指定したものです。ログイン後のCognitoアプリユーザの権限です。ログインユーザがどのような権限でS3やDynamoDBのリソースにアクセスできるかを記述しています。

Cognito_sandAppIDPoolAuth_Role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/contents/images/*",
                "arn:aws:s3:::バケット名/contents/thumbnail/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/contents/cache.json"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:Put",
                "dynamodb:DeleteItem",
                "dynamodb:Update",
                "dynamodb:UpdateItem",
                "dynamodb:Query"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/掲示板テーブル",
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/掲示板テーブル/index/partitionYear-uploadTime-index"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:Query",
                "dynamodb:DeleteItem",
                "dynamodb:Put",
                "dynamodb:PutItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:xxxxxx:table/コメントテーブル"
            ]
        }
    ]
}

3.CreateKukiThumbnailAndStoreInDB

 このアプリには画像がS3にアップロードされたタイミングで起動するLambda関数があります。これはオリジナル画像を縮小しサムネイル画像に変換し、再度S3にアップロードするものです。1つの画像に対してS3には常に2つの画像が存在します。オリジナル画像とサムネイル画像です。このRoleはこのLambda関数の定義の時に指定するものです。

CreateKukiThumbnailAndStoreInDB
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/contents/images/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名/contents/thumbnail/*"
            ]
        }
    ]
}

4.sandPool-SMS-Role

 このRoleも「AWS+Reactアプリ作成入門(Cognito編」でsandPoolというユーザプールを作成した時に定義したものです。ユーザ登録時とかにSNSサービスを使って確認メールを送信しますが、その権限を規定したものです。

sandPool-SMS-Role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sns:publish"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

今回は以上です。

続きを読む