20200920のNode.jsに関する記事は9件です。

E2Eテストを行うNightWatchについて調べる(随時更新)

はじめに

この記事はWebアプリケーションのエンドトゥーエンドのテストを行うためのツールであるNightWatchについてのメモになります。
https://nightwatchjs.org/

この記事は随時更新され、更新時にストックされているユーザに通知する想定です。

NightWatchはSeleniumを基盤として作られており、Node.jsでブラウザを操作してエンドトゥーエンドのテストを行うツールです。
なお、vue-cliでプロジェクトを作成するとe2eのテストとしてインストールされています。

本記事での実行環境は以下のようになります。
OS:MacOS Catalina 10.15.6
Node.js:v14.10.1
Java:openjdk 14.0.1
ChromeとFireFoxをインストール済み

簡単なサンプルの実行

コマンド実行
npm init -y

# nightwatchのインストール
# nightwatch.conf.jsが作成される。
npm install nightwatch --save-dev

# ChromeのSeleniumドライバーのインストール
npm install chromedriver --save-dev

# FireFoxのSeleniumドライバーのインストール
npm install geckodriver --save-dev

# seleniumserverのインストール
npm install selenium-server --save-dev

# FireFoxが起動してブラウザの操作を行う
npx nightwatch node_modules/nightwatch/examples/tests/ecosia.js

ecosia.jsの内容は以下の通りです。

node_modules/nightwatch/examples/tests/ecosia.js
describe('Ecosia.org Demo', function() {

  before(browser => browser.url('https://www.ecosia.org/'));

  test('Demo test ecosia.org', function (browser) {
    browser
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
  });

  after(browser => browser.end());
});

設定ファイル

https://nightwatchjs.org/gettingstarted/configuration/

nightwatchはデフォルトではカレントディレクトリのnightwatch.jsonまたはnightwatch.conf.jsを設定ファイルとして動作します。両方のファイルが存在する場合は、nightwatch.conf.jsを優先して使用します。

また、nightwatchを実行した際に、nightwatch.jsonまたはnightwatch.conf.jsが存在しない場合、nightwatch.conf.jsが作成されます。

特定の設定ファイルを指定して動作させる場合は以下の--configオプションを使用して起動します。

npx nightwatch --config nightwatch.json node_modules/nightwatch/examples/tests/ecosia.js

nightwatch.jsonの記述例

以下の設定ファイルはchromeでテストを動かすnightwatch.jsonの設定例です。

nightwatch.json
{
  "webdriver" : {
    "start_process": true,
    "server_path": "node_modules/.bin/chromedriver",
    "port": 9515
  },

  "test_settings" : {
    "default" : {
      "desiredCapabilities": {
        "browserName": "chrome"
      }
    }
  }
}

nightwatch.conf.jsの記述例

以下の設定ファイルはfirefoxでテストを動かすnightwatch.conf.jsの設定例です。

nightwatch.conf.js
module.exports = {
  webdriver: {
    start_process: true,
    port: 4444,
    server_path: require('geckodriver').path,
  },
  test_settings: {
    default: {
      launch_url: 'https://nightwatchjs.org',
      desiredCapabilities : {
        browserName : 'firefox'
      }
    }
  }
};

よく使用する設定

https://nightwatchjs.org/gettingstarted/configuration/

名前 タイプ デフォルト 解説
src_folders Stringの配列 なし テストが配置されているフォルダーの配列。指定したフォルダのサブフォルダ中のファイルも実行されます。指定されていない場合は、テストソースをテストランナーの 2番目の引数としてインラインで渡す必要があります。

VSCodeでデバッグする方法

(1).vscode/launch.jsonを以下のように作成します。

.vscode/launch.json
 {
     "version": "0.0.1",
     "configurations": [
         {
         "type": "node",
         "request": "attach",
         "name": "Attach to Process",
         "port": 9229
         }
     ]
 }

(2)ターミナルで以下のコマンドを実行する

ターミナル
 node --inspect-brk ./node_modules/nightwatch/bin/runner.js sample.js

(3)VSCodeでAttachボタンを押下してデバッグを実行する

image.png

コマンド

コマンドキュー

公式の解説
https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue

Nightwatchがテストを実行すると、そのコマンドはコマンドキューと呼ばれるリストで処理されます。 コマンドキューのコマンドは非同期実行で実行されており、先入先出法(FIFO)でコマンドが実行されます。

コマンドAPIは以下のサンプルコードのように、テストケースに渡されたbrowserやclientといったオブジェクトを介してアクセス可能になっています。

サンプルコード.js
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

このコードを実行した時点では、Seleniumのclickなど処理を実行するのではなく、コマンドキューにコマンドを追加します。
nightwatchのコマンドは1つまたは複数のSeleniumのリクエストをラップしたものとなります。このSeleniumのリクエストは非同期で順番に実行されていきます。
ほぼすべてのネイティブのNightwatchコマンドは、コマンドがコマンドキュー内で実行された際のコールバック関数をサポートしています。

例えばclickコマンドは以下のようにコールバックを記述することができます。

サンプル.js
browser.click('button', function (result) {
  // コマンドキュー中のクリックコマンドが終了した場合に
  // seleniumのclick操作の応答を引数としてコールバックが実行されます。
});

コールバックの主な目的は、テストの実行中にテストからデータをキャプチャできるようにすることです。
例えば、以下の例ではgetValueにコールバックを指定して、取得した値をコンソールに出力しています。

サンプルコード.js
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .getValue('input[type=search]', function(result) {
        console.log(result);
      })
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

また、このコールバック内でコマンドキューにコマンドを追加した場合、実行したコマンドの後に新しいコマンドが追加されることになります。以下の例では、clickをした後に5000ms処理を停止します。

コールバック中のコマンドの追加
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]',function(result) {
        browser.pause(5000);
      })
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

コマンドのコールバックで取得した値を、別のコマンドの引数として利用するにはどうしたらいいでしょうか?
例えば、以下のコードは期待通りに動作しません。

