woshidan's loose leaf

ぼんやり勉強しています

AWSのCLIで各種リソースやサービスにアクセスするためにIAMでアクセスキーを発行する

AWSのAthenaクライアント aws-sdk-athena を使ってみようとしたら、Athenaクライアントは aws cli を利用していて、これを利用するために IAMAWSアカウントにアクセスするユーザを作る必要があったので手順メモ。

ちなみに IAMIdentity and Access Management だそうです。

事前準備

まず、事前準備としてルートアカウントの多要素認証(MFA)を有効化します。

MFAって多要素認証の略なんですね。なるほどです。

IAMユーザの作成

次にAWSの各種サービスにアクセスするためにIAMユーザを作成します。 IAMユーザを作成するにあたって、Webの画面からアクセス可能か、CLIからアクセスできる可能か、あるいは両方可能かを最初に設定します。

f:id:woshidan:20170929143416p:plain

f:id:woshidan:20170929143437p:plain

グループの作成・設定

IAMユーザを作成したあとは、そのユーザが所属するグループを設定します。 IAMではユーザーの権限をグループ単位で設定し、グループ単位で権限を設定する際には権限に関する設定が書かれたポリシーをいくつ採用するか、といった方式を用います。

f:id:woshidan:20170929143708p:plain

f:id:woshidan:20170929143724p:plain

f:id:woshidan:20170929143739p:plain

なお、AthenaのCLIで結果をS3のバケットに書き込む場合(必須)、上記以外に書き込み先のS3のバケットへの読み書きの権限もいります(一敗)。

完了

確認画面で内容をもう一度確認したら「ユーザー作成」ボタンを押して完了です。

f:id:woshidan:20170929143752p:plain

f:id:woshidan:20170929143821p:plain

なお、シークレットアクセスキーはユーザー作成時かアクセスキーID発行*1時しかできないので注意しましょう。

*1:一応これは後からいつでもすぐできます

整形されたJSON文字列を見たい時、minifyしたい時、jqコマンドがとても便利だったという話

昨日頑張って sed をかじったため

sed -e '1,10s/\([}\]]\),/\1,\
/g' -e '1,10s/:{/&\
  /g' -e '1,10s/e,/&,\
  /g' test.json 

みたいな涙ぐましい事してたんですが、検索してたら jq ってコマンドで簡単にできるらしいです。

まずは単に整形する

例えばこんなminifyされたJSONがあったとして

{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}

これをこうじゃ。

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .
{
  "photo": {
    "taken_at": 1506609841,
    "created_at": 1504609841,
    "title": "さっちゃん"
  }
}

この標準出力をファイルへリダイレクトする事で、整形されたJSON文字列をファイルへ書き込むこともできます。

必要な要素だけを取り出してみる

jqコマンドは .キー名 で、そのキーの要素だけを取り出して表示させることが可能です*1

これを利用すると、

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .photo
{
  "taken_at": 1506609841,
  "created_at": 1504609841,
  "title": "さっちゃん"
}

$ echo '{"photo":{"taken_at":1506609841,"created_at":1504609841,"title":"さっちゃん"}}' | jq .photo.title
"さっちゃん"

といったことができます。

配列の中の指定した要素を列挙する

さらに、配列については以下のようなことも可能です。

こういうJSONがあったとして

// sample.json
{
  "order" : {
      "items" : [
          {
            "id": 1,
            "name": "野球ボール",
            "price": 1296,
            "count": 3
          },
          {
            "id": 2,
            "name": "野球バット",
            "price": 1600,
            "count": 1
          }
      ]
  }
}
$ cat sample.json | jq .order.items
[
  {
    "id": 1,
    "name": "野球ボール",
    "price": 1296,
    "count": 3
  },
  {
    "id": 2,
    "name": "野球バット",
    "price": 1600,
    "count": 1
  }
]

// []で出力から配列の[]をはぐ
$ cat sample.json | jq .order.items[]
{
  "id": 1,
  "name": "野球ボール",
  "price": 1296,
  "count": 3
}
{
  "id": 2,
  "name": "野球バット",
  "price": 1600,
  "count": 1
}

// [].キー名で配列の中の要素でそのキーだけを列挙する
$ cat sample.json | jq .order.items[].name
"野球ボール"
"野球バット"

出力から "(ダブルクオート) をはぐ

列挙した要素を他のコマンドの入力にしたい場合など " が邪魔な場合には -r オプションではげます

cat sample.json | jq .order.items[].name -r
野球ボール
野球バット

パイプが利用できる

なんと、 jq の中だけでもパイプを使うことができます。これによって複雑なJSONの整形、要素の抽出や簡単な集計処理が行えます。

$ cat sample.json | jq .order.items[].price | awk '{m+=$1} END{print m;}'
2896

minifyする

-c オプションを利用することで、整形されているJSON文字列をminifyすることも可能です。

$ jq -c . < sample.json
{"order":{"items":[{"id":1,"name":"野球ボール","price":1296,"count":3},{"id":2,"name":"野球バット","price":1600,"count":1}]}}

めっちゃ便利! 現場からは以上です。

なんか awk もやらねばいけないような気がするけど、明日は簡易自作コマンドの話をする。

参考

*1:先ほどの . は全体を表示する、という意味

いい加減わからない感じが強いのでsedについて少し調べてみた

sedとは

sedstream editor の略で、主にファイルかファイルの指定がなければ標準入出力を読んで何かしたら入力を修正したら標準出力へ書き込んでくれる、といった使い方が主なスクリプト言語です。

sedの記法

コマンドを一つだけ入力する場合とそうでない場合で記法が異なります。

     sed [-Ealn] command [file ...] #
     sed [-Ealn] [-e command] [-f command_file] [-i extension] [file ...]

sedのコマンドの種類

コマンドには、 http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230879/ で紹介されているものから

  • a 文字列 ... 文字列を追加する。ただし改行をしたい場合はその前に\を付ける
  • i 文字列 ... 文字列を挿入する。ただし改行をしたい場合はその前に\を付ける
  • r ファイル名 ... 指定したファイルを読み出し,追加する
  • d ... パターン・スペースを削除する
  • s/置換条件/置換文字/ ... 置換条件を置換文字に変換する。最後にgを付けた場合は置換条件に当てはまるすべての文字列が置換される
  • p ... 行を出力する

などがあります。a コマンドなどはMac OS環境下だとうまく動かないことがあるので、その場合はこちらのページを参考に gnu-sed を入れるとよいようです。

$ echo "test" | sed s/t/gu/
guest
$ echo "test" | sed p
test
test

// test.csv
// cat,dog,cow
// horse,wolf,giraffe
// anteater,bear,ape

$ sed 1d test.csv
horse,wolf,giraffe
anteater,bear,ape

今回は主に s コマンドの話をしたいので先に進みます。

アドレスとは

各コマンドについて、アドレス、コマンド1文字、コマンドパラメータなどを指定しますが、アドレスとは、処理を行う範囲の指定のことだそうです。指定方法について、 https://qiita.com/mattintosh4/items/4e4d44016be15333af11 の記事より引用させていただくと、

アドレス 範囲 行番号指定 パターン指定
0アドレス 全ての行 何も書かない 何も書かない
1アドレス 指定行 1、や 2、$ など /^1行目/ など
2アドレス 指定行〜指定行 1,3、2,$ など /^1行目/,/^3行目/ など

という風になっています。わかりづらいので、

cat,dog,cow
horse,wolf,giraffe
anteater,bear,ape

のようなCSVファイルを処理して確認してみましょう。

// 0アドレス(何も記述していない) => すべての行で処理される
$ sed s/a/A/g test.csv
cAt,dog,cow
horse,wolf,girAffe
AnteAter,beAr,Ape
// 1アドレス(数字や記号を一つだけ書く) => その行だけ処理される
$ sed 3s/a/A/g test.csv
cat,dog,cow
horse,wolf,giraffe
AnteAter,beAr,Ape
// 2アドレス(コンマ区切りで二つ指定する) => 指定した複数行が処理される
$ sed 2,3s/a/A/g test.csv
cat,dog,cow
horse,wolf,girAffe
AnteAter,beAr,Ape

sedのsコマンドでできること

眠たいのでバーッと書いちゃいます。

// 置換文字の中でマッチしたキーワードを利用する
$ echo "test" | sed s/t/gu/
guest

// s/置換条件/置換文字/ の置換文字の部分に&を置くと、マッチした単語の部分が出力される
$ echo "test" | sed 's/t/gu &/'
gu test
// 3行目の内容 anteater,bear,ape
$ sed -n '3s/a/ Changed &/gp' test.csv
 Changed ante Changed ater,be Changed ar, Changed ape

// s/置換条件/置換文字/ で置換条件に()を使ってグループ化し、置換文字の部分に\nを置くと、n番目に()のグループでマッチした部分が出力される
$ echo "test" | sed 's/\(te\)\(st\)/\2  \1/'

// s/置換条件/置換文字/ の置換条件に正規表現が使える
// 行頭 ^, 行末 $ など
$ sed -n 's/\([bcd]\)/\1_/gp' test.csv
c_at,d_og,c_ow
anteater,b_ear,ape

// -e コマンドで sコマンドをつないで、複数の条件で置換ができる
$ sed -e 's/\([bcd]\)/B/g' -e 's/\([efg]\)/E/g' test.csv
Bat,BoE,Bow
horsE,wolE,EiraEEE
antEatEr,BEar,apE

// s/置換条件/置換文字/ の後ろの部分にgをつけるとマッチした部分全体が、pをつけるとその部分を追加で出力になる
$ sed 3s/a/A/gp test.csv
cat,dog,cow
horse,wolf,giraffe
AnteAter,beAr,Ape
AnteAter,beAr,Ape

// s/置換条件/置換文字/ の後ろの部分にgをつけるとマッチした部分全体が、pをつけるとその部分を追加で出力になる
$ sed 3s/a/A/gp test.csv
cat,dog,cow
horse,wolf,giraffe
AnteAter,beAr,Ape
AnteAter,beAr,Ape

// s/置換条件/置換文字/p を -nオプション(対象の行だけ出力)と合わせると、置換処理した部分だけ出力できる
$ sed -n 3s/a/A/gp test.csv
AnteAter,beAr,Ape

他にも色々ありますが、 https://qiita.com/hirohiro77/items/7fe2f68781c41777e507 などを見ることにして慣れてきたので今日はここまでにします。

参考

利用条件が緩いzlibライセンスについて

ライブラリのライセンス表記についてはアプリやライブラリを開発していると悩ましいところで、使ってはいけなさそうなLPGL系を避けながら雑にすべてのライブラリのライセンス表示ページを作ったりして誤魔化しがちです。

しかし、中には著作者の偽装はしてはいけないものの、ライセンス表記を要求されないものもあるようです。

その一つがzlibライセンスです。Wikipediaから条項の概要を引用すると、

- ソフトウェアはas-is(現状のまま)ベースで利用される。著作者は利用により起こりうる損害に対する責任を負わない。
- 商用利用を含め、ソフトウエアの改変、配布は以下の制限の下で許可される:
  - 1. このソフトウェアの原著作者であると詐称してはならない(ただしその表示義務は無い)。
  - 2. ソースコードを改変した場合はオリジナルのままだと勘違いされないようにしなければならない。
  - 3. このライセンス表示をソースコード配布物から削除してはならない。

ライセンスは、バイナリコードが配布されているならば、利用可能なソースコードを必要としない。

となっています。もう少し自分に近い言葉でまとめなおすと

  • ソースコードの状態でライブラリを自身のコードに含む場合はこのライセンス文をソースコードから削除してはいけない
  • 改変した場合は改変したことがわかるようにする
  • ライブラリに変更がない状態で静的リンクで利用するならば、ライセンス表記は不要

となります。一つ一つ具体的に確認しないと怖いですね、現場からは以上です。

参考

S3に入っているファイルの状況を調べるのにCloudWatchが便利、という話

S3に限った話ではないのですが、ちょっと追加で調べたいなというときにメトリクスやその間隔を調整できて便利でした。まる。

S3は上部メニューの分析から各バケットのメトリクスを確認できるのですが、このメトリクス表示する期間がかなり限定された相対期間のみなのが、メトリクスの数値を見積もりに使いたいのでちょっと例外的なイベントがあった日を外したいなどの場合にちょっと不便です。

f:id:woshidan:20170923120743p:plain

この場合、上図で囲まれた部分をクリックすると同じメトリクスを CloudWatchで確認することができるようになります。

f:id:woshidan:20170923121015p:plain

CloudWatchでは、日付をカスタムで変更することが可能です。

f:id:woshidan:20170923121739p:plain f:id:woshidan:20170923121742p:plain

また、ログファイルのバケットの場合、CloudWatch Logsでログのフィルタリングを設定してアラートを出したり、といった設定も可能です。

現場からは以上です。

配列の中のハッシュの要素で検索したい

cross joinUNNEST を利用する。

UNNEST はSQLの中では UNNEST 関数は、指定された配列の各エレメントにつき 1 行が含まれる結果表を戻す ものだそうです。

前に書いた Athenaの記事 で少し調べたのですが、覚えられてなかったので上記を踏まえてもう一回。

{
    "animals": [
                {"name": "ぽち", "kind": "dog"},
                {"name": "たま", "kind": "cat"}
            ]
}
CREATE EXTERNAL TABLE IF NOT EXISTS animal_logs (
  animals array<struct<name:string, kind:string>>
  )           
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://exmaple-woshidan-test/athena_nest_sample';
SELECT * FROM mydatabase."animal_logs" limit 10;
animals
1 [{name=ぽち, kind=dog}, {name=たま, kind=cat}]
SELECT * FROM mydatabase."animal_logs" cross join UNNEST (animals) AS t (animal);
animals animal
1 [{name=ぽち, kind=dog}, {name=たま, kind=cat}] {name=ぽち, kind=dog}
2 [{name=ぽち, kind=dog}, {name=たま, kind=cat}] {name=たま, kind=cat}
animals
1 [{name=ぽち, kind=dog}, {name=たま, kind=cat}]

の表と UNNSET で作成される

animal
1 {name=ぽち, kind=dog}
2 {name=たま, kind=cat}

の表を掛け合わせているので、元のテーブルの一行 x UNNESTしたanimalsの中の行ある結果の表が返ってくる。

これで、 UNNEST した列の配列の各要素へアクセス可能になるので、これを利用して、

SELECT * FROM mydatabase."animal_logs" cross join UNNEST (animals) AS t (animal) WHERE animal.kind='dog';
animals animal
1 [{name=ぽち, kind=dog}, {name=たま, kind=cat}] {name=ぽち, kind=dog}

のように絞り込むことができる。

参考

セマフォとロックとPauseからの復帰

  • セマフォは獲得してから解放するまでのコードを一つのスレッドしか通ってくれるなよ、的なもの
    • 1つ、というのはコンストラクタの引数1から
      • 同時に実行していい数が指定できるロック的な
    • と現状理解している
  • サンプル見てるとこのセマフォでCamera2 APIの利用のタイミングの同期を取っているっぽい
  • 獲得 ~ 解放までの流れ 1
    • CameraManager.openCameraCameraDevice インスタンスを経由して Camera2 APIにアクセスしようとする*1
    • CameraDevice.StateCallbackonOpened, onDisconnected, onError で解放
      • openCamera でCamera2 API へのアクセスを試すあたりの処理に他のスレッドが入ってくるのを制限している
  • 獲得 ~ 解放までの流れ 2
    • CameraCaptureSession, CameraDevice, 他のSurface をcloseする前に獲得
    • 上記close後 or closeに失敗した後に解放
  • ちなみに昨日の自分のコードはそもそも onPauseCameraDevice をcloseしていなかったのでした…

獲得 ~ 解放までの流れ 1

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L268-L271

    /**
     * A {@link Semaphore} to prevent the app from exiting before closing the camera.
     */
    private Semaphore mCameraOpenCloseLock = new Semaphore(1);

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L599-L621

    /**
     * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
     */
    private void openCamera(int width, int height) {
        if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            requestCameraPermission();
            return;
        }
        setUpCameraOutputs(width, height);
        configureTransform(width, height);
        Activity activity = getActivity();
        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }
    }

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L187-L215

    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // This method is called when the camera is opened.  We start camera preview here.
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            Activity activity = getActivity();
            if (null != activity) {
                activity.finish();
            }
        }

    };

獲得 ~ 解放までの流れ 2

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L624-L647

    /**
     * Closes the current {@link CameraDevice}.
     */
    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

回転とかでうまく表示されなくなるの、デバイスとかのせいでないことがわかったから回転再チャレンジしてもいいかも

  • [既存課題]縦向きの場合と横向きの場合でちょうどいいrotationのMatrixを見つける 関連: http://mslgt.hatenablog.com/entry/2015/05/12/013013#rotatescreen 横向き or 縦向きだけの対応で十分では、となり、横向き固定の場合の行列見つけて安堵中
  • [既存課題]縦向きの場合と横向きの場合でそれぞれちょうどよくCameraのPreviewが表示されるようにする
  • [新しい課題]作成した画像のファイルへの書き込み、あるいはImageWriter
  • [新しい課題]ImageReaderでできること他に
  • [新しい課題]TextureViewのカスタムクラス読みに行ってはどうか

*1:という言い方がエラーメッセージ見てるかぎり良さそうなんだが、書いてるノリとしてコールバックを経由してCameraDeviceのインスタンスを取得とか、カメラ起動とかそれくらいのノリ