超簡単にサイト公開できるAWS Amplify Consoleを使ってAngularのCI/CD環境を作ってみる #reinvent

どうも!大阪オフィスの西村祐二です。

この記事はAngular Advent Calendar 2018の17日目の記事です。

今回はAWS Amplify Consoleを使ったAngularのCI/CD環境を作ってみたいと思います。

AWS Amplify Consoleとは?

AWS Amplify Consoleは簡単に言うと Webアプリを公開する環境を超簡単にセットアップできるサービス です。

詳細は下記ブログを参照ください。

新サービス「AWS Amplify Console」登場!簡単3ステップでWebアプリのCI/CD環境を構築! #reinvent

re:Growth 2018で「AWS Amplify一族の話」を話しました! #reinvent #cmregrowth

HIGOBASHI.AWS 第9回 AWS re:Invent 2018 報告会「モダンなWebアプリを楽々デプロイ!! AWS Amplify Console」を話しました #higobashiaws #reinvent

環境

  • Angular CLI: 7.1.3
  • Node: 8.12.0
  • OS: darwin x64
  • Angular: 7.1.3

Angularのプロジェクトを作成

今回は「amplify-console-angular」という名前にしておきます。各々好きな名前を設定してください。

$ ng new amplify-console-angular
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS

作成されたファイルをリポジトリにpushします。今回はGithubを使います。

例:Githubにpushする。

$ git remote add origin https://github.com/<repo-name>.git
$ git push -u origin master

AWS Amplify Consoleを使ってサイトを公開する

Amplifyのマネージメントコンソールに移動します。Deployの「GET STARTED」をクリックします。

▼今回はGithubを選択

▼認証設定を行い、リポジトリを選択します。先程Angularのファイルをpushしたリポジトリを選択しておきます。

▼AWS Amplify Consoleが自動的にフレームワークを認識し、ビルドファイルを作成してくれます。

▼設定を確認し、「保存してデプロイ」をクリックします。これだけで、サイトを公開することができます。

▼デプロイ状況を確認することができます。

▼様々な解像度での見え方などスクリーンショットを自動的に取得してくれたりもします。デフォルトで

▼超簡単にBasic認証を設定できます。

詳細は下記ブログを参照してください。

超簡単!AWS Amplify ConsoleでWebサイトにアクセス制限(Basic認証)を設定する #reinvent

▼超簡単にドメインの設定ができます。

詳細は下記ブログを参照してください。

「AWS Amplify Console」を使ってみた 〜カスタムドメイン編〜 #reinvent

CI/CD環境を作る

ここからが本題です。

AWS Amplify Consoleがデプロイ周りなど自動でやってくれるので、自動テストの設定やブランチ設定などを行い、より良いCI/CD環境を作っていきたいと思います。

デプロイ時に自動テストを組み込む

ng test, ng e2eをデプロイ時に実行させる

Angularではデフォルトで単体テストとe2eテストをng testng e2eで実行することができます。

これを組み込んでみたいと思います。

デフォルトのデプロイファイルは下記の様になっております。

version: 0.1
frontend:
  phases:
    preBuild:
      commands:
        - yarn install
    build:
      commands:
        - yarn run build
  artifacts:
    baseDirectory: dist/<project-name>
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*

今回、下記のように修正しました。

version: 0.1
frontend:
  phases:
    preBuild:
      commands:
        - yarn install
        - export PATH=$PATH:$PWD/node_modules/.bin/
        - curl https://intoli.com/install-google-chrome.sh | bash
        - ng test --watch=false
        - ng e2e
    build:
      commands:
        - yarn run build
  artifacts:
    baseDirectory: dist/<project-name>
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*

簡単に解説します。

  • 7行目
    • export PATH=$PATH:$PWD/node_modules/.bin/
    • ngコマンドを使いたかったのでパスを通してます。
  • 8行目
    • curl https://intoli.com/install-google-chrome.sh | bash
    • chromeブラウザをng test, ng e2e実行時に利用するため、インストールしています。
    • 画面キャプチャなど行ってくれているのでインストールされているかと思ったのですがエラーとなったり、/usr/bin/配下をみたのですが、それらしいものがなかったのでインストールしています。

▼Angular内の設定ファイルも修正します。

e2eテスト用の設定ファイル

// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts

const { SpecReporter } = require('jasmine-spec-reporter');

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './src/**/*.e2e-spec.ts'
  ],
  capabilities: {
    'browserName': 'chrome',
    chromeOptions: {
      args: ["--headless", "--disable-gpu", "--window-size=800,600", "--disable-dev-shm-usage", "--no-sandbox"]
    }
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'jasmine',
  jasmineNodeOpts: {
    showColors: true,
    defaultTimeoutInterval: 30000,
    print: function() {}
  },
  onPrepare() {
    require('ts-node').register({
      project: require('path').join(__dirname, './tsconfig.e2e.json')
    });
    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
  }
};

単体テスト用のテストファイル

// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, '../coverage'),
      reports: ['html', 'lcovonly', 'text-summary'],
      fixWebpackSourcePaths: true
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['ChromeHeadlessNoSandbox'],
      customLaunchers: {
        ChromeHeadlessNoSandbox: {
          base: 'ChromeHeadless',
          flags: ['--no-sandbox']
        }
      },
    singleRun: false
  });
};

これでブランチにpushしてみます。

下記のようなログが出力されれば成功です!

2018-12-16T16:58:19.224Z [INFO]: # Executing command: ng test --watch=false
2018-12-16T16:58:25.804Z [INFO]: 16 12 2018 16:58:25.802:INFO [karma-server]: Karma v3.1.3 server started at http://0.0.0.0:9876/
2018-12-16T16:58:25.807Z [INFO]: 16 12 2018 16:58:25.804:INFO [launcher]: Launching browsers ChromeHeadlessNoSandbox with concurrency unlimited
2018-12-16T16:58:25.809Z [INFO]: 16 12 2018 16:58:25.809:INFO [launcher]: Starting browser ChromeHeadless
2018-12-16T16:58:29.247Z [INFO]: 16 12 2018 16:58:29.246:INFO [HeadlessChrome 71.0.3578 (Linux 0.0.0)]: Connected on socket XXX with id 000000000000
2018-12-16T16:58:30.769Z [INFO]: HeadlessChrome 71.0.3578 (Linux 0.0.0): Executed 0 of 3 SUCCESS (0 secs / 0 secs)
2018-12-16T16:58:30.857Z [INFO]: HeadlessChrome 71.0.3578 (Linux 0.0.0): Executed 1 of 3 SUCCESS (0 secs / 0.086 secs)
2018-12-16T16:58:30.900Z [INFO]: HeadlessChrome 71.0.3578 (Linux 0.0.0): Executed 2 of 3 SUCCESS (0 secs / 0.129 secs)
2018-12-16T16:58:30.927Z [INFO]: HeadlessChrome 71.0.3578 (Linux 0.0.0): Executed 3 of 3 SUCCESS (0 secs / 0.155 secs)
2018-12-16T16:58:30.938Z [INFO]: HeadlessChrome 71.0.3578 (Linux 0.0.0): Executed 3 of 3 SUCCESS (0.168 secs / 0.155 secs)
TOTAL: 3 SUCCESS
TOTAL: 3 SUCCESS
2018-12-16T16:58:31.010Z [INFO]: # Executing command: ng e2e
2018-12-16T16:58:33.163Z [INFO]: ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
2018-12-16T16:58:33.166Z [INFO]: 
2018-12-16T16:58:40.758Z [INFO]: 
Date: 2018-12-16T16:58:40.758Z
Hash: 
Time: 7466ms
chunk {main} main.js, main.js.map (main) 11.6 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 223 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 16.3 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.67 MB [initial] [rendered]
2018-12-16T16:58:40.891Z [INFO]: [16:58:40] I/config_source - curl -o/codebuild/output/src459569155/src/amplify-cli-console-angular/node_modules/webdriver-manager/selenium/chrome-response.xml https://chromedriver.storage.googleapis.com/
2018-12-16T16:58:40.902Z [INFO]: ℹ 「wdm」: Compiled successfully.
2018-12-16T16:58:41.398Z [INFO]: [16:58:41] I/update - chromedriver: file exists /codebuild/output/src459569155/src/amplify-cli-console-angular/node_modules/webdriver-manager/selenium/chromedriver_2.45.zip
2018-12-16T16:58:41.399Z [INFO]: [16:58:41] I/update - chromedriver: unzipping chromedriver_2.45.zip
2018-12-16T16:58:41.544Z [INFO]: [16:58:41] I/update - chromedriver: setting permissions to 0755 for /codebuild/output/src459569155/src/amplify-cli-console-angular/node_modules/webdriver-manager/selenium/chromedriver_2.45
2018-12-16T16:58:41.545Z [INFO]: [16:58:41] I/update - chromedriver: chromedriver_2.45 up to date
2018-12-16T16:58:41.789Z [INFO]: [16:58:41] I/launcher - Running 1 instances of WebDriver
2018-12-16T16:58:41.790Z [INFO]: [16:58:41] I/direct - Using ChromeDriver directly...
2018-12-16T16:58:43.382Z [INFO]: Jasmine started
2018-12-16T16:58:43.982Z [INFO]: 
2018-12-16T16:58:43.982Z [INFO]:   workspace-project App
2018-12-16T16:58:43.982Z [INFO]:     ✓ should display welcome message
2018-12-16T16:58:43.983Z [INFO]: 
2018-12-16T16:58:43.983Z [INFO]: Executed 1 of 1 spec SUCCESS in 0.602 sec.
2018-12-16T16:58:44.038Z [INFO]: [16:58:44] I/launcher - 0 instance(s) of WebDriver still running
2018-12-16T16:58:44.038Z [INFO]: [16:58:44] I/launcher - chrome #01 passed
2018-12-16T16:58:44.132Z [INFO]: # Completed phase: preBuild

