OmniAICreator/Qiita-1.07M huggingface dataset:
【RPA】Automation AnywhereでHello Worldを出力 

#Automation AnywhereでHello Worldを出力#
RPAの一つであるAutomation AnywhereでHello Worldを出力するBotを作ります！
今回はAutomation Anywhereの無料版であるcommunity editionで作ります！ 


##まずはAA Clientをインストール##
まずはcommunity editionをインストールしましょう！
リンクはこちら↓
https://www.automationanywhere.com/jp/lp/rpa-editions-comparison/

名前やアドレスなどを登録します。するとメールでControl Room(Automation Anywhereの管理機能です)のアドレス
とID、パスワードが送られてきます。

Control Roomにログインすると「DOWNLOAD CLIENT」と大きく表示されたボタンがありますのでそこからインストールします！

インストールしてControl Roomにログインできましたら準備は完了です！


##ワークベンチを開く##

まずログインするとこんな画面になっていると思います。

![client.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/d186f616-290c-5a70-ef18-fbef2d950342.png)

左上の「New」のボタンを押します。すると

![Automate.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/b3725160-776f-4047-dd4f-2b5f3b9c44fc.png)

このような画面がでます。この中で**Workbench**を選択します。

これでワークベンチが開きます。

![workbench.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/2f4b0daf-bd37-9bf9-b4f4-31d83647177a.png)


##Hello Worldを出力##

ワークベンチの左にあるCommandsから「**Message box**」を選択します。
これをAction Listにドラッグ&ドロップかダブルクリックしてください。

するとこちらのウインドウが開きます。

![Message box.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/f1f7f085-5d91-d34e-4812-818997c3058a.png)

このPlease enter message to show the userにHello　Worldと入力してSaveを押します。

![Message box2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/a1983d04-b17e-8ac2-7fca-7523f3d79364.png)

あとはワークベンチの上にあるSaveボタンで保存します。

![save.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/474aa82b-1ae9-ba4d-4e00-d27d94a33167.png)

ファイルの名前を決めるウインドウが出ますのでファイル名を入力し保存します。

![save task.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/163986bc-a06a-1e45-5e6e-e9a609ea7b10.png)





**これで準備は完了です！ワークベンチの上にあるRunボタンで実行してみましょう！**

![HelloWorld.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395789/b08f459c-948e-b770-7ed2-b4bd4567694e.png)

このようにでましたか？？今回は以上です！また他のコマンドの使い方も後日載せます！！
SeleniumでChromeのユーザープロファイルを指定しつつ同時に自分もChromeを使う方法

#はじめに
初めてQiitaで記事を書かせていただきます。最近プログラミングの勉強を始めた者です。

前置きはさておき、やりたかったのは**SeleniumでChromeを動かすときに、特定のユーザープロファイルを指定したい。**です。

が、私の環境だと以下のようなエラーが発生し、Chromeを閉じてからでないとSelenium経由では起動してくれませんでした。
```"invalid argument: user data directory is already in use, please specify a unique value for --user-data-dir argument, or don't use --user-data-dir"```

どうやら**今起動しているChromeが参照しているユーザプロファイルがあるフォルダ**と同じフォルダをSeleniumで指定することはできないようです。
このままでは、SeleniumからChromeを起動する度に、いつも使っているChromeを閉じなければなりません。~~めんどくさいです~~
そこで、ユーザプロファイルが置かれるフォルダを新しく作って、そちらを参照してみるとうまくいきました。