正常に動作しない例.js
var text;
browser
  .getValue('#input', function (result) {
    text = result.value;
  })
  .setValue('#output', text); // WRONG: text is undefined

これを正しく動作させるにはgetValueのコールバック中にsetValueコマンドを実行する必要があります。

正常に動作する例.js
var text;
browser.getValue('#input', function (result) {
  text = result.value;
  browser.setValue('#output', text); // RIGHT
});

Protocol Element Commands

Protocol Element CommandsはWebElementIDをパラメータとして動作する低レベルのコマンドです。これらのコマンドは通常、カスタムコマンドやカスタムアサーションを作成するのに利用します。

WebElementIDの取得方法

nightwatchでweb elementi idを取得するにはelementコマンドまたはelementsコマンドを使用します。

サンプルコード
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .element('css selector', 'div', (result)=> {
        console.log('element', result);
      })
      .elements('css selector', 'div', (result)=> {
        console.log(result);
        result.value.forEach(elem=>{
          const key = Object.keys(elem)[0];
          const ElementIDvalue = elem[key];
          console.log(key, ElementIDvalue);
          browser.elementIdAttribute(ElementIDvalue, 'class', function(attr) {
            console.log(attr.value);
          });
        });
      })
      .end();
  }
};

第一引数にはロケーターストラテジーの種類を以下のいづれかから選択します。

  • css selector
  • link text
  • partial link text
  • tag name
  • xpath

第二引数にはロケーターストラテジーにあった要素を絞り込むための値を指定します。

第三引数はコールバック関数になります。
elemntコマンドとelementsコマンドの違いは結果の値になります。elementコマンドの場合は条件に合うどれか一つの要素のWebElementIDのみが取得できますが、elementsコマンドの場合は条件にあう全てのWebElementIDが取得できます。

elementコマンドの結果例
{
  value: {
    'element-6066-11e4-a52e-4f735466cecf': '8b24022d-1455-de40-bf78-3135a6883dfa'
  }
}
elementsコマンドの結果例
{
  value: [
    {
      'element-6066-11e4-a52e-4f735466cecf': '8b24022d-1455-de40-bf78-3135a6883dfa'
    },
    {
      'element-6066-11e4-a52e-4f735466cecf': '625db85e-9d09-cb45-b93d-ed5c1c9e7b0c'
    },
    {
      'element-6066-11e4-a52e-4f735466cecf': 'f27f778d-3a93-e64c-b5a2-dfeff18cc026'
    },
    // 略
  ]
}

Protocol Element Commandsはここで取得したWebElementIDを使用する必要があります。

Protocol Element Commandsの種類

名称 説明
elementIdAttribute 要素の属性を取得します
elementIdClear 要素を表示するようにスクロール後、要素の値をクリアします
elementIdClick 要素を表示するようにスクロール後、要素をクリックします
elementIdCssProperty 指定された要素の指定されたCSSプロパティを取得します。
elementIdDisplayed 指定された要素が現在表示されているか確認します。
elementIdEnabled 要素が有効になっているか確認する
elementIdLocation ページの左上を(0,0)とした場合の要素の位置を取得する。
elementIdLocationInView 要素がスクロールされていて表示されている場合に画面上の位置を取得する
elementIdName 指定された要素のタグ名を取得する
elementIdSelected option,radio,checkboxの場合に指定された要素が選択中か調べる
elementIdSize 要素のサイズを取得する
elementIdText 要素の表示中のテキストを取得する
elementIdValue スクロールしてフォームの要素に値を入力するかまたは現在の値を取得します
submit FORMまたはFORMの子要素を指定してsubmitを行います
elementIdValueのサンプルコード
サンプルコード
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .element('xpath', '//input[contains(@type, "search")]', (elem)=> {
        const key = Object.keys(elem.value)[0];
        const elemId = elem.value[key];
        browser.elementIdValue(elemId, 'nightwatch', (x)=> {
          browser.elementIdValue(elemId, (v)=> {
            console.log(v);
          });
        });
      })
      .end();
  }
};

このコードはwebdriverによって挙動が変わります。
Chromeの場合elementIdValueは以下の値を返します。

結果
{
  sessionId: '1d1752ad633221dbc083acd93574b7a2',
  status: 0,
  value: 'nightwatch'
}

Firefox(geckodriver 1.20.0)の場合は以下の値を返します。

結果
{ value: '' }

なお、Firefoxの場合でもgetValueを使用した場合はnightwathが取得できます。

getValueとelementIdValueの取得の違い

WebDriverに対して発行しているコマンドが異なるため挙動が異なります。

getValueの場合
/element/${id}/property/value

elementIdValueの場合
/element/${id}/attribute/value

修正案1

elementIdValue実行時に呼び出す関数を書き換えます。
ただし、Firefoxの当該事象だけを直すだけなら以下の修正で直るが影響範囲が不明です。

nightwatch/lib/api/protocol/elementIdValue.js
module.exports = class Session extends ProtocolAction {
  command(webElementId, value, callback) {
    ProtocolAction.validateElementId(webElementId, 'elementIdValue');

    if (arguments.length === 1 || typeof arguments[1] == 'function') {
      callback = arguments[1] || function (r) {return r};
      // 以下のように修正
      //return this.transportActions.getElementAttribute(webElementId, 'value', callback);
      return this.transportActions.getElementProperty(webElementId, 'value', callback);
    }

    return this.transportActions.setElementValue(webElementId, value, callback);
  }
};
修正案2

valueの取得を行う際にelementIdValueを使用しないでelementIdProperty関数を使用します。
ただし、当該関数はアンドキュメントの関数です。

          browser.elementIdProperty(elemId, 'value', (v)=> {
            console.log(v);
          });

カスタムコマンド

nightwatchでは独自のカスタムコマンドを作成することができます。
https://nightwatchjs.org/guide/extending-nightwatch/custom-commands.html

単純な例

まず設定ファイルにカスタムコマンドを配置するフォルダを指定します。
以下の例ではcommandsというフォルダに配置します。

nightwatch.conf.js
module.exports = {
  // 略
  custom_commands_path:  'commands',
  // 略
}

次にカスタムコマンドをcommandsフォルダに作成します。以下の例ではmycmdというコマンドを作成します。このコマンドはpauseコマンドと同じで操作の待機を行います。

js/commands/mycmd.js
module.exports = class CustomPause {
  command(ms, cb) {
    // If we don't pass the milliseconds, the client will
    // be suspended indefinitely
    if (!ms) {
      return;
    }

    const returnValue = {
      value: 'something'
    };

    return new Promise((resolve) => {
      setTimeout(() => {
        // if we have a callback, call it right before the complete event
        if (cb) {
          cb.call(this.api);
        }

        resolve(returnValue);
      }, ms);
    });
  }
}

カスタムコマンドは以下のように利用します。

module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .mycmd(5000) // カスタムコマンド
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

既存のコマンドを組み合わせたカスタムコマンド

カスタムコマンドはthis.apiを経由してbrowserオブジェクトにアクセスできます。これを利用して既存のコマンドを組み合わせたカスタムコマンドを作成できます。

commands/mysearch.js
module.exports = class CustomSearch {
  command(term, cb) {
    const returnValue = {
      value: 'something'
    };

    return new Promise((resolve) => {
      this.api.url('https://www.ecosia.org/')
              .waitForElementVisible('input[type=search]')
              .setValue('input[type=search]', term)
              .click('button[type=submit]', ()=> {
                if (cb) {
                  cb.call(this.api);
                }
                resolve(returnValue);
              })
    });
  }
}

カスタムコマンドの使用例は以下のようになります。

module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .mysearch('nightwatch')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

ページオブジェクトの利用

SeleniumでE2Eテストを行う場合、ページの操作をまとめるページオブジェクトパターンがよく利用されます。
https://www.selenium.dev/documentation/ja/guidelines_and_recommendations/page_object_models/

nightwatchでもページオブジェクトを利用するための機能が提供されています。
https://nightwatchjs.org/guide/working-with-page-objects/

単純な例

まず設定ファイルにページオブジェクトを配置するためのフォルダをpage_objects_pathを利用して指定します。

nightwatch.conf.js
//略
  page_objects_path: 'pageobjects',
//略

次に指定したフォルダにページオブジェクトを格納します。この例ではpageobjects/ecosiaPage.jsを作成します。

pageobjects/ecosiaPage.js
var customCommands = {
  search: function(term) {
    return this.setValue('@searchText', term)
      .click('@searchButton')
  }
};

module.exports = {
  url: 'https://www.ecosia.org/',
  commands: [customCommands],
  elements: {
    searchText: {
      selector: 'input[type=search]'
    },
    searchButton: {
      selector: 'button[type=submit]'
    }
  }
};

ページオブジェクトではelementsで指定したserchText,serchButtonのセレクタに@を付与してアクセスすることができます。
このページオブジェクトを利用したテストコードは以下のようになります。

module.exports = {
  'pageobject test' : function(browser) {
     const targetPage = browser.page.ecosiaPage()
     targetPage.navigate()
      .waitForElementVisible('@searchText')
      .search('nightwatch')
     browser
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

リモートの環境でテストする

リモートのseleniumでテストするための設定と手順

(1)設定ファイルを以下のように設定する

nightwatch.conf.js
module.exports = {
  src_folders: ['tests'],

  webdriver: {
    // ローカルのwebdriverを起動しないようにする
    "start_process" : false
  },

  test_settings: {
    default: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :4444,
      selenium_host : "192.168.0.18"
    }
  }
};

(2)ブラウザを動作させたい端末でseleniumserverを起動する。

java -jar selenium-server-standalone-3.141.59.jar

*この例ではchromedriverとselenium-serverを同じフォルダに格納している。

(3)nightwatchでテストを開始する。

自己認証をしているサーバーにアクセスする場合の設定

chromeの場合、起動オプションに「-ignore-certificate-errors」を付与すると自己認証のエラーページをスキップできる

nightwatch.conf.js
// 略
    seleniumC: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false,
          args : [
            '--use-fake-ui-for-media-stream',
            '--use-fake-device-for-media-stream',
            '--no-sandbox',
            '-ignore-certificate-errors'
          ]
        }
      },
// 略

チャットプログラムのようなアプリのテスト

以下のチャットアプリをテストする例を考えてみます。
https://github.com/mima3/webrtc_sample/tree/master/webrtc02
image.png

複数のseleniumを起動する環境の作成

まず複数のseleniumを起動するための環境を作成します。

docker run --name seleniumA -d -p 14444:4444 -p 15900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug

docker run --name seleniumB -d -p 24444:4444 -p 25900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug

macosの場合、作成したseleniumの環境にはFinder→移動→サーバーへ接続で接続が可能です。

接続先:vnc://localhost:15900
パスワード:secret

image.png

nightwatchで複数のseleniumを同時に操作する方法

seleniumでテストコードを記述した場合と違って、nightwatchのテストコード中からseleniumを操作するためのbrowserオブジェクトを複数操作することはできません。
その代わり、nightwatchでは複数の環境で同時に同じテストを動かすということが可能です。

まず、設定ファイルのtest_settingsに作成したdocker中のseleniumAとseleniumBへの接続情報を記載します。

nightwatch.conf.js
  test_settings: {
    seleniumA: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :14444,
      selenium_host : "localhost"
    },
    seleniumB: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :24444,
      selenium_host : "localhost"
    }
  }

// 略

設定ファイルを更新後、以下のように-eコマンドで実行したい環境を複数指定することで同じテストを同時に複数の環境で動かすことができます。

npx nightwatch tests/webrtc.js -e seleniumA,seleniumB

複数環境を指定して起動した場合は以下のようにnightwatchは環境ごとに子プロセスを作成して動作します。

+npx nightwatch tests/webrtc.js -e seleniumA,seleniumB
  |--- nightwatch tests/webrtc.js --env seleniumA --parallel-mode
  \--- nightwatch tests/webrtc.js --env seleniumB --parallel-mode

そのため、「process.env.__NIGHTWATCH_ENV」を使用して、現在のプロセスで動作している環境を取得して、環境ごとに処理を記述することでチャットプログラムのテストコードを記載することができます。

以下のコードはそのサンプルコードとなります。

webrtc.js
module.exports = {
  'WebRTC' : function(browser) {
    const seleniumA = process.env.__NIGHTWATCH_ENV === 'seleniumA';
    browser
      .url('http://192.168.10.239:3000/')
      .waitForElementVisible('xpath', '//button[contains(.,"公開")]')
      .pause(10000)  // 全ての環境が起動するまで待機する。
    if(seleniumA) {
      browser.click('xpath', '//button[contains(.,"公開")]')
             .waitForElementVisible('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//ul/li[contains(.,"テスト入力B")]')
             .setValue('xpath', '//input', 'テスト入力A')
             .click('xpath', '//button[contains(.,"送信")]')
             .pause(5000)
    } else {
      browser.waitForElementVisible('xpath', '//button[contains(.,"offer")]')
             .click('xpath', '//button[contains(.,"offer")]')
             .waitForElementVisible('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//input')
             .setValue('xpath', '//input', 'テスト入力B')
             .click('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//ul/li[contains(.,"テスト入力A")]')
             .pause(5000)
    }
    browser
      .end();
  }
};

さて、このプログラムでは複数の環境でブラウザが起動するというケースをpauseで待機していますが、本来であれば、プロセス間通信とかを利用して待ち合わせを行った方が望ましいでしょう。

以下のテストコードは上記のテストコードを発展させて、node-ipcを使用してプロセス間通信を行い環境間のタイミングを考慮しながらテストを行うためのサンプルコードになっています。

https://github.com/mima3/webrtc_sample/tree/master/webrtc02_test

その他参考

run test in two different browser tabs or windows
https://groups.google.com/g/nightwatchjs/c/GAL0CfPLrMU

HOW TO EXECUTE TESTS IN MULTIPLE BROWSERS PARALLELY WITH NIGHTWATCH JS
https://testersdock.com/execute-parallel-tests-nightwatchjs/

Testing WebRTC Apps with Nightwatch
http://v09.nightwatchjs.org/blog/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbol testnet node 自動構築 shellscript

☕自動構築 script の使い方

サーバをレンタルします
インストール OS を ubuntu 20.04/18.04/16.04 のどれかを選びます

サーバへのアクセスには これを使っています お勧めです
Tera Term
https://ja.osdn.net/projects/ttssh2/

インストール後に rootでサーバにログインします

vi b

"i" と入力 これで入力モードに切り替わります 左下に "INSERT" と表示されます 

下に示した scriptを コピー ペーストします

"ESC"キーを押します 左下の "INSERT" が消えます 入力モードが終了します

":wq" と入力 これで内容を保存 viは終了します

sh b

"b" が始まります
① username
② sshd port
③ IP アドレス/ドメイン
④ friendlyname
この4つを聞いて来ますので それぞれ入力して下さい
Egqr7uCUMAQ5am4.png
"①"で入力した user の パスワードを入力
 確認の為にもう一度聞いて来ますので 同じパスワードを入力して下さい
その後は "ENTER" を数回押して良いです ここで最後に出て来る [Y/n]は "ENTER" で良いです

しばらくすると あと2回 [Y/n]を聞いて来ます
ここは "y" を入力 "ENTER" で

この後は勝手に構築が始まります およそ10分~15分位でしょうか
しばらくするとログが流れるのが止まって 入力待ちになりますので
もう閉じて構いません
こうなったら 閉じて良いです?
無題.png


一旦ログアウトしてからの 再ログインは
root へ root の パスワードで
sshd port22 からは ログイン出来ない様になっています

"①" で設定した user と そのパスワードで
"②" で設定した sshd port から
ログイン出来ます

オール手動での構築方法はコッチを見てくだしい
Symbol Testnet node 構築備忘録
https://qiita.com/dusanjp/items/53fdac033e6df5f75139

?以下をコピペ?

#!/bin/bash
#user input-----
#new username
echo "please input new username"
read username

#new sshd port
echo "please input new sshd port"
read sshd

#ip
echo "please input ip"
read ip

#friendly_name
echo "please input friendly_name"
read friendlyname
#end of user input-----

#usernameset
adduser $username
gpasswd -a $username sudo

#sshdset
sed -i -e s/"#Port 22"/"Port 22"/ /etc/ssh/sshd_config
sed -i -e s/"Port 22"/"Port $sshd"/ /etc/ssh/sshd_config
sed -i -e s/"PermitRootLogin yes"/"PermitRootLogin no"/ /etc/ssh/sshd_config
systemctl restart sshd

#ufw set
apt-get install ufw
ufw allow $sshd/tcp

#ufw start
sudo ufw enable
sudo ufw status

#docker
curl https://get.docker.com | sh
usermod -aG docker $username
systemctl start docker
systemctl enable docker
docker --version

#docker-compose
curl -L https://github.com/docker/compose/releases/download/1.27.3/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

systemctl restart docker

#git
apt-get install git
git --version
git config --global user.name $username
git config --global user.email $username

#change userdir
cd /home/$username

#bootstrap_download
git clone https://github.com/nemfoundation/symbol-testnet-bootstrap

#config_ip/friendlyname
cd symbol-testnet-bootstrap/api-harvest-assembly
sed -i -e s/"host ="/"host = $ip"/ api-node/userconfig/resources/config-node.properties.template
sed -i -e s/"FRIENDLY_NAME"/"$friendlyname"/ api-node/userconfig/resources/config-node.properties.template 
#build_symbol_testnet_node
docker-compose up --build --detach

☕止め方

...あ~そうそう
止め方はこのスクリプトを実行ね
新しく作った user(※①) と パスワードで 設定した sshd port(※②)からログインしてね

vi stop

で 以下コピペして保存

#!/bin/bash
cd symbol-testnet-bootstrap/api-harvest-assembly
docker-compose down

で こう打つ

sh stop

☕再始動

vi start

以下コピペ保存

#!/bin/bash
cd symbol-testnet-bootstrap/api-harvest-assembly
docker-compose up --detach

で こう打つ

sh start

☕updateがあった時

vi up

以下コピペ保存(3行目と4行目は自分で修正してね)

#!/bin/bash
#ip/friendlyname set
ip="サーバのIPアドレス"
friendlyname="nodeに付ける名前"

cd symbol-testnet-bootstrap/api-harvest-assembly
docker-compose down
cd
sudo rm -rf symbol-testnet-bootstrap
docker system prune -a
git clone https://github.com/nemfoundation/symbol-testnet-bootstrap.git
cd symbol-testnet-bootstrap/api-harvest-assembly
#config_ip/friendlyname
sed -i -e s/"host ="/"host = $ip"/ api-node/userconfig/resources/config-node.properties.template
sed -i -e s/"FRIENDLY_NAME"/"$friendlyname"/ api-node/userconfig/resources/config-node.properties.template

docker-compose up --build --detach

3行目の "サーバのIPアドレス" には IPアドレス(※③と同じアドレス)を入力
4行目の "ノードに付ける名前" には node名(※④と同じでも良いし 変えたい場合は好き好きで)
無題.png

保存したら

sh up

途中で "[Y/n]" 聞いて来るから "y" で

これは 一度設定しとけば update ある度にそのまま実行すれば良いです

?以上 カレントディレクトリを全く移動しないやり方でしたw

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm経由で取得したNode-REDをカスタマイズして起動する(1)

何のための記事?

Node-REDをHerokuで運用する際に、Heroku環境では一部機能が動作しない場合があった。
その際、Node-REDの実装を調査する必要があったので関連するメモを整理して投稿します。
※本記事はHerokuと直接関係はありません

1. Node-REDを自作プログラムから動かす

  • Node.jsなどは予めインストールしておく。

1.1 導入

$ mkdir node-red-sample
$ cd node-red-sample
$ npm -y init 
$ npm install node-red
$ node ./node_modules/node-red/red.js  --userDir ./data --settings ./node_modules/node-red/settings.js

ブラウザで http://127.0.0.1:1880/ を開く。

1.2. 解説

上記の手順では、下記のオプションを指定しています。

  • --userDir : 追加するノードなどが保存される場所を指定する

  • --settings : 使用する設定ファイルを指定する

    ※デフォルトで red.js と同じフォルダに存在するsettings.jsを参照するので--settings以降は省略可能

2. 設定をカスタマイズする

2.1 カスタマイズ方法(1) : settings.js を指定して起動

settings.js を自分用に書き換えてred.jsの起動パラメータに渡す事で、好みの設定で起動するように変更できます。

package.json と同じフォルダに settings.js をコピーする。

$ cp ./node_modules/node-red/settings.js ./

settings.js を書き換えたら下記のようにコマンドを入力して起動する。

$ node ./node_modules/node-red/red.js --userDir ./data --settings ./settings.js

2.2 カスタマイズ方法(2) : settings.js を userDir に入れておく

$ mkdir data
$ cp ./node_modules/node-red/settings.js ./data/

settings.js を書き換えたら下記のようにコマンドを入力して起動する。

$ node ./node_modules/node-red/red.js --userDir ./data

3. エントリーポイント(?)を変更する

エントリーポイントを ./node_modules/node-red/red.js ではなく、独自のファイルにする方法。

$ cp node_modules/node-red/red.js ./main.js

コピーして作成した main.js を一部修正する。

- var RED = require("./lib/red.js");
+ var RED = require("node-red");

書き換えたら起動する。

$ node main.js --userDir ./data

まとめ

これでローカルでのデバッグできる環境は整いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm から取得した Node-RED をカスタマイズして起動する(1)

何のための記事?

Node-REDをHerokuで運用する際に、Heroku環境では一部機能が動作しない場合があった。
その際、Node-REDの実装を調査する必要があったので関連するメモを整理して投稿します。
※本記事はHerokuと直接関係はありません

1. Node-REDを自作プログラムから動かす

  • Node.jsなどは予めインストールしておく。

1.1 導入

$ mkdir node-red-sample
$ cd node-red-sample
$ npm -y init 
$ npm install node-red
$ node ./node_modules/node-red/red.js  --userDir ./data --settings ./node_modules/node-red/settings.js

ブラウザで http://127.0.0.1:1880/ を開く。

1.2. 解説

上記の手順では、下記のオプションを指定しています。

  • --userDir : 追加するノードなどが保存される場所を指定する

  • --settings : 使用する設定ファイルを指定する

    ※デフォルトで red.js と同じフォルダに存在するsettings.jsを参照するので--settings以降は省略可能

2. 設定をカスタマイズする

2.1 カスタマイズ方法(1) : settings.js を指定して起動

settings.js を自分用に書き換えてred.jsの起動パラメータに渡す事で、好みの設定で起動するように変更できます。

package.json と同じフォルダに settings.js をコピーする。

$ cp ./node_modules/node-red/settings.js ./

settings.js を書き換えたら下記のようにコマンドを入力して起動する。

$ node ./node_modules/node-red/red.js --userDir ./data --settings ./settings.js

2.2 カスタマイズ方法(2) : settings.js を userDir に入れておく

$ mkdir data
$ cp ./node_modules/node-red/settings.js ./data/

settings.js を書き換えたら下記のようにコマンドを入力して起動する。

$ node ./node_modules/node-red/red.js --userDir ./data

3. エントリーポイント(?)を変更する

エントリーポイントを ./node_modules/node-red/red.js ではなく、独自のファイルにする方法。

$ cp node_modules/node-red/red.js ./main.js

コピーして作成した main.js を一部修正する。

- var RED = require("./lib/red.js");
+ var RED = require("node-red");

書き換えたら起動する。

$ node main.js --userDir ./data

まとめ

これでローカルでのデバッグできる環境は整いました。

次の記事 → npm から取得した Node-RED をカスタマイズして起動する(2)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

textlintrcを使わないで設定する

動的に設定を流したいよねって話

$ npm init --yes
$ npm install --save-dev textlint
// TODOをチェックするルール
$ npm install --save-dev textlint-rule-no-todo
// 連続する漢字をチェックするルール
$ npm install --save-dev textlint-rule-max-kanji-continuous-len

textlintrc: falseしておくことで.textlintrcを拾わないようにする

cl.js
"use strict";
const TextLintEngine = require('textlint').TextLintEngine;

const text = "TODO: this is TODO 一二三四五六";
const options = {
  rules: [
    "no-todo",
    "max-kanji-continuous-len",
  ],
  rulesConfig: {
    "no-todo": true,
    "max-kanji-continuous-len": true,
  },
  textlintrc: false,
};
const engine = new TextLintEngine(options);
engine.executeOnText(text).then(results => {
  if (engine.isErrorResults(results)) {
    var output = engine.formatResults(results);
    console.log(output);
  } else {
    console.log("All Passed!");
  }
});
$ node cl.js

<text>
  1:1   error  Found TODO: 'TODO: this is TODO 一二三四五六'  no-todo
  1:20  error  漢字が6つ以上連続しています: 一二三四五六      max-kanji-continuous-len

? 2 problems (2 errors, 0 warnings)

参照情報

textlint/index.js at master · textlint/textlint

textlint/config.ts at 0503af68f4a34beaab2e45c34463ad4e0e24a958 · textlint/textlint

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Discordで通話中のみのチャットを作るBotをdiscord.jsで作った

discord.js-12.3.1 request-2.88.2 Node.js-12.18.3
Discordで通話中のみのチャットが作りたい!
こちらの記事を見て、社内Discordで使いたいという要望があったので、discord.jsで作ってみました。
処理の流れやソースの構成はほぼそのままです。

ソースコード

全体はこちらにあります。

index.js
const Discord = require("discord.js");
const client = new Discord.Client();
const privateChat = require('./main.js')

client.login(process.env.DISCORD_BOT_TOKEN);

// ボイスチャンネルの状態変更イベント時に実行
client.on('voiceStateUpdate', (oldState, newState) => privateChat.onVoiceStateUpdate(oldState, newState));

client.on("ready", () => {
  console.log(`Logged in as ${client.user.tag}!`);
});
main.js
const Discord = require("discord.js");
const client = new Discord.Client();
const request = require("request");

module.exports = {
  onVoiceStateUpdate: onVoiceStateUpdate
};

const CHANNEL_PREFIX = "?";
const BOT_ROLE_NAME = "BOT";

async function onVoiceStateUpdate(oldState, newState) {
  if (oldState.channelID === newState.channelID) {
    return;
  }

  if (oldState.channelID != null) {
    const oldChannel = oldState.guild.channels.cache.get(oldState.channelID);
    if (oldChannel.members.size == 0) {
      await txChDelete(oldChannel);
    } else {
      await chExit(oldChannel, newState.member);
    }
  }

  if (newState.channelID != null) {
    let txtChannel;
    const newChannel = newState.guild.channels.cache.get(newState.channelID);
    if (newChannel.members.size == 1) {
      txtChannel = await txChCreate(newChannel, newState.member);
    } else {
      txtChannel = await chJoin(newChannel, newState.member);
    }
    await chSendNotification(txtChannel, newState.member);
  }
}

// テキストチャンネルを作成
async function txChCreate(voiceChannel, voiceJoinedMember) {
  try {
    const guild = voiceChannel.guild;
    // チャンネル名の後ろにボイスチャンネルのIDを付与して一意に
    let chName = CHANNEL_PREFIX + voiceChannel.name + "_" + voiceChannel.id;
    let botRole = guild.roles.cache.find(val => val.name === BOT_ROLE_NAME);
    let result = await guild.channels.create(chName, {
      parent: voiceChannel.parent,
      type: "text",
      // denyでeveryoneユーザは見れないように
      // allowでボイスチャンネルに参加したメンバーは見れるように
      permissionOverwrites: [
        {
          id: guild.roles.everyone.id,
          deny: ["VIEW_CHANNEL"]
        },
        {
          id: voiceJoinedMember.id,
          allow: ["VIEW_CHANNEL"]
        },
        {
          id: botRole.id,
          allow: ["VIEW_CHANNEL"]
        }
      ],
    });
    return result;
  } catch (err) {
    console.log(err);
  }
}

// ボイスチャンネルのIDがついたテキストチャンネルを検索
function chFind(voiceChannel) {
  const guild = voiceChannel.guild;
  let searchCondition = voiceChannel.id;
  let result = guild.channels.cache.find(val => val.name.endsWith(searchCondition));
  return result;
}

// テキストチャンネルを削除
async function txChDelete(ch) {
  let target = await chFind(ch);
  if (target != null) {
    target.delete().catch(console.error);
  } else {
    console.log("削除するチャンネルがないンゴ");
  }
}

// テキストチャンネルの権限を付与
async function chJoin(ch, user) {
  let target = await chFind(ch);
  if (target != null) {
    target.updateOverwrite(user, { VIEW_CHANNEL: true });
    return target;
  } else {
    console.log("チャンネルがないンゴ");
  }
}

// テキストチャンネルの権限を削除
async function chExit(ch, user) {
  let target = await chFind(ch);
  if (target != null) {
    target.updateOverwrite(user, { VIEW_CHANNEL: false });
  } else {
    console.log("チャンネルがないンゴ");
  }
}

// 入室時にメンションを飛ばして案内
async function chSendNotification(ch, user) {
  const guild = ch.guild;
  const sendChannel = await guild.channels.cache.find(val => val.name === ch.name);
  await sendChannel.send(`<@!${user.id}>`)
    .catch(console.error);

  const embed = new Discord.MessageEmbed()
    .setTitle("プライベートチャットに参加しました。")
    .setAuthor("To " + user.displayName)
    .setDescription(
      "ボイスチャンネルに参加している人だけに見えるチャンネルです。\n全員が退出すると削除されます。"
    );
  sendChannel.send(embed);
}

ハマったところ

同じカテゴリにいくつもチャンネルがあるのでテキストチャンネル名の中にボイスチャンネル名を含めて識別しやすくしたいと考えました。
最初はボイスチャンネルと同じ名前でテキストチャンネルを作成しようとしていましたが、Discordのテキストチャンネルとボイスチャンネルに使える文字種が異なってること(大文字不可、[]不可など)を知らなかったため、チャンネル名の不一致によりchFindがうまく動きませんでした。
ボイスチャンネル:Apex Legends - A
テキストチャンネル:apex-legends-a

結果として参考にした記事と同じようにボイスチャンネルIDを末尾につけて識別できるようにしています。

let chName = CHANNEL_PREFIX + voiceChannel.name + "_" + voiceChannel.id;

最後に

導入以来、社内MTGをDiscordのボイスチャンネルで行う際などに重宝してくれてるようです。
また、discord.pyをdiscord.jsで書き直すのも双方の言語やライブラリ仕様の違いについての理解を深めるいい機会になりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

簡単なtextlintサーバーを作る

textlint/textlint: The pluggable natural language linter for text and markdown.

より、apiで問い合わせられるようにしてみる。

ファイル構成

appフォルダーをtextlintなどこの後にインストールするパッケージ名と同じにするとエラーになるので注意。

app/
  ├ .textlintrc // 設定
  ├ app.js      // webメイン
  └ a.py        // クライアント

パッケージのインストール

--saveが良いとかは適宜

$ npm init --yes
$ npm install --save-dev express
$ npm install --save-dev textlint
// TODOをチェックするルール
$ npm install --save-dev textlint-rule-no-todo
// 連続する漢字をチェックするルール
$ npm install --save-dev textlint-rule-max-kanji-continuous-len

現在のバージョンは以下の通り。

$ node --version
v10.21.0
$ npm --version
6.14.4
$ rpm list
express@4.17.1
textlint@11.7.6
textlint-rule-no-todo@2.0.1
textlint-rule-max-kanji-continuous-len@1.1.1

ファイル

app.js
const TextLintEngine = require('textlint').TextLintEngine;
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

// postデータのjsonをパースするおまじない
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

// 8080番ポートで待ちうける
app.listen(8080, () => {
    console.log('Running at Port 8080...');
});

app.post('/api/textlint', (req, res, next) => {
    const req_text = req.body.text;
    const engine = new TextLintEngine();
    engine.executeOnText(req_text).then(results => {
        res.json({
            messages: results[0].messages
        });
    });
});

// その他のリクエストに対する404エラー
app.use((req, res) => {
    res.sendStatus(404);
});

書き方は各ルールのgithub参照

.textlintrc
{
  "filters": {},
  "rules": {
    "no-todo": true,
    "max-kanji-continuous-len": true,
  }
}

text変数の文字列に対してlintをかける

a.py
import json
import urllib.request

url = 'http://localhost:8080/api/textlint'
text = '''
TODO: this is TODO
一二三四五六
'''.strip()

data = {"text": text}
headers = {
    'Content-Type': 'application/json',
}
req = urllib.request.Request(url, json.dumps(data).encode(), headers)
try:
    with urllib.request.urlopen(req) as res:
        body = json.load(res)
        json_str = json.dumps(body)
        print(json_str)
except:
    print("fail")

サーバーを起動

$ node app.js
Running at Port 8080...

リクエストしてみる

$ python3 a.py | jq .
{
  "messages": [
    {
      "type": "lint",
      "ruleId": "no-todo",
      "message": "Found TODO: 'TODO: this is TODO'",
      "index": 0,
      "line": 1,
      "column": 1,
      "severity": 2
    },
    {
      "type": "lint",
      "ruleId": "max-kanji-continuous-len",
      "message": "漢字が6つ以上連続しています: 一二三四五六",
      "index": 19,
      "line": 2,
      "column": 1,
      "severity": 2
    }
  ]
}

もちろんコマンドラインからも呼べる

$ curl -X POST -H "Content-Type: application/json" \
  -d '{"text":"TODO: this is TODO\n一二三四五六"}'  \
  localhost:8080/api/textlint | jq .
{
  "messages": [
    {
      "type": "lint",
      "ruleId": "no-todo",
      "message": "Found TODO: 'TODO: this is TODO'",
      "index": 0,
      "line": 1,
      "column": 1,
      "severity": 2
    },
    {
      "type": "lint",
      "ruleId": "max-kanji-continuous-len",
      "message": "漢字が6つ以上連続しています: 一二三四五六",
      "index": 19,
      "line": 2,
      "column": 1,
      "severity": 2
    }
  ]
}

参考情報

Node.jsとExpressでローカルサーバーを構築する(1) ―Node.jsとnpmの導入― - Qiita
コードからtextlintを実行する - Qiita
textlintをnodejs/express環境で動作させる
node.js - Textlint の実行結果が空になる - スタック・オーバーフロー
Node.js + Express でPOSTデータを取得後、WebAPIへ問い合わせる - Qiita
node.js(express)のbody-parserを理解する - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vercel・typescript・next.js・redux toolkit・express・bootstrap5・react-string-replace。無料でSSRなWebアプリを動かす。

Node.jsを無料で使える神サービスVercelに作ったWebアプリを乗せる。
https://vercel.com

成果物

https://nextboard.itsumen.com

リポジトリ

https://github.com/yuzuru2/nextboard2

構成

フロントエンド: Vercel(無料) Next.js

バックエンド: Vercel(無料) TypeScript Express

データベース: MongoDB Atlas(DBaaS) 無料

ワイの成果物

https://qiita.com/yuzuru2/items/b5a34ad07d38ab1e7378

①コード共有サイト(SSR) Next.js
https://code.itsumen.com

②画像共有サイト(SPA) React
https://gazou.itsumen.com

③チャット(SSR) Nuxt.js
https://nuxtchat.itsumen.com

④チャット(SPA) React
https://chat4.itsumen.com

⑤掲示板(SSR) Next.js
https://nextboard.itsumen.com

⑥掲示板(SPA) Vue
https://board.itsumen.com

⑦レジの店員を呼ぶスマホアプリ(Android)
https://play.google.com/store/apps/details?id=com.itsumen.regi&hl=ja

⑧ブログ(静的サイトジェネレータ) Hugo
https://yuzuru.itsumen.com

DM送信先
Twitter: https://twitter.com/yuzuru_program
LINE: https://line.me/ti/p/-GXpQkyXAm

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超初心者がDockerでRails6 + Postgresql 環境構築を最後まで

構築開始にいたるまで

Dockerに触れておきたかったので、上記環境を構築することに。
前知識がまったくないのでトライアンドエラーを繰り返しました...
しかしなんとか構築できたので、メモを残します。
いまいちよくわかってない箇所は注釈や説明に?を付けました

必要なフォルダ、ファイル

任意の名前のフォルダーを用意、ここではmy_appとします。

$ mkdir my_app

my_app内に以下のファイルを用意

$ touch xxx(必要なファイル名)

- my_app
    - .dockerignore
    - Dockerfile
    - docker-compose.yml
    - Gemfile
    - Gemfile.lock
    - entrypoint.sh

それぞれのファイルは以下のような中身に編集していきます。

.dockerignore

ローカルモジュールとデバッグログがDockerイメージにコピーされるのを防ぎます。
Mac環境とDocker上のLinux環境とでは必要なモジュールが違うから...なのかな?
とにかく、これがないと Node.js 12.xの関係でエラーがでます。

.dockerignore
node_modules
npm-debug.log

.Dockerfile

Postgresqlと、rails6のWebpackerに必要なyarnとNode.jsをインストールをします。
羅列されている各コマンドについてはざっくりでしか理解できてません(焦)
勉強の必要がありそうです...

Dockerfile
FROM ruby:2.7.1

# 必要なライブラリインストール
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client

# yarnパッケージ管理ツールをインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
  curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
  echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
  apt-get update && apt-get install -y yarn

# Node.jsをインストール
RUN curl -SL https://deb.nodesource.com/setup_12.x | bash
RUN apt-get install -y nodejs

# コンテナ内の作業フォルダ構築
RUN mkdir /my_app
WORKDIR /my_app
COPY Gemfile /my_app/Gemfile
COPY Gemfile.lock /my_app/Gemfile.lock
RUN bundle install
COPY . /my_app

#entrypoint.shと接続の設定
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml

ここではPostgresqlで使うユーザー名とパスワードをpostgresとします。
サービスにwebdbがありますね。

docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/my_app
    ports:
      - "3000:3000"
    depends_on:
      - db

Gemfile

Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6'

Gemfile.lock

ファイル生成のみで、中身を編集する必要はありません。

entrypoint.sh

entrypoint.sh
#!/bin/bash
set -e

# 前回のrailsサーバー起動時から残ったserver.pidを消す処理?
rm -f /my_app/tmp/pids/server.pid

# コンテナのメインプロセスを実行(Dockerfile内のCMDで指定したコマンド)
exec "$@"

Railsアプリの作成

runでweb単体?がbuildされ、webコンテナ内でrails newを実行

$ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundle

--no-deps:リンクしたサービスを起動しない(webにリンクしたdbを今は起動しない?)
--skip-bundle:bundleを実行しない

bundle install

さきほどのrails newで、Gemfileが書き換えられています。
イメージをbuildすることによって、bundle installも行われるため、以下のコマンドを実行します。

$ docker-compose build

データベース設定

作業用フォルダには見慣れたrailsのファイル一式がすでにあると思うので、
config/database.ymlを以下のように編集します。

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  #docker-compose.yml内で書いたPostgresqlのユーザー名、パスワードと一致させること
  username: postgres
  password: postgres
  pool: 5

development:
  <<: *default
  database: my_app_development

test:
  <<: *default
  database: my_app_test

webpackerのインストールと設定

webpackerのインストール

webコンテナ内でbundle exec rails webpacker:installを実行。

$ docker-compose run web bundle exec rails webpacker:install

webpackerの設定

自分の場合、この設定を行わないとサーバー起動時にエラーが起ってしまいます。
rails webpacker:installによって生成されたconfig/webpacker.ymlを以下のように編集します。

config/webpacker.yml
development:
  dev_server:
    host: webpacker
    hmr: true

またconfig/environments/development.rbに以下のコードを加えます。
でもひょっとしたらこの工程だけは必要ないかもしれません。

config/environments/development.rb
config.webpacker.check_yarn_integrity = false

コンテナ起動

$ docker-compose up

この時点でhttp://localhost:3000/にアクセスできますが、
DBがまだ作成されてないので、エラー画面が表示されます。
実に初歩的ですが長い道のりだっただけに、自分はかなり焦りました(笑)

DB作成

$ docker-compose run web rails db:create

構築完了

http://localhost:3000/に「Yay! You're on Rails!」があれば成功です!
もし不要なdockerイメージやコンテナが生成されていれば各自で削除してくださいね。

初めての投稿記事につき、至らない点がたくさんあると思います。
そんな記事に最後までお付き合いいただき、ありがとうございました。
記事に間違いがありましたら、ご指摘ください。

参考

Docker + Rails6 + PostgreSQL 環境構築
docker-compose upの直後Yarnのエラーでコンテナが落ちる問題と解決
Node.js Web アプリケーションを Docker 化する
既存のrails6アプリをDocker,webpackerの組み合わせで使いたい
docker-compose 'up' とか 'build' とか 'start' とかの違いを理解できていなかったのでまとめてみた

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む