ブランチごとに環境を用意しコマンド切り替える

test環境と、本番環境を用意し、test環境で動作確認をしてOKだったら本番環境に適用するといった戦略をとることがあるかと思います。

さらに、testブランチにpushされたらtest環境にデプロイされ、masterブランチにpushされた本番環境にデプロイするといったこともよくやるかと思います。

今回はこれをやってみます。

testブランチをpush

下記のようなコマンドでGithubのリポジトリにtestブランチを作成します。

$ git checkout -b test
$ git push origin test

AWS Amplify Consoleでtestブランチを接続して複数環境を作成

ブランチの接続をクリックし、「test」を選択し環境を作成します。

処理が完了すると下記のような2つの環境ができあがります。

本番環境時のみng build --prodコマンドを実行させる

現状だと、下記画像のように本番環境用に最適化されていないファイルがデプロイされているので、

  • masterブランチにpushされたらng build --prodが実行され、
  • それ以外のブランチにpushされたときはng buildが実行される

ようにしたいと思います。

▼AWS Amplify Consoleで環境変数を設定する

下記画像のように、masterブランチにpushされたときはENVを上書きして値をprodに変更するようにします。

▼Angular内にビルドスクリプトを配置する

環境変数によってコマンドを切り替えるためのスクリプトを配置します。

#!/bin/sh

set -xeu

if [ "$ENV" = "test" ]; then
	export NG_CMD="ng build"
elif [ "$ENV" = "prod" ]; then
	export NG_CMD="ng build --prod"
fi

$NG_CMD

▼ビルド設定を修正

スクリプト実行するようにビルド設定を修正します。

version: 0.1
frontend:
  phases:
    preBuild:
      commands:
        - yarn install
        - export PATH=$PATH:$PWD/node_modules/.bin/
        - curl https://intoli.com/install-google-chrome.sh | bash
        - ng test --watch=false
        - ng e2e
    build:
      commands:
        - sh ./build.sh
  artifacts:
    baseDirectory: dist/amplify-cli-console-angular
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*

これで準備は完了です。

▼試してみる

masterブランチにpushした際に下記のようなログが出力されれは成功です!

.
.
.
.
# Starting phase: build
2018-12-16T16:58:44.132Z [INFO]: # Executing command: sh ./build.sh
2018-12-16T16:58:44.134Z [WARNING]: + '[' prod = test ']'
+ '[' prod = prod ']'
+ export 'NG_CMD=ng build --prod'
2018-12-16T16:58:44.134Z [WARNING]: + NG_CMD='ng build --prod'
+ ng build --prod
.
.
.
.

さらに、それ以外のブランチにpushした際は下記のようなログが出力されているはずです。

.
.
.
.
# Starting phase: build
# Executing command: sh ./build.sh
2018-12-16T16:49:55.651Z [WARNING]: + '[' test = test ']'
+ export 'NG_CMD=ng build'
2018-12-16T16:49:55.651Z [WARNING]: + NG_CMD='ng build'
+ ng build
.
.
.
.

さいごに

いかがだったでしょうか。

AWS Amplify Consoleを使ってAngularのCI/CD環境を作ってみました。

デプロイ時にテストを挟んだり、複数環境用意し、環境変数でコマンド切り替えることも簡単にできますので、興味のある方は試してみてはいかがでしょうか。

誰かの参考になれば幸いです。