狛ログ

2019年7月30日火曜日

踏み台サーバー(EC2)経由でSQLServer(RDS)にSSH接続する方法(ポートフォワーディング)・MacOS編

7月 30, 2019

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

以前、弊社のメンバーが、踏み台サーバー(EC2)経由でSQLServer(RDS)にSSH接続する方法(ポートフォワーディング)・Windows編という記事を書きました。

今回は、MacOSでのやり方について記載しようと思います。
(「ポートフォワーディングとは?」の説明については、Windows版の記事をご覧ください。)
今回の接続イメージは下図の通りです。


前回のWindows版は、踏み台サーバーから直接データベースへ接続していましたが、今回は、踏み台からさらにWebサーバーを経由してデータベースへ接続する事にします。
手順は以下となります。
(1)Webサーバー(EC2)へポートフォワーディングする設定で、踏み台サーバー(EC2)へSSH接続
(2)データベース(RDS)へポートフォワーディングする設定で、(1)のポートフォワーディング状態でSSH接続
(3)上記までのポートフォワーディングした状態でSQLServerにログインしDB操作
※各SSH接続で使う鍵はローカルに持っている事を前提とします。

手順(文章)だけだと、分かり難いですね・・・・ということで、具体的な手順を書いていきます。


(1)Webサーバー(EC2)へポートフォワーディングする設定で、踏み台サーバー(EC2)へSSH接続

まずは、踏み台経由でWebサーバー(EC2)へ接続します。
イメージ図で言うと、下記の赤字の「(1)」の部分になります。

まず、mac標準ツールのターミナルを開き、以下のコマンドを入力し、実行します。
ssh -L 2200:10.0.1.1:22 -i key-hoge-bastion.pem bastionuser@xx.yy.zz.001

それぞれ、説明をしていきます。

「ssh -L 2200:10.0.1.1:22」:
ローカルとリモートを繋ぐ設定になります。この場合、最終的に「localhost:2200」が「10.0.1.1:22」へと繋がる事になります。
localhost側のポートは、使用していないものであれば、何でも構いません。
最終的に接続する先のIP(今回は「10.0.1.1」)はプライベートIPである事に気を付けてください。

「-i key-hoge-bastion.pem bastionuser@xx.yy.zz.001」:
これは踏み台への接続設定です。「-i」の後に、踏み台の鍵を指定し、その後ろに、「接続ユーザー名@パブリックIP or ドメイン」を指定します。

コマンド実行後、ターミナルは踏み台に繋がった状態になっています。
ですので、さらにポートフォワードする場合には、ターミナルを新たに起動する必要があります。

※「ssh -fNL」とする事で、バックグラウンドでコマンドが実行されるので、そのまま同じターミナルを使う事が可能です。ですが、今、どこにポートフォワードしているのか分からなく可能性がありますし、タスクをkillするのも面倒だったりするので、最初の内は、ポートフォワードする単位でターミナルを開くことをお勧めします。


(2)データベース(RDS)へポートフォワーディングする設定で、(1)のポートフォワーディング状態でSSH接続

続いて、Webサーバー(EC2)経由でデータベース(RDS)へ接続します。
イメージ図で言うと、下記の青字の「(2)」の部分になります。


ssh -L 1433:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433 -i key-hoge-web.pem webuser@localhost -p 2200

それぞれ、説明をしていきます。

「ssh -L 1433:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433」:
ローカルとリモートを繋ぐ設定になります。この場合、最終的に「localhost:1433」がRDSのエンドポイント「hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433」へと繋がる事になります。
localhost側のポートは、使用していないものであれば、何でも構いませんが、後のことを考えて、1433にしました。

「-i key-hoge-web.pem webuser@localhost -p 2200」:
これは、Webサーバー(EC2)への接続設定です。「-i」の後に、Webサーバー(EC2)の鍵を指定し、その後ろに、「接続ユーザー名@localhost -p (1)で設定したポート番号」を指定します。
ポイントは、接続するのはあくまで「ポートフォワード設定をしたlocalhost」と言うことです。

コマンド実行後、ターミナルはWebサーバー(EC2)に繋がった状態になっています。
今回はこれ以上はポートフォワード設定を追加しないので、ターミナルはこのままにしておきます。


(3)上記までのポートフォワーディングした状態でSQLServerにログインしDB操作

ここまで来たら後はデータベースへの接続のみです。
今回は、Azure Data Studioを使って、接続します。

