[Node.js] sinon の stub を使う
Node.js のユニットテストが初めてで一苦労したのでまとめておきます。
目的
Node.js のユニットテストで Python でいうところの
をやりたい。
例えば AWS Lambda 関数で SSM および DynamoDB から値を取得する箇所があった場合、外部サービスとの結合なので実際に AWS にアクセスするのではなくモック化してテストを行いたいのです。
前提
環境
- Lambda ランライム Node.js 14.x
- aws-sdk 2.1215.0
- mocha 10.0.0
- power-assert 1.6.1
- proxyquire 2.1.3
- sinon 14.0.0
実装
Lambda
const aws = require('aws-sdk') const docClient = new aws.DynamoDB.DocumentClient() exports.handler = async (event) => { // SSM const { Parameters } = await (new aws.SSM()) .getParameters({ Names: ["SECRET_KEY_1", "SECRET_KEY_2"], WithDecryption: true, }) .promise() // DynamoDB const params = { TableName: process.env.TABLE_NAME, Key: { id: event.arguments.id } } const result = await docClient.get(params).promise() // 処理 return {} }
テストコード
'use strict' const assert = require('power-assert') const sinon = require('sinon') const proxyquire = require('proxyquire') describe('Test', () => { let lambda let proxyDynamoDB let proxySSM describe('#handler()', () => { beforeEach(() => { // hide lambda log sinon.stub(console, 'log').returns() sinon.stub(console, 'error').returns() // stub aws proxyDynamoDB = class { batchGet() { } get() { } query() { } put() { } delete() { } } proxySSM = class { getParameters() { } } // inject lambda = proxyquire('../index.js', { 'aws-sdk': { DynamoDB: { DocumentClient: proxyDynamoDB }, SSM: proxySSM, } }) // mock SSM getParameters sinon.stub(proxySSM.prototype, 'getParameters').returns({ promise: () => Promise.resolve({ Parameters: [ { Name: 'KEY_1', Value: 'DUMMY_VALUE_1', }, { Name: 'KEY_2', Value: 'DUMMY_VALUE_2', } ] }) }) process.env.TABLE_NAME = '' sinon.stub(process.env, 'TABLE_NAME').value('DUMMY_TABLE_NAME') const dynamoDbStub = sinon.stub(proxyDynamoDB.prototype, 'get') // mock DynamoDB get dynamoDbStub.withArgs({ TableName: process.env.TABLE_NAME, Key: { id: 'DUMMY_ID' } }).returns({ promise: () => Promise.resolve({ Item: { name: 'DUMMY_NAME', email: 'DUMMY_EMAIL', } }) }) }) it('テスト1', async () => { const event = { "requestContext": { "id": "DUMMY", } } await lambda.handler(event, context, () => { }).then( (data) => { assert.equal(data.isAuthorized, true) }, (err) => { # FIXME: Lambda で error 発生時にテスト失敗としたい。 # この書き方間違ってる気がするが、とりあえずテスト失敗には気がつける。 assert.equal(0, 1, 'test failed.') }) }) afterEach(() => { sinon.restore() }) }) })
テスト実施
$ npm i $ npm test
補足
下書きのまま長期間熟成した記事なので色々古いと思います。
[AWS SES] AWS SES でサンドボックス外に移動してから送信元メールアドレスを設定したい
結論
できません。
では先に検証済み ID を作れとの記載はないですが、通常は送信元にするメールアドレスを準備の上 SES サンドボックス解除を行うと思いますので、ほとんどの方には無意味な記事になっております。
前提
- 日毎の送信メール数の上限緩和をしたい。 → つまりサンドボックス解除したい。
- 送信元メールアドレスはまだ決まっていない。→ つまりまだ検証済み ID を登録できない。
要は「サンドボックスから脱出だけしておいて、メールアドレスを取得してから送信元メールアドレスを指定したい」という状況です。
やったこと
AWS 管理コンソールから Amazon SES > Account dashboard を開くとサンドボックス環境下である旨の警告が表示されているので、「Request production access」を選択します。
ユースケースに沿った内容を「Use case description」に記載して「Submit request」を選択します。
AWS Support > Your support cases にユースケースの記述が不足している旨の返信が即座に届きます。(最初からしっかり書いていれば届かないかもしれません。)
Amazon から届いた返信
平素は Amazon Web Servicesをご利用いただき、誠にありがとうございます。
このたびは、送信制限の申請をいただきありがとうございます。残念ながら現時点ではお客様のユースケースについて十分な情報がないため、この申請を承認することはできません。
Amazon SES の使用計画について詳しい情報をご提供いただければ、申請が承認される可能性があります。返信の際に、お客様のメール送信プロセスや手順について、可能な限り詳しい情報を記載してください。
たとえば、メールを送信する頻度、受信者リストのメンテナンス方法、バウンス、申し立て、解除申請の管理方法についてご説明ください。送信する予定のメールのサンプルをご提供いただければ、お客様が高品質のコンテンツを送信していることを確認するために役立ちます.
この情報を受け取り次第、お客様の申請を審査させていただきます。24時間以内にご連絡をいたします。必要な情報をすべてご提示いただいた場合は、24時間以内に申請を承認いたします。追加で情報が必要な場合は、申請の実装に長くかかる可能性がございますことをご了承ください。
Amazon Web Services にお問い合わせいただき、誠にありがとうございます。AWS Support > Your support cases > Details の画面の「Reply」を選択し、ユースケースについて詳細を記述して「Submit」を選択します。
- 返信は即座に返ってこないため翌営業日まで待ちます。(午前中に返信していれば当日中に返信来るかもしれません。)
- 検証済み ID を登録する必要がある旨の返信が届きます。
Amazon から届いた返信
平素は Amazon Web Servicesをご利用いただき、誠にありがとうございます。
このたびは送信送信制限の引き上げの申請をご送信いただき、ありがとうございます。 残念ながら、お客様のアジアパシフィック (東京)リージョンに検証済みIDが登録されていないため、現時点ではお客様の申請を承認することができません。
IDとはメールを送信するドメインまたは E メールアドレスのことを指します。詳細については、こちらをご覧ください(https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/verify-domains.html)。
Amazon SESのメールが送信されるメールアドレスやドメインについての情報をご提供いただければ、申請が承認される可能性があります。この情報をご提供いただける場合は、このメールにご返信ください。
返信の際、検証済みIDの登録の確認をお願いいたします。
この情報を受け取り次第、お客様の申請を審査させていただきます。24時間以内にご連絡をいたします。また、必要な情報をすべてご提示いただいた場合は、24時間以内に申請を承認いたしますが、追加で情報が必要な場合は、申請の実装に長くかかる可能性がございますことをご了承ください。
Amazon Web Services にお問い合わせいただき、誠にありがとうございます。 - Amazon ご指摘の通り、検証済み ID は未登録です。
おわりに
先に Amazon SES > Configuration: Verified identities > Create identity にて検証済み ID を登録してからサンドボックス解除申請(Request production access)を行いましょう。
ちなみにまだ送信元メールアドレスは手に入ってないので、このあと検証済み ID を登録すればそれでサンドボックス解除できるかは確証無いです。(他にもハマリポイントあるかも。)
2022.10.7 追記
このあと検証済み ID を登録すればそれでサンドボックス解除できるかは確証無いです。(他にもハマリポイントあるかも。)
検証済み ID を作成し、作成した旨をサポートの「Reply」から返答したところ翌営業日にはサンドボックス解除と上限緩和ができました。
[Amplify] ARM マシンでの開発で Amplify の Lambda Layer に x64 の sharp をインストールしたい
8 ヶ月ぶりの更新がこんなしょぼい内容であれですが、メモ代わりということで。
前提
課題
AWS Lambda で sharp を利用するには arch=x64 で npm install 必要。 sharp.pixelplumbing.com
従って
npm install --arch=x64 sharp
でデプロイしたいがamplify push
の際にはnpm install
でインストールされるようなのでオプション指定をどうする?
※ x64 のマシンで運用する分には問題ありませんが、ARM のマシン(例:M1 Mac)の際に端末のアーキテクチャでインストールされてしまいます。
対策
※ 最善策である保証はありません。
script の postinstall を利用して ARM でインストール後に x64 でのインストールで上書きするようにします。
{ "version": "1.0.0", "description": "", "main": "index.js", "dependencies": { "sharp": "^0.31.0" }, "scripts": { "postinstall": "SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp" } }
これによって npm install
した際に x64 で sharp がインストールされます。
めでたし。めでたし。
2022-09-13 追記
初回は問題ないのですが、複数回 push していると sharp/build ディレクトリが生成されない現象がありました。
npm install でそもそも端末アーキテクチャ依存の sharp をインストールしないように preinstall で消すことで対応できました。
{ "version": "1.0.0", "description": "", "main": "index.js", "dependencies": { "sharp": "^0.31.0" }, "scripts": { "preinstall": "npm uninstall sharp", "postinstall": "SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp" } }
[DNS] BIND でローカル DNS サーバー構築
ちゃちゃっと DNS サーバーを構築をします。
AWS Route53 はゾーン設定が Web コンソールでできたので使いやすかったんだなぁと今更実感しました。
⌨️ 環境
- Ubuntu 20.04
はじめに
DNS サーバーには BIND 以外にも PowerDNS や NSD などあるようですが、
にて
As of 2015, it is the most widely used domain name server software, and is the de facto standard on Unix-like operating systems.
と、BIND こそが Unix ライク OS の DNS サーバーでデファクトスタンダート(自称)だと仰るので、初めてですし BIND を使います。
余談ですがバージョンとしては BIND10 もあったらしいですが、プロジェクト消滅して BIND9 のリファクタリングを行う方針になったようです。(そもそも ISC の ホームページ の Products に BIND9 しかない。)
BIND9 には脆弱性があるという声が多いのが気になりますが、ローカル DNS ということで今回は見なかったことにします。
参考:https://ubuntu.com/search?q=Bind+vulnerability
(解消してるものも多いと思いますが、結構あります。)
1. BIND9 インストール
apt-get で BIND9 をインストールします。
$ sudo apt-get install -y bind9
2. /etc/bind/named.conf.options 設定
/etc/bind/named.conf.options
を編集します。
options {
directory "/var/cache/bind";
dnssec-validation auto;
//listen-on-v6 { any; };
listen-on port 53 { localhost; };
allow-query { any; };
forwarders { 8.8.8.8; };
recursion no;
}
補足
3. ゾーンの設定
/etc/bind/named.conf
を見てみると
include "/etc/bind/named.conf.options"; include "/etc/bind/named.conf.local"; include "/etc/bind/named.conf.default-zones";
とあるので、
/etc/bind/named.conf.local
に zone を追記/etc/bind/named.conf.default-zones
に zone を追記- 自分で追加したファイルに zone を記述して include させる
などの方法でゾーンの設定を追加できそうです。
.conf が全部そうなのかわかってませんが、やり口は Nginx の conf と同じですね。
今回は named.conf.local に追記します。
zone "mydomain.com" { type master; file "/etc/bind/db.mydomain.com"; };
ローカル DNS なので、ここでつける名前(手順の例では mydomain.com
)は取得してないドメインでも名前つけ放題です。
file "/etc/bind/db.mydomain.com"
のファイルは次の手順で作成します。
4. ゾーンファイルの作成
/etc/bind/db.local
をもとに編集します。
$ sudo cp /etc/bind/db.local /etc/bind/db.mydomain.com
; ; BIND data file for local loopback interface ; $TTL 604800 @ IN SOA mydomain.com. root.mydomain.com. ( 2 ; Serial 604800 ; Refresh 86400 ; Retry 2419200 ; Expire 604800 ) ; Negative Cache TTL ; @ IN NS www www IN A 192.168.0.x
この場合は www.mydomain.com
が 192.168.0.x
に名前解決されます。
test.mydomain.com
も追加したい場合は NS レコードと A レコードを追加します。
@ IN NS www www IN A 192.168.0.x @ IN NS test test IN A 192.168.0.x
余談ですが、
- NS レコード左辺の @ は何なのか
- SOA レコードってそもそも何なのか
あたりがさっぱりなので、今後書くであろうゾーンファイルを掘り下げる記事でお会いしましょう。
5. BIND9 再起動
$ sudo systemctl restart bind9
6. 確認
dig コマンドで確認。
$ dig www.mydomain.com ・・・ ;; ANSWER SECTION: www.mydomain.com. 604800 IN A 192.168.0.x ・・・
名前つけ放題と先ほど言いましたが、この世に存在するドメインを名付けてパブリック DNS サーバーが先に参照されて名前解決された場合は当然ローカル DNS サーバーでの名前解決はされません。
そのため、/etc/resolv.conf
でパブリック DNS サーバーの前にローカル DNS サーバーを記述してください。
resolv.conf の nameserver の順序については
を参考にしてください。
名前解決先に Web アプリがあればそれぞれ curl で確認して同じ内容が得られます。
$ curl http://192.168.0.x (何かしらの HTML) $ curl http://www.mydomain.com (何かしらの HTML)
おわりに
ゾーンファイルはテンプレートの /etc/bind/db.local
をもとにしただけで使いこなせていません。
なので、今後はゾーンファイルを掘り下げていきたいですね。
[kubernetes][Secret] kubernetes の Secret を使って credentials を Pod にマウントする
credentials を秘匿しながら Pod にマウントしよう。
🤔 kubernetes の Secret とは?
まず kubernetes には ConfigMap と Secret と、Pod に値を渡すための役割として似たようなものが2種類あります。
公式の ConfigMap の注意書きにも
注意: ConfigMapは機密性や暗号化を提供しません。保存したいデータが機密情報である場合は、ConfigMapの代わりにSecretを使用するか、追加の(サードパーティー)ツールを使用してデータが非公開になるようにしてください。
と記載されているので、
- 環境変数は ConfigMap
- credentials の類は Secret
と使い分けたいところです。
🔍 ConfigMap と Secret の違い
に書いてあるとおりですが、
- Secret は dataフィールドのすべてのキーの値は base64 でエンコードされた文字列である必要がある
- Secret は
tmpfs
に保存されるのでディスクストレージに書き込まれない- 復元のリスクが少ない(と思われる)
機密情報を Secret にする根拠としては「tmpfs
に保存されるのでディスクストレージに書き込まれない」からですかね〜。(base64 は根拠にならないよね)
📜 Secret を使って credentials を Pod にマウントする
ようやく本題です。
まずは手元の credentials.json
をもとに Secret を作成します。
$ kubectl create secret generic {Secret名} --from-file={podに配置するファイル名}={credentials.json格納先} # 例 $ kubectl create secret generic my-credentials --from-file=credentials.json=/home/test-user/credentials.json
次に Pod から Secret を参照します。
マニフェストファイルは以下のような形になります。
apiVersion: apps/v1 kind: Deployment metadata: name: sample-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: volumes: - name: my-key secret: secretName: my-credentials containers: - image: nginx name: sample-pod ports: - containerPort: 80 volumeMounts: - name: my-key mountPath: /var/secret/
Secret 作成時の kubectl create secret generic {Secret名}
で設定した Secret名 を secretName: の値として設定します。
mountPath が credentials の格納先ですね。
kubectl apply -f {マニフェストファイル}
で起動して Pod の中に入って mountPath で指定したパスを確認すると
credentials が格納されていることを確認できます。
おわりに
ConfigMap と Secret についてはなんとなく使える状態まで仕上がりました。
kubernetes 大変ですが、やってて損はないと信じて今後も頑張りましょう。💪
[soccer-cli] ターミナルで海外サッカーの試合結果を確認しよう
記事数稼ぐためにツール紹介の小ネタでお茶を濁します。
はじめに
クリスティアーノ・ロナウドのマンチェスター・ユナイテッド復帰、セリエ A 昨シーズン MVP ロメル・ルカクのチェルシー復帰、冨安健洋選手のアーセナル移籍など、今シーズンもプレミアリーグから目が離せませんね。
さて、皆さんターミナルでサッカーの試合結果や順位を確認したくなることありますよね?
そんなときにおすすめしたいのが soccer-cli です。
早速ターミナルで我らがチェルシーの試合結果と順位を確認してみましょう。
🎸 余談
元 OASIS ノエル・ギャラガー氏によるとチェルシーファンは人間以下のクズどもとのことです。😇
⌨️ 環境
API トークン取得
football-data.org で API token を取得する必要があります。
こちらから名前とメールアドレスを入力して登録すると「Your free API-key for football-data.org」の件名のメールが届き、本文に
Your API token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
と記載されています。
soccer-cli インストール
soccer-cli の Build from source に従い soccer-cli をインストールします。
$ git clone https://github.com/architv/soccer-cli.git $ cd soccer-cli $ python setup.py install
⚽️ 使う
我らがチェルシーの試合結果と現在の順位を確認してみます。
まず、対象チームの試合結果を確認するには --team
オプションでチームを指定します。
しかし、--team
で指定するチームコードがわからないので、soccer --list
でチームコードを確認してみます。
$ soccer --list ...(プレミアリーグ以外も出力されますが割愛します) Premier League AFC: Arsenal FC AFCB: AFC Bournemouth AVFC: Aston Villa FC CFC: Chelsea FC CRY: Crystal Palace FC EFC: Everton FC LCFC: Leicester City FC LFC: Liverpool FC MCFC: Manchester City FC MUFC: Manchester United FC NCFC: Norwich City FC NUFC: Newcastle United FC SCFC: Stoke City FC SFC: Southampton FC SUN: Sunderland AFC SWA: Swansea City FC THFC: Tottenham Hotspur FC WAT: Watford FC WBA: West Bromwich Albion FC WHU: West Ham United FC ...(プレミアリーグ以外も出力されますが割愛します)
我らがチェルシーのチームコードは CFC
であることがわかりました。
soccer --team CFC
で試合結果を確認してみます。
初回実行時は API token の入力を求められるので、先程取得した API token を設定します。
(なお、ここで設定した API token は ~/.soccer-cli.ini
で保持されるようです。)
$ soccer --team CFC No API key found! Please visit http://api.football-data.org/v2/ and get an API token. Enter API key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 2021-08-14 Chelsea FC 3 vs 0 Crystal Palace FC 2021-08-22 Arsenal FC 0 vs 2 Chelsea FC 2021-08-28 Liverpool FC 1 vs 1 Chelsea FC 2021-09-11 Chelsea FC 3 vs 0 Aston Villa FC 2021-09-14 Chelsea FC 1 vs 0 FK Zenit Sankt-Petersburg 2021-09-19 Tottenham Hotspur FC 0 vs 3 Chelsea FC 2021-09-25 Chelsea FC 0 vs 1 Manchester City FC 2021-09-29 Juventus FC 1 vs 0 Chelsea FC 2021-10-02 Chelsea FC 3 vs 1 Southampton FC 2021-10-16 Brentford FC 0 vs 1 Chelsea FC 2021-10-20 Chelsea FC 4 vs 0 Malmö FF 2021-10-23 Chelsea FC 7 vs 0 Norwich City FC 2021-10-30 Newcastle United FC 0 vs 3 Chelsea FC 2021-11-02 Malmö FF 0 vs 1 Chelsea FC 2021-11-06 Chelsea FC 1 vs 1 Burnley FC
今シーズンのチェルシーの試合結果の一覧が取得できました。
マンチェスター・シティに悔しい敗戦を喫しましたが、リーグ戦はその後は無敗です。
さて、気になる順位は…
$ soccer --standings --league=PL POS CLUB PLAYED GOAL DIFF POINTS 1 Chelsea FC 11 23 26 2 Manchester City FC 11 16 23 3 West Ham United FC 11 10 23 4 Liverpool FC 11 20 22 5 Arsenal FC 11 0 20 6 Manchester United FC 11 2 17 7 Brighton & Hove Albion FC 11 0 17 8 Wolverhampton Wanderers FC 11 -1 16 9 Tottenham Hotspur FC 11 -7 16 10 Crystal Palace FC 11 1 15 11 Everton FC 11 0 15 12 Leicester City FC 11 -2 15 13 Southampton FC 11 -2 14 14 Brentford FC 11 -1 12 15 Leeds United FC 11 -7 11 16 Aston Villa FC 11 -6 10 17 Watford FC 11 -7 10 18 Burnley FC 11 -6 8 19 Newcastle United FC 11 -12 5 20 Norwich City FC 11 -21 5
11月19日現在チェルシーはプレミアリーグ1位です!!!🎉🎉🎉
おわりに
これでターミナルから試合結果を確認できるようになりました。
基本的にリアルタイム観戦、FotMob等のアプリ、Twitter で試合結果を確認することが多いと思いますが、ターミナルで贔屓チームの試合結果や順位をみてモチベーションをあげる際には soccer-cli を活用してみてください。
[kubernetes] Error registering network: failed to acquire lease: node "ノード名" pod cidr not assigned
flannel のエラー解消についてメモ残しておきます。
事象
kube-flannel が CrashLoopBackOff になるので、kubectl logs でログを確認すると表題のエラーが出力されていた。
対策
の通り、/etc/kubernetes/manifests/kube-controller-manager.yaml
のコマンドに
--allocate-node-cidrs=true
--cluster-cidr=10.244.0.0/16
を追加して systemctl restart kubelet
実行することで kube-flannel のステータスが無事に Running になりました。
そもそも、kubeadm init
の際に --pod-network-cidr
で CIDR を設定していれば問題なかった?