#環境
- Windows 10 Home
- Selenium(C#用) v3.14
- Chrome v75
- Powershell v5.1

私はPowershellを使っていますが、記事の内容自体はChromeのプロファイルの指定の方法なので言語は問わないと思います。

#通常とは別のユーザープロファイルを置くフォルダを作る
Chromeを起動するときにオプションで任意のフォルダを指定してやれば、そのフォルダがユーザープロファイルを置くフォルダ```user data directory```になってくれるようです。
起動時のオプションとして```--user-data-dir=```の後に適当なフォルダを指定するだけです。

例えば以下のようなコマンドになります。「ファイル名を指定して実行」で実行してやればOKです。
```"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir="C:\Learn\tools\test" 
```

コマンドを実行するとChromeが起動するので、Selenium用のユーザを作成してやります。
ユーザの作成方法は[こちら(Chrome公式のヘルプ)](https://support.google.com/chrome/answer/2364824?co=GENIE.Platform%3DDesktop&hl=ja)に手順があります。


ユーザを作成すると、指定したフォルダ内に```Profile 1```というフォルダができるので、そのフォルダをSeleniumでのChrome起動時にオプションで指定してやります。
※```Profile 1```となるのは、そのフォルダをオプションで指定して初めてユーザを作成した場合の名前です。複数ユーザを作った場合は末尾の番号が増えていきます。


#ユーザプロファイルを指定してSeleniumからChromeを起動する
Powershellで書くとこんな感じなりました。

```
$chromeDriverDirPath = "C:\\Learn\tools\\selenium\\chromedriver.exe"
$options = New-Object OpenQA.Selenium.Chrome.ChromeOptions

#プロファイルを指定
$PROFILE_PATH = "C:\\Learn\\tools\\test"
$options.AddArgument("--user-data-dir=$PROFILE_PATH") 
$options.AddArgument("--profile-directory=Profile 1")

#プロファイルを指定してchrome起動
$chromeDriver = New-Object OpenQA.Selenium.Chrome.ChromeDriver($chromeDriverDirPath, $options)
```

Chromeの起動オプションについては詳細な解説をしていただいている記事があるので割愛します。
参考：[Python + Selenium + Chrome で自動ログインいくつか](https://qiita.com/memakura/items/dbe7f6edadd456da1c5d)


実行して、右上に指定したユーザのアイコンが表示されていたので成功です。
![ユーザプロファイル起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375279/60597655-a09f-29ee-62e6-19a49220f76a.png)

起動後に、Webサービスなどに一度ログインしておけば、次回からはログインをスキップできたりして、手間が省けます。やったー
【Laravel5.8】テストでLaravel外にリクエストを投げられない

Laravelのテストでは頻繁に`$response = $this->get($url);`とか書くと思いますが、実はこれ、実際にリクエストを送信してはいません。
内部で何かごちゃごちゃとやっていて、一見リクエストを送ったように見せかけてレスポンスを返してきています。

その実害が前起こった[laravel_sessionが取れない](https://qiita.com/rana_kualu/items/d93df4909021bf7a5db4)というものですが、他にも`$_SERVER['REQUEST_URI']`などの環境変数を直接使っているとテスト時に[値が入ってこない](https://qiita.com/reflet/items/0d463bd283b67fa438a9)ので死にます。
まあ、そちらは`Request::getPathInfo()`などを使うべきだということでしょう。

本題はここからで、実はgetやpostは、外部サーバにリクエストを飛ばすことができません。
`$this->get('https://example.com/a/b');`って打っても、ドメイン部分は完全に無視されて`a/b`に飛びます。
なんで。

さらにLaravelをサブディレクトリ運用している場合、その外側に行くこともできません。
Laravelを`https://example.com/laravel/`の中で運用していたとして、`https://example.com/other/`にすら行けないということです。
`$this->get('https://example.com/other/foo/bar');`とすると、実際は`https://example.com/laravel/other/foo/bar`に行ってしまいます。
なんで。

内部リクエストではなく、実際に該当URLにHTTPリクエストを送る方法はないでしょうか。
URLの作成は[MakesHttpRequests](https://github.com/laravel/framework/blob/5.8/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php)や[HttpFoundation\Request](https://github.com/symfony/http-foundation/blob/master/Request.php)のあたりで行われているのですが、読んでも全く理解できませんでした。

[Laravel Dusk](https://readouble.com/laravel/5.8/ja/dusk.html)というパッケージはあるのですが、ちょっと思ったのと違います。
これはWeb画面を表示してテキストを入力してボタンを押してといったテストを行えるようになるヘッドレスブラウザというものです。

いや、そんな高機能な代物を求めてるわけじゃないんだ。
ただただ単に外部サーバにちょっくらGETしたいだけなんだ。

こういう場合どうするのがベストな方法なんだろうか？
やっぱGuzzleとか使って手動？
HerokuにPython+Flaskをデプロイ時，R10 Errorが出た場合の解決法

##現象
以下ようなPython+FlaskコードをHerokuにデプロイ時，[R10 Error (Boot timeout)](https://devcenter.heroku.com/articles/error-codes#r10-boot-timeout)が出てしまった．

```python
import os
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/", methods=["GET", 'POST'])
def download():
    .
    .
    .
    return jsonify(response)

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=5000)
```

## 問題点
**ポート番号があっていなかったのがR10 Errorの原因**だった．

Herokuは動的にポート番号を割り当てるようで，ポートオプションを`port=5000`と書いていたのがまずい点．

参考サイト：[Heroku + node.js error (Web process failed to bind to $PORT within 60 seconds of launch)](https://stackoverflow.com/questions/15693192/heroku-node-js-error-web-process-failed-to-bind-to-port-within-60-seconds-of)


## 解決法
`os.environ.get()`を使用して，以下のような表記とする．
これなら，Herokuが5000以外のポート番号を割り当てた時にも対応でき，R10 Errorを回避できる．

```python
if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))

```

##### 処理の説明
`os.environ.get('PORT', 5000)`は，環境変数`'PORT'`を取得を試みる．
├> 取得できた場合：その取得した値が返される．
└> 取得できない場合：`5000`が返される．

## 参考サイト
- [Node.jsのアプリをHerokuにデプロイするときポート番号に気をつけなくちゃいけない](https://qiita.com/kenju/items/1d61c9fb45496feed1bf)
githubでリポジトリを消してしまった時の対処法

#はじめに
Qiita初投稿です🙂🙂
先日、個人アプリを作成していた際、Github上からリポジトリを全て消してしまいました。（ちなみに何を血迷ったのか、私はローカルのファイルも削除してしまいました、、、）
頭が真っ白になり、血の気が引きました:frowning2::frowning2:
しかし、約30分後には全て元どおりになりました。
同じような経験があればこの記事を参考にしてください。

#何をしたのか
やることは一つです！！
Githubのお問い合わせフォームに連絡する！！ただそれだけです！！
<img width="1436" alt="スクリーンショット 2019-09-23 17.57.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/94ec4ee1-245e-0495-e6b1-5d8359cd3fd8.png">
①右上のお問い合わせをクリック
<img width="1440" alt="スクリーンショット 2019-09-23 18.46.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/022c11b0-d607-531d-d14c-19ed4e5820a3.png">
②右側の無料バージョンのお問い合わせフォームに飛ぶ（有料バージョンを使用されている方は専用のページがあるのでそちらへ！）
<img width="1440" alt="スクリーンショット 2019-09-23 18.50.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/c8cd8ff2-04c4-9dbd-61c1-30e7d26c06f2.png">
③必要事項を記入し、送信（英語で記入しなければならないので例文を載せておきます。）
【例】
Hi, GitHub Team

I accidentally deleted a "Githubのニックネーム/リポジトリ名" repository. Can you resurrect it?


その後、しばらく待つとGitHub Developer Supportからメールが届きます。（私は10分くらいで返信が来ました。）
<img width="1380" alt="スクリーンショット 2019-09-23 18.56.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501638/649d276d-2395-fe0c-e773-611ad23c5857.png">

こんな感じ。
あとは指示にしたがってどのリポジトリを復活させるのか返信します！！

返信してから15分後くらいには元どおりにもどりました！！！

#補足
調べるとGithubは一ヶ月以内のリポジトリはすべてバックアップを取っているようです。
さすが大企業、、素晴らしいサービスですね。
第一に無闇にリポジトリを消さないのはもちろんのことですが、間違えて消してしまった場合でも大丈夫です。
Github最高😋

記事を書くモチベーションになるので、もし参考になったらいいねお願いします:thumbsup_tone1::thumbsup_tone1:
侮れないGoogle翻訳

#概要
機械翻訳の性能について、偉人の名言を例にとって調査しました。

#まえがき
Globalなご時世なので、日本にいても英語の文章に触れる機会が多々あるかと思います。
私自身も英語の文章は記事や論文等、読むことがあります。私はネイティブではありません。英語を読むのは、個人的には好きですが、やはり日本語の文章と比較して読むスピードが確実に遅くなります。速読するとたくさん読めますが、頭にはあまり残りません。かと言って、いちいち頭の中で日本語に起こして、精読しようとすると時間がかかり過ぎて非効率ですし、かえってそれまでの話の流れを忘れてしまったり、なかったり。
最近、機械翻訳の性能が飛躍していることは周知の事実です。ですが、私個人の意見としてはまだまだ人間の能力の方が上だと考え、殆ど活用してきませんでした。また、英語の能力には多少自身がありました。しかし、あるときに機械翻訳におけるある著名な方のご講演を聞く機会があり、すでにAIによる翻訳の性能はTOEIC940相当と聞いて、仰天しました。そして、実際にGoogle翻訳を使用してみると、一昔前とは全く異なり、割と自然な英語の文章になっていることがわかりました。多少の手直しが必要な箇所はありますが、全体的な翻訳は機械翻訳に任せて、微調整は人間が行うというやり方ならば、人間単独で行う作業よりも優れているように感じました。

#実際に翻訳してみた
偉人の名言を題材にGoogle翻訳の性能を見てみます。最近、話題になっているみらい翻訳も使ってみました。なぜ偉人の名言にしたかと言うと、論文や記事よりかは、心に響く偉人の名言の方がその内容の腹落ち度を直感的に理解できるかなと思ったからです。
そして、私の独断と偏見により、全くジャンルの異なるアメリカ出身のご三方の名言を拝借させて頂きました。よく知られている日本語訳とも比較しています。


##[キング牧師](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%83%BB%E3%83%AB%E3%83%BC%E3%82%B5%E3%83%BC%E3%83%BB%E3%82%AD%E3%83%B3%E3%82%B0%E3%83%BB%E3%82%B8%E3%83%A5%E3%83%8B%E3%82%A2)
###原文
**_I have a dream that one day this nation will rise up and live out the true meaning of its creed: “We hold these truths to be self-evident, that all men are created equal.”_**

###人間による翻訳
**_私には夢がある。それは、いつの日か、この国が立ち上がり、「すべての人間は平等に作られているということは、自明の真実であると考える」というこの国の信条を、真の意味で実現させるという夢である。_**※

どこかの大統領にも聞かせたくなりますね。。
※[米国大使館サイト](https://americancenterjapan.com/aboutusa/translations/2368/)より引用。

###Google翻訳
**_いつかこの国が立ち上がり、信条の真の意味を生き抜くという夢があります。「私たちはこれらの真実を自明であり、すべての人が平等に創造されると考えています。」_**

理解するには問題ありませんね。演説となるともう少しパンチが必要かも。

###みらい翻訳
**_いつの日か、この国が立ち上がり、「私たちは、すべての人が平等に創造されるというこれらの真実は自明であると考えている。」という信条の真意を実現することを夢見ています。_**

正直、想像以上に自然な日本語で驚きました。

##[アームストロング船長](https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%BC%E3%83%AB%E3%83%BB%E3%82%A2%E3%83%BC%E3%83%A0%E3%82%B9%E3%83%88%E3%83%AD%E3%83%B3%E3%82%B0)
###原文
**_That's one small step for man, one giant leap for mankind._**

###人間による翻訳
**_これは一人の人間にとっては小さな一歩だが、人類にとっては偉大な飛躍である。_**

名言中の名言。
###Google翻訳
**_これは人間にとっては小さな一歩であり、人類にとっては大きな飛躍です。_**

まさかのほとんど一致です。。ただ、「ひとりの」の訳がありません。実は、原文ではmanの前にaが無く、文法上おかしいのですが、そこにひっかかってしまったみたいですね。

###みらい翻訳
**_これは人間にとっては小さな一歩であり、人類にとっては大きな飛躍です。_**

Google翻訳と完全一致。流石に文法上のミスまで考慮して訳してはくれないということですね。

##[ヘミングウェイ](https://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%BC%E3%83%8D%E3%82%B9%E3%83%88%E3%83%BB%E3%83%98%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%A6%E3%82%A7%E3%82%A4)

###原文
**_You can’t get away from yourself by moving from one place to another._**

###人間による翻訳
**_あちこち旅をしてまわっても、自分から逃げることはできない。_**

奥が深いですね。

###Google翻訳
**_ある場所から別の場所に移動しても、自分から逃げることはできません。_**

まさにそのとおり。
###みらい翻訳
**_ある場所から別の場所に移動しても、自分から離れることはできません。_**

Google翻訳との違いは、逃げるか離れるかの違いのみ。両機械翻訳ともに、今回のように「場所の移動＝旅」のような意訳はできないようです。

#さいごに
どうでしたでしょうか。
たしかに、詩的な文章では機械翻訳はまだまだかもしれませんが、実用上は全く問題ないことがわかっていただけたかと思います。
私はこれからも機械翻訳を使っていきます。
それでは。
nginx unitをmacで使う。【ruby(rbenv)編】

macでnginx-unitを使うのにちょっと苦戦したのでメモ代わりに。

# インストール
`brew`にはないのでソースからインストール
（公式を参照の上、ソースをダウンロード）
`git clone https://github.com/nginx/unit`
（ソースはどこでもOK）
ダウンロードしたフォルダに移動して、`./configure`を。
だいたい代表的なオプションは以下の通りかと。

```
configure options
--prefix=/usr/local/    →だいたいこのへんの場所でOK
```


フルになるとこんな感じ
`./configure --prefix=/usr/local/ とか`

# ruby用のconfigure
今回はrubyを動かしたいので、
`./configure ruby`
も実行。
ここで`rbenv`からrubyをインストールしてる場合は、rbenvのバージョンと合わない場合あり。
(元から入ってるやつを指定してしまう？)

```
＊参考
$ ./configure ruby
configuring Ruby module
checking for Ruby library ... found
checking for Ruby version ... 2.3.7
 + Ruby module: ruby.unit.so
(ほんとはrbenvで2.4.1のrubyが入ってる)
```

これはこのissueみて解決。
https://github.com/nginx/unit/issues/224
`CONFIGURE_OPTS="--enable-shared" rbenv install 2.4.1`
どうやらso作ってないと駄目なそうな。
ちなみに、`--enable-shared`はダイナミックリンクライブラリを作るオプション。

# build & install
`make`
`make install`
で入るはず。上のprefixでroot所有のフォルダだと`sudo make install`で。

# 起動
`unitd`でひとまず起動できる。
他のオプションは
https://unit.nginx.org/installation/#startup
を参照。
大体は、`--control --log --pid`あたりのオプションを付けるかな。
(デフォルトだとunix-socketになるので、`--control 127.0.0.1:8080`とかの方が楽かも。まぁテストだし。)

# 設定ファイルのPUTなど
これは公式を参照するほうが良い。railsはうまく起動できなかったりでハマリ気味。
https://unit.nginx.org/configuration/
JSONをunitdのcontrolにPUTしたりする。

## configureオプションミスったり、変なのinstallしちゃったとき
`make clean`か
`make uninstall`
で解決
android-midi-lib の ビルド方法

#0.はじめに
音楽加工アプリを作成中のKayleeです。
MIDIファイルの操作ができるOSSの「android-midi-lib」のビルド・androidstudioへの取り込み方について紹介します。

#1. 必要な環境
- android studio
- Gradleコマンド

ちなみにですが、私の環境はこんな感じです。
- android studio:3.4.1

#２. Gitから持ってこよう！
gitからダウンロードでもcloneでもどちらでもいいので、とりあえず、自分の環境にソースを持って来ましょう。

#3. ビルドしよう！
pom.xmlファイルがあるディレクトリに移動して、下記コマンドでgradleビルドしてください。

```ruby:コンソール
gradle init --type pom
gradle build
```

![スクリーンショット 2019-08-12 16.43.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/433507/66edb7f6-653a-0d87-1b45-b947b51ffb7f.png)

 
#4. 出来上がり！
どこかにjarファイルができているはずです。
それをAndroidStudioに取り込んで、サンプル等を動かしていたら、うごくかと思います。
(ファインダーでみた場合のjarファイルを載せておきます。)


![スクリーンショット 2019-08-12 17.02.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/433507/1457667e-f3e7-65f0-27f7-43382e41dacd.png)


#5. 実装方法

ReadMeに記載のあるやり方では、AndroidStudioでは動きません。X(
実装方法について、次回の記事で書こうと思います！
Vostro 1310 に Lubuntu 18.04.3 LTS をインストールすると起動, シャットダウンが遅い

# 参考文献

- [16.04 - Boot very slow because of drm_kms_helper errors - Ask Ubuntu](https://askubuntu.com/questions/893817/)

# 結論

- `/etc/default/grub` の `GRUB_CMDLINE_LINUX_DEFAULT` 変数に `video=SVIDEO-1:d` と追記する。

# 詳細

- 症状はタイトルの通り。
- `/var/log/syslog` には以下のメッセージが出力されていた。

```text:/var/log/syslog
Sep 23 17:55:32 xxxxxxxx kernel: [   14.735436] [drm:drm_atomic_helper_wait_for_flip_done [drm_kms_helper]] *ERROR* [CRTC:36:pipe A] flip_done timed out
Sep 23 17:55:43 xxxxxxxx kernel: [   24.975842] [drm:drm_atomic_helper_wait_for_dependencies [drm_kms_helper]] *ERROR* [CRTC:36:pipe A] flip_done timed out
Sep 23 17:55:53 xxxxxxxx kernel: [   35.215836] [drm:drm_atomic_helper_wait_for_dependencies [drm_kms_helper]] *ERROR* [CONNECTOR:53:SVIDEO-1] flip_done timed out
```

- `/etc/default/grub` を以下の通りに編集したところ, 問題なく起動, シャットダウンするようになった。

```diff:/etc/default/grub
--- grub.20190923       2019-09-23 17:52:58.890496178 +0900
+++ grub        2019-09-23 18:04:41.276018758 +0900
@@ -7,7 +7,7 @@
 GRUB_TIMEOUT_STYLE=hidden
 GRUB_TIMEOUT=0
 GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
-GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
+GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=SVIDEO-1:d"
 GRUB_CMDLINE_LINUX=""

 # Uncomment to enable BadRAM filtering, modify to suit your needs
```

どっとはらい。
dynamoidをlocalで動かし、dynamo-adminで確認するdocker-compose



clone && build && up && create dynamodb data

```sh
git clone git@github.com:kajirikajiri/dynamo_test.git
cd dynamo_test
docker-compose build
docker-compose up -d
docker-compose run --rm rails bash
rails c
User.new.save
```

open browser and check

http://localhost:8001/


tell me if you find a bug
俺のUbuntu18.04 LTSの初期設定メモ(前半)

毎度のことながら、覚えが悪く、いろんなサイトを調べて回るのが億劫になってきたので、UbuntuでResponderを始めるまでの環境構築の備忘録 :frowning2:


##サーバー環境
VPSもレンタルサーバーも両方Conohaを使っています。
このはたん。かわいい。
[レンタルサーバー Conoha](https://www.conoha.jp/?btn_id=header_top)

サーバー：Conoha VPS
OS：Ubunu18.04 LTS

[このとも(お友達紹介制度)｜VPSならConoHa](https://www.conoha.jp/referral/?token=BXHW23zfVDfjtzE0H7oMdWYBx716qp.dwlK30xvRmvG9TEUI_Ec-GAE)
「このとも」になってやってもいいって方は、ぜひこちらから
みんなでクーポンGETだぜ。

##Ubuntuの初期設定
ひとまず以下の内容で、簡単に基礎構築。
`ラインの頭が $ の時はユーザー。 # の時はrootです。念の為。`

- ユーザー追加
- SSH設定
- iptablesでポート開放設定

###rootでSSH接続
ConohaVPSで設定したパスワードと発行されたIPを使ってSSH接続


```terminal:local
$ ssh root@xxx.xxx.xxx.xxx    #発行されたIPアドレス
```

###ユーザー追加

```terminal:remote
# adduser hoge    #いろいろ聞かれる、PW以外は空でOK
# gpasswd -a hoge sudo    #ユーザーにsudo権限付与
```


###SSH設定

#####新規ユーザーでパスワードログイン

```terminal:local
$ ssh hoge@xxx.xxx.xxx.xxx 
```

#####.sshディレクトリの作成とパーミッション変更

```terminal:remote
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
```
ローカルに戻って、鍵の用意と転送

```terminal:local
$ ssh-keygen
$ scp ~/.ssh/id_rsa.pub hoge@xxx.xxx.xxx.xxx:~/.ssh/authorized_keys
```
最後に転送した`authorized_keys`のパーミッションを変更

```terminal:remote
$ chmod 600 ~/.ssh/authorized_keys
```


#####SSH設定ファイルのバックアップ

```terminal:remote
$ sudo cp -p /etc/ssh/sshd_confg /etc/ssh/sshd_config.orig
$ sudo nano /etc/ssh/sshd_config
```

#####SSH設定ファイルの編集
`PasswordAuthentication`の設定は、万が一、鍵の生成等でミスってたらログインできなくなる可能性があるので、自信のない方は、先に鍵認証でログインできることを確認してから設定を行うことをおすすめします。

ポート変更

```diff:/etc/ssh/sshd_config
- #Port 22
+ Port SSHポート番号
```
rootでのログインを禁止

```diff:/etc/ssh/sshd_config
- PermitRootLogin yes
+ PermitRootLogin no
```

公開鍵での認証を許可

```diff:/etc/ssh/sshd_config
- #PubkeyAuthentication yes
+ PubkeyAuthentication yes
```

パスワードを使ったログインを禁止

```diff:/etc/ssh/sshd_config
- PasswordAuthentication yes
+ PasswordAuthentication no
```

空のパスワードを禁止

```diff:/etc/ssh/sshd_config
- #PermitEmptyPasswords no
+ PermitEmptyPasswords no
```

sshdの設定ファイルのシンタックスチェックしてから再起動

```terminal:remote
$ sudo sshd -t
$ sudo systemctl reload sshd
```
最後に鍵認証でSSH接続できたら完了。



###iptablesの設定

```terminal:remote
$ sudo ufw allow 80   # http

$ sudo ufw allow 443  # https

$ sudo ufw allow SSHポート番号  # SSH

$ sudo ufw default deny # 基本拒否

$ sudo ufw enable # エナブル

$ sudo ufw status verbose # 設定確認
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
xxxxx                      ALLOW IN    Anywhere
80                         ALLOW IN    Anywhere
443                        ALLOW IN    Anywhere
xxxxx (v6)                 ALLOW IN    Anywhere (v6)
80 (v6)                    ALLOW IN    Anywhere (v6)
443 (v6)                   ALLOW IN    Anywhere (v6)

```

今後導入していきたいやつ：

- Python(pyenv)
- Responder
- nginx
- certbot


参考：
[Ubuntu でユーザを追加する (adduser を使うべき)](http://masaoo.blogspot.com/2009/08/ubuntu-adduser.html)
[Ubuntuユーザ追加とsudo権限付与。ユーザ/グループ操作まとめ](http://www-creators.com/archives/241#Ubuntu)
[Ubuntu ServerのSSHのポート番号を変更し鍵認証に変更する](https://sioyaki.com/entry/2018/06/19/171439)


###後半に続く
[俺のUbuntu18.04 LTSの初期設定メモ(後編)](https://qiita.com/dansuke@github/items/5a45b078d9ee9dedccaa)
CircleCI2.1 Orb入門

# 環境

- CircleCI version 2.1
- Slack Orb 3.3.0

# やりたいこと

CircleCIの特定のJob（デプロイ等）の結果（成功 / 失敗）をSlackに通知したい。

# 設定

- Slack上で、通知用のチャンネルを作成する。

- Slack上で、`Incomming Webhook`の設定を行う。

- CircleCI上で、該当プロジェクトの`Environment Variables`に、`Incomming Webhook`の設定画面で取得した`Webhook URL`を`SLACK_WEBHOOK`として設定する。

- CircleCIのconfigを設定する（以下、必要な箇所のみ抜粋）。
  - stepsの最後に、`slack/status`を設定する。
  - `slack/status`の他に、`slack/approval`や`slack/notify`も設定できる。

```.circleci/config.yml
version: 2.1

orbs:
  slack: circleci/slack@3.3.0

jobs:
  :
  deploy_to_staging:
    executor: default
    steps:
      :
      - slack/status:
          mentions: channel # mention先を指定。複数ユーザ設定時は、"USER1,USER2,USER3"とする。
```

# 参考URL

- [slack-orbのgithubリポジトリ](https://github.com/CircleCI-Public/slack-orb)
- [CircleCIのOrbクイックスタートガイド](https://circleci.com/orbs/registry/orb/circleci/slack)
Android Studio x Wear Os by Googleでハマったところ

ハマったところを追記していきます

## 環境
Windows 10
Android Studio 3.5
Tic Watch Pro

## Wear OS by Google(スマートウオッチ)にアプリのインストールができない
`Gradle build`はすぐ終わるが、`Install`で進行度が進まない。
提供元不明のアプリのインストールの許可をしていないか、きちんと接続されているかをチェックしたがおかしいところはなかった。

### 原因
Bluetooth経由でデバッグしていたので、スピードが遅くアプリのインストールに非常に時間がかかり、止まっているように見えただけであった。

### 解決方法
[Wifi経由でのデバッグ](https://developer.android.com/training/wearables/apps/debugging?hl=ja)をすることで、一瞬で(正確には10sで)アプリのインストールが終了した。
Maxトレーニング (2) jitterによる映像の再生

## jitterについて

maxには映像信号を取り扱うことのできる「jitter」というソフトウェアが内蔵されています

・**jitter**で可能なこと

　動画の再生, メディアファイルの制御, 画像表示, メディアデータの記録と加工

　PC内蔵カメラを始めとしたビデオキャプチャと映像の出力, 2D/3DCGの合成



## jit.qt.movieの配置とHelpの確認

今回はjitterで映像再生を試してみましょう

・新規パッチを開く
`File → NewPacher`
・jit.qt.movieの配置
`n(object) → jit.qt.movieと入力`
・ヘルプの表示
`jit.qt.movieオブジェクトを選択 → 右クリックから「`~ Helpを選択」

![movieHelp](https://tva1.sinaimg.cn/large/006y8mN6gy1g79lrfq46bg30oh0jm7do.gif)

各オブジェクトを右クリックし、Helpを選択することでオブジェクトの使い方を見ることができます
Helpファイルのロックを外すと、編集ができます

## 必要なものをコピー＆ペーストで持ってくる（よく使う）

ここから使いたいメッセージなどを選択し、コピーできます
オブジェクトの複数選択は、`shiftキー`を押しながら行います

`read`, `start`, `stop`, `rate`, `autostart`,`jit.qt.movie`, `toggle`, `jit.world`を選択し
`Ctrl+C`でコピーします 

Helpファイルを`Ctrl+w`で閉じて、`ctrl+V`で貼り付け
![copyNpaste](https://tva1.sinaimg.cn/large/006y8mN6gy1g79lrh13fqg30sa0mmk91.gif)

`read`ボタンを押すと、映像ファイルを開くことができるので
下記のパスをたどってファイルを選択します
**C:\Program Files\Cycling '74\Max 8\resources\media\jitter\rca**

> ちょっといじってみましょう
> `start`を押してから`jit.world`の上の`toggle`をONにすると別ウィンドウで映像が再生されます
> rateをいじると、どうなるでしょうか

`jit.world`は映像をウィンドウに出力するオブジェクトです
バックグラウンドで再生されていると少しわかりにくいので、プレビューウィンドウを表示するオブジェクトを追加します

## プレビュー用のjit.pwindowの配置

・jit.pwindowの配置
`n(object) → jit.pwindowと入力`
`jit.movie`オブジェクトと接続

必要に応じてウィンドウの大きさを変えます
![jitPwindow](https://tva1.sinaimg.cn/large/006y8mN6gy1g79ko8nns7g30sa0mmkjt.gif)





## Attributesを用いたフルスクリーン表示

各オブジェクトの持っている機能（Attributes：アトリビュート）は、左上のインレットを右クリックすると表示させることができます
ここでは、`jit.world`の機能であるフルスクリーン表示とフルスクリーンの解除をするためのアトリビュートを2つ追加しています

・fullscreen（フルスクリーン表示）
`fullscreen`アトリビュートの右にあるToggleをクリックするとフルスクリーン表示

・esc_fullscreen（escキーでフルスクリーンを解除）

`esc_fullscreen`アトリビュートのToggleにチェックが入っている状態がON
![jitWorldFullscreen](https://tva1.sinaimg.cn/large/006y8mN6gy1g79l0hjxd7g30jo0mmx6t.gif)



パッチは[こちら](https://www.dropbox.com/s/u1mmt31g4eheqyk/simplemovieplayer.maxpat?dl=1
)からダウンロードできます

Desktopに作った「DEbasic」フォルダ内に「Max」フォルダを作り、その中にパッチを保存しましょう

> 少し機能を追加したMoviePlayerを[こちら](https://www.dropbox.com/s/xmhsuilvjchctja/someFuncMoviePlayer.zip?dl=1
)からダウンロードし、挙動を確かめてみましょう
> `receive`と`send`でやり取りされる内容も確認してみます
CentOS7でOpenJDKでServletのwarファイル作りたい。mvn無しで。インターネット非接続で。

大体５分くらいで行けます。
unzipをインストールするところ以外はroot以外のユーザーでも行けます。
環境を汚す程度も極小なはず。

**（ターゲット環境）**
CentOS 7 1810(RHEL 7.6相当)
Minimal Install
インターネット非接続

**（インターネットに接続した端末で実施）**
１．OpenJDK 13をダウンロードしてCentOSサーバーに転送する。
https://jdk.java.net/13/
(Linux/x64) openjdk-13_linux-x64_bin.tar.gz
187MB。ダウンロード１分ぐらい。

２．unzipをダウンロードしてCentOSサーバーに転送する。
※CentOSサーバーで「yum install unzip」が使えるならそれで。
http://mirror.centos.org/centos/7/os/x86_64/Packages/unzip-6.0-20.el7.x86_64.rpm
169KB。ダウンロード数秒。

３．Open LibertyをダウンロードしてCentOSサーバーに転送する。
https://openliberty.io/downloads/#runtime_releases
（All GA Features）openliberty-19.0.0.9.zip
141MB。ダウンロード２分ぐらい。

**（CentOSサーバーで実施）**
１．OpenJDK展開する。
```# tar xf openjdk-13_linux-x64_bin.tar.gz```
→ jdk-13 と言うディレクトリ出来る。

２．unzipインストールする。
```# sudo rpm -i unzip-6.0-20.el7.x86_64.rpm```

３．Open Liberty展開する。
```# unzip openliberty-19.0.0.9.zip```
→ wlp というディレクトリ出来る。

４．サンプルアプリ用のディレクトリ作る。
```# mkdir -p sample/src```
```# mkdir -p sample/WebContent/WEB-INF/classes```
```# mkdir -p sample/WebContent/WEB-INF/lib```

５．Servletのソース作る。

```
# cat << 'EOF' > sample/src/Hello.java
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/")
public class Hello extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("Hello!");
    }
}
EOF
```

６．Servlet、コンパイルする。
```# jdk-13/bin/javac sample/src/Hello.java \```
```  -d sample/WebContent/WEB-INF/classes \```
```  -cp "wlp/dev/api/spec/*"```
→ sample/WebContent/WEB-INF/classes/Hello.classファイル、出来る

７．warファイル、作る。
```# jdk-13/bin/jar cf sample/sample.war -C sample/WebContent .```
→ sample/sample.warファイル、出来る。

**（動作確認）**
１．Open Liberty、動かす。
```# export JAVA_HOME=~/jdk-13```
```# wlp/bin/server start```
→ CentOSサーバーのport 9080が他のサービスで使われている場合は wlp/usr/servers/defaultServer/server.xml ファイルを編集して、httpPortを9080から他の適当な番号に修正する。

２．warファイル、置く。
```# cp sample/sample.war wlp/usr/servers/defaultServer/dropins/```
→ wlp/usr/servers/defaultServer/logs/messages.log に ```A CWWKT0016I: Web application available (default_host): http://localhost:9080/sample/```、出る。

３．curl、繋ぐ。
```# curl http://localhost:9080/sample/```
"Hello!"、出る。

４．(Optional) Open Liberty、止める。
```wlp/bin/server stop```
Android使ってて必要になったこと①

#PCからAndroidにファイル転送する方法
###面倒な方法
windowsに表示されるAndroid内部ストレージは、Androidがwindowsに見せるためのパスなので**実際のAndroidパスではない。**

実際のAndroid内部のパスを調べるためには、
**adb shell**
をコマンド入力することで内部に入ることができる。

Android内部はlinuxなので
**lsコマンド**でディレクトリを確認することができる。

sdcardにcdコマンドでいどうすると、
testフォルダが見つかるのでそこまでのパスをpwdコマンドで出し、コピーしておく。

exitコマンドでAndroid内部からwindowsに出て、
**コピーしたパスを使用しadb pushコマンドでAndroidにファイル転送することができる。**

###楽な方法
DeviceFileExplorerを使用する。
入れたいファイルを選択し、右クリックでUploadする。

#アプリの署名
アプリの署名が異なるときは上書きすることができない。
そのため**一旦アプリ自体をアンインストールする必要がある。**

上書きインストールするコマンド
**adb -r install アプリ名**

###ログをメモに出力したいときのコマンド
保存場所に移動してから、以下のコマンド。
**adb logcat -v time > (ファイル名).txt**
デバイス内での機密情報の暗号化テスト

#はじめに#

デバイス内でWifiのSSIDやパスワード、クラウド接続トークンなどを保存したいが、平文での保存が適当でない場合が考えられる。その際に如何に解読されにくくするかという実験。

#AESによる暗号化#

今回はAES-GCMを選択。いろいろ勉強してみたところ以下のような構成を考えた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/316d65a2-3a0e-38e7-fcdf-181d2530147f.png)

TLS1.2で使用される際は、ハンドシェイク後に相手と共有されるIV（初期化ベクトル）、Key（共通鍵）はどちらも生成してIVはフラッシュ上（保護なし）、KeyはATECC608Aの読み出し禁止スロットに保管。

暗号化された暗号文、その際に出るTagも併せてフラッシュ上（保護なし）に置く。
これを読みだして復号ができるかを試す。

#前提条件#
[セキュアなIoTデバイス通信をやってみた(MbedTLS移植編-Configure)](https://qiita.com/kmitsu76/items/a37f285d4f2eff377f66)で行ったATECC608Aの初期化が完了していること。
ソースコードは下記。
[https://github.com/kmwebnet/ECC608-Configure](https://github.com/kmwebnet/ECC608-Configure)


#実装#

コードを実際に書いて実装を試していく。

[kmwebnet/ECC608-AES-GCM-test
](https://github.com/kmwebnet/ECC608-AES-GCM-test)にてコードを公開中。

スロット5番をAES Keyとして書き込みのみ、読み出し不可の設定にする。

これを実現するため、Microchip社のテストコードのコンフィグを確認。
[cryptoauthlib/python/tests/test_device.py](https://github.com/MicrochipTech/cryptoauthlib/blob/master/python/tests/test_device.py)
今回のATECC608Aでのコンフィグは下記の通りとなる。

Config Zone
バイト13：'AES_Enable': 1に設定
'SlotConfig No5': 0x36C4　→　0x00C4
ビット15-12	：Writeconfig　
ビット8-11	：WriteKey
上記を0にし書き込み可とする。

'KeyConfig No5' : 0x001C →　0x0018
ビット2-4	：Keytypeを　0b111(7)から0b110(6)に変更し、AESキーを扱うスロットとして設定する。

上記の設定をcrypto.c冒頭でも確認する。

#crypto.c実装解説#

crypto.hにてスロット8の416バイトの内訳を定義した。
76バイトの位置から12バイト：IV
88バイトの位置から16バイト：Tag
104バイトの位置から240バイト：暗号化データ

2つの関数を作成。
ATCA_STATUS encryptwrite(uint8_t *data, size_t length)
デバイス内へdataを書き込む
1. atcab_randomで32バイトの乱数を得る。
2. スロット5へ乱数を書き込み、AES Keyとする。
3. atcab_aes_gcm_init_randでIVの初期化を行い、スロット8へ書き込む
4. 暗号化する240バイトを16バイト単位で暗号化し、15回ループを回す。
5. atcab_aes_gcm_encrypt_finishでループ終了、Tagを取得
6. Tag,データをスロット8へ書き込み終了  

ATCA_STATUS decryptread(uint8_t *data, size_t length) 
デバイス内からdataを読み込む
1. デバイス内からIV,Tagを読み込む
2. atcab_aes_gcm_initでIVを設定する。
3. 復号する240バイトを16バイト単位で復号し、15回ループを回す。
4. atcab_aes_gcm_decrypt_finishでTagの確認を行い、正常なら終了。

上記の2つで、AESキーをデバイスに秘匿した状態、すなわちこのデバイスがないと復号できないデータを作成することができた。
Pythonのarray.arrayとnumpy.ndarrayを比べる

#目的
Pythonは、C言語等と違って、配列というはあまり目立たない？、というか、
配列はnumpyに任せている感じ。
ただ、arrayというモジュールが一応あるので、紛らわしいかもしれないので、
比較してみる。

この記事の目的は、
numpyを使う際に、細かいところで迷わないため。。。
一応、**array**というモジュール名は、少なくとも、numpyを指さないので、
別のものがあることを明示したいだけです。

最近、Pythonを使い始めたものとしては、ここに記載のarrayに出会ったことは
一度もないので、杞憂だと思いますが。。。。


#比較

array.arrayとnumpy.arrayの両方を動作させてみる。
以下のとおり、いろいろ、違います。

```
>>> a = array.array('I',[5,9])
>>> a
array('I', [5, 9])
>>>
>>> b = array.array('I',[20,300])
>>> b
array('I', [20, 300])
>>> type(b)
<class 'array.array'>
>>> c = a + b
>>> c
array('I', [5, 9, 20, 300])
>>>
```

```
>>>
>>> import numpy as np
>>> aa = np.array([5,9])
>>> aa
array([5, 9])
>>>
>>> bb = np.array([20,300])
>>> bb
array([ 20, 300])
>>> type(bb)
<class 'numpy.ndarray'>
>>> cc = aa + bb
>>> cc
array([ 25, 309])
>>>
>>>

```


#まとめ
コメントなどあれば、お願いします。
lambdaに関数を渡す

# lambdaに関数を渡す

初めて投稿します。アウトプットの場としてもQiitaを利用していこうと思っています。
現在なんちゃってエンジニアのため生暖かい目で見守ってもらえると嬉しいです。

listに対して一部のパラメータを変更しながら処理するための手法です。
ベタに書かせてもらいますので特に意味のある結果を求めていません。

```
any_data_list = (range(10)) # 0 to 9

# 固定で渡される変数
val1 = 10
val2 = 20

# ３つの値を受け取って単に掛け算結果を返す関数
def func(fix_val1, fix_val2, var_val3):
    return fix_val1 * fix_val2 * var_val3

# any_data_listの全要素に対してfunc関数を処理する
new_list = map(lambda var_val:func(val1, val2, var_val), any_data_list)

# listに変換して結果を出力
print(list(new_list))
```

```
出力結果
0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800]
```

```
要素毎の計算結果
var_val3（3番目の値）だけが変動している

 1回目: 10 * 20 * 0 = 0
 2回目: 10 * 20 * 1 = 200
...
10回目: 10 * 20 * 9 = 1800
```

以下ハマったというか理解するまでの流れです。
> new_list = map(lambda var_val:func(val1, val2, var_val), any_data_list)

この行の書き方が最初腑に落ちず処理される順番を1つずつ考えて見ました。
1. var_valにany_data_listの要素が順番にくる
2. 受けとったvar_valを使ってfunc関数が呼ばれる
3. func関数の第3引数にany_data_listの要素が入ってくる

とここまで書いてみたものの、結局の所下記のようにlambdaで処理を完結する場合と考え方同じですね。
最後に関数に渡る部分で少し混乱していました。

```
map(lambda x: x**2, any_data_list)
```

1. xにany_data_listの要素が順番にくる
2. 渡ってきたxを介してany_data_listの要素を二乗する

(何か自分の中でスッキリしたので良しとしておこう)
Software Defined Perimeter (SDP)って何だ？

# はじめに
少し前にSoftware Defined Perimeter(以降SDP)という単語を知り、少し調べたので備忘録程度にまとめました。

従来、外部ネットワークから、内部ネットワークにアクセスする際は、SSL-VPN等を用いてアクセスしていました。
また、外部と内部の間には、FW等を配置して内外のアクセスを制御しています。

しかし近年では、クラウド環境や個人端末の利用など、接続元や接続先が多種多様になり、従来の方法では柔軟に防御することが難しくなってきました。
そこで出てきたのがSDPというアーキテクチャです。
SDPは、ゼロトラストモデルに基づいており、VPN+FWのみよりも安全にリモートアクセスができます。

# SDP
SDPは、接続認証を行うためのコントロールプレーンと、接続先とVPN接続するデータプレーンの2つから構成されます。
> ![sdp-arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14266/9230ecfb-8c7b-1746-b7ca-b209ec14c031.png)
> 引用: SDP Architecture Guide v2

最初、接続元となるホストは、コントローラとVPN接続を行い、コントローラに登録されている自身の情報と接続依頼先を送信します。
コントローラは、受け取った情報から認証を行い、認証を通過すると接続依頼先が存在するかチェックします。
接続依頼先が存在する場合は、接続依頼先となるホストに接続元ホストからの接続要求を行い、接続先ホストのIPアドレス等のネットワーク情報を接続元ホストに返却します。
接続元ホストは、受け取ったネットワーク情報を基に接続先ホストにVPN接続を依頼し、通信が確立されます。

特徴としては、

- 接続先となるホストは、IPアドレス等のネットワーク情報が隠されており、直接的に攻撃を受けることがない。
- コントローラが、どのホストとどのホストを接続可能とするか管理している。
- 接続元は、コントローラの認証を通らないと接続先のネットワーク情報が得られない。

という感じですかね。

# おわりに
SDP自体は、結構前からあるようですが、身近なところで使われている印象は薄いです。
製品としては、テクマトリックス社が販売開始しているようですが、どの程度利用されているのでしょうか。

# 参考
[SDP Architecture Guide v2 | Cloud Security Alliance]
(https://cloudsecurityalliance.org/artifacts/sdp-architecture-guide-v2)
[SPA （Single Packet Authorization）解説 | CSAジャパンブログページ](https://cloudsecurityalliance.jp/newblog/2019/08/27/448/)
[SDP_WG – csajapan](https://www.cloudsecurityalliance.jp/site/?page_id=1167)
[ゼロトラスト・セキュリティ・アーキテクチャ：ソフトウェア定義境界 - Akamai Japan Blog](https://blogs.akamai.com/jp/2018/12/post-14.html)
[テクマトリックス、ゼロ・トラストモデルに基づくセキュアアクセスソリューション「AppGate SDP」を販売開始：EnterpriseZine（エンタープライズジン）](https://enterprisezine.jp/article/detail/11586)
dockerでJenkinsを立ち上げて見た

docker hubから取ってきたJenkinsイメージはどうやら一年くらい前から更新が止まっているらしく動作しないプラグインがある。
そのため、今回ubuntuイメージをベースにJenkinsのコンテナを作成し起動させてみた。
あと、Jenkinsを立ち上げるためにDockerのコマンドについて勉強したので最後にまとめておく。

# Jenkinsイメージの作成
## コンテナの立ち上げ
```bash
$ docker pull ubuntu
$ docker run -ti ubuntu
```
## Jenkinsをインストールするための下準備
```bash
$ apt-get update
$ apt-get install wget gnupg openjdk-8-jre
```
## Jenkinsをインストール
[公式を参考](https://wiki.jenkins.io/display/JENKINS/Installing+Jenkins+on+Ubuntu)

```bash
$ wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | apt-key add -
$ sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
$ apt-get update
$ apt-get install jenkins
```
## イメージの作成
```bash
$ exit
```
上記のコマンドでコンテナから一度抜ける。
その後、下記のコマンドでイメージを作成する。

```bash
$ docker ps -a
# 表示されたコンテナの「CONTAINER ID」を指定する
$ docker commit <CONTAINER ID> <イメージ名>
# イメージ名は、「製作者の名前/イメージ名」にするのが慣習になってるらしい
# ご自由にどうぞ
```

# Jenkinsの起動
## Jenkinsコンテナの作成
```bash
$ docker run -ti -p 8080:8080 <Jenkinsのイメージ名>
```

## Jenkinsサービスの起動
```bash
service jenkins start
```

## Jenkinsにアクセス
`http://localhost:8080/`にアクセスする
その際、パスワードを要求されるので、下記のコマンドを実行しパスワードを入力する

```bash
$ cat /var/lib/jenkins/secrets/initialAdminPassword
```
パスワードが正しければインストールが始まるので、あとはユーザ登録を済ませれば
自由に使うことができます。

データの永続化が。。。という話があるのですが、よくわからないので今回はここまでとする。
時間があれば、Dockerfileを作成してみようと思う。

# docker勉強
##コマンド一覧
- docker pull <イメージ名>
    - docker hubから指定したイメージを取ってくる
- docker run <イメージ名> <実行コマンド>
    - 指定したイメージのコンテナの作成
    - オプション
        - `-d`　バックグラウンド実行
        - `-i`　フォアグラウンド実行
        - `-t`　ターミナルの起動
        - `-p`　ポートの指定（例：8080:80)
    - 終了は`exit`で行う
- docker commit <コンテナID> <イメージ名>
    - イメージの作成
    - イメージ名は、`製作者:イメージ名`にするのが慣習になっているみたい
- docker search <イメージ名>
    - イメージの検索を行う
- docker inspect <イメージ名>
    - イメージの詳細を表示する
- docker ps
    - 実行中のコンテナの一覧を表示
    - オプション
        - `-a`　存在するコンテナの一覧を表示
        - `-n=<数値>`　表示するコンテナの一覧の上位<数値>個だけ表示
- docker logs <コンテナID>
    - 指定してコンテナの実行ログを表示
- docker attach --sig-proxy=false <コンテナID>
    - 実行中のコンテナの中に入る
    - 抜けるときは`ctr + c`
    - `exit`で抜けた場合は、コンテナが停止する
- docker kill(stop) <コンテナID>
    - 実行中のコンテナの停止
- docker start <コンテナID>
    - 指定したコンテナを起動する
- docker build -t <作成するイメージ名> <Dockerfileが存在するディレクトリパス>
    - 指定したDockerfileを元にイメージの作成

## Dockerfileの文法(?)
```Dockerfile:Dockerfile
# FROMで指定したイメージを元に以下の命令を実行する
FROM <イメージ名>
# MAINTAINERは製作者の名前と連絡先を記述
MAINTAINER <名前> <連絡先>
# RUN : ビルド中に実行するコマンド
RUN echo "now building..."
# ADD : ローカルファイルをコンテナ内に転送
ADD <ローカルファイル> <リモートディレクトリ>
# EXPORT : 指定したポート番号の解放
EXPORT <ポート番号>
# CMD : コンテナを起動する際に実行するコマンド
CMD "now runninng..."
```

# 参考資料
- https://dotinstall.com/lessons/basic_docker
- https://tech.cloudgarage.jp/archives/5736
- https://qiita.com/koara-local/items/268dab580346fcef59b5
テキストファイル読み書き(C#)

# 概要

いつもいつもファイル操作を忘れるのでメモ

- 読み込みにはStreamReader、書き込みにはStreamWriterを使う
- Closeし忘れを防ぐため、usingステートメントを使う

### 読み込み

ファイルが存在しない場合は"System.IO.FileNotFoundException"がスローされる。

```cs
// インスタンス生成
StreamReader reader = new StreamReader(<ファイルパス>);

// エンコーディング指定(Shift_JISを指定する場合)
StreamReader reader = new StreamReader(<ファイルパス>, Encoding.GetEncoding("Shift_JIS"));
```

**テキストファイルの内容をコンソール出力するサンプル**

```cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace TextFileReadSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // "TextFile1.txt"を開く
            using (StreamReader reader = new StreamReader("TextFile1.txt"))
            {
                string line;

                // ファイル末尾まで繰り返す
                while(!reader.EndOfStream)
                {
                    // ファイルから1行read
                    line = reader.ReadLine();

                    Console.WriteLine(line);
                }
            }
        }
    }
}
```

### 書き込み

ファイルが存在しない場合は作成される。

```cs
// インスタンス生成
StreamWriter writer = new StreamWriter(<ファイルパス>);

// エンコーディング指定(Shift_JISを指定する場合)
StreamWriter writer = new StreamWriter(<ファイルパス>, Encoding.GetEncoding("Shift_JIS"));
```
**文字列をファイルに書き込み、その内容をコンソール出力するサンプル**

```cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace TextFileWriteSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // "TextFile2.txt" を開く
            using (StreamWriter writer = new StreamWriter("TextFile2.txt"))
            {
                // 文字列を書き込む
                writer.WriteLine("TextFileWriteSample");
            }


            // 書き込んだファイルを開いて内容をコンソール出力する
            using (StreamReader reader = new StreamReader("TextFile2.txt"))
            {
                string line;

                while (!reader.EndOfStream)
                {
                    line = reader.ReadLine();
                    Console.WriteLine(line);
                }
            }
        }
    }
}
```
パラレル・シリアル変換回路について

PoCなどで、プロトタイピングをされている方
多いと思います。

センサーがパラレル出力だったり、シリアル出力なのにパラレルでしか受け取れない…最近では少ないと思いますが、そういう事に遭遇することがあると思います。

今はCPUのインターフェースにシリアル入力(SPIやI2C)があったりしますが、それでも、パラレルにしないと、D/Aが出来ないとか、そう言ったことがあるでしょう。

そういう時は、昔から使われているシリアル-パラレル変換ICや、パラレル-シリアル変換ICなどを使いましょう。

昔のポケコン時代の記事で、6ビットの出力から無理して8ビットを出したりとか、見かけましたが、よく考えると、この変換は実はIC一つで済んで、6ビットも出力が要らなかったりとかそう思ったりします。

話がずれましたが、
シリアル-パラレル変換だと
74HC595に代表されるような品種を選択すれば、4本の線で、
シリアル-パラレル変換が可能です。
基本はシフトレジスタと、ラッチ用のフリップフロップ、
アウトプット用の制御線で4本です。
SPIバスで言うと、MOSI、SCLK、CE、あと一本必要という事です。


逆のパラレル-シリアル変換は、色々な種類がありますが、
74HC165に代表されるような品種があります。
これは、シフトレジスタとだけなようですが、
ほぼSPIバスのMISO、SCLK、CEが必要という事になります。
当然、入力のラッチのためにもう1本必要となりますが。

私はラズパイで、SPIパスに適用して、動したことがあります。
回路さえ組めば、ほぼ100％動くものです。

実際の回路は、また後ほど掲載したいと思います。

SPIバスは意外にも拡張性が高いので、別の実例も次回以降
載せたいと思います。

読者の方のヒントになれば。
Apache Beam/DataflowのPythonとJava比較

# tl;dr
（今のところ）JavaのほうがBeam/Dataflowで使える機能多いよ。
けどPythonも良くなっているよ


# Streaming
Beam/Dataflowでは、無限に来る入力に対してStreaming処理が出来ます。
Streaming処理はPythonでも書くことは出来ますが、DataflowRunnerでは

* ~~[まだBeta](https://cloud.google.com/dataflow/docs/guides/specifying-exec-params#streaming-execution)~~
* ~~[オートスケーリング出来ない](https://cloud.google.com/dataflow/docs/guides/deploying-a-pipeline#autoscaling)~~
* ~~[StreamingEngine出来ない](https://cloud.google.com/dataflow/docs/guides/deploying-a-pipeline#streaming-engine)~~
* ~~[Drain出来ない](https://cloud.google.com/dataflow/docs/guides/stopping-a-pipeline#drain)
（すでにある入力を処理してから、ジョブを止める処理）~~
* ~~[Pipelineの更新](https://cloud.google.com/dataflow/docs/guides/updating-a-pipeline)~~

~~などの制限があります。~~
↑
[最近できるようになりました！](https://cloud.google.com/dataflow/docs/resources/release-notes-service#October_08_2019)

他にもいくつかあり、Beamの[ドキュメント](https://beam.apache.org/documentation/sdks/python-streaming/#unsupported-features)に記載されています。



## Dynamic Destination
PCollectionの中身（Pub/Subのメッセージなど）に応じて、出力先（ファイル名とかテーブル名）を変更することを、BeamではDynamic Destinationと呼びます。

Beam 2.14以上でPythonでも[出来るようになった](https://beam.apache.org/releases/pydoc/2.14.0/apache_beam.io.fileio.html#dynamic-destinations)のですが、まだExperimentです。
（具体的に機能に差があるかは追えてません…）


# 組み込みIO
Beam/Dataflowは、組み込みでいくつかの入出力を[提供しています](https://beam.apache.org/documentation/io/built-in/)。
GCSやBigQuery、Pub/SubなどはJavaでもPythonでもサポートされていますが、S3やKinesisなどはJavaだけです。

（自前で書けば出来るかも…）


# 雑記
* Java書きたくない…そんなワガママな貴方にはScala+[Scio](https://github.com/spotify/scio)もいいかもしれません。
* Pythonの未来は[Portability Framework](https://beam.apache.org/roadmap/portability/)に期待
    * Spark Runnerなどは、この機能でPythonでも書けるようになった？

# 事例
日本に限っても、Dataflow/Beam結構つかわれていますね。
## Java・Scala
* [Bizreachさん（Scala）](https://cloud.google.com/blog/ja/topics/customers/bizreach-google-cloud-platform)
* [Merpayさん](https://tech.mercari.com/entry/2019/05/30/120000)
* [Plaidさん](https://qiita.com/elcih17/items/f77a2d9e58bfab0dd5a3)
* [CA Wiseさん](https://techblog.ca-wise.co.jp/2017/02/post-143.html)
* [GMOアドマーケティングさん](https://techblog.gmo-ap.jp/2018/07/10/cloud-dataflow%E3%81%A7%E8%A4%87%E6%95%B0%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%92%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%80%E6%96%B9%E6%B3%95/)

## Python
[All Aboutさん](https://allabout-tech.hatenablog.com/entry/2017/08/16/114800)
[リクルートライフスタイルさん](https://www.slideshare.net/GoogleCloudPlatformJP/ss-82505732)

## その他（KotlinとかGoとか）
見つけたらコッソリ教えてください
テスト14

```vb

'Excel

Public wd As Variant

Sub テスト()

Set wd = CreateObject("Word.Application")
wd.Visible = True
 
Dim wdDoc As Variant
Dim i As Long
Dim FSO As Object
Dim f As Object
Dim P As String
Dim flg As Integer

P = ThisWorkbook.Path
Set FSO = CreateObject("Scripting.FileSystemObject")
flg = 0
'Wordファイルだけ処理
For Each f In FSO.GetFolder(P).Files

    If FSO.GetExtensionName(f.Path) Like "*doc*" And Not (f.Path Like "*$*") Then
    
        Set wdDoc = wd.Documents.Open(ThisWorkbook.Path & "\" & Dir(f.Path))
         
        KanriNo = Sheets("書類チェック").Range("C3")
        Stype = Sheets("書類チェック").Range("C4")
        ShisyaName = Sheets("書類チェック").Range("C5")
        
        '----------------------------------------------------------------
        'ポイントはファイル名は入れない、マクロ名だけ入れる
'        wd.Run "チェックとPDF保存", KanriNo, Stype, ShisyaName
         
Call チェックとPDF保存(KanriNo, Stype, ShisyaName)
        
        '----------------------------------------------------------------
        
        Set wdDoc = Nothing
        flg = 1
    End If

Next f


wd.Visible = False
Set wd = Nothing
Set FSO = Nothing

If flg = 1 Then
    MsgBox "文書のチェックとPDFへの変換が終わりました。", vbInformation
Else
    MsgBox "チェックするWord文書がありませんでした。", vbInformation
End If

End Sub


Sub チェックとPDF保存(KanriNo, Stype, ShisyaName)

Dim myFileName As String
Dim myFilePath As String
Dim myDoc As Variant
Dim intPos As Integer

'------------------------------------------------------------------------
'2行目から管理番号をチェックする
Dim Rng As Variant
Set Rng = wd.ActiveDocument.Range(0, 60)

With Rng.Find

   .Text = KanriNo
   .MatchByte = True
   .MatchCase = True
   
   If .Execute = False Then
       MsgBox "番号が一致しません。", vbInformation
       Set myDoc = Nothing
       End
   End If

End With

Set Rng = Nothing
 
 '3行目から書類の種類を検索する
Set Rng = wd.ActiveDocument.Range(0, 60)
 
With Rng.Find

   .Text = Stype
   .MatchByte = True
   .MatchCase = True
   
   If .Execute = False Then
       MsgBox "書類の種類が一致しません。", vbInformation
       Set myDoc = Nothing
       End
   End If

End With

Set Rng = Nothing
 
'5行目から支社名を検索する
Set Rng = wd.ActiveDocument.Range(0, 60)

With Rng.Find

   .Text = ShisyaName
   .MatchByte = True
   .MatchCase = True
   
   If .Execute = False Then
       MsgBox "支社名が一致しません。", vbInformation
       Set myDoc = Nothing
       End
   End If

End With

Set Rng = Nothing
 
'------------------------------------------------------------------------

Set myDoc = wd.ActiveDocument

'拡張子のない名称を取得
myFileName = myDoc.Name
intPos = InStrRev(myFileName, ".")
myFileName = Left(myFileName, intPos - 1)

'PDFファイルで保存
myFilePath = myDoc.Path  'Wordファイルと同じフォルダ
myDoc.ExportAsFixedFormat _
  OutputFileName:=myFilePath & "\" & myFileName & ".pdf", _
  ExportFormat:=17
  
'「https://tyama-blog.blog.ss-blog.jp/2017-01-18」を参照
'wdExportFormatPDF=17
'------------------------------------------------------------------------
 
wd.ActiveDocument.Close

Set myDoc = Nothing

End Sub

```
【初心者向け】SQLで主に扱う4構文について

<h1 class = "">今回のテーマ：SQLで主に扱う4つの構文について</h1>
<ul>
  <li>書いた理由：SELECT, UPDATE, INSERT, DELETE など学習していく中でそれぞれの書き方を整理するため。 </li>
  <li>SQLで主に扱う4構文についての備忘録</li>
</ul>


<p>[目次]</p>
<ul class="table_of_contents">
  <li>
    <a href="#取得">取得</a>
  </li>
  <li>
    <a href="#挿入">挿入</a>
  </li>
  <li>
    <a href="#更新">更新</a>
  </li>
  <li>
    <a href="#削除">削除</a>
  </li>
</ul>

<p>[本題]</p>
<h3 id="取得">取得</h3>
```sql
SELECT カラムA, カラムB
FROM テーブル名;
```

<h3 id="挿入" >挿入</h3>
```sql
INSERT INTO テーブル名 (カラムA, カラムB...)
VALUES (値1, 値2...);
```

<h3 id="更新">更新</h3>
```sql
UPDATE テーブル名
SET カラムA = 値1, カラムB = 値2
WHERE 条件;
```

<h3 id="削除">削除</h3>
```sql
DELETE FROM テーブル名
WHERE 条件;
```
【Android】ViewModel + LiveData + DataBinding + CustomView で変更を通知させる

# はじめに
DatabindingのViewModelに今まではずっとObservableFieldを使っていたのですが、LiveDataを使う+いきなりCustomViewにチャレンジしたら変更が通知されなくてハマったので対処法を書き記します。

# Databinding × LiveData
ViewModelにObservableFieldだけを利用する場合との違いとして、LiveDataを利用する場合はbindingに対してsetLifecycleOwnerをする必要があります。

> To use a LiveData object with your binding class, you need to specify a lifecycle owner to define the scope of the LiveData object. 

https://developer.android.com/topic/libraries/data-binding/architecture
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192408/d6cadcf1-6189-e45f-a2d4-e243bac1b65b.png)

# CustomView x Databinding x LiveData
しかし、Databindingを利用したCustomViewのViewModelでLiveDataを使いたい場合は、そのCustomViewを利用しているActivity / Fragmentでbinding.setLifecycleOwnerをしても変更が通知されませんでした。
どうやら、CustomView側でもsetLifecycleOwnerをする必要があるようです。

```kotlin:CustomView.kt
class CustomView(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) {
    val binding = ViewCustomBinding.inflate(LayoutInflater.from(context), this, true).apply{
        lifecycleOwner = context as? LifecycleOwner
    }
}
```
こうすることで、ActivityまたはFragmentで利用しているViewであれば、LifecycleOwnerに紐付けられるようになります。

ちなみにsetLifecycleOwnerの引数はNullableなので、as?で問題ないです。
contextがLifecycleOwnerを実装していなかった場合は何も起きません。

```java:(参考)ViewDatabinding.setLifecycleOwner
    /**
     * Sets the {@link LifecycleOwner} that should be used for observing changes of
     * LiveData in this binding. If a {@link LiveData} is in one of the binding expressions
     * and no LifecycleOwner is set, the LiveData will not be observed and updates to it
     * will not be propagated to the UI.
     *
     * @param lifecycleOwner The LifecycleOwner that should be used for observing changes of
     *                       LiveData in this binding.
     */
    @MainThread
    public void setLifecycleOwner(@Nullable LifecycleOwner lifecycleOwner) {
        if (mLifecycleOwner == lifecycleOwner) {
            return;
        }
        if (mLifecycleOwner != null) {
            mLifecycleOwner.getLifecycle().removeObserver(mOnStartListener);
        }
        mLifecycleOwner = lifecycleOwner;
        if (lifecycleOwner != null) {
            if (mOnStartListener == null) {
                mOnStartListener = new OnStartListener();
            }
            lifecycleOwner.getLifecycle().addObserver(mOnStartListener);
        }
        for (WeakListener<?> weakListener : mLocalFieldObservers) {
            if (weakListener != null) {
                weakListener.setLifecycleOwner(lifecycleOwner);
            }
        }
    }
```

# 最後に
あまりCustomViewでDatabindingって需要はないんだろうか。
Goのスライスについて

##スライスとは
goでは配列の他に**配列の一部を切り出した**スライスがあるらしい

配列とは違って宣言の時に[]の中が空白

##スライスの扱い方
###初期化
```go

//初期値はnil
var slice []int

//長さと容量も指定できる
slice = make([]int,3,10)
//                 ↑　↑
//   　　　　  　　 長さ 容量
```

###配列とスライスの関係
スライスは配列の一部を切り出したもので`開始`と`終了`を指定すると切り出せる
`{配列名}[開始位置:終了位置]`

```go
//配列
var a = [3]int{0,1,2}

//スライス
a[1:2]
//→[1]
```

配列を`|0|1|2|`とした時に
スライスは(一番左が0)
開始位置：左から数えたときの区切り線の位置
終了位置：左から数えたときの区切り線の位置
なので
`a[1:2]`は
左から数えたときの1番目と２番目の区切り線の間を取ってくるので`1`がスライスとして取得される
ncでhttp server


Just a memo. これでいける。

```
# while true; do echo 'Hello nc server!' | nc -l 80; [ $? != 0 ] && break; done
```

* `[ $? != 0 ]` は Control + c で終了するために必要
* Mac の場合は `sudo su -` してから実行

Getting with curl.

```
$ curl -s http://localhost/
Hello nc server!
```

bye.
【無料】CloudWatchのアラームをSlackへ通知する（Python版）備忘録

#■はじめに
CloudWatchのアラームをSlackへ通知する方法に関する備忘録。
Lambdaのランタイムはpython3.7。

#■基本的な設定について
【参考】CloudWatchアラーム通知をSlackにする
https://qiita.com/hf7777hi/items/e0f43f0fb7e2effa0af8

KMSは20,000リクエストの無料枠がありますが、KMSキーの管理費用は発生する。
KMSのキー削除は最低7日間の保留期間が設定されるため即時削除できない。
![bill-kms.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160237/9b76de82-100f-e9ab-0687-c2ed1a7164ce.png)

#■KMSを使用しない設定について
【参考】Lambdaのcloudwatch-alarm-to-slackで無料と思ってKMS暗号化キーを使ったら2円かかったので関数修正した
https://qiita.com/hf7777hi/items/6226a17ddfc17ad9268c

環境変数「kmsEncryptedHookUrl」の値をIncoming WebhookのURLに設定する。
![環境変数設定.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160237/f40f183e-da0b-6ed3-98f7-6d718a1d1925.png)

環境変数に設定するURLが平文になったので、コードを修正。
（57行目あたりで2か所）

```lamda_function.py
# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
###（１）次の行を修正###
HOOK_URL = os.environ['kmsEncryptedHookUrl'] 
#ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']  #元のコードを一応記載。
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']

###（２）次の行をコメントアウト###
#HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')

```

#■テスト用データのサンプル
【参考】[JAWS-UG CLI] Lambda:#17 Lambda cloudwatch-alarm-to-slack (Python版)
https://qiita.com/tcsh/items/aba31b172cae992aebb3

```サンプルデータ.json
{
        "Records": [
          {
            "EventVersion": "1.0",
            "EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
            "EventSource": "aws:sns",
            "Sns": {
              "SignatureVersion": "1",
              "Timestamp": "1970-01-01T00:00:00.000Z",
              "Signature": "EXAMPLE",
              "SigningCertUrl": "EXAMPLE",
              "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e",
              "Message": "{\"AlarmName\": \"testSlack\", \"NewStateValue\": \"NG\", \"NewStateReason\": \"test for Slack\"}",
              "MessageAttributes": {
                "Test": {
                  "Type": "String",
                  "Value": "TestString"
                },
                "TestBinary": {
                  "Type": "Binary",
                  "Value": "TestBinary"
                }
              },
              "Type": "Notification",
              "UnsubscribeUrl": "EXAMPLE",
              "TopicArn": "arn:aws:sns:EXAMPLE",
              "Subject": "TestInvoke"
            }
          }
        ]
}
```