すでに、「localhost:1433」は、「踏み台→Webサーバー」を経由して、「endpoint:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com」に繋がっている状態なので・・・


上記のように、接続先を「localhost」(Azure Data Studioはデフォルト1433ポートを使用するので、1433は省略可能)として、ユーザー名とパスワードを入力すれば、無事に接続出来ると思います。

以上となります。今回はSQLServerに限定しましたが、他のDBサーバーでも、基本的な流れは変わらないと思います。


2019年7月29日月曜日

C#でListの多次元化をやってみる。

7月 29, 2019

オフィス狛 技術部のHammarです。

C#で開発中に、ある処理で値を多次元で持たせる必要があり、最初は配列で多次元化を考えたのですが、処理内容的にListを使っているので、Listで多次元化を行ってみました。
php等で配列の多次元はよく行いますが、C#でかつListで行ったことがなかったので、いろいろ調べつつ、自分への備忘録も兼ねて今回記載しようと思います。

まず基本的なListでの動きですが、通常Listに値をセットする場合等は以下のように記述すると思います。
var list = new List<string>();
list.Add("みかん");
list.Add("りんご");
list.Add("バナナ");

上記は1次元Listなので、そのまま
list[0]=>"みかん"
list[1]=>"りんご"
list[2]=>"バナナ"
というような感じで値が入ります。

で、多次元Listですが、基本的には2次元Listができればあとは応用という感じなので、一旦わかりやすく2次元Listを作ってみたいと思います。
例えば以下のようなphpの多次元配列のような感じでデータを持ちたい場合があるとします。
    [0]=>{
        [0]=>"みかん"
        [1]=>"orange"
    }
    [1]=>{
        [0]=>"りんご"
        [1]=> "apple"
    }
    [2]=>{
        [0]=>"バナナ"
        [1]=> "banana"
    }

上記を2次元Listにするには以下のように記述します。
var list = new List<List<string>>()
list.Add(new List<string>());
list[0].Add("みかん");
list[0].Add("orange");
list[1].Add("りんご");
list[1].Add("apple");
list[2].Add("バナナ");
list[2].Add("banana");
結果上記でいくと例えば
list[0][1]=>"orange"
list[2][0]=>"バナナ"
がデータとしてセットされていることになります。

Listの<>内にはクラスも入れる事ができるということなので、多次元化ができるということらしいですね。
なので、3次元、4次元としたい場合(そんなにないと思いますが)、さらにListの<>を入れ子にしていけば可能になります。

あと1次元Listと違って少し注意したいのが、Listに追加するときにはListの中もnewしてインスタンスする必要があります。自分はここで少しハマりました。。

以上のような感じで、Listを多次元化して処理を行う場合に参考になればと思います。

2019年7月26日金曜日

ローカルにあるhtmlファイルでChromeの拡張機能を使う設定。

7月 26, 2019
こんにちは、オフィス狛 デザイン部のSatoです。

今回は、ほんの小技なのですが、ローカルにあるhtmlファイルをChromeで開いた際に、拡張機能を使う方法(設定)です。

何の設定もしていない状態だと、Chromeでローカルファイルを開いた時には拡張機能が使えないので、少々不便です。
私はコーディング中のページ全体を拡張機能でスクリーンショットを撮りたい時があるので、結構困っていました。

コーディング中でも拡張機能が使いたいと思う方は、私以外にもいらっしゃると思いますので設定方法をご紹介します。

拡張機能のアイコンバーを右クリックして、「拡張機能を管理」をクリックし、詳細設定画面に遷移します。

「ファイルの URL へのアクセスを許可する」という項目があるのでONにするとローカルファイルをChromeで開いた際も拡張機能を使えるようになります。

ただ、セキュリティ的に不安のある拡張機能では使わないようにした方が良いかと思います。
(ですので「ファイルの URL へのアクセスを許可する」をONする際は、拡張機能のセキュリティ要件を確認し、慎重に判断してください)

2019年7月5日金曜日

Amazon ConnectとLambdaで電話をかける ③Lambda関数の作成と、④実行

7月 05, 2019

こんにちは、オフィス狛 モバイル開発担当 Aika-yuy です。
今回の投稿は、 AmazonConnectとLambdaで任意の番号に電話をかける方法をご紹介します。


AmazonConnectはコールセンターをメインとして、簡易に作成できるというものなので、
受電する記事をよく見かけますが、架電方法の記事は少なかったので記事にしました。




難しそうに見えますが、簡単で短時間で作成できますので試してみてください。
こちらの4ステップで紹介していきます。

①Amazon Connectで電話番号、お問い合わせフローの作成
②IAMでロールを作成
③Lambda関数の作成← 今回はこちら
④実行!!← 今回はこちら

 

③Lambda関数の作成

今回はいよいよ、Lambda関数からスマフォに電話をかけてみます! とっても簡単なので、あと少し頑張りましょう。

〜必要なもの〜

1、①で作成した電話番号
2、①で作成したお問い合わせフローのインスタンスID、リージョン名、コンタクトフローID
3、②で作成した、ロール
4、任意の電話番号


AWSにログイン後、Lambdaを選択します。
ページ遷移後、関数の作成ボタンを押してください。
下の画像のように入力選択していきます。
1.関数名に任意の関数名をつけてください。
2.実行ロールを既存のロールを使用するを選択してください。
3.②IAMでロールを作成で、作成したロールを選択してください。
※作成したロールがすぐに反映されないことがあるので、作成したロールが見つからない場合は少し時間をおいてからもう一度試してみてください。
4.関数を作成ボタンを押す

それではいよいよ関数を作っていきましょう!

exports.handler = (event) => {
     
    // import entire SDK
    var AWS = require('aws-sdk');
 
    var connect;
    // create amazon connect object
    // ①で作成したお問い合わせフローのリージョン名を入力してください。
    connect = new AWS.Connect({apiVersion: '2017-08-08', region:"ap-northeast-1"});
 
    // create API request parameter
    var params = {
      // ①で作成したお問い合わせフローのコンタクトフローID
      ContactFlowId: "XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      // 今回かける先の電話番号
      DestinationPhoneNumber: "XXXXXXXXXXXXXX",
      // ①で作成したお問い合わせフローのインスタンスID
      InstanceId: "XXXXXXXXXXXXXXXXXXXXXX",
      // かける元の電話番号(作成した電話番号)
      SourcePhoneNumber: "+8XXXXXXXXXXX"
    };
  
  // call API with parameter
  console.log("開始!!");
  var calling = connect.startOutboundVoiceContact(params, function(err, data) {
    if (err) {
      console.log(err);
    } else {
      console.log("終了!!電話を切る");
    }
  });
  return calling;
};


これで完成です!

④実行!!

それではいよいよ電話をかけてみます。
Lambdaのページの一番上、右にあるテストボタンを押してください。

任意の電話番号に、電話がかかれば成功です。

意外と簡単に作成できましたね。
アレクサから電話したり色々できそうで、夢が広がりますね。
次回はアプリ(ios)から電話をかけるをやっていきます。

2019年6月30日日曜日

AngularのdetectChanges()で「ViewDestroyedError: Attempt to use a destroyed」が発生した時の対応方法。

6月 30, 2019

オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

私の担当がいつの間にかAngular専任になっていますが・・・・今回もやっぱりAngularネタです。

Angularで、NgRxなどでAPIから値を取得した場合、そのままView(template・html)側にデータを流し込んであげれば、取得したデータは問題なく表示されます。
ところが、component(TypeScript)側で取得したデータをstore.selectで取得すると、View(template・html)側に表示データが反映されない場合があります。

うーん、言葉だけで説明するのが難しい。
この辺はいつかNgRxの使い方とかで詳しく説明したいですが、前者の場合、ストリーム(川の流れ)は繋ぎっぱなしですが、
後者は、一度ストリーム(川の流れ)から、データ取っているので、流れが止まっている、という事ですね。

強制的に画面に反映させる為には、「changeDetectorRef の detectChanges() 」メソッドを使います。

では、ここで使い方の例を一つ。
import { Component, ChangeDetectorRef, OnInit } from '@angular/core';
// (中略)
export class HogeKomaComponent implements OnInit {
// (中略)
  constructor(
    public store: Store,
    private changeDetectorRef: ChangeDetectorRef
  ) {}
 
  ngOnInit() {
    this.hogeKomaSubscription.push(
      this.store
        .select(fromHoge.getKomaList)
        .pipe(skip(1))
        .subscribe(result => {
            //
            // ここで取得したデータを編集し、Template側の変数へ再設定(これだけだと、画面は更新されない)
            //
 
            // 画面の表示を更新する(これを実施する事で画面が更新される)
            this.changeDetectorRef.detectChanges();
    }));
 
    // データ取得
    this.store.dispatch(new KomaListActions.GetKomaList());
  }

で、ここからが本題なのですが、「detectChanges()」を使っていると、以下のようなエラーが発生する事があります。
    common.96cfb6ba445916612966.js:1 ERROR Error: ViewDestroyedError: Attempt to use a destroyed view: detectChanges
    at pm (main.35927d7a5d9b4fa62e08.js:1)
    at Object.m_ [as updateDirectives] (main.35927d7a5d9b4fa62e08.js:1)
    at jv (main.35927d7a5d9b4fa62e08.js:1)
    at D_ (main.35927d7a5d9b4fa62e08.js:1)
    at Object.i_ [as checkAndUpdateView] (main.35927d7a5d9b4fa62e08.js:1)
    at n.detectChanges (main.35927d7a5d9b4fa62e08.js:1)
    at e._next (10.ab643854ddb1ed074304.js:1)
    at e.__tryOrUnsub (main.35927d7a5d9b4fa62e08.js:1)
    at e.next (main.35927d7a5d9b4fa62e08.js:1)
    at e._next (main.35927d7a5d9b4fa62e08.js:1)

エラーメッセージは、
ViewDestroyedError: Attempt to use a destroyed view: detectChanges
という事ですが、要は、「画面表示を行おうとしたけど、もう対象のViewが存在しない」という事ですね。

こういう場合は、
  if (!this.changeDetectorRef['destroyed']) {
    this.changeDetectorRef.detectChanges();
  }

上記のように、既にViewが破棄されていないか確認する事で回避出来ます。

今回も内容の割には長くなってしまいました・・・・
(エラーメッセージと対応内容書けば、ある意味終了なんですけどね。)

こんな内容でも、誰かの役に立つ事を願って・・・・

では、より良いAngularライフを!


Dockerの開発環境作成で発生した問題(BIOS is mandatory、bashエラー、no such file or directory 等)と、その対処方法。

6月 30, 2019

オフィス狛 技術部のJoeです。

先日、Dockerによる開発環境を作成しました。
とはいっても、Docker初心者なので一から構築した訳でなく、既にymlファイルなどの定義は作成されていたので、必要なパッケージをインストールして起動するだけ。。。
のはずだったのですが、なぜか私の端末でのみいくつか問題が発生したので、対処方法と併せてご紹介します。

【環境】
・Windows10 Home
・Docker Toolbox 18.03.0-ce

問題1.仮想化を有効にできない。

まず、Docker用の仮想マシンを構築するため、「Docker Quickstart Terminal」を実行すると以下のエラーが発生しました。
Running pre-create checks...
Error with pre-create check: "This computer doesn't have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory"

すぐにBIOSで仮想化の有効化を忘れていたと思い、BIOSを起動しましたが、「VirtualizationTechnology」など、仮想化に関する項目が見当たりません。

これが意外と事例が無く、解決までに結構時間が掛かってしまったのですが、結局、BIOSをアップデートすることで仮想化の項目が現れ、有効化することができました。

参考までに、今回アップデートしたBIOS情報を載せておきます。
・BIOS:VC65-C1
・バージョン:更新前)0501 更新後)0801
・ダウンロード先:https://www.asus.com/jp/Mini-PCs/VivoMini-VC65-C1/HelpDesk_BIOS/


問題2.bashが実行できない。

仮想化を有効できたので、再度「Docker Quickstart Terminal」を実行すると、今度は以下のエラーが発生しました。
このショートカットは、リンク先の'bash.exe'が変更または移動されているので、正しく機能しません。

「Docker Quickstart Terminal」で実行される「start.sh」はbashなのですが、Docker Toolboxと一緒にインストールするGitの「bash.exe」を使用します。

こちらは、Docker Toolboxをインストールする前に、Gitを既にDドライブにインストールしていたことが原因でした。
「Docker Quickstart Terminal」のショートカットのリンク先、「bash.exe」のパスをCからDドライブに変更することで、エラーを解消できました。

問題3.コンテナが起動しない。

スタートからつまずきましたが、やっとコンテナの起動ということで、「docker-compose up」コマンドを実行したところ、以下のエラーで起動に失敗しました。

【コンテナのログ】
npm ERR! path /usr/src/app/package.json
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall open
npm ERR! enoent ENOENT: no such file or directory, open '/usr/src/app/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

「package.json」が見つからないと言ってますが、コンテナにコピーした時にエラーは出てなく、npmのインストールも正常に実行できていました。

docker psコマンドでSTATUSを見ると、Restartingが繰り返されている状態になっています。
CONTAINER ID  IMAGE  COMMAND                CREATED         STATUS                          PORTS  NAMES
6438e1e2df74  test   "bash -c 'npm run de…" 29 seconds ago  Restarting (254) 2 seconds ago         test

エラーになったコマンド「docker-compose up」では、ymlファイルを2ファイル(共通、環境別用に分けている)指定していたのですが、定義内容を1ファイルにまとめて指定することで起動できました。

2ファイルで起動できなかった原因がどうしても見つからなかったので、こちらは引き続き調べてみたいと思います。

2019年5月31日金曜日

Node.jsでknex.jsを使ったSQL書き方Tips

5月 31, 2019

オフィス狛 技術部のHammarです。

皆さんNode.jsとRDBを使って開発をされるときに、クエリビルダは何をお使いでしょうか?

以前にもブログで何度か書きましたが、弊社の一部プロジェクトではknex.jsというクエリビルダを使っています。

結構クセのある書き方ではじめは手こずりましたが、やはり人間慣れは重要で、今では意外と使いやすいなと思う部分も結構あります。
幅広いDBの種類に対応しているので、DBの種類によってインストールするモジュールを変える必要はなく、これ1つで同じ書き方ができるのでお勧めです。

ただあまり日本語の解説記事がないので、SQLの特殊な書き方をするときにどう書けばいいかわからなくて、ちょっとだけハマった事象について、自分への備忘録も兼ねて書こうと思います。

■基本的なSQL

例えば出身地、名前、年齢が登録されているテーブルがあったとして、下記のようなSQLでとあるデータを取得するとします。
    select
        name
    from users
    where
        birth_place = '東京都'
        and age >= '20';
これをnodeでknexを使って書くと(knexはrequire済みとします)
knex
    .select('name')
    .from('users')
    .where('birth_place', '東京都')
    .andWhere('age', '>=', '20')

という感じで基本的にはSQLの書き方に沿った感じで書くことが可能です。

基本的な書き方は公式リファレンスをみていただければほとんど大丈夫なのですが、公式にも詳しくのっていなかったりするものもあります。

■数値をプレースホルダにいれる場合

以前ブログにも書きました数値をプレースホルダにいれる場合、下記のように書きます。
狛ログ:knexライブラリのIN句でプレースホルダを使うときの注意点
const array = [1, 2, 3];
knex
    .select('name')
    .from('users')
    .whereRaw('id in (?)', [array]);

■from句でエイリアスを使いたい場合

knex
    .select('name')
    .from(users.clone().as('u'));
これはたとえばサブクエリでいろいろ結合した結果をセレクトしたいとき等で、別名にしたいときに利用できます。
const subQuery = サブクエリの内容
knex
    .select('sub_name')
    .from(subQuery.clone().as('sub'));

■order byで複数設定したい場合

通常のorder byの記述の方法で複数ソートしたいとき、公式の書き方だとちょっと面倒です。
    select *
    from users
    order by email asc, age desc
これをknexで書くと
knex
    .('users')
    .orderBy(['email', { column: 'age', order: 'desc' }])

上記でも全然問題ないんですが、ちょっとぱっと見わかりにくいのと、ソートが沢山あるとさらにわかりにくくなるので、下記のような書き方のほうがいいと思っています。
knex
    .('users')
    .orderByRaw('email asc, age desc');

このRawというのは基本的にそのままSQL文を記述が可能で、join句にもjoinRawというのがあったり、where句にもwhereRawというのあります。
select句にはselectRawみたいな書き方はないのですが、knex.raw()を使うことができ、例えば下記のようにselectにcase文を使いたい時など、select句で特殊なことをするときに使えます。
knex
    .select(
        'name',
        knex.raw('case when age >= 10 then categoryA else categoryB end as category'),
    )
    .from('users')
    .where('birth_place', '東京都')

で、knex.raw()はfrom句だろうが、where句だろうがどこにでも使えて、これを使うともはやknexの書き方に従わなくともSQLがそのまま書けますw
そこは臨機応変にだと思いますが、このように結構自由度も高いのでknexは個人的にはお勧めだと思います。

またtips的な事象が出てきたら書きたいと